Use common NowPlaying component for station profile.
This commit is contained in:
parent
6e96d11804
commit
ff2402c556
|
@ -230,11 +230,11 @@ import AccountEditModal from "./Account/EditModal";
|
|||
import Avatar from "~/components/Common/Avatar";
|
||||
import InfoCard from "~/components/Common/InfoCard";
|
||||
import EnabledBadge from "~/components/Common/Badges/EnabledBadge.vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {ref} from "vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {useNotify} from "~/vendor/bootstrapVue";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
import {useSweetAlert} from "~/vendor/sweetalert";
|
||||
import useConfirmAndDelete from "~/functions/useConfirmAndDelete";
|
||||
import useRefreshableAsyncState from "~/functions/useRefreshableAsyncState";
|
||||
|
||||
const props = defineProps({
|
||||
userUrl: {
|
||||
|
@ -261,22 +261,32 @@ const props = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
const userLoading = ref(true);
|
||||
const user = ref({
|
||||
name: null,
|
||||
email: null,
|
||||
avatar: {
|
||||
url: null,
|
||||
service: null,
|
||||
serviceUrl: null
|
||||
},
|
||||
roles: [],
|
||||
});
|
||||
const {axios} = useAxios();
|
||||
|
||||
const securityLoading = ref(true);
|
||||
const security = ref({
|
||||
twoFactorEnabled: false,
|
||||
});
|
||||
const {state: user, isLoading: userLoading, execute: reloadUser} = useRefreshableAsyncState(
|
||||
() => axios.get(props.userUrl).then((r) => r.data),
|
||||
{
|
||||
name: null,
|
||||
email: null,
|
||||
avatar: {
|
||||
url: null,
|
||||
service: null,
|
||||
serviceUrl: null
|
||||
},
|
||||
roles: [],
|
||||
},
|
||||
);
|
||||
|
||||
const {state: security, isLoading: securityLoading, execute: reloadSecurity} = useRefreshableAsyncState(
|
||||
() => axios.get(props.twoFactorUrl).then((r) => {
|
||||
return {
|
||||
twoFactorEnabled: r.data.two_factor_enabled
|
||||
};
|
||||
}),
|
||||
{
|
||||
twoFactorEnabled: false,
|
||||
},
|
||||
);
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
|
@ -296,42 +306,13 @@ const apiKeyFields = [
|
|||
];
|
||||
|
||||
const $dataTable = ref(); // DataTable
|
||||
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||
const {axios} = useAxios();
|
||||
|
||||
const relist = () => {
|
||||
userLoading.value = true;
|
||||
|
||||
wrapWithLoading(
|
||||
axios.get(props.userUrl)
|
||||
).then((resp) => {
|
||||
user.value = {
|
||||
name: resp.data.name,
|
||||
email: resp.data.email,
|
||||
roles: resp.data.roles,
|
||||
avatar: {
|
||||
url: resp.data.avatar.url_64,
|
||||
service: resp.data.avatar.service_name,
|
||||
serviceUrl: resp.data.avatar.service_url
|
||||
}
|
||||
};
|
||||
userLoading.value = false;
|
||||
});
|
||||
|
||||
securityLoading.value = true;
|
||||
|
||||
wrapWithLoading(
|
||||
axios.get(props.twoFactorUrl)
|
||||
).then((resp) => {
|
||||
security.value.twoFactorEnabled = resp.data.two_factor_enabled;
|
||||
securityLoading.value = false;
|
||||
});
|
||||
|
||||
reloadUser();
|
||||
reloadSecurity();
|
||||
$dataTable.value?.relist();
|
||||
};
|
||||
|
||||
onMounted(relist);
|
||||
|
||||
const reload = () => {
|
||||
location.reload();
|
||||
};
|
||||
|
@ -354,22 +335,11 @@ const enableTwoFactor = () => {
|
|||
$twoFactorModal.value?.open();
|
||||
};
|
||||
|
||||
const {confirmDelete} = useSweetAlert();
|
||||
|
||||
const disableTwoFactor = () => {
|
||||
confirmDelete({
|
||||
title: $gettext('Disable two-factor authentication?'),
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
wrapWithLoading(
|
||||
axios.delete(props.twoFactorUrl)
|
||||
).then((resp) => {
|
||||
notifySuccess(resp.data.message);
|
||||
relist();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const {doDelete: doDisableTwoFactor} = useConfirmAndDelete(
|
||||
$gettext('Disable two-factor authentication?'),
|
||||
relist
|
||||
);
|
||||
const disableTwoFactor = () => doDisableTwoFactor(props.twoFactorUrl);
|
||||
|
||||
const $apiKeyModal = ref(); // ApiKeyModal
|
||||
|
||||
|
@ -377,18 +347,8 @@ const createApiKey = () => {
|
|||
$apiKeyModal.value?.create();
|
||||
};
|
||||
|
||||
const deleteApiKey = (url) => {
|
||||
confirmDelete({
|
||||
title: $gettext('Delete API Key?'),
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
wrapWithLoading(
|
||||
axios.delete(url)
|
||||
).then((resp) => {
|
||||
notifySuccess(resp.data.message);
|
||||
relist();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const {doDelete: deleteApiKey} = useConfirmAndDelete(
|
||||
$gettext('Delete API Key?'),
|
||||
relist
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<profile-header
|
||||
v-bind="pickProps(props, headerPanelProps)"
|
||||
:np="np"
|
||||
:station="profileInfo.station"
|
||||
/>
|
||||
|
||||
<div
|
||||
|
@ -11,16 +11,15 @@
|
|||
<div class="col-lg-7">
|
||||
<profile-now-playing
|
||||
v-bind="pickProps(props, nowPlayingPanelProps)"
|
||||
:np="np"
|
||||
/>
|
||||
|
||||
<profile-schedule
|
||||
:station-time-zone="stationTimeZone"
|
||||
:schedule-items="np.schedule"
|
||||
:schedule-items="profileInfo.schedule"
|
||||
/>
|
||||
|
||||
<profile-streams
|
||||
:np="np"
|
||||
:station="profileInfo.station"
|
||||
/>
|
||||
|
||||
<profile-public-pages
|
||||
|
@ -42,14 +41,14 @@
|
|||
<template v-if="hasActiveFrontend">
|
||||
<profile-frontend
|
||||
v-bind="pickProps(props, frontendPanelProps)"
|
||||
:np="np"
|
||||
:frontend-running="profileInfo.services.frontend_running"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="hasActiveBackend">
|
||||
<profile-backend
|
||||
v-bind="pickProps(props, backendPanelProps)"
|
||||
:np="np"
|
||||
:backend-running="profileInfo.services.backend_running"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
@ -72,7 +71,7 @@ import ProfileBackendNone from './Profile/BackendNonePanel';
|
|||
import ProfileBackend from './Profile/BackendPanel';
|
||||
import {BACKEND_NONE, FRONTEND_REMOTE} from '~/components/Entity/RadioAdapters';
|
||||
import NowPlaying from '~/components/Entity/NowPlaying';
|
||||
import {computed, onMounted, shallowRef} from "vue";
|
||||
import {computed} from "vue";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
import backendPanelProps from "./Profile/backendPanelProps";
|
||||
import embedModalProps from "./Profile/embedModalProps";
|
||||
|
@ -83,6 +82,8 @@ import publicPagesPanelProps from "./Profile/publicPagesPanelProps";
|
|||
import requestsPanelProps from "./Profile/requestsPanelProps";
|
||||
import streamersPanelProps from "./Profile/streamersPanelProps";
|
||||
import {pickProps} from "~/functions/pickProps";
|
||||
import useRefreshableAsyncState from "~/functions/useRefreshableAsyncState";
|
||||
import {useIntervalFn} from "@vueuse/core";
|
||||
|
||||
const props = defineProps({
|
||||
...backendPanelProps,
|
||||
|
@ -111,16 +112,6 @@ const props = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
const np = shallowRef({
|
||||
...NowPlaying,
|
||||
loading: true,
|
||||
services: {
|
||||
backend_running: false,
|
||||
frontend_running: false
|
||||
},
|
||||
schedule: []
|
||||
});
|
||||
|
||||
const hasActiveFrontend = computed(() => {
|
||||
return props.frontendType !== FRONTEND_REMOTE;
|
||||
});
|
||||
|
@ -131,20 +122,28 @@ const hasActiveBackend = computed(() => {
|
|||
|
||||
const {axios} = useAxios();
|
||||
|
||||
const checkNowPlaying = () => {
|
||||
axios.get(props.profileApiUri).then((response) => {
|
||||
let np_new = response.data;
|
||||
np_new.loading = false;
|
||||
const {state: profileInfo, execute: reloadProfile} = useRefreshableAsyncState(
|
||||
() => axios.get(props.profileApiUri).then((r) => r.data),
|
||||
{
|
||||
station: {
|
||||
...NowPlaying.station
|
||||
},
|
||||
services: {
|
||||
backend_running: false,
|
||||
frontend_running: false,
|
||||
has_started: false,
|
||||
needs_restart: false
|
||||
},
|
||||
schedule: []
|
||||
}
|
||||
);
|
||||
|
||||
np.value = np_new;
|
||||
const profileReloadTimeout = computed(() => {
|
||||
return (!document.hidden) ? 15000 : 30000
|
||||
});
|
||||
|
||||
setTimeout(checkNowPlaying, (!document.hidden) ? 15000 : 30000);
|
||||
}).catch((error) => {
|
||||
if (!error.response || error.response.data.code !== 403) {
|
||||
setTimeout(checkNowPlaying, (!document.hidden) ? 30000 : 120000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(checkNowPlaying);
|
||||
useIntervalFn(
|
||||
reloadProfile,
|
||||
profileReloadTimeout
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="card-header bg-primary-dark">
|
||||
<h3 class="card-title">
|
||||
{{ $gettext('AutoDJ Service') }}
|
||||
<running-badge :running="np.services.backend_running" />
|
||||
<running-badge :running="backendRunning" />
|
||||
<br>
|
||||
<small>{{ backendName }}</small>
|
||||
</h3>
|
||||
|
@ -43,7 +43,7 @@
|
|||
{{ $gettext('Restart') }}
|
||||
</a>
|
||||
<a
|
||||
v-show="!np.services.backend_running"
|
||||
v-show="!backendRunning"
|
||||
class="api-call no-reload btn btn-outline-success"
|
||||
:href="backendStartUri"
|
||||
>
|
||||
|
@ -51,7 +51,7 @@
|
|||
{{ $gettext('Start') }}
|
||||
</a>
|
||||
<a
|
||||
v-show="np.services.backend_running"
|
||||
v-show="backendRunning"
|
||||
class="api-call no-reload btn btn-outline-danger"
|
||||
:href="backendStopUri"
|
||||
>
|
||||
|
@ -72,8 +72,8 @@ import backendPanelProps from "~/components/Stations/Profile/backendPanelProps";
|
|||
|
||||
const props = defineProps({
|
||||
...backendPanelProps,
|
||||
np: {
|
||||
type: Object,
|
||||
backendRunning: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<h3 class="card-title">
|
||||
{{ $gettext('Broadcasting Service') }}
|
||||
|
||||
<running-badge :running="np.services.frontend_running" />
|
||||
<running-badge :running="frontendRunning" />
|
||||
<br>
|
||||
<small>{{ frontendName }}</small>
|
||||
</h3>
|
||||
|
@ -103,7 +103,7 @@
|
|||
{{ $gettext('Restart') }}
|
||||
</a>
|
||||
<a
|
||||
v-show="!np.services.frontend_running"
|
||||
v-show="!frontendRunning"
|
||||
class="api-call no-reload btn btn-outline-success"
|
||||
:href="frontendStartUri"
|
||||
>
|
||||
|
@ -111,7 +111,7 @@
|
|||
{{ $gettext('Start') }}
|
||||
</a>
|
||||
<a
|
||||
v-show="np.services.frontend_running"
|
||||
v-show="frontendRunning"
|
||||
class="api-call no-reload btn btn-outline-danger"
|
||||
:href="frontendStopUri"
|
||||
>
|
||||
|
@ -133,8 +133,8 @@ import frontendPanelProps from "~/components/Stations/Profile/frontendPanelProps
|
|||
|
||||
const props = defineProps({
|
||||
...frontendPanelProps,
|
||||
np: {
|
||||
type: Object,
|
||||
frontendRunning: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="outside-card-header d-flex align-items-center mb-3">
|
||||
<div
|
||||
v-if="np.station.listen_url"
|
||||
v-if="station.listen_url"
|
||||
class="flex-shrink-0 mr-3"
|
||||
>
|
||||
<play-button
|
||||
icon-class="outlined xl"
|
||||
:url="np.station.listen_url"
|
||||
:url="station.listen_url"
|
||||
is-stream
|
||||
/>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@ import headerPanelProps from "~/components/Stations/Profile/headerPanelProps";
|
|||
|
||||
const props = defineProps({
|
||||
...headerPanelProps,
|
||||
np: {
|
||||
station: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
|
|
|
@ -198,38 +198,17 @@
|
|||
<script setup>
|
||||
import {BACKEND_LIQUIDSOAP} from '~/components/Entity/RadioAdapters';
|
||||
import Icon from '~/components/Common/Icon';
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {useIntervalFn} from "@vueuse/core";
|
||||
import {computed} from "vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import formatTime from "~/functions/formatTime";
|
||||
import nowPlayingPanelProps from "~/components/Stations/Profile/nowPlayingPanelProps";
|
||||
import useNowPlaying from "~/functions/useNowPlaying";
|
||||
|
||||
const props = defineProps({
|
||||
...nowPlayingPanelProps,
|
||||
np: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const npElapsed = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
useIntervalFn(
|
||||
() => {
|
||||
let current_time = Math.floor(Date.now() / 1000);
|
||||
let np_elapsed = current_time - props.np.now_playing.played_at;
|
||||
if (np_elapsed < 0) {
|
||||
np_elapsed = 0;
|
||||
} else if (np_elapsed >= props.np.now_playing.duration) {
|
||||
np_elapsed = props.np.now_playing.duration;
|
||||
}
|
||||
|
||||
npElapsed.value = np_elapsed;
|
||||
},
|
||||
1000
|
||||
);
|
||||
});
|
||||
const {np, currentTrackDuration, currentTrackElapsed} = useNowPlaying(props);
|
||||
|
||||
const {$ngettext} = useTranslate();
|
||||
|
||||
|
@ -237,8 +216,8 @@ const langListeners = computed(() => {
|
|||
return $ngettext(
|
||||
'%{listeners} Listener',
|
||||
'%{listeners} Listeners',
|
||||
props.np.listeners.total,
|
||||
{listeners: props.np.listeners.total}
|
||||
np.value?.listeners?.total ?? 0,
|
||||
{listeners: np.value?.listeners?.total ?? 0}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -247,17 +226,17 @@ const isLiquidsoap = computed(() => {
|
|||
});
|
||||
|
||||
const timeDisplay = computed(() => {
|
||||
let time_played = npElapsed.value;
|
||||
let time_total = props.np.now_playing.duration;
|
||||
let currentTrackDurationValue = currentTrackDuration.value ?? null;
|
||||
let currentTrackElapsedValue = currentTrackElapsed.value ?? null;
|
||||
|
||||
if (!time_total) {
|
||||
if (!currentTrackDurationValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (time_played > time_total) {
|
||||
time_played = time_total;
|
||||
if (currentTrackElapsedValue > currentTrackDurationValue) {
|
||||
currentTrackElapsedValue = currentTrackDurationValue;
|
||||
}
|
||||
|
||||
return formatTime(time_played) + ' / ' + formatTime(time_total);
|
||||
return formatTime(currentTrackElapsedValue) + ' / ' + formatTime(currentTrackDurationValue);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<col style="width: 78%;">
|
||||
<col style="width: 20%;">
|
||||
</colgroup>
|
||||
<template v-if="np.station.mounts.length > 0">
|
||||
<template v-if="station.mounts.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
|
@ -27,7 +27,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="mount in np.station.mounts"
|
||||
v-for="mount in station.mounts"
|
||||
:key="mount.id"
|
||||
class="align-middle"
|
||||
>
|
||||
|
@ -62,7 +62,7 @@
|
|||
</tbody>
|
||||
</template>
|
||||
|
||||
<template v-if="np.station.remotes.length > 0">
|
||||
<template v-if="station.remotes.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
|
@ -75,7 +75,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="remote in np.station.remotes"
|
||||
v-for="remote in station.remotes"
|
||||
:key="remote.id"
|
||||
class="align-middle"
|
||||
>
|
||||
|
@ -110,7 +110,7 @@
|
|||
</tbody>
|
||||
</template>
|
||||
|
||||
<template v-if="np.station.hls_enabled">
|
||||
<template v-if="station.hls_enabled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
|
@ -126,23 +126,23 @@
|
|||
<td class="pr-1">
|
||||
<play-button
|
||||
icon-class="outlined"
|
||||
:url="np.station.hls_url"
|
||||
:url="station.hls_url"
|
||||
is-stream
|
||||
is-hls
|
||||
/>
|
||||
</td>
|
||||
<td class="pl-1">
|
||||
<a
|
||||
:href="np.station.hls_url"
|
||||
:href="station.hls_url"
|
||||
target="_blank"
|
||||
>{{ np.station.hls_url }}</a>
|
||||
>{{ station.hls_url }}</a>
|
||||
</td>
|
||||
<td class="pl-1 text-right">
|
||||
<icon
|
||||
class="sm align-middle"
|
||||
icon="headset"
|
||||
/>
|
||||
<span class="listeners-total pl-1">{{ np.station.hls_listeners }}</span>
|
||||
<span class="listeners-total pl-1">{{ station.hls_listeners }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -151,14 +151,14 @@
|
|||
<div class="card-actions">
|
||||
<a
|
||||
class="btn btn-outline-primary"
|
||||
:href="np.station.playlist_pls_url"
|
||||
:href="station.playlist_pls_url"
|
||||
>
|
||||
<icon icon="file_download" />
|
||||
{{ $gettext('Download PLS') }}
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-outline-primary"
|
||||
:href="np.station.playlist_m3u_url"
|
||||
:href="station.playlist_m3u_url"
|
||||
>
|
||||
<icon icon="file_download" />
|
||||
{{ $gettext('Download M3U') }}
|
||||
|
@ -172,7 +172,7 @@ import Icon from '~/components/Common/Icon';
|
|||
import PlayButton from "~/components/Common/PlayButton";
|
||||
|
||||
const props = defineProps({
|
||||
np: {
|
||||
station: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import {nowPlayingProps} from "~/functions/useNowPlaying";
|
||||
|
||||
export default {
|
||||
...nowPlayingProps,
|
||||
backendType: {
|
||||
type: String,
|
||||
required: true
|
||||
|
|
|
@ -15,7 +15,6 @@ final class ProfileAction
|
|||
{
|
||||
public function __construct(
|
||||
private readonly Entity\Repository\StationScheduleRepository $scheduleRepo,
|
||||
private readonly Entity\ApiGenerator\NowPlayingApiGenerator $nowPlayingApiGenerator,
|
||||
private readonly Entity\ApiGenerator\StationApiGenerator $stationApiGenerator,
|
||||
private readonly Adapters $adapters,
|
||||
) {
|
||||
|
@ -31,13 +30,10 @@ final class ProfileAction
|
|||
$frontend = $this->adapters->getFrontendAdapter($station);
|
||||
|
||||
$baseUri = new Uri('');
|
||||
$nowPlayingApi = $this->nowPlayingApiGenerator->currentOrEmpty($station, $baseUri);
|
||||
|
||||
$apiResponse = new Entity\Api\StationProfile();
|
||||
$apiResponse->fromParentObject($nowPlayingApi);
|
||||
|
||||
$apiResponse->station = ($this->stationApiGenerator)($station, $baseUri, true);
|
||||
$apiResponse->cache = 'database';
|
||||
|
||||
$apiResponse->services = new Entity\Api\StationServiceStatus(
|
||||
null !== $backend && $backend->isRunning($station),
|
||||
|
@ -48,7 +44,6 @@ final class ProfileAction
|
|||
|
||||
$apiResponse->schedule = $this->scheduleRepo->getUpcomingSchedule($station);
|
||||
|
||||
$apiResponse->update();
|
||||
$apiResponse->resolveUrls($request->getRouter()->getBaseUrl());
|
||||
|
||||
return $response->withJson($apiResponse);
|
||||
|
|
|
@ -8,6 +8,7 @@ use App\Enums\StationPermissions;
|
|||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Radio\Adapters;
|
||||
use App\VueComponent\NowPlayingComponent;
|
||||
use App\VueComponent\StationFormComponent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
@ -19,6 +20,7 @@ final class ProfileController
|
|||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly StationFormComponent $stationFormComponent,
|
||||
private readonly NowPlayingComponent $nowPlayingComponent,
|
||||
private readonly Adapters $adapters,
|
||||
) {
|
||||
}
|
||||
|
@ -73,6 +75,8 @@ final class ProfileController
|
|||
id: 'profile',
|
||||
title: __('Profile'),
|
||||
props: [
|
||||
...$this->nowPlayingComponent->getProps($request),
|
||||
|
||||
// Common
|
||||
'backendType' => $station->getBackendType(),
|
||||
'frontendType' => $station->getFrontendType(),
|
||||
|
|
|
@ -4,15 +4,23 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Api;
|
||||
|
||||
use App\Entity\Api\NowPlaying\NowPlaying;
|
||||
use App\Traits\LoadFromParentObject;
|
||||
use App\Entity\Api\NowPlaying\Station;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class StationProfile extends NowPlaying
|
||||
final class StationProfile
|
||||
{
|
||||
use LoadFromParentObject;
|
||||
public Station $station;
|
||||
|
||||
public StationServiceStatus $services;
|
||||
|
||||
/** @var StationSchedule[] */
|
||||
public array $schedule = [];
|
||||
|
||||
/**
|
||||
* Iterate through sub-items and re-resolve any Uri instances to reflect base URL changes.
|
||||
*/
|
||||
public function resolveUrls(UriInterface $base): void
|
||||
{
|
||||
$this->station->resolveUrls($base);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue