add missing translation, add bigger drop area when list is empty

This commit is contained in:
jb-alvarado 2024-04-19 10:59:17 +02:00
parent 3886a6187e
commit 76cbc901c1
6 changed files with 196 additions and 107 deletions

View File

@ -5,7 +5,7 @@
<div
class="relative flex flex-col bg-base-100 w-[800px] min-w-[300px] max-w-[90vw] h-[680px] rounded-md p-5 shadow-xl"
>
<div class="font-bold text-lg">Generate Program</div>
<div class="font-bold text-lg">{{ $t('player.generateProgram') }}</div>
<div class="h-[calc(100%-95px)] mt-3">
<div role="tablist" class="tabs tabs-bordered">
@ -14,7 +14,7 @@
name="my_tabs_2"
role="tab"
class="tab"
aria-label="Simple"
:aria-label="$t('player.simple')"
checked
@change="advancedGenerator = false"
/>
@ -91,7 +91,7 @@
name="my_tabs_2"
role="tab"
class="tab"
aria-label="Advanced"
:aria-label="$t('player.advanced')"
@change=";(advancedGenerator = true), resetCheckboxes()"
/>
<div role="tabpanel" class="tab-content pt-3">
@ -118,7 +118,7 @@
<button
type="button"
class="btn btn-sm btn-primary"
title="Add time block"
:title="$t('player.addBlock')"
@click="addTemplate()"
>
<i class="bi bi-folder-plus" />
@ -170,11 +170,11 @@
:key="item.start"
class="flex flex-col gap-1 justify-center items-center border border-my-gray rounded mt-1 p-1"
>
<div class="grid grid-cols-[50px_67px_70px_67px_50px] join">
<div class="grid grid-cols-[60px_67px_70px_67px_70px] join">
<div
class="input input-sm input-bordered join-item px-2 text-center bg-base-200"
class="input input-sm input-bordered join-item px-2 text-center bg-base-200 leading-7"
>
Start:
{{ $t('player.start') }}:
</div>
<input
v-model="item.start"
@ -182,9 +182,9 @@
class="input input-sm input-bordered join-item px-2 text-center"
/>
<div
class="input input-sm input-bordered join-item px-2 text-center bg-base-200"
class="input input-sm input-bordered join-item px-2 text-center bg-base-200 leading-7"
>
Duration:
{{ $t('player.duration') }}:
</div>
<input
v-model="item.duration"
@ -196,7 +196,7 @@
:class="item.shuffle ? 'bg-base-100' : 'bg-base-300'"
@click="item.shuffle = !item.shuffle"
>
{{ item.shuffle ? 'Shuffle' : 'Sorted' }}
{{ item.shuffle ? $t('player.shuffle') : $t('player.sorted') }}
</button>
</div>
@ -241,7 +241,7 @@
<div class="flex h-14 pt-6 justify-end items-center">
<div v-if="!advancedGenerator" class="form-control">
<label class="label cursor-pointer w-12">
<span class="label-text">All</span>
<span class="label-text">{{ $t('player.all') }}</span>
<input
v-model="generateFromAll"
type="checkbox"
@ -256,10 +256,10 @@
class="btn btn-sm btn-primary join-item"
@click="resetCheckboxes(), resetTemplate(), close()"
>
Cancel
{{ $t('cancel') }}
</button>
<button type="button" class="btn btn-sm btn-primary join-item" @click="generatePlaylist(), close()">
Ok
{{ $t('ok') }}
</button>
</div>
</div>
@ -278,13 +278,19 @@ const playlistStore = usePlaylist()
const { processPlaylist } = playlistOperations()
defineProps({
const prop = defineProps({
close: {
type: Function,
default() {
return ''
},
},
switchClass: {
type: Function,
default() {
return ''
},
},
})
const advancedGenerator = ref(false)
@ -311,42 +317,6 @@ const templateTargetSortOptions = {
handle: '.grabbing',
}
async function generatePlaylist() {
playlistStore.isLoading = true
let body = null as BodyObject | null
if (selectedFolders.value.length > 0 && !generateFromAll.value) {
body = { paths: selectedFolders.value }
}
if (advancedGenerator.value) {
if (body) {
body.template = template.value
} else {
body = { template: template.value }
}
}
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${playlistStore.listDate}`, {
method: 'POST',
headers: { ...configStore.contentType, ...authStore.authHeader },
body,
})
.then((response: any) => {
playlistStore.playlist = processPlaylist(playlistStore.listDate, response.program, false)
indexStore.msgAlert('success', 'Generate Playlist done...', 2)
})
.catch((e: any) => {
indexStore.msgAlert('error', e.data ? e.data : e, 4)
})
// reset selections
resetCheckboxes()
resetTemplate()
playlistStore.isLoading = false
}
function setSelectedFolder(event: any, folder: string) {
if (event.target.checked) {
selectedFolders.value.push(folder)
@ -414,4 +384,41 @@ function addTemplate() {
paths: [],
})
}
async function generatePlaylist() {
playlistStore.isLoading = true
let body = null as BodyObject | null
if (selectedFolders.value.length > 0 && !generateFromAll.value) {
body = { paths: selectedFolders.value }
}
if (advancedGenerator.value) {
if (body) {
body.template = template.value
} else {
body = { template: template.value }
}
}
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${playlistStore.listDate}`, {
method: 'POST',
headers: { ...configStore.contentType, ...authStore.authHeader },
body,
})
.then((response: any) => {
playlistStore.playlist = processPlaylist(playlistStore.listDate, response.program, false)
prop.switchClass()
indexStore.msgAlert('success', 'Generate Playlist done...', 2)
})
.catch((e: any) => {
indexStore.msgAlert('error', e.data ? e.data : e, 4)
})
// reset selections
resetCheckboxes()
resetTemplate()
playlistStore.isLoading = false
}
</script>

View File

@ -11,7 +11,7 @@
{{ $t('player.start') }}
</div>
</th>
<th class="w-auto p-0 text-left">
<th class="w-full p-0 text-left">
<div class="border-b border-my-gray px-4 py-3 -mb-[2px]">
{{ $t('player.file') }}
</div>
@ -53,7 +53,10 @@
</th>
</tr>
</thead>
<Sortable
id="sort-container"
ref="sortContainer"
:list="playlistStore.playlist"
item-key="uid"
tag="tbody"
@ -127,6 +130,7 @@ const playlistStore = usePlaylist()
const { secToHMS, filename, secondsToTime } = stringFormatter()
const { processPlaylist, genUID } = playlistOperations()
const sortContainer = ref()
const todayDate = ref($dayjs().utcOffset(configStore.utcOffset).format('YYYY-MM-DD'))
const { listDate } = storeToRefs(usePlaylist())
@ -161,10 +165,22 @@ onMounted(() => {
props.getPlaylist()
})
watch([listDate], async () => {
await props.getPlaylist()
watch([listDate], () => {
props.getPlaylist()
})
defineExpose({
classSwitcher,
})
function classSwitcher() {
if (playlistStore.playlist.length === 0 && sortContainer.value) {
sortContainer.value.sortable.el.classList.add('is-empty')
} else {
sortContainer.value.sortable.el.classList.remove('is-empty')
}
}
function setCategory(event: any, item: PlaylistItem) {
if (event.target.checked) {
item.category = 'advertisement'
@ -209,7 +225,8 @@ function addClip(event: any) {
duration: mediaStore.folderTree.files[o].duration,
})
playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
classSwitcher()
processPlaylist(listDate.value, playlistStore.playlist, false)
nextTick(() => {
const newNode = document.getElementById(`clip-${n}`)
@ -221,16 +238,30 @@ function addClip(event: any) {
function moveItemInArray(event: any) {
playlistStore.playlist.splice(event.newIndex, 0, playlistStore.playlist.splice(event.oldIndex, 1)[0])
playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
processPlaylist(listDate.value, playlistStore.playlist, false)
removeBG(event.item)
}
function deletePlaylistItem(index: number) {
playlistStore.playlist.splice(index, 1)
classSwitcher()
}
</script>
<style>
#sort-container.is-empty:not(:has(.sortable-ghost)):after {
content: '\f1bc';
font-family: 'bootstrap-icons';
opacity: 0.3;
font-size: 50px;
width: 100%;
height: 210px;
display: flex;
position: absolute;
justify-content: center;
align-items: center;
}
/*
format dragging element
*/

View File

@ -190,40 +190,44 @@ export const playlistOperations = () => {
for (const item of list) {
if (configStore.playout.playlist.startInSec === begin) {
if (!forSave) {
item.date = date
} else {
delete item.date
item.date = date
}
if (forSave) {
delete item.date
if (!item.audio) {
delete item.audio
}
}
if (!item.uid) {
item.uid = genUID()
}
if (!item.category) {
delete item.category
}
item.begin = begin
if (!item.custom_filter) {
delete item.custom_filter
}
if (!item.audio) {
delete item.audio
}
if (
begin + (item.out - item.in) >
configStore.playout.playlist.startInSec + configStore.playout.playlist.lengthInSec
) {
item.out =
configStore.playout.playlist.startInSec + configStore.playout.playlist.lengthInSec - begin
}
} else {
if (!item.uid) {
item.uid = genUID()
}
if (!item.category) {
delete item.category
}
if (!item.custom_filter) {
delete item.custom_filter
}
if (
begin >= configStore.playout.playlist.startInSec + configStore.playout.playlist.lengthInSec &&
!configStore.playout.playlist.infinit
) {
if (forSave) {
break
} else {
if (
begin >= configStore.playout.playlist.startInSec + configStore.playout.playlist.lengthInSec &&
!configStore.playout.playlist.infinit
) {
item.overtime = true
}
item.begin = begin
}
newList.push(item)

View File

@ -68,6 +68,19 @@ export default {
save: 'Wiedergabeliste speichern',
deletePlaylist: 'Wiedergabeliste löschen',
unsavedProgram: 'Es existiert Programm das nicht gespeichert ist!',
copyTo: 'Kopiere aktuelles Programm nach',
addEdit: 'Quelle hinzufügen/bearbeiten',
audio: 'Audio',
customFilter: 'Benutzerdefinierter Filter',
deleteFrom: 'Programm löschen von',
deleteSuccess: 'Wiedergabeliste gelöscht...',
generateProgram: 'Programm generieren',
simple: 'Einfach',
advanced: 'Erweitert',
sorted: 'Sortiert',
shuffle: 'Zufall',
all: 'Alle',
addBlock: 'Zeitblock hinzufügen',
},
media: {
notExists: 'Speicher existiert nicht!',

View File

@ -68,6 +68,19 @@ export default {
save: 'Save Playlist',
deletePlaylist: 'Delete Playlist',
unsavedProgram: 'There is a program that is not saved!',
copyTo: 'Copy current Program to',
addEdit: 'Add/Edit Source',
audio: 'Audio',
customFilter: 'Custom Filter',
deleteFrom: 'Delete program from',
deleteSuccess: 'Playlist deleted...',
generateProgram: 'Generate Program',
simple: 'Simple',
advanced: 'Advanced',
sorted: 'Sorted',
shuffle: 'Shuffle',
all: 'All',
addBlock: 'Add time block',
},
media: {
notExists: 'Storage not exist!',

View File

@ -31,6 +31,7 @@
</pane>
<pane>
<PlaylistTable
ref="playlistTable"
:get-playlist="getPlaylist"
:edit-item="editPlaylistItem"
:preview="setPreviewData"
@ -97,7 +98,7 @@
<GenericModal
:show="showPreviewModal"
:title="`Preview: ${previewName}`"
:title="`${$t('media.preview')}: ${previewName}`"
:hide-buttons="true"
:modal-action="closePlayer"
>
@ -107,25 +108,41 @@
</div>
</GenericModal>
<GenericModal :show="showSourceModal" title="Add/Edit Source" :modal-action="processSource">
<GenericModal :show="showCopyModal" :title="$t('player.copyTo')" :modal-action="savePlaylist">
<VueDatePicker
v-model="targetDate"
:clearable="false"
:hide-navigation="['time']"
:action-row="{ showCancel: false, showSelect: false, showPreview: false }"
:format="calendarFormat"
model-type="yyyy-MM-dd"
auto-apply
:locale="locale"
:dark="colorMode.value === 'dark'"
input-class-name="input input-sm !input-bordered !w-[full text-right !pe-3"
required
/>
</GenericModal>
<GenericModal :show="showSourceModal" :title="$t('player.addEdit')" :modal-action="processSource">
<div>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">In</span>
<span class="label-text">{{ $t('player.in') }}</span>
</div>
<input v-model.number="newSource.in" type="number" class="input input-sm input-bordered w-full" />
</label>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Out</span>
<span class="label-text">{{ $t('player.Out') }}</span>
</div>
<input v-model.number="newSource.out" type="number" class="input input-sm input-bordered w-full" />
</label>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Duration</span>
<span class="label-text">{{ $t('player.duration') }}</span>
</div>
<input
v-model.number="newSource.duration"
@ -136,35 +153,35 @@
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Source</span>
<span class="label-text">{{ $t('player.file') }}</span>
</div>
<input v-model="newSource.source" type="text" class="input input-sm input-bordered w-full" />
</label>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Audio</span>
<span class="label-text">{{ $t('player.audio') }}</span>
</div>
<input v-model="newSource.audio" type="text" class="input input-sm input-bordered w-full" />
</label>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Custom Filter</span>
<span class="label-text">{{ $t('player.customFilter') }}</span>
</div>
<input v-model="newSource.custom_filter" type="text" class="input input-sm input-bordered w-full" />
</label>
<div class="form-control">
<label class="cursor-pointer label">
<span class="label-text">Advertisement</span>
<span class="label-text">{{ $t('player.ad') }}</span>
<input type="checkbox" class="checkbox checkbox-sm" @click="isAd" />
</label>
</div>
</div>
</GenericModal>
<GenericModal :show="showImportModal" title="Import Playlist" :modal-action="importPlaylist">
<GenericModal :show="showImportModal" :title="$t('player.import')" :modal-action="importPlaylist">
<input
type="file"
class="file-input file-input-sm file-input-bordered w-full"
@ -173,17 +190,17 @@
/>
</GenericModal>
<GenericModal :show="showCopyModal" :title="`Copy Program ${listDate}`" :modal-action="savePlaylist">
<input v-model="targetDate" type="date" class="input input-sm input-bordered w-full" />
</GenericModal>
<GenericModal :show="showDeleteModal" title="Delete Program" :modal-action="deletePlaylist">
<span>
Delete program from <strong>{{ listDate }}</strong>
{{ $t('player.deleteFrom') }} <strong>{{ listDate }}</strong>
</span>
</GenericModal>
<PlaylistGenerator v-if="showPlaylistGenerator" :close="closeGenerator" />
<PlaylistGenerator
v-if="showPlaylistGenerator"
:close="closeGenerator"
:switch-class="playlistTable.classSwitcher"
/>
</div>
</template>
@ -211,6 +228,7 @@ const { listDate } = storeToRefs(usePlaylist())
const todayDate = ref($dayjs().utcOffset(configStore.utcOffset).format('YYYY-MM-DD'))
const targetDate = ref($dayjs().utcOffset(configStore.utcOffset).format('YYYY-MM-DD'))
const playlistTable = ref()
const editId = ref(-1)
const textFile = ref()
@ -262,6 +280,8 @@ async function getPlaylist() {
} else {
scrollTo(0)
}
playlistTable.value.classSwitcher()
}
function closeGenerator() {
@ -331,11 +351,12 @@ function processSource(process: boolean) {
if (process) {
if (editId.value === -1) {
playlistStore.playlist.push(newSource.value)
playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
} else {
playlistStore.playlist[editId.value] = newSource.value
playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
}
processPlaylist(listDate.value, playlistStore.playlist, false)
playlistTable.value.classSwitcher()
}
editId.value = -1
@ -428,9 +449,10 @@ async function importPlaylist(imp: boolean) {
body: formData,
}
)
.then((response) => {
.then(async (response) => {
indexStore.msgAlert('success', String(response), 2)
playlistStore.getPlaylist(listDate.value)
await playlistStore.getPlaylist(listDate.value)
playlistTable.value.classSwitcher()
})
.catch((e: string) => {
indexStore.msgAlert('error', e, 4)
@ -449,8 +471,7 @@ async function savePlaylist(save: boolean) {
return
}
playlistStore.playlist = processPlaylist(listDate.value, $_.cloneDeep(playlistStore.playlist), true)
const saveList = playlistStore.playlist.map(({ begin, ...item }) => item)
const saveList = processPlaylist(listDate.value, $_.cloneDeep(playlistStore.playlist), true)
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/`, {
method: 'POST',
@ -462,6 +483,7 @@ async function savePlaylist(save: boolean) {
}),
})
.then((response: any) => {
playlistTable.value.classSwitcher()
indexStore.msgAlert('success', response, 2)
})
.catch((e: any) => {
@ -483,10 +505,9 @@ async function deletePlaylist(del: boolean) {
headers: { ...configStore.contentType, ...authStore.authHeader },
}).then(() => {
playlistStore.playlist = []
indexStore.msgAlert('warning', 'Playlist deleted...', 2)
playlistTable.value.classSwitcher()
indexStore.msgAlert('warning', t('player.deleteSuccess'), 2)
})
}
}
</script>