diff --git a/src/components/media/AMediaSourceEditor.vue b/src/components/media/AMediaSourceEditor.vue index cfeef94aa4ce7cfbbc7cd2a6dfdd320abb454963..260267a80bb3e9a83f57e9cb480f5bca51146367 100644 --- a/src/components/media/AMediaSourceEditor.vue +++ b/src/components/media/AMediaSourceEditor.vue @@ -91,6 +91,7 @@ :title="t('media.editor.editMediaSource')" :save="save" class="tw-w-[90vw] md:tw-w-min" + @close="resetData" > <FormTable> <template v-if="type === 'file'"> @@ -105,7 +106,13 @@ </FormGroup> </template> - <FormGroup v-if="type !== 'file'" v-slot="attrs" :label="t('file.duration')" center> + <FormGroup + v-if="type !== 'file'" + v-slot="attrs" + :label="t('file.duration')" + center + :errors="errors.forField('duration')" + > <input autofocus type="text" @@ -119,6 +126,12 @@ </FormGroup> </FormTable> + <hr /> + + <section> + <AAttributionEditor v-model="licensing" :errors="errors.subtree('licensing')" /> + </section> + <template #footer-buttons-extra="{ isSaving }"> <div class="tw-ml-auto"> <button @@ -159,11 +172,11 @@ import { useObjectFromStore } from '@rokoli/bnb/drf' import { computed, ref } from 'vue' -import { useCopy } from '@/form' +import { useCopy, useSaveBehaviour } from '@/form' import { useI18n } from '@/i18n' import { useFilesStore, useMediaSourceStore } from '@/stores' import { useInput } from '@/stores/playout' -import { FileMetadata, MediaSource } from '@/types' +import { FileMetadata, MediaSource, MediaSourceUpdateData } from '@/types' import { parseTime, secondsToDurationString } from '@/util' import SafeHTML from '@/components/generic/SafeHTML' import AEditDialog from '@/components/generic/AEditDialog.vue' @@ -171,6 +184,8 @@ import FormTable from '@/components/generic/FormTable.vue' import FormGroup from '@/components/generic/FormGroup.vue' import AFileImportLog from '@/components/media/AFileImportLog.vue' import AStatus from '@/components/generic/AStatus.vue' +import { isEqual } from 'lodash' +import AAttributionEditor from '@/components/license/AAttributionEditor.vue' const props = withDefaults( defineProps<{ @@ -201,6 +216,8 @@ const title = useCopy(() => file.value?.metadata?.title) const artist = useCopy(() => file.value?.metadata?.artist) const album = useCopy(() => file.value?.metadata?.album) const duration = useCopy(() => props.mediaSource.duration) +const licensing = useCopy(() => props.mediaSource.licensing) + const uri = computed(() => props.mediaSource.uri ? new URL(props.mediaSource.uri as string) : null, ) @@ -221,6 +238,14 @@ const artistInfo = computed(() => { } }) +function resetData() { + title.reset() + artist.reset() + album.reset() + duration.reset() + licensing.reset() +} + function setDuration(event: Event) { const value = (event.target as HTMLInputElement).value // A duration of zero should be treated as null so that the @@ -228,9 +253,16 @@ function setDuration(event: Event) { duration.value = parseTime(value) || null } -async function save() { +const { save, errors } = useSaveBehaviour(async () => { + const mediaSourceData: MediaSourceUpdateData = {} if (duration.value !== props.mediaSource.duration) { - await mediaSourceStore.partialUpdate(props.mediaSource.id, { duration: duration.value }) + mediaSourceData.duration = duration.value + } + if (!isEqual(licensing.value, props.mediaSource.licensing)) { + mediaSourceData.licensing = licensing.value + } + if (Object.keys(mediaSourceData).length > 0) { + await mediaSourceStore.partialUpdate(props.mediaSource.id, mediaSourceData) } if (file.value) { @@ -245,7 +277,7 @@ async function save() { } editDialog.value.close() -} +}) async function remove() { isDeleted.value = true diff --git a/src/stores/media-manager.ts b/src/stores/media-manager.ts index f19f96bfed6f8710c9a59b34fd7385b0db2f4994..2f3ccdcb0955c5dfb753820d31d21e58aa621419 100644 --- a/src/stores/media-manager.ts +++ b/src/stores/media-manager.ts @@ -132,9 +132,9 @@ const importFile: MediaSourceJobProcessor = async (job: MediaSourceJob, next: () async function makeMediaSourceFromJob(job: MediaSourceJob): Promise<MediaSourceCreateData> { const mediaId = (await job.getMedia()).id if (job.tankFile) { - return { mediaId, duration: job.tankFile.duration, fileId: job.tankFile.id } + return { mediaId, duration: job.tankFile.duration, licensing: null, fileId: job.tankFile.id } } else if ('url' in job.data) { - return { mediaId, duration: null, uri: job.data.url } + return { mediaId, duration: null, licensing: null, uri: job.data.url } } else { throw new TypeError('Invalid job type. Cannot convert to media source.') } diff --git a/src/types.ts b/src/types.ts index e00239bfc74c0ef119973fc4df990d12ad0a8ac9..85b48ecca2acf711fc60b28120a0298606651d2e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -55,8 +55,8 @@ export type ImageUpdateData = Omit<Image, _ImageReadonlyAttrs> type _MediaSource = steeringComponents['schemas']['MediaSource'] export type MediaSource = Required<_MediaSource> export type MediaSourceCreateData = - | Pick<MediaSource, 'mediaId' | 'duration' | 'fileId'> - | Pick<MediaSource, 'mediaId' | 'duration' | 'uri'> + | Pick<MediaSource, 'mediaId' | 'duration' | 'licensing' | 'fileId'> + | Pick<MediaSource, 'mediaId' | 'duration' | 'licensing' | 'uri'> export type MediaSourceUpdateData = Partial<MediaSourceCreateData> type _Media = steeringComponents['schemas']['Media']