<template> <div class="tw-relative" v-bind="attrs"> <AMediaDurationCheck v-if="media && media.entries.length > 0 && requiredDurationSeconds > 0" :media="media" :required-duration-seconds="requiredDurationSeconds" class="tw-mb-6" /> <AMediaSources v-if="sources.length > 0" :sources="sources" class="tw-mb-6" :can-sort="!disabled" :can-edit="!disabled" /> <p v-if="disabled && sources.length === 0" class="tw-m-0"> {{ t('media.editor.noEntries') }} </p> <section class="tw-mb-6 empty:tw-hidden"> <AMediaSourceJobQueue :context-key="contextKey" :label="t('media.editor.upload.pending')" /> </section> <fieldset v-if="!disabled" ref="dropzoneEl" class="tw-rounded tw-border-2 tw-flex tw-mb-3 tw-p-6 dark:tw-border-neutral-700" :class="{ 'tw-border-teal-600': isOverDropZone && isAllowedToAddFiles, 'tw-border-gray-200': !isOverDropZone, 'tw-border-dashed': isAllowedToAddFiles, }" > <div class="tw-place-self-center tw-mx-auto tw-flex tw-flex-col tw-gap-2 tw-items-center"> <template v-if="isAllowedToAddFiles"> <icon-system-uicons-file-upload class="tw-text-xl" /> <p class="tw-mb-0">{{ t('media.editor.control.dropFiles') }}</p> <p class="tw-text-gray-400 tw-text-sm tw-mb-1.5 tw-leading-none"> {{ t('media.editor.control._or') }} </p> </template> <div class="tw-flex tw-flex-wrap tw-justify-center tw-items-center tw-gap-3"> <button v-if="isAllowedToAddFiles" type="button" class="btn btn-default" data-testid="media-editor:open-file-dialog" @click="openFileDialog()" > <icon-iconamoon-file-audio-thin class="tw-flex-none" /> {{ t('media.editor.control.selectFiles') }} </button> <APermissionGuard show-permissions="program.add__import"> <button type="button" class="btn btn-default" @click="importFileFromURL()"> <icon-formkit-url class="tw-flex-none" /> {{ t('media.editor.control.importFile') }} </button> <GetFileImportUrl v-slot="{ resolve }"> <AFileUrlDialog @save="resolve($event)" @close="resolve(null)" /> </GetFileImportUrl> </APermissionGuard> </div> <div class="tw-flex tw-flex-wrap tw-justify-center tw-items-center tw-gap-3 tw-mt-1"> <APermissionGuard show-permissions="program.add__stream"> <button type="button" class="btn btn-default" @click="addStreamMediaSource()"> <icon-solar-play-stream-bold class="tw-flex-none" /> {{ t('media.editor.control.addStream') }} </button> <GetStreamUrl v-slot="{ resolve }"> <AStreamURLDialog @save="resolve($event)" @close="resolve(null)" /> </GetStreamUrl> </APermissionGuard> <APermissionGuard show-permissions="program.add__line"> <button type="button" class="btn btn-default" @click="addInputMediaSource"> <icon-game-icons-jack-plug class="tw-flex-none" /> {{ t('media.editor.control.addInput') }} </button> <GetInputUrl v-slot="{ resolve }"> <AInputUrlDialog @save="resolve($event)" @close="resolve(null)" /> </GetInputUrl> </APermissionGuard> <APermissionGuard show-permissions="program.add__m3ufile"> <button type="button" class="btn btn-default" @click="addM3UMediaSource"> <icon-ph-playlist-light class="tw-flex-none" /> {{ t('media.editor.control.addM3u') }} </button> <GetM3uUrl v-slot="{ resolve }"> <AM3uUrlDialog @save="resolve($event)" @close="resolve(null)" /> </GetM3uUrl> </APermissionGuard> </div> </div> </fieldset> </div> </template> <script lang="ts" setup> import { createTemplatePromise, useDropZone, useFileDialog } from '@vueuse/core' import { computed, ref, useAttrs, watch } from 'vue' import { useI18n } from '@/i18n' import { Media, Show } from '@/types' import AStreamURLDialog from '@/components/media/AStreamURLDialog.vue' import AFileUrlDialog from '@/components/media/AFileUrlDialog.vue' import AInputUrlDialog from '@/components/media/AInputUrlDialog.vue' import AM3uUrlDialog from '@/components/media/AM3uUrlDialog.vue' import AMediaDurationCheck from '@/components/media/AMediaDurationCheck.vue' import AMediaSources from '@/components/media/AMediaSources.vue' import APermissionGuard from '@/components/generic/APermissionGuard.vue' import { useHasUserPermission } from '@/stores/auth' import { MediaResolver, useMediaSourceController } from '@/stores/media-manager' import AMediaSourceJobQueue from '@/components/media/AMediaSourceJobQueue.vue' import { usePlayoutStore } from '@/stores' const props = withDefaults( defineProps<{ show: Show getMedia: MediaResolver disabled?: boolean media?: Media | null contextKey?: string | null | undefined requiredDurationSeconds?: number }>(), { requiredDurationSeconds: -1, contextKey: undefined, media: null, }, ) const attrs = useAttrs() const { t } = useI18n() const sources = computed(() => props.media?.entries ?? []) const playoutStore = usePlayoutStore() const mediaSourceManager = useMediaSourceController({ show: () => props.show, contextKey: () => props.contextKey, getMedia: props.getMedia, }) const isAllowedToAddFiles = useHasUserPermission(['program.add__file']) function addFiles(files: File[] | FileList | null) { if (isAllowedToAddFiles.value) { mediaSourceManager.addFiles(files) } } // files handled through file dialog const { open: openFileDialog, files } = useFileDialog({ accept: 'audio/*', multiple: true }) watch(files, (files) => addFiles(files)) // files handled through dropzone const dropzoneEl = ref<HTMLDivElement>() const { isOverDropZone, files: dropzoneFiles } = useDropZone(dropzoneEl) watch(dropzoneFiles, (files) => addFiles(files)) // files imported by URL const GetFileImportUrl = createTemplatePromise<string | null>() async function importFileFromURL() { const fileUrl = await GetFileImportUrl.start() if (!fileUrl) return mediaSourceManager.addImports(fileUrl) } // streams const GetStreamUrl = createTemplatePromise<string | null>() async function addStreamMediaSource() { const streamURL = await GetStreamUrl.start() if (!streamURL) return mediaSourceManager.addUrls(streamURL) } // inputs const GetInputUrl = createTemplatePromise<string | null>() async function addInputMediaSource() { const url = await GetInputUrl.start() if (!url) return const input = playoutStore.inputs.find((input) => input.uri === url) const name = input?.label ?? t('input.singular') mediaSourceManager.addUrls({ url, name }) } // M3U const GetM3uUrl = createTemplatePromise<string | null>() async function addM3UMediaSource() { const m3uUrl = await GetM3uUrl.start() if (!m3uUrl) return mediaSourceManager.addUrls(m3uUrl) } </script>