Newer
Older
<template>
<div>
<PageHeader
:title="t('navigation.show.basicData')"
:lead="show.name"
:editing-metadata="show"
/>
class="tw-grid tw-gap-6 tw-items-start tw-grid-cols-1 xl:tw-grid-cols-2 2xl:tw-grid-cols-3 tw-mb-6 tw-max-w-[1600px]"
>
<AFieldset
class="tw-bg-white tw-col-span-full 2xl:tw-col-span-2"
:title="t('show.section.basic.title')"
<FormGroup
v-slot="attrs"
:label="t('show.fields.name')"
:is-saving="name.isSaving"
:errors="name.errors"
class="tw-col-span-2"
edit-permissions="edit__show__name"
>
<input v-bind="attrs" v-model="name.value" type="text" @blur="name.save" />
<ADescription class="tw-text-xs">
<span>{{ t('show.slugDetail.title') }}: </span>
<code
class="tw-text-inherit tw-bg-gray-200 tw-px-2 tw-py-1 tw-rounded tw-text-gray-500"
>
{{ show.slug }}
</code>
<br />
<SafeHTML
:html="
t('show.slugDetail.editRemark', {
dangerZone: t('show.housekeeping.title'),
dangerZoneId: 'danger-zone',
})
"
sanitize-preset="safe-html"
/>
</ADescription>
</FormGroup>
<FormGroup
:label="t('show.fields.shortDescription')"
:is-saving="shortDescription.isSaving"
:errors="shortDescription.errors"
class="tw-col-span-2"
edit-permissions="edit__show__short_description"
<AHTMLEditor
:id="id"
v-model="shortDescription.value"
:disabled="disabled"
@blur="shortDescription.save()"
/>
</FormGroup>
<FormGroup
:label="t('show.fields.description')"
:is-saving="description.isSaving"
:errors="description.errors"
edit-permissions="edit__show__description"
class="tw-col-span-2"
custom-control
>
<AHTMLEditor
:id="id"
v-model="description.value"
:disabled="disabled"
@blur="shortDescription.save()"
/>
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
</FormGroup>
<div class="tw-flex tw-gap-6 tw-flex-wrap">
<FormGroup
:label="t('showMeta.logo')"
:errors="logoId.errors"
:is-saving="logoId.isSaving"
class="tw-mb-0"
custom-control
>
<template #default="attrs">
<div>
<ImagePicker v-model="logoId.value" v-bind="attrs" />
</div>
</template>
</FormGroup>
<FormGroup
:label="t('showMeta.image')"
:errors="imageId.errors"
:is-saving="imageId.isSaving"
custom-control
>
<template #default="attrs">
<div>
<ImagePicker v-model="imageId.value" v-bind="attrs" />
</div>
</template>
</FormGroup>
</div>
</AFieldset>
<div class="tw-hidden 2xl:tw-block" />
<AFieldset class="tw-bg-white" :title="t('show.section.content.title')">
<FormGroup
:label="t('showMeta.categories')"
custom-control
:is-saving="categories.isSaving"
:errors="categories.errors"
>
<ComboBoxSimple v-model="categories.value" :choices="categories.choices">
<template #selected="{ deselect }">
<Tag
v-for="category in categories.value"
:key="category.id"
removable
@remove="deselect(category)"
>
<span>
<span class="tw-block">{{ category.name }}</span>
<span v-if="category.subtitle.trim()" class="tw-text-xs">
{{ category.subtitle }}
</span>
</span>
</Tag>
</template>
</ComboBoxSimple>
</FormGroup>
<FormGroup
:label="t('showMeta.topics')"
custom-control
:is-saving="topics.isSaving"
:errors="topics.errors"
>
<ComboBoxSimple v-model="topics.value" :choices="topics.choices" />
</FormGroup>
<FormGroup
:label="t('showMeta.genres')"
custom-control
:is-saving="musicFocuses.isSaving"
:errors="musicFocuses.errors"
>
<ComboBoxSimple v-model="musicFocuses.value" :choices="musicFocuses.choices" />
</FormGroup>
<FormGroup
:label="t('showMeta.languages')"
custom-control
:is-saving="languages.isSaving"
:errors="languages.errors"
>
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<ComboBoxSimple v-model="languages.value" :choices="languages.choices" />
</FormGroup>
<FormGroup :label="t('showMeta.type')" :errors="type.errors" :is-saving="type.isSaving">
<template #default="attrs">
<select v-model="type.valueId" v-bind="attrs">
<option
v-for="choice in type.choices"
:key="choice.id"
:value="choice.id"
:label="choice.name"
:disabled="!choice.isActive"
/>
</select>
</template>
</FormGroup>
</AFieldset>
<AFieldset class="tw-bg-white" :title="t('show.section.contact.title')">
<FormGroup :label="t('showMeta.email')" :errors="email.errors" :is-saving="email.isSaving">
<template #default="attrs">
<input v-model="email.value" type="email" v-bind="attrs" @blur="email.save" />
</template>
</FormGroup>
<FormGroup
:label="t('show.fields.links')"
:is-saving="links.isSaving"
:errors="links.errors.forField('links', '')"
:has-error="links.errors.length > 0"
custom-control
>
<ALinkCollectionEditor
v-model="links.value"
:error-lists="links.errors.siblings('links')"
allow-add
@save="links.save()"
<FormGroup
:label="t('showMeta.hosts')"
custom-control
:is-saving="hosts.isSaving"
:errors="hosts.errors"
>
<ComboBoxSimple v-model="hosts.value" :choices="hosts.choices" />
</FormGroup>
<FormGroup
:label="t('showMeta.owners')"
class="tw-order-last"
:is-saving="owners.isSaving"
:errors="owners.errors"
>
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
<ComboBoxSimple
v-model="owners.value"
:disabled="!authStore.isSuperuser"
:search-provider="searchUsers"
>
<template #default="{ choice, ...itemAttrs }">
<li v-bind="itemAttrs">
<UserPreview :user="choice as SteeringUser" transparent />
</li>
</template>
<template #selected="{ deselect }">
<template v-for="user in owners.value" :key="user.id">
<UserPreview :user="user" removable @remove="deselect(user)" />
</template>
</template>
</ComboBoxSimple>
</FormGroup>
</AFieldset>
<AFieldset class="tw-bg-white" :title="t('show.section.administrative.title')">
<FormGroup
v-slot="attrs"
:label="`${t('showMeta.fundingCategory')} ${t('showMeta.fundingCategoryRtr')}`"
:errors="fundingCategory.errors"
:is-saving="fundingCategory.isSaving"
>
<select v-model="fundingCategory.valueId" v-bind="attrs">
<option
v-for="choice in fundingCategory.choices"
:key="choice.id"
:value="choice.id"
:label="choice.name"
:disabled="!choice.isActive"
/>
</select>
</FormGroup>
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
<FormGroup
:label="t('showMeta.cbaSeriesId')"
:errors="cbaSeriesId.errors"
:is-saving="cbaSeriesId.isSaving"
>
<template #default="attrs">
<input
v-model="cbaSeriesId.value"
type="text"
inputmode="numeric"
pattern="[0-9]+"
v-bind="attrs"
@blur="cbaSeriesId.save"
/>
</template>
</FormGroup>
<FormGroup
:label="t('showMeta.predecessor')"
:errors="predecessor.errors"
:is-saving="predecessor.isSaving"
>
<template #default="attrs">
<select v-model="predecessor.valueId" v-bind="attrs">
<option
v-for="choice in predecessor.choices"
:key="choice.id"
:value="choice.id"
:label="sanitizeHTML(choice.name)"
:disabled="!choice.isActive"
/>
</select>
</template>
</FormGroup>
<template v-if="authStore.isSuperuser">
<FormGroup
:label="t('showMeta.internalNote')"
:errors="internalNote.errors"
class="md:tw-col-span-2 tw-order-last"
:is-saving="internalNote.isSaving"
>
<template #default="attrs">
<textarea
ref="internalNoteEl"
v-model="internalNote.value"
class="tw-min-h-[100px]"
v-bind="attrs"
@blur="internalNote.save"
/>
</template>
</FormGroup>
</template>
</AFieldset>
<AHousekeeping :show="show" class="tw-col-span-full" />
</div>
<hr />
<p class="tw-text-sm">
<ATimeEditInfo
v-if="show.updatedAt"
:edit-info="{ time: show.updatedAt, author: show.updatedBy }"
type="modified"
/>
<br />
<ATimeEditInfo :edit-info="{ time: show.createdAt, author: show.createdBy }" type="created" />
</p>
</div>
</template>
<script lang="ts" setup>
import { useTextareaAutosize } from '@vueuse/core'
import { computed } from 'vue'
import { useI18n } from '@/i18n'
import { Show } from '@/types'
import { useAPIObjectFieldCopy, useRelation, useRelationList } from '@/form'
import {
useAuthStore,
useCategoryStore,
useFundingCategoryStore,
useHostStore,
useLanguageStore,
useMusicFocusStore,
useShowStore,
useTopicStore,
useTypeStore,
useUserStore,
} from '@/stores'
import { useBreadcrumbs } from '@/stores/nav'
import { SteeringUser } from '@/stores/auth'
import { sanitizeHTML } from '@/util'
import PageHeader from '@/components/PageHeader.vue'
import ATimeEditInfo from '@/components/generic/ATimeEditInfo.vue'
import FormGroup from '@/components/generic/FormGroup.vue'
import AHousekeeping from '@/components/shows/AHousekeeping.vue'
import ADescription from '@/components/generic/ADescription.vue'
import SafeHTML from '@/components/generic/SafeHTML'
import ALinkCollectionEditor from '@/components/generic/ALinkCollectionEditor.vue'
import AHTMLEditor from '@/components/generic/AHTMLEditor.vue'
import ComboBoxSimple from '@/components/ComboBoxSimple.vue'
import UserPreview from '@/components/UserPreview.vue'
import Tag from '@/components/generic/Tag.vue'
import ImagePicker from '@/components/images/ImagePicker.vue'
import AFieldset from '@/components/generic/AFieldset.vue'
show: Show
}>()
const { t } = useI18n()
const authStore = useAuthStore()
const userStore = useUserStore()
const showStore = useShowStore()
const typeStore = useTypeStore()
const categoryStore = useCategoryStore()
const topicStore = useTopicStore()
const musicFocusStore = useMusicFocusStore()
const languageStore = useLanguageStore()
const hostStore = useHostStore()
const fundingCategoryStore = useFundingCategoryStore()
const show = computed(() => props.show)
const name = useAPIObjectFieldCopy(showStore, show, 'name', { debounce: 2 })
const shortDescription = useAPIObjectFieldCopy(showStore, show, 'shortDescription', { debounce: 2 })
const description = useAPIObjectFieldCopy(showStore, show, 'description', { debounce: 2 })
const links = useAPIObjectFieldCopy(showStore, show, 'links', { debounce: 2 })
const email = useAPIObjectFieldCopy(showStore, show, 'email', { debounce: 2 })
const cbaSeriesId = useAPIObjectFieldCopy(showStore, show, 'cbaSeriesId', { debounce: 2 })
const internalNote = useAPIObjectFieldCopy(showStore, show, 'internalNote', { debounce: 2 })
const type = useRelation(showStore, show, 'typeId', typeStore)
const fundingCategory = useRelation(showStore, show, 'fundingCategoryId', fundingCategoryStore)
const predecessor = useRelation(showStore, show, 'predecessorId', showStore)
const categories = useRelationList(showStore, show, 'categoryIds', categoryStore)
const topics = useRelationList(showStore, show, 'topicIds', topicStore)
const languages = useRelationList(showStore, show, 'languageIds', languageStore)
const hosts = useRelationList(showStore, show, 'hostIds', hostStore)
const musicFocuses = useRelationList(showStore, show, 'musicFocusIds', musicFocusStore)
const logoId = useAPIObjectFieldCopy(showStore, () => props.show, 'logoId', { debounce: 0 })
const imageId = useAPIObjectFieldCopy(showStore, () => props.show, 'imageId', { debounce: 0 })
const owners = useRelationList(showStore, () => props.show, 'ownerIds', userStore, {
sortBy: ['lastName', 'firstName', 'username', 'email'],
})
function searchUsers(query: string, signal: AbortSignal) {
return userStore.list({
query: new URLSearchParams({ search: query }),
requestInit: { signal },
})
}
useBreadcrumbs(() => [
{ title: t('navigation.shows'), route: { name: 'shows' } },
{ title: props.show.name, route: { name: 'show', params: { showId: props.show.id.toString() } } },
t('navigation.show.basicData'),
])
const { textarea: internalNoteEl } = useTextareaAutosize({
input: computed(() => internalNote.value),
})