diff --git a/src/Pages/ShowBasicData.vue b/src/Pages/ShowBasicData.vue index 542c741c8a5d8d321911c9b8c91eeb6dcd5afacc..00f4881bc23448dae64f6c8c5f9fad7eff9569ef 100644 --- a/src/Pages/ShowBasicData.vue +++ b/src/Pages/ShowBasicData.vue @@ -235,7 +235,7 @@ :errors="hosts.errors" edit-permissions="program.edit__show__hosts" > - <ComboBoxSimple v-model="hosts.value" :choices="hosts.choices" :disabled="disabled" /> + <AProfileSelector v-model="hosts.value" :disabled="disabled" /> </FormGroup> <FormGroup @@ -397,6 +397,7 @@ import ImagePicker from '@/components/images/ImagePicker.vue' import AFieldset from '@/components/generic/AFieldset.vue' import APlaylistEditor from '@/components/playlist/APlaylistEditor.vue' import AUserSelector from '@/components/identities/AUserSelector.vue' +import AProfileSelector from '@/components/identities/AProfileSelector.vue' const props = defineProps<{ show: Show diff --git a/src/components/identities/AProfileSelector.vue b/src/components/identities/AProfileSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..1fd2587f8c3088c24e0ab503642202986715d1ef --- /dev/null +++ b/src/components/identities/AProfileSelector.vue @@ -0,0 +1,65 @@ +<template> + <ComboBoxSimple + v-bind="props" + v-model="modelValue" + v-model:searchTerm="searchTerm" + :search-provider="searchProfiles" + :resolve-choice="resolveProfileChoice" + > + <template #default="{ choice, index, activeIndex, ...itemAttrs }"> + <li v-bind="itemAttrs"> + {{ (choice as Host).name }} + <span + v-if="choice.id === -1 && index === activeIndex && existingSimilarResults.length > 0" + class="tw-block tw-mt-1 tw-pt-1 tw-text-sm tw-text-white/75 tw-border-t tw-border-t-black/10" + > + {{ t('profile.labels.similarSelected', { names: existingSimilarResults.join(', ') }) }} + </span> + </li> + </template> + </ComboBoxSimple> +</template> + +<script setup lang="ts"> +import { computed, ref } from 'vue' +import { useHostStore } from '@/stores' +import { Host } from '@/types' +import ComboBoxSimple, { ComboBoxSimpleProps } from '@/components/ComboBoxSimple.vue' +import { useI18n } from '@/i18n' +import { matchesSearch } from '@/util' + +const modelValue = defineModel<null | Host | Host[]>({ required: true }) +const props = defineProps<Omit<ComboBoxSimpleProps<Host>, 'choices' | 'searchProvider'>>() + +const { t } = useI18n() +const hostStore = useHostStore() + +const searchTerm = ref('') + +const existingSimilarResults = computed(() => { + const value = Array.isArray(modelValue.value) + ? modelValue.value + : modelValue.value + ? [modelValue.value] + : [] + return value.filter((v) => matchesSearch(v.name, searchTerm.value)).map((v) => v.name) +}) + +async function searchProfiles(query: string, signal: AbortSignal) { + const result = await hostStore.list(1, { + query: new URLSearchParams({ search: query, limit: '10' }), + requestInit: { signal }, + }) + if (query.trim()) { + result.push({ id: -1, name: t('profile.labels.createNew', { name: query }) } as Host) + } + return result +} + +async function resolveProfileChoice(choice: Host | null | undefined, name: string) { + if (choice?.id === -1 && name) { + return await hostStore.create({ name, ownerIds: [] }) + } + return choice ?? null +} +</script> diff --git a/src/components/shows/NoteDescriptionEditor.vue b/src/components/shows/NoteDescriptionEditor.vue index e311b8af1591c8ea4bd09005e73e8bbdd3033df3..1ad67789f16c2d8cec0425610ef553b51f316c09 100644 --- a/src/components/shows/NoteDescriptionEditor.vue +++ b/src/components/shows/NoteDescriptionEditor.vue @@ -68,11 +68,7 @@ :is-saving="contributors.isSaving" show-permissions="program.edit__note__contributors" > - <ComboBoxSimple - v-model="contributors.value" - :choices="contributors.choices" - :disabled="disabled" - /> + <AProfileSelector v-model="contributors.value" :disabled="disabled" /> </FormGroup> <FormGroup @@ -129,16 +125,18 @@ import { computed } from 'vue' import { useI18n } from '@/i18n' -import { Note, Show, TimeSlot } from '@/types' +import { useAPIObjectFieldCopy, useRelationList } from '@/form' import { useHostStore, useLanguageStore, useNoteStore, useTopicStore } from '@/stores' +import { Note, Show, TimeSlot } from '@/types' + import FormGroup from '@/components/generic/FormGroup.vue' import ImagePicker from '@/components/images/ImagePicker.vue' import TagInput from '@/components/generic/TagInput.vue' import ComboBoxSimple from '@/components/ComboBoxSimple.vue' -import { useAPIObjectFieldCopy, useRelationList } from '@/form' import FormTable from '@/components/generic/FormTable.vue' import ALinkCollectionEditor from '@/components/generic/ALinkCollectionEditor.vue' import AHTMLEditor from '@/components/generic/AHTMLEditor.vue' +import AProfileSelector from '@/components/identities/AProfileSelector.vue' const props = defineProps<{ timeslot: TimeSlot diff --git a/src/i18n/de.js b/src/i18n/de.js index d0c29c5acb70d3b62341cc96cd6711ed63736548..47de4445aaa5a8533e6d4333f41c1973615c7f52 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -43,6 +43,8 @@ export default { }, labels: { hasAccount: 'Hat Konto', + createNew: '„%{name}“ anlegen', + similarSelected: 'Ähnliche bereits ausgewählte Profile: %{names}', }, editor: { deletion: { diff --git a/src/i18n/en.js b/src/i18n/en.js index b4f6aaadbd112316d22cb07d837fad866238b205..52dfc3cba9503fea18108c76c0d54e2c91b84fc3 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -43,6 +43,8 @@ export default { }, labels: { hasAccount: 'Has account', + createNew: 'Create “%{name}â€', + similarSelected: 'Similar already selected profiles: %{names}', }, editor: { deletion: { diff --git a/src/stores/hosts.ts b/src/stores/hosts.ts index b2370cf1b7a423d8d553d1bbae75e69062d4e6ed..cbd588e1ed698e2b8d381508c7ff543f199544df 100644 --- a/src/stores/hosts.ts +++ b/src/stores/hosts.ts @@ -1,4 +1,5 @@ import { + APICreate, APIListPaginated, APIRemove, APIRetrieve, @@ -23,6 +24,7 @@ export const useHostStore = defineStore('hosts', () => { ...base, ...APIListPaginated(api), ...APIRetrieve(api), + ...APICreate(api), ...APIUpdate<Host, HostUpdateData, HostPartialUpdateData>(api), ...APIRemove(api), }