mirror of
https://github.com/AzuraCast/AzuraCast.git
synced 2024-06-17 22:47:04 +00:00
307 lines
14 KiB
Vue
307 lines
14 KiB
Vue
<template>
|
|
<div>
|
|
<b-card no-body>
|
|
<b-card-header header-bg-variant="primary-dark">
|
|
<b-row class="align-items-center">
|
|
<b-col md="6">
|
|
<h2 class="card-title" key="lang_playlists" v-translate>Playlists</h2>
|
|
</b-col>
|
|
<b-col md="6" class="text-right text-muted">
|
|
<translate key="lang_station_tz" :translate-params="{ tz: stationTimeZone }">This station's time zone is currently %{tz}.</translate>
|
|
</b-col>
|
|
</b-row>
|
|
</b-card-header>
|
|
<b-tabs pills card lazy>
|
|
<b-tab :title="langAllPlaylistsTab" no-body>
|
|
<b-card-body body-class="card-padding-sm">
|
|
<b-button variant="outline-primary" @click.prevent="doCreate">
|
|
<icon icon="add"></icon>
|
|
<translate key="lang_add_playlist">Add Playlist</translate>
|
|
</b-button>
|
|
</b-card-body>
|
|
|
|
<data-table ref="datatable" id="station_playlists" paginated :fields="fields" :responsive="false"
|
|
:api-url="listUrl">
|
|
<template #cell(actions)="row">
|
|
<b-button-group size="sm">
|
|
<b-button size="sm" variant="primary" @click.prevent="doEdit(row.item.links.self)">
|
|
<translate key="lang_btn_edit">Edit</translate>
|
|
</b-button>
|
|
<b-button size="sm" variant="danger" @click.prevent="doDelete(row.item.links.self)">
|
|
<translate key="lang_btn_delete">Delete</translate>
|
|
</b-button>
|
|
|
|
<b-dropdown size="sm" variant="dark" boundary="window" :text="langMore">
|
|
<b-dropdown-item @click.prevent="doModify(row.item.links.toggle)">
|
|
{{ langToggleButton(row.item) }}
|
|
</b-dropdown-item>
|
|
<b-dropdown-item @click.prevent="doImport(row.item.links.import)"
|
|
v-if="row.item.source === 'songs'">
|
|
{{ langImportButton }}
|
|
</b-dropdown-item>
|
|
<b-dropdown-item @click.prevent="doReorder(row.item.links.order)"
|
|
v-if="row.item.source === 'songs' && row.item.order === 'sequential'">
|
|
{{ langReorderButton }}
|
|
</b-dropdown-item>
|
|
<b-dropdown-item @click.prevent="doQueue(row.item.links.queue)"
|
|
v-if="row.item.source === 'songs' && row.item.order !== 'random'">
|
|
{{ langQueueButton }}
|
|
</b-dropdown-item>
|
|
<b-dropdown-item @click.prevent="doModify(row.item.links.reshuffle)"
|
|
v-if="row.item.order === 'shuffle'">
|
|
{{ langReshuffleButton }}
|
|
</b-dropdown-item>
|
|
<b-dropdown-item @click.prevent="doClone(row.item.name, row.item.links.clone)">
|
|
{{ langCloneButton }}
|
|
</b-dropdown-item>
|
|
<template v-for="format in ['pls', 'm3u']">
|
|
<b-dropdown-item :href="row.item.links.export[format]" target="_blank">
|
|
<translate :key="'lang_format_'+format" :translate-params="{ format: format.toUpperCase() }">
|
|
Export %{format}
|
|
</translate>
|
|
</b-dropdown-item>
|
|
</template>
|
|
</b-dropdown>
|
|
</b-button-group>
|
|
</template>
|
|
<template #cell(name)="row">
|
|
<h5 class="m-0">{{ row.item.name }}</h5>
|
|
<div>
|
|
<span class="badge badge-dark">
|
|
<translate key="lang_song_based_playlist" v-if="row.item.source === 'songs'">
|
|
Song-based
|
|
</translate>
|
|
<translate key="lang_remote_url_playlist" v-else>
|
|
Remote URL
|
|
</translate>
|
|
</span>
|
|
<span class="badge badge-primary" v-if="row.item.is_jingle">
|
|
<translate key="lang_jingle_mode">Jingle Mode</translate>
|
|
</span>
|
|
<span class="badge badge-info"
|
|
v-if="row.item.source === 'songs' && row.item.order === 'sequential'">
|
|
<translate key="lang_sequential">Sequential</translate>
|
|
</span>
|
|
<span class="badge badge-info" v-if="row.item.include_in_on_demand">
|
|
<translate key="lang_on_demand">On-Demand</translate>
|
|
</span>
|
|
<span class="badge badge-success" v-if="row.item.include_in_automation">
|
|
<translate key="lang_auto_assigned">Auto-Assigned</translate>
|
|
</span>
|
|
<span class="badge badge-danger" v-if="!row.item.is_enabled">
|
|
<translate key="lang_disabled">Disabled</translate>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
<template #cell(scheduling)="row">
|
|
<span v-html="formatType(row.item)"></span>
|
|
</template>
|
|
<template #cell(num_songs)="row">
|
|
<template v-if="row.item.source === 'songs'">
|
|
<a :href="filesUrl+'#playlist:'+encodeURIComponent(row.item.name)">
|
|
{{ row.item.num_songs }}
|
|
</a>
|
|
({{ formatLength(row.item.total_length) }})
|
|
</template>
|
|
<template v-else> </template>
|
|
</template>
|
|
</data-table>
|
|
</b-tab>
|
|
<b-tab :title="langScheduleViewTab" no-body>
|
|
<schedule ref="schedule" :schedule-url="scheduleUrl" :station-time-zone="stationTimeZone"
|
|
@click="doCalendarClick"></schedule>
|
|
</b-tab>
|
|
</b-tabs>
|
|
</b-card>
|
|
|
|
<edit-modal ref="editModal" :create-url="listUrl" :station-time-zone="stationTimeZone"
|
|
:enable-advanced-features="enableAdvancedFeatures"
|
|
@relist="relist" @needs-restart="mayNeedRestart"></edit-modal>
|
|
<reorder-modal ref="reorderModal"></reorder-modal>
|
|
<queue-modal ref="queueModal"></queue-modal>
|
|
<reorder-modal ref="reorderModal"></reorder-modal>
|
|
<import-modal ref="importModal" @relist="relist"></import-modal>
|
|
<clone-modal ref="cloneModal" @relist="relist" @needs-restart="mayNeedRestart"></clone-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import DataTable from '~/components/Common/DataTable';
|
|
import Schedule from '~/components/Common/ScheduleView';
|
|
import EditModal from './Playlists/EditModal';
|
|
import ReorderModal from './Playlists/ReorderModal';
|
|
import ImportModal from './Playlists/ImportModal';
|
|
import QueueModal from './Playlists/QueueModal';
|
|
import Icon from '~/components/Common/Icon';
|
|
import CloneModal from './Playlists/CloneModal';
|
|
import {DateTime} from 'luxon';
|
|
import humanizeDuration from 'humanize-duration';
|
|
|
|
export default {
|
|
name: 'StationPlaylists',
|
|
components: {CloneModal, Icon, QueueModal, ImportModal, ReorderModal, EditModal, Schedule, DataTable},
|
|
props: {
|
|
listUrl: String,
|
|
scheduleUrl: String,
|
|
filesUrl: String,
|
|
restartStatusUrl: String,
|
|
stationTimeZone: String,
|
|
useManualAutoDj: Boolean,
|
|
enableAdvancedFeatures: Boolean
|
|
},
|
|
data () {
|
|
return {
|
|
fields: [
|
|
{key: 'name', isRowHeader: true, label: this.$gettext('Playlist'), sortable: true},
|
|
{key: 'scheduling', label: this.$gettext('Scheduling'), sortable: false},
|
|
{key: 'num_songs', label: this.$gettext('# Songs'), sortable: false},
|
|
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
|
|
]
|
|
};
|
|
},
|
|
computed: {
|
|
langAllPlaylistsTab () {
|
|
return this.$gettext('All Playlists');
|
|
},
|
|
langScheduleViewTab () {
|
|
return this.$gettext('Schedule View');
|
|
},
|
|
langMore () {
|
|
return this.$gettext('More');
|
|
},
|
|
langReorderButton () {
|
|
return this.$gettext('Reorder');
|
|
},
|
|
langQueueButton () {
|
|
return this.$gettext('Playback Queue');
|
|
},
|
|
langReshuffleButton () {
|
|
return this.$gettext('Reshuffle');
|
|
},
|
|
langCloneButton () {
|
|
return this.$gettext('Duplicate');
|
|
},
|
|
langImportButton () {
|
|
return this.$gettext('Import from PLS/M3U');
|
|
}
|
|
},
|
|
methods: {
|
|
langToggleButton (record) {
|
|
return (record.is_enabled)
|
|
? this.$gettext('Disable')
|
|
: this.$gettext('Enable');
|
|
},
|
|
formatTime (time) {
|
|
return DateTime.fromSeconds(time).setZone(this.stationTimeZone).toLocaleString(
|
|
{...DateTime.DATETIME_MED, ...App.time_config}
|
|
);
|
|
},
|
|
formatLength (length) {
|
|
return humanizeDuration(length * 1000, {
|
|
round: true,
|
|
language: App.locale_short,
|
|
fallbacks: ['en']
|
|
});
|
|
},
|
|
formatType (record) {
|
|
if (!record.is_enabled) {
|
|
return this.$gettext('Disabled');
|
|
}
|
|
|
|
switch (record.type) {
|
|
case 'default':
|
|
return this.$gettext('General Rotation') + '<br>' + this.$gettext('Weight') + ': ' + record.weight;
|
|
|
|
case 'once_per_x_songs':
|
|
let oncePerSongs = this.$gettext('Once per %{songs} Songs');
|
|
return this.$gettextInterpolate(oncePerSongs, { songs: record.play_per_songs });
|
|
|
|
case 'once_per_x_minutes':
|
|
let oncePerMinutes = this.$gettext('Once per %{minutes} Minutes');
|
|
return this.$gettextInterpolate(oncePerMinutes, { minutes: record.play_per_minutes });
|
|
|
|
case 'once_per_hour':
|
|
let oncePerHour = this.$gettext('Once per Hour (at %{minute})');
|
|
return this.$gettextInterpolate(oncePerHour, { minute: record.play_per_hour_minute });
|
|
|
|
default:
|
|
return this.$gettext('Custom');
|
|
}
|
|
},
|
|
relist () {
|
|
if (this.$refs.datatable) {
|
|
this.$refs.datatable.refresh();
|
|
}
|
|
if (this.$refs.schedule) {
|
|
this.$refs.schedule.refresh();
|
|
}
|
|
},
|
|
doCreate () {
|
|
this.$refs.editModal.create();
|
|
},
|
|
doCalendarClick (event) {
|
|
this.doEdit(event.extendedProps.edit_url);
|
|
},
|
|
doEdit (url) {
|
|
this.$refs.editModal.edit(url);
|
|
},
|
|
doReorder (url) {
|
|
this.$refs.reorderModal.open(url);
|
|
},
|
|
doQueue (url) {
|
|
this.$refs.queueModal.open(url);
|
|
},
|
|
doImport (url) {
|
|
this.$refs.importModal.open(url);
|
|
},
|
|
doClone (name, url) {
|
|
this.$refs.cloneModal.open(name, url);
|
|
},
|
|
doModify (url) {
|
|
this.$wrapWithLoading(
|
|
this.axios.put(url)
|
|
).then((resp) => {
|
|
this.needsRestart();
|
|
|
|
this.$notifySuccess(resp.data.message);
|
|
this.relist();
|
|
});
|
|
},
|
|
doDelete (url) {
|
|
this.$confirmDelete({
|
|
title: this.$gettext('Delete Playlist?'),
|
|
}).then((result) => {
|
|
if (result.value) {
|
|
this.$wrapWithLoading(
|
|
this.axios.delete(url)
|
|
).then((resp) => {
|
|
this.needsRestart();
|
|
|
|
this.$notifySuccess(resp.data.message);
|
|
this.relist();
|
|
});
|
|
}
|
|
});
|
|
},
|
|
mayNeedRestart() {
|
|
if (!this.useManualAutoDj) {
|
|
return;
|
|
}
|
|
|
|
this.axios.get(this.restartStatusUrl).then((resp) => {
|
|
if (resp.data.needs_restart) {
|
|
this.needsRestart();
|
|
}
|
|
});
|
|
},
|
|
needsRestart() {
|
|
if (!this.useManualAutoDj) {
|
|
return;
|
|
}
|
|
|
|
document.dispatchEvent(new CustomEvent("station-needs-restart"));
|
|
}
|
|
}
|
|
};
|
|
</script>
|