Skip to content
Snippets Groups Projects
Commit 3a824a9a authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt :koala:
Browse files

refactor: use ComboBox component for various show attributes

refs #179
parent 67ff1592
No related branches found
No related tags found
No related merge requests found
<template>
<FormGroup
:label="t('showMeta.categories')"
with-edit-button
@edit="() => modalCategories.show()"
>
<div class="tw-flex tw-flex-wrap tw-gap-2">
<Tag v-for="category in usedCategories" :key="category.id">
<span>
<span class="tw-block">{{ category.name }}</span>
<span v-if="category.subtitle.trim()" class="tw-text-xs">
{{ category.subtitle }}
<FormGroup :label="t('showMeta.categories')" custom-control>
<ComboBoxSimple v-model="selectedCategories" :choices="categories">
<template #selected="{ deselect }">
<Tag
v-for="category in selectedCategories"
: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>
</span>
</Tag>
</div>
</Tag>
</template>
</ComboBoxSimple>
</FormGroup>
<FormGroup :label="t('showMeta.topics')" with-edit-button @edit="modalTopics.show()">
<div class="tw-flex tw-flex-wrap tw-gap-2">
<Tag v-for="topic in usedTopics" :key="topic.id" :label="topic.name" />
</div>
<FormGroup :label="t('showMeta.topics')" custom-control>
<ComboBoxSimple v-model="selectedTopics" :choices="topics" />
</FormGroup>
<FormGroup :label="t('showMeta.genres')" with-edit-button @edit="modalMusicFocus.show()">
<div class="tw-flex tw-flex-wrap tw-gap-2">
<Tag v-for="musicFocus in usedMusicFocuses" :key="musicFocus.id" :label="musicFocus.name" />
</div>
<FormGroup :label="t('showMeta.genres')" custom-control>
<ComboBoxSimple v-model="selectedMusicFocuses" :choices="musicFocuses" />
</FormGroup>
<FormGroup :label="t('showMeta.languages')" with-edit-button @edit="modalLanguages.show()">
<div class="tw-flex tw-flex-wrap tw-gap-2">
<Tag v-for="language in usedLanguages" :key="language.id" :label="language.name" />
</div>
<FormGroup :label="t('showMeta.languages')" custom-control>
<ComboBoxSimple v-model="selectedLanguages" :choices="languages" />
</FormGroup>
<FormGroup :label="t('showMeta.hosts')" with-edit-button @edit="modalHosts.show()">
<div class="tw-flex tw-flex-wrap tw-gap-2">
<Tag v-for="host in usedHosts" :key="host.id" :label="host.name" />
</div>
<FormGroup :label="t('showMeta.hosts')" custom-control>
<ComboBoxSimple v-model="selectedHosts" :choices="hosts" />
</FormGroup>
<Teleport to="body">
<b-modal
ref="modalCategories"
:title="t('showMeta.editCategories')"
:cancel-title="t('cancel')"
size="lg"
@ok="saveArray('categoryIds', categoryIds, modalCategories)"
>
<b-row>
<b-col align="center">
<div>
<p>{{ t('showMeta.categoriesLabel') }}:</p>
<b-form-select
v-model="categoryIds"
multiple
:options="categoryChoices"
:select-size="5"
/>
<br /><br />
<b-alert show dismissible variant="info">
<span v-html="t('showMeta.multiselect')" />
</b-alert>
</div>
</b-col>
</b-row>
</b-modal>
<b-modal
ref="modalTopics"
:title="t('showMeta.editTopics')"
:cancel-title="t('cancel')"
size="lg"
@ok="saveArray('topicIds', topicIds, modalTopics)"
>
<b-row>
<b-col align="center">
<div>
<p>{{ t('showMeta.topicsLabel') }}:</p>
<b-form-select v-model="topicIds" multiple :options="topicChoices" :select-size="5" />
<br /><br />
<b-alert show dismissible variant="info">
<span v-html="t('showMeta.multiselect')" />
</b-alert>
</div>
</b-col>
</b-row>
</b-modal>
<b-modal
ref="modalMusicFocus"
:title="t('showMeta.editGenres')"
:cancel-title="t('cancel')"
size="lg"
@ok="saveArray('musicFocusIds', musicFocusIds, modalMusicFocus)"
>
<b-row>
<b-col align="center">
<div>
<p>{{ t('showMeta.genresLabel') }}:</p>
<b-form-select
v-model="musicFocusIds"
multiple
:options="musicFocusChoices"
:select-size="5"
/>
<br /><br />
<b-alert show dismissible variant="info">
<span v-html="t('showMeta.multiselect')" />
</b-alert>
</div>
</b-col>
</b-row>
</b-modal>
<b-modal
ref="modalLanguages"
:title="t('showMeta.editLanguages')"
:cancel-title="t('cancel')"
size="lg"
@ok="saveArray('languageIds', languageIds, modalLanguages)"
>
<b-row>
<b-col align="center">
<div>
<p>{{ t('showMeta.languagesLabel') }}:</p>
<b-form-select
v-model="languageIds"
multiple
:options="languageChoices"
:select-size="5"
/>
<br /><br />
<b-alert show dismissible variant="info">
<span v-html="t('showMeta.multiselect')" />
</b-alert>
</div>
</b-col>
</b-row>
</b-modal>
<b-modal
ref="modalHosts"
:title="t('showMeta.hosts')"
:cancel-title="t('cancel')"
size="lg"
@ok="saveArray('hostIds', hostIds, modalHosts)"
>
<b-row>
<b-col align="center">
<div>
<p>{{ t('showMeta.hostsLabel') }}</p>
<b-form-select v-model="hostIds" multiple :options="hostChoices" :select-size="5" />
<br /><br />
<b-alert show dismissible variant="info">
<span v-html="t('showMeta.multiselect')" />
</b-alert>
</div>
</b-col>
</b-row>
</b-modal>
</Teleport>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
import { computed, ref } from 'vue'
import { toRef } from 'vue'
import { Category, Language, MusicFocus, Topic } from '@/types'
import { useCopy, useSelectedShow } from '@/util'
import { useSelectedShow } from '@/util'
import { useHostStore } from '@/stores/hosts'
import FormGroup from '@/components/generic/FormGroup.vue'
import { useI18n } from '@/i18n'
import Tag from '@/components/generic/Tag.vue'
import { useItems } from './helper'
import ComboBoxSimple from '@/components/ComboBoxSimple.vue'
const { t } = useI18n()
const store = useStore()
const hostStore = useHostStore()
const selectedShow = useSelectedShow()
const categories = computed<Category[]>(() => store.state.shows.categories)
const topics = computed<Topic[]>(() => store.state.shows.topics)
const musicFocuses = computed<MusicFocus[]>(() => store.state.shows.musicFocus)
const languages = computed<Language[]>(() => store.state.shows.languages)
const { choices: categoryChoices, usedItems: usedCategories } = useItems(
categories,
computed(() => selectedShow.value.categoryIds),
)
const { choices: topicChoices, usedItems: usedTopics } = useItems(
topics,
computed(() => selectedShow.value.topicIds),
const { choices: categories, usedItems: selectedCategories } = useItems(
toRef(() => store.state.shows.categories as Category[]),
toRef(() => selectedShow.value.categoryIds),
{ sortBy: ['name'], save: (ids) => saveArray('categoryIds', ids) },
)
const { choices: musicFocusChoices, usedItems: usedMusicFocuses } = useItems(
musicFocuses,
computed(() => selectedShow.value.musicFocusIds),
const { choices: topics, usedItems: selectedTopics } = useItems(
toRef(() => store.state.shows.topics as Topic[]),
toRef(() => selectedShow.value.topicIds),
{ sortBy: ['name'], save: (ids) => saveArray('topicIds', ids) },
)
const { choices: languageChoices, usedItems: usedLanguages } = useItems(
languages,
computed(() => selectedShow.value.languageIds),
const { choices: musicFocuses, usedItems: selectedMusicFocuses } = useItems(
toRef(() => store.state.shows.musicFocus as MusicFocus[]),
toRef(() => selectedShow.value.musicFocusIds),
{ sortBy: ['name'], save: (ids) => saveArray('musicFocusIds', ids) },
)
const { choices: hostChoices, usedItems: usedHosts } = useItems(
computed(() => hostStore.items),
computed(() => selectedShow.value.hostIds),
const { choices: languages, usedItems: selectedLanguages } = useItems(
toRef(() => store.state.shows.languages as Language[]),
toRef(() => selectedShow.value.languageIds),
{ sortBy: ['name'], save: (ids) => saveArray('languageIds', ids) },
)
const categoryIds = useCopy(computed(() => selectedShow.value.categoryIds))
const topicIds = useCopy(computed(() => selectedShow.value.topicIds))
const musicFocusIds = useCopy(computed(() => selectedShow.value.musicFocusIds))
const languageIds = useCopy(computed(() => selectedShow.value.languageIds))
const hostIds = useCopy(computed(() => selectedShow.value.hostIds))
const modalCategories = ref()
const modalTopics = ref()
const modalMusicFocus = ref()
const modalLanguages = ref()
const modalHosts = ref()
const { itemMap: hostMap, items: hosts } = useHostStore()
const { usedItems: selectedHosts } = useItems(
hostMap,
toRef(() => selectedShow.value.hostIds),
{ sortBy: ['name'], save: (ids) => saveArray('hostIds', ids) },
)
async function saveArray(
property: 'categoryIds' | 'topicIds' | 'musicFocusIds' | 'languageIds' | 'hostIds',
value: number[],
modal: { hide: () => void },
) {
if (
value.length !== selectedShow.value[property].length ||
......@@ -228,7 +96,6 @@ async function saveArray(
property: property,
value: value,
})
modal.hide()
}
}
</script>
import { computed, Ref } from 'vue'
import { sort } from 'fast-sort'
import { computed, MaybeRefOrGetter, Ref, ref, toRef, toValue } from 'vue'
import { ID } from '@/api'
export function useItems<T extends { id: ID; isActive: boolean; name: string }>(
objects: Ref<T[]>,
usedIds: Ref<ID | undefined | null | ID[]>,
function _sort<T extends { isActive?: boolean }>(items: T[], keys: (keyof T)[]): T[] {
return sort(items).by([
{ asc: (item) => ('isActive' in item ? (item.isActive ? 0 : 1) : 0) },
...keys.map((key) => ({ asc: (item: T) => item[key] })),
])
}
export function useItems<T extends { id: ID; isActive?: boolean }, K = T['id']>(
objects: MaybeRefOrGetter<T[] | Map<K, T>>,
usedIds: MaybeRefOrGetter<undefined | null | K | K[]>,
options?: {
save?: (id: K[]) => Promise<void>
sortBy?: MaybeRefOrGetter<keyof T | (keyof T)[]>
},
) {
const idMap = computed(() => new Map(objects.value.map((obj) => [obj.id, obj])))
const choices = computed(() =>
objects.value.map((obj) => ({ value: obj.id, text: obj.name, disabled: !obj.isActive })),
)
const usedItems = computed(() => {
const value = usedIds.value
return (Array.isArray(value) ? value : value ? [value] : [])
.filter((id) => idMap.value.has(id))
.map((id) => idMap.value.get(id) as T)
const sortByKeys = toRef(options?.sortBy ?? undefined) as Ref<keyof T | (keyof T)[] | undefined>
const sortByKeysList = computed<(keyof T)[]>(() => {
const _value = toValue(sortByKeys)
if (_value === undefined) {
return []
} else if (Array.isArray(_value)) {
return Array.from(_value)
} else {
return [_value]
}
})
const idMap = computed<Map<K, T>>(() => {
const value = toValue(objects)
return Array.isArray(value) ? new Map(value.map((obj) => [obj.id as K, obj])) : value
})
const choices = computed<T[]>(() => {
const value = toValue(objects)
const choices = Array.isArray(value) ? value : Array.from(value.values())
return _sort(
choices.filter((item) => item?.isActive !== false),
sortByKeysList.value,
)
})
const isSaving = ref(false)
const usedItems = computed({
get() {
const value = toValue(usedIds)
return _sort(
(Array.isArray(value) ? value : value ? [value] : [])
.filter((id) => idMap.value.has(id))
.map((id) => idMap.value.get(id) as T),
sortByKeysList.value,
)
},
set(value: T[]) {
const save = options?.save
if (save) {
isSaving.value = true
save(value.map((item) => item.id as K)).finally(() => (isSaving.value = false))
}
},
})
const usedItem = computed(() => usedItems.value[0])
return { choices, usedItem, usedItems }
return { choices, usedItem, usedItems, isSaving }
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment