add advanced playlist generator, update packages

This commit is contained in:
jb-alvarado 2023-09-08 10:16:34 +02:00
parent 2f3234221a
commit 806d533bc2
8 changed files with 2237 additions and 1260 deletions

2940
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
"@nuxt/types": "^2.17.1", "@nuxt/types": "^2.17.1",
"@pinia/nuxt": "^0.4.11", "@pinia/nuxt": "^0.4.11",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@vueuse/core": "^10.3.0", "@vueuse/core": "^10.4.1",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
"cookie-universal-nuxt": "^2.2.2", "cookie-universal-nuxt": "^2.2.2",
@ -28,22 +28,22 @@
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"sortablejs-vue3": "^1.2.9", "sortablejs-vue3": "^1.2.9",
"splitpanes": "^3.1.5", "splitpanes": "^3.1.5",
"video.js": "^8.3.0", "video.js": "^8.5.2",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxtjs/eslint-config": "^12.0.0", "@nuxtjs/eslint-config": "^12.0.0",
"@types/bootstrap": "^5.2.6", "@types/bootstrap": "^5.2.6",
"@types/lodash": "^4.14.196", "@types/lodash": "^4.14.198",
"@types/splitpanes": "^2.2.1", "@types/splitpanes": "^2.2.1",
"@types/video.js": "^7.3.52", "@types/video.js": "^7.3.52",
"eslint": "^8.46.0", "eslint": "^8.48.0",
"eslint-plugin-nuxt": "^4.0.0", "eslint-plugin-nuxt": "^4.0.0",
"fibers": "^5.0.3", "fibers": "^5.0.3",
"nuxt": "3.6.5", "nuxt": "3.7.1",
"postcss": "^8.4.27", "postcss": "^8.4.29",
"postcss-loader": "^7.3.3", "postcss-loader": "^7.3.3",
"sass": "^1.64.2", "sass": "^1.66.1",
"sass-loader": "^13.3.2" "sass-loader": "^13.3.2"
} }
} }

View File

@ -2,8 +2,8 @@
<div> <div>
<Menu /> <Menu />
<div class="container-fluid configure-container"> <div class="container-fluid configure-container">
<div class="d-flex align-items-start config-tab"> <div class="d-flex align-items-start h-100">
<div class="nav flex-column nav-pills me-3" id="v-pills-tab" role="tablist" aria-orientation="vertical"> <div class="nav flex-column nav-pills h-100 me-3" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<button <button
class="nav-link active" class="nav-link active"
id="v-pills-gui-tab" id="v-pills-gui-tab"
@ -44,9 +44,9 @@
User User
</button> </button>
</div> </div>
<div class="tab-content" id="v-pills-tabContent"> <div class="tab-content h-100" id="v-pills-tabContent">
<div <div
class="tab-pane show active" class="tab-pane h-100 overflow-y-auto show active"
id="v-pills-gui" id="v-pills-gui"
role="tabpanel" role="tabpanel"
aria-labelledby="v-pills-gui-tab" aria-labelledby="v-pills-gui-tab"
@ -55,12 +55,12 @@
<GuiConfig /> <GuiConfig />
</div> </div>
</div> </div>
<div class="tab-pane" id="v-pills-playout" role="tabpanel" aria-labelledby="v-pills-playout-tab"> <div class="tab-pane h-100 overflow-y-auto" id="v-pills-playout" role="tabpanel" aria-labelledby="v-pills-playout-tab">
<div class="config-container"> <div class="config-container">
<PlayoutConfig /> <PlayoutConfig />
</div> </div>
</div> </div>
<div class="tab-pane" id="v-pills-user" role="tabpanel" aria-labelledby="v-pills-user-tab"> <div class="tab-pane h-100 overflow-y-auto" id="v-pills-user" role="tabpanel" aria-labelledby="v-pills-user-tab">
<div class="config-container"> <div class="config-container">
<UserConfig /> <UserConfig />
</div> </div>
@ -99,15 +99,4 @@ onBeforeUnmount(() => {
padding: 0; padding: 0;
width: 90vw; width: 90vw;
} }
.config-tab,
.tab-content,
.nav-pills {
height: 100%;
}
.tab-pane {
height: 100%;
overflow-y: auto;
}
</style> </style>

View File

@ -49,7 +49,7 @@
<li <li
class="list-group-item browser-item" class="list-group-item browser-item"
v-for="folder in mediaStore.folderTree.folders" v-for="folder in mediaStore.folderTree.folders"
:key="folder" :key="folder.uid"
> >
<div class="row"> <div class="row">
<div class="col-1 browser-icons-col"> <div class="col-1 browser-icons-col">
@ -59,9 +59,9 @@
<a <a
class="link-light" class="link-light"
href="#" href="#"
@click="getPath(`/${mediaStore.folderTree.source}/${folder}`)" @click="getPath(`/${mediaStore.folderTree.source}/${folder.name}`)"
> >
{{ folder }} {{ folder.name }}
</a> </a>
</div> </div>
<div class="col-1 folder-delete"> <div class="col-1 folder-delete">
@ -71,7 +71,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#deleteModal" data-bs-target="#deleteModal"
@click=" @click="
deleteName = `/${mediaStore.folderTree.source}/${folder}`.replace( deleteName = `/${mediaStore.folderTree.source}/${folder.name}`.replace(
/\/[/]+/g, /\/[/]+/g,
'/' '/'
) )
@ -300,7 +300,7 @@
</div> </div>
<form @submit.prevent="onSubmitCreateFolder" @reset="onCancelCreateFolder"> <form @submit.prevent="onSubmitCreateFolder" @reset="onCancelCreateFolder">
<div class="modal-body"> <div class="modal-body">
<input type="text" class="form-control" v-model="folderName" /> <input type="text" class="form-control" v-model="folderName.name" />
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="reset" class="btn btn-primary" data-bs-dismiss="modal" aria-label="Cancel"> <button type="reset" class="btn btn-primary" data-bs-dismiss="modal" aria-label="Cancel">
@ -428,7 +428,7 @@ const previewOpt = ref()
const isVideo = ref(false) const isVideo = ref(false)
const uploadModal = ref() const uploadModal = ref()
const extensions = ref('') const extensions = ref('')
const folderName = ref('') const folderName = ref({} as Folder)
const inputFiles = ref([] as File[]) const inputFiles = ref([] as File[])
const fileInputName = ref() const fileInputName = ref()
const currentNumber = ref(0) const currentNumber = ref(0)
@ -584,12 +584,12 @@ function closePlayer() {
async function onSubmitCreateFolder(evt: any) { async function onSubmitCreateFolder(evt: any) {
evt.preventDefault() evt.preventDefault()
const path = `${mediaStore.folderTree.source}/${folderName.value}`.replace(/\/[/]+/g, '/') const path = `${mediaStore.folderTree.source}/${folderName.value.name}`.replace(/\/[/]+/g, '/')
lastPath.value = mediaStore.folderTree.source lastPath.value = mediaStore.folderTree.source
if (mediaStore.folderTree.folders.includes(folderName.value)) { if (mediaStore.folderTree.folders.includes(folderName.value)) {
indexStore.alertVariant = 'alert-warning' indexStore.alertVariant = 'alert-warning'
indexStore.alertMsg = `Folder "${folderName.value}" exists already!` indexStore.alertMsg = `Folder "${folderName.value.name}" exists already!`
indexStore.showAlert = true indexStore.showAlert = true
return return
@ -610,7 +610,7 @@ async function onSubmitCreateFolder(evt: any) {
}) })
indexStore.showAlert = true indexStore.showAlert = true
folderName.value = '' folderName.value = {} as Folder
setTimeout(() => { setTimeout(() => {
indexStore.alertMsg = '' indexStore.alertMsg = ''
@ -622,7 +622,7 @@ async function onSubmitCreateFolder(evt: any) {
function onCancelCreateFolder(evt: any) { function onCancelCreateFolder(evt: any) {
evt.preventDefault() evt.preventDefault()
folderName.value = '' folderName.value = {} as Folder
} }
function onFileChange(evt: any) { function onFileChange(evt: any) {

View File

@ -38,7 +38,7 @@
<li <li
class="list-group-item browser-item" class="list-group-item browser-item"
v-for="folder in mediaStore.folderTree.folders" v-for="folder in mediaStore.folderTree.folders"
:key="folder" :key="folder.uid"
> >
<div class="row"> <div class="row">
<div class="col-1 browser-icons-col"> <div class="col-1 browser-icons-col">
@ -48,9 +48,9 @@
<a <a
class="link-light" class="link-light"
href="#" href="#"
@click="getPath(`/${mediaStore.folderTree.source}/${folder}`)" @click="getPath(`/${mediaStore.folderTree.source}/${folder.name}`)"
> >
{{ folder }} {{ folder.name }}
</a> </a>
</div> </div>
</div> </div>
@ -451,79 +451,285 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div> <div class="browser-col">
<nav aria-label="breadcrumb"> <div class="nav nav-tabs" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<ol class="breadcrumb"> <button
<li class="nav-link active"
class="breadcrumb-item" id="v-pills-gui-tab"
v-for="(crumb, index) in mediaStore.folderCrumbs" data-bs-toggle="pill"
:key="index" data-bs-target="#v-pills-gui"
:active="index === mediaStore.folderCrumbs.length - 1" type="button"
@click="mediaStore.getTree(crumb.path, true)" role="tab"
> aria-controls="v-pills-gui"
<a aria-selected="true"
v-if=" @click="advancedGenerator = false"
mediaStore.folderCrumbs.length > 1 && >
mediaStore.folderCrumbs.length - 1 > index Simple
" </button>
class="link-secondary" <button
href="#" class="nav-link"
> id="v-pills-playout-tab"
{{ crumb.text }} data-bs-toggle="pill"
</a> data-bs-target="#v-pills-playout"
<span v-else>{{ crumb.text }}</span> type="button"
</li> role="tab"
</ol> aria-controls="v-pills-playout"
</nav> aria-selected="false"
</div> @click="advancedGenerator = true; resetCheckboxes()"
<ul class="list-group media-browser-scroll browser-div"> >
<li Advanced
class="list-group-item browser-item" </button>
v-for="folder in mediaStore.folderList.folders" </div>
:key="folder" <div class="tab-content h-100" id="v-pills-tabContent">
> <div
<div class="row"> class="tab-pane h-100 show active"
<div class="col-1 browser-icons-col"> id="v-pills-gui"
<i class="bi-folder-fill browser-icons" /> role="tabpanel"
</div> aria-labelledby="v-pills-gui-tab"
<div class="col browser-item-text"> >
<a <div class="h-100">
class="link-light" <nav aria-label="breadcrumb">
href="#" <ol class="breadcrumb border-0">
@click=" <li
;[ class="breadcrumb-item"
(selectedFolders = []), v-for="(crumb, index) in mediaStore.folderCrumbs"
mediaStore.getTree( :key="index"
`/${mediaStore.folderList.source}/${folder}`.replace( :active="index === mediaStore.folderCrumbs.length - 1"
/\/[/]+/g, @click="mediaStore.getTree(crumb.path, true)"
'/' >
), <a
true v-if="
), mediaStore.folderCrumbs.length > 1 &&
] mediaStore.folderCrumbs.length - 1 > index
" "
> class="link-secondary"
{{ folder }} href="#"
</a> >
</div> {{ crumb.text }}
<div v-if="!generateFromAll" class="col-1 text-center playlist-input"> </a>
<input <span v-else>{{ crumb.text }}</span>
class="form-check-input" </li>
type="checkbox" </ol>
@change=" </nav>
setSelectedFolder( <ul class="list-group media-browser-scroll browser-div">
$event, <li
`/${mediaStore.folderList.source}/${folder}`.replace(/\/[/]+/g, '/') class="list-group-item browser-item"
) v-for="folder in mediaStore.folderList.folders"
" :key="folder.uid"
/> >
<div class="row">
<div class="col-1 browser-icons-col">
<i class="bi-folder-fill browser-icons" />
</div>
<div class="col browser-item-text">
<a
class="link-light"
href="#"
@click="
;[
(selectedFolders = []),
mediaStore.getTree(
`/${mediaStore.folderList.source}/${folder.name}`.replace(
/\/[/]+/g,
'/'
),
true
),
]
"
>
{{ folder.name }}
</a>
</div>
<div
v-if="!generateFromAll"
class="col-1 text-center playlist-input"
>
<input
class="form-check-input folder-check"
type="checkbox"
@change="
setSelectedFolder(
$event,
`/${mediaStore.folderList.source}/${folder.name}`.replace(
/\/[/]+/g,
'/'
)
)
"
/>
</div>
</div>
</li>
</ul>
</div> </div>
</div> </div>
</li> <div
</ul> class="tab-pane"
id="v-pills-playout"
role="tabpanel"
aria-labelledby="v-pills-playout-tab"
>
<div>
<div class="row">
<div class="col col-10">
<nav aria-label="breadcrumb">
<ol class="breadcrumb border-0">
<li
class="breadcrumb-item"
v-for="(crumb, index) in mediaStore.folderCrumbs"
:key="index"
:active="index === mediaStore.folderCrumbs.length - 1"
@click.prevent="mediaStore.getTree(crumb.path, true)"
>
<a
v-if="
mediaStore.folderCrumbs.length > 1 &&
mediaStore.folderCrumbs.length - 1 > index
"
class="link-secondary"
href="#"
>
{{ crumb.text }}
</a>
<span v-else>{{ crumb.text }}</span>
</li>
</ol>
</nav>
</div>
<div class="col d-flex justify-content-end">
<button
type="button"
class="btn btn-primary p-2 py-0 m-1"
@click="addTemplate()"
>
<i class="bi bi-folder-plus"></i>
</button>
</div>
</div>
<div class="row">
<div class="col col-5 browser-col">
<Sortable
:list="mediaStore.folderList.folders"
:options="templateBrowserSortOptions"
item-key="uid"
class="list-group media-browser-scroll browser-div"
tag="ul"
>
<template #item="{ element, index }">
<li
:id="`adv_folder_${index}`"
class="draggable list-group-item browser-item"
:key="element.uid"
>
<div class="row">
<div class="col-1 browser-icons-col">
<i class="bi-folder-fill browser-icons" />
</div>
<div class="col browser-item-text">
<a
class="link-light"
href="#"
@click="
;[
(selectedFolders = []),
mediaStore.getTree(
`/${mediaStore.folderList.source}/${element.name}`.replace(
/\/[/]+/g,
'/'
),
true
),
]
"
>
{{ element.name }}
</a>
</div>
</div>
</li>
</template>
</Sortable>
</div>
<div class="col template-col">
<ul class="list-group media-browser-scroll">
<li
v-for="item in template.sources"
:key="item.start"
class="list-group-item"
>
<div class="input-group mb-3">
<span class="input-group-text">Start</span>
<input
type="test"
class="form-control"
aria-label="Start"
v-model="item.start"
/>
<span class="input-group-text">Duration</span>
<input
type="test"
class="form-control"
aria-label="Duration"
v-model="item.duration"
/>
<input
type="checkbox"
class="btn-check"
:id="`shuffle-${item.start}`"
autocomplete="off"
v-model="item.shuffle"
/>
<label
class="btn btn-outline-primary"
:for="`shuffle-${item.start}`"
>
Shuffle
</label>
</div>
<Sortable
:list="item.paths"
item-key="index"
class="list-group w-100 border"
:style="`height: ${
item.paths ? item.paths.length * 23 + 31 : 300
}px`"
tag="ul"
:options="templateTargetSortOptions"
@add="addFolderToTemplate($event, item)"
>
<template #item="{ element, index }">
<li
:id="`path_${index}`"
class="draggable grabbing list-group-item py-0"
:key="index"
>
{{ element.split(/[\\/]+/).pop() }}
</li>
</template>
</Sortable>
<div class="col d-flex justify-content-end">
<button
type="button"
class="btn btn-primary p-2 py-0 m-1"
@click="removeTemplate(item)"
>
<i class="bi-trash" />
</button>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<div class="form-check select-all-div"> <div v-if="!advancedGenerator" class="form-check select-all-div">
<input id="checkAll" class="form-check-input" type="checkbox" v-model="generateFromAll" /> <input id="checkAll" class="form-check-input" type="checkbox" v-model="generateFromAll" />
<label class="form-check-label" for="checkAll">All</label> <label class="form-check-label" for="checkAll">All</label>
</div> </div>
@ -575,6 +781,7 @@ useHead({
const { configID } = storeToRefs(useConfig()) const { configID } = storeToRefs(useConfig())
const advancedGenerator = ref(false)
const fileImport = ref() const fileImport = ref()
const browserIsLoading = ref(false) const browserIsLoading = ref(false)
const playlistIsLoading = ref(false) const playlistIsLoading = ref(false)
@ -589,15 +796,24 @@ const previewOpt = ref()
const isVideo = ref(false) const isVideo = ref(false)
const selectedFolders = ref([] as string[]) const selectedFolders = ref([] as string[])
const generateFromAll = ref(false) const generateFromAll = ref(false)
const browserSortOptions = ref({ const browserSortOptions = {
group: { name: 'playlist', pull: 'clone', put: false }, group: { name: 'playlist', pull: 'clone', put: false },
sort: false, sort: false,
}) }
const playlistSortOptions = ref({ const playlistSortOptions = {
group: 'playlist', group: 'playlist',
animation: 100, animation: 100,
handle: '.grabbing', handle: '.grabbing',
}) }
const templateBrowserSortOptions = {
group: { name: 'folder', pull: 'clone', put: false },
sort: false,
}
const templateTargetSortOptions = {
group: 'folder',
animation: 100,
handle: '.grabbing',
}
const newSource = ref({ const newSource = ref({
begin: 0, begin: 0,
in: 0, in: 0,
@ -610,6 +826,10 @@ const newSource = ref({
uid: '', uid: '',
} as PlaylistItem) } as PlaylistItem)
const template = ref({
sources: [],
} as Template)
onMounted(() => { onMounted(() => {
if (!mediaStore.folderTree.parent) { if (!mediaStore.folderTree.parent) {
getPath('') getPath('')
@ -701,6 +921,27 @@ function cloneClip(event: any) {
) )
} }
function addFolderToTemplate(event: any, item: TemplateItem) {
const o = event.oldIndex
const n = event.newIndex
event.item.remove()
const storagePath = configStore.configPlayout.storage.path
const navPath = mediaStore.folderCrumbs[mediaStore.folderCrumbs.length - 1].path
const sourcePath = `${storagePath}/${navPath}/${mediaStore.folderList.folders[o].name}`.replace(/\/[/]+/g, '/')
if (!item.paths.includes(sourcePath)) {
item.paths.splice(n, 0, sourcePath)
}
}
function removeTemplate(item: TemplateItem) {
const index = template.value.sources.indexOf(item)
template.value.sources.splice(index, 1)
}
function moveItemInArray(event: any) { function moveItemInArray(event: any) {
playlistStore.playlist.splice(event.newIndex, 0, playlistStore.playlist.splice(event.oldIndex, 1)[0]) playlistStore.playlist.splice(event.newIndex, 0, playlistStore.playlist.splice(event.oldIndex, 1)[0])
@ -897,11 +1138,24 @@ async function onSubmitImport(evt: any) {
async function generatePlaylist() { async function generatePlaylist() {
playlistIsLoading.value = true playlistIsLoading.value = 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/${listDate.value}`, { await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${listDate.value}`, {
method: 'POST', method: 'POST',
headers: { ...contentType, ...authStore.authHeader }, headers: { ...contentType, ...authStore.authHeader },
body: selectedFolders.value.length > 0 && !generateFromAll.value ? { paths: selectedFolders.value } : null, body,
}) })
.then((response: any) => { .then((response: any) => {
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(
@ -928,6 +1182,9 @@ async function generatePlaylist() {
}, 4000) }, 4000)
}) })
// reset selections
resetCheckboxes()
playlistIsLoading.value = false playlistIsLoading.value = false
} }
@ -1011,6 +1268,38 @@ function setSelectedFolder(event: any, folder: string) {
} }
} }
} }
function resetCheckboxes() {
selectedFolders.value = []
const checkboxes = document.getElementsByClassName('folder-check')
if (checkboxes) {
for (const box of checkboxes) {
// @ts-ignore
box.checked = false
}
}
}
function addTemplate() {
const last = template.value.sources[template.value.sources.length - 1]
// @ts-ignore
let start = $dayjs('00:00:00', 'HH:mm:ss')
if (last) {
// @ts-ignore
const t = $dayjs(last.duration, 'HH:mm:ss')
// @ts-ignore
start = $dayjs(last.start, 'HH:mm:ss').add(t.hour(), 'hour').add(t.minute(), 'minute').add(t.second(), 'second')
}
template.value.sources.push({
start: start.format('HH:mm:ss'),
duration: '02:00:00',
shuffle: false,
paths: [],
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -1100,11 +1389,16 @@ function setSelectedFolder(event: any, folder: string) {
} }
#generateModal .modal-body { #generateModal .modal-body {
height: 500px; height: 600px;
}
.browser-col,
.template-col {
height: 532px;
} }
#generateModal { #generateModal {
--bs-modal-width: 600px; --bs-modal-width: 800px;
} }
#generateModal .media-browser-scroll { #generateModal .media-browser-scroll {

View File

@ -1,20 +1,23 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js' import customParseFormat from 'dayjs/plugin/customParseFormat.js'
import timezone from 'dayjs/plugin/timezone.js' import timezone from 'dayjs/plugin/timezone.js'
import utc from 'dayjs/plugin/utc.js'
declare module '#app' { declare module '#app' {
interface NuxtApp { interface NuxtApp {
$dayjs(date?: dayjs.ConfigType): dayjs.Dayjs; $dayjs(date?: dayjs.ConfigType): dayjs.Dayjs
} }
} }
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
$dayjs(date?: dayjs.ConfigType): dayjs.Dayjs; $dayjs(date?: dayjs.ConfigType): dayjs.Dayjs
} }
} }
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin((nuxtApp) => {
dayjs.extend(utc) dayjs.extend(customParseFormat)
dayjs.extend(timezone) dayjs.extend(timezone)
dayjs.extend(utc)
nuxtApp.provide('dayjs', dayjs) nuxtApp.provide('dayjs', dayjs)
}) })

View File

@ -4,6 +4,8 @@ import { useAuth } from '~/stores/auth'
import { useConfig } from '~/stores/config' import { useConfig } from '~/stores/config'
import { useIndex } from '~/stores/index' import { useIndex } from '~/stores/index'
const { genUID } = playlistOperations()
export const useMedia = defineStore('media', { export const useMedia = defineStore('media', {
state: () => ({ state: () => ({
currentPath: '', currentPath: '',
@ -65,10 +67,12 @@ export const useMedia = defineStore('media', {
if (foldersOnly) { if (foldersOnly) {
this.folderCrumbs = crumbs this.folderCrumbs = crumbs
data.folders = data.folders.map((i: any) => ({uid: genUID(), name: i}))
this.folderList = data this.folderList = data
} else { } else {
this.currentPath = path this.currentPath = path
this.crumbs = crumbs this.crumbs = crumbs
data.folders = data.folders.map((i: any) => ({uid: genUID(), name: i}))
this.folderTree = data this.folderTree = data
} }
}) })

View File

@ -30,21 +30,42 @@ declare global {
duration: number duration: number
} }
interface Folder {
uid: string
name: string
}
interface FileFolderObject { interface FileFolderObject {
source: string source: string
parent: string parent: string
folders: string[] folders: Folder[]
files: FileObject[] files: FileObject[]
} }
interface FolderObject { interface FolderObject {
source: string source: string
parent: string parent: string
folders: string[] folders: Folder[]
} }
interface SourceObject { interface SourceObject {
type: string type: string
src: string src: string
} }
interface TemplateItem {
start: string
duration: string
shuffle: boolean
paths: string[]
}
interface Template {
sources: TemplateItem[]
}
interface BodyObject {
paths?: string[]
template?: Template
}
} }