add advanced playlist generator, update packages
This commit is contained in:
parent
2f3234221a
commit
806d533bc2
2940
package-lock.json
generated
2940
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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) {
|
||||||
|
452
pages/player.vue
452
pages/player.vue
@ -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 {
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user