From a97a8d53d15f766e855cc82517a87afa9e983250 Mon Sep 17 00:00:00 2001 From: Flupsi Date: Fri, 6 Jun 2025 20:38:22 +0200 Subject: [PATCH] feat(front): suggest previously used tags in `` component #2448 --- front/src/components/audio/ChannelForm.vue | 4 +++- front/src/components/library/Albums.vue | 5 +++-- front/src/components/library/Artists.vue | 5 +++-- front/src/components/library/EditForm.vue | 2 +- front/src/components/library/Podcasts.vue | 10 +++------ front/src/ui/stores/data.ts | 24 ++++++++++++++++++++-- 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/front/src/components/audio/ChannelForm.vue b/front/src/components/audio/ChannelForm.vue index e257cb92a..fe6d9d784 100644 --- a/front/src/components/audio/ChannelForm.vue +++ b/front/src/components/audio/ChannelForm.vue @@ -5,6 +5,7 @@ import type { paths } from '~/generated/types' import { slugify } from 'transliteration' import { reactive, computed, ref, watchEffect, watch } from 'vue' import { useI18n } from 'vue-i18n' +import { useDataStore } from '~/ui/stores/data' import axios from 'axios' import AttachmentInput from '~/components/common/AttachmentInput.vue' @@ -35,6 +36,7 @@ const props = withDefaults(defineProps(), { }) const { t } = useI18n() +const dataStore = useDataStore() const newValues = reactive({ name: props.object?.artist?.name ?? '', @@ -261,7 +263,7 @@ defineExpose({ :get="model => { newValues.tags = model.currents.map(({ label }) => label) }" :set="model => ({ currents: newValues.tags.map(tag => ({ type: 'custom' as const, label: tag })), - others: [].map(tag => ({ type: 'custom' as const, label: tag })) + others: dataStore.tags().value.map(({ name }) => ({ type: 'custom' as const, label: name })) })" :label="t('components.audio.ChannelForm.label.tags')" /> diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue index 6ba62effe..05f0d616f 100644 --- a/front/src/components/library/Albums.vue +++ b/front/src/components/library/Albums.vue @@ -11,6 +11,7 @@ import { useI18n } from 'vue-i18n' import { syncRef } from '@vueuse/core' import { sortedUniq } from 'lodash-es' import { useStore } from '~/store' +import { useDataStore } from '~/ui/stores/data' import { useModal } from '~/ui/composables/useModal.ts' import axios from 'axios' @@ -101,6 +102,7 @@ const fetchData = async () => { } const store = useStore() +const dataStore = useDataStore() watch(() => store.state.moderation.lastUpdate, fetchData) watch([page, tags, q, ordering, orderingDirection, () => props.scope], fetchData) fetchData() @@ -153,9 +155,8 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] v-if="typeof tags === 'object'" :get="model => { tags = model.currents.map(({ label }) => label) }" :set="model => ({ - ...model, - others: [], currents: tags.map(tag => ({ type: 'custom' as const, label: tag })), + others: dataStore.tags().value.map(({ name }) => ({ type: 'preset' as const, label: name })), })" :label="t('components.library.Albums.label.tags')" style="max-width: 150px;" diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue index f92672017..197c0d387 100644 --- a/front/src/components/library/Artists.vue +++ b/front/src/components/library/Artists.vue @@ -10,6 +10,7 @@ import { useI18n } from 'vue-i18n' import { syncRef } from '@vueuse/core' import { sortedUniq } from 'lodash-es' import { useStore } from '~/store' +import { useDataStore } from '~/ui/stores/data' import { useModal } from '~/ui/composables/useModal.ts' import axios from 'axios' @@ -101,6 +102,7 @@ const fetchData = async () => { } const store = useStore() +const dataStore = useDataStore() watch([() => store.state.moderation.lastUpdate, excludeCompilation], fetchData) watch([page, tags, q, ordering, orderingDirection, () => props.scope], fetchData) fetchData() @@ -153,9 +155,8 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] v-if="typeof tags === 'object'" :get="model => { tags = model.currents.map(({ label }) => label) }" :set="model => ({ - ...model, - others: [], currents: tags.map(tag => ({ type: 'custom' as const, label: tag })), + others: dataStore.tags().value.map(({ name }) => ({ type: 'preset' as const, label: name })), })" :label="t('components.library.Artists.label.tags')" style="max-width: 150px;" diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue index bea3f6d42..509bb0116 100644 --- a/front/src/components/library/EditForm.vue +++ b/front/src/components/library/EditForm.vue @@ -318,8 +318,8 @@ const resetField = (fieldId: string) => { :get="model => { values[fieldConfig.id] = model.currents.map(({ label }) => label) }" :set="model => ({ ...model, - others: [], currents: (values[fieldConfig.id] as string[]).map(tag => ({ type: 'custom' as const, label: tag })), + others: dataStore.tags().value.map(({ name }) => ({ type: 'preset' as const, label: name })), })" :label="fieldConfig.label" :required="fieldConfig.required" diff --git a/front/src/components/library/Podcasts.vue b/front/src/components/library/Podcasts.vue index 824d2fe27..902760934 100644 --- a/front/src/components/library/Podcasts.vue +++ b/front/src/components/library/Podcasts.vue @@ -10,6 +10,7 @@ import { useI18n } from 'vue-i18n' import { syncRef } from '@vueuse/core' import { sortedUniq } from 'lodash-es' import { useStore } from '~/store' +import { useDataStore } from '~/ui/stores/data' import { useModal } from '~/ui/composables/useModal.ts' import axios from 'axios' @@ -60,11 +61,6 @@ const submittable = ref(false) const tags = useRouteQuery('tag', []) -computed(() => ({ - currents: [].map(tag => ({ type: 'custom' as const, label: tag })), - others: tags.value.map(tag => ({ type: 'custom' as const, label: tag })) -})) - const q = useRouteQuery('query', '') const query = ref(q.value) syncRef(q, query, { direction: 'ltr' }) @@ -116,6 +112,7 @@ const fetchData = async () => { } const store = useStore() +const dataStore = useDataStore() watch(() => store.state.moderation.lastUpdate, fetchData) watch([page, tags, q, ordering, orderingDirection], fetchData) fetchData() @@ -179,9 +176,8 @@ const { to: upload } = useModal('upload') v-if="typeof tags === 'object'" :get="model => { tags = model.currents.map(({ label }) => label) }" :set="model => ({ - ...model, - others: [], currents: tags.map(tag => ({ type: 'custom' as const, label: tag })), + others: dataStore.tags().value.map(({ name }) => ({ type: 'preset' as const, label: name })), })" :label="t('components.library.Podcasts.label.tags')" style="max-width: 150px;" diff --git a/front/src/ui/stores/data.ts b/front/src/ui/stores/data.ts index f1952714a..0c2d911c7 100644 --- a/front/src/ui/stores/data.ts +++ b/front/src/ui/stores/data.ts @@ -2,7 +2,8 @@ import { defineStore } from 'pinia' import { ref, computed, type Ref } from 'vue' import axios from 'axios' -import type { Album, Artist, Track } from '~/types' +import type { Album, Artist, Tag, Track } from '~/types' +import type { components } from '~/generated/types' /** * Fetch an item from the API. @@ -51,6 +52,23 @@ export const useDataStore track: {} } + const tagsCache = ref([]) + var tagsTimestamp = 0 + + /** + * @returns an auto-updating reference to all tags or `[]` if either none are loaded yet, or there was an error + */ + const tags = () => { + // Re-fetch if immediate is true or the item is not cached or older than 1 second + if (tagsTimestamp< Date.now() - 1000) { + axios.get('tags/', { params: { page_size: 10000 } }).then(({ data }) => { + tagsTimestamp = Date.now(); + tagsCache.value = data.results; + }); + } + return tagsCache; + } + /** Inspect the cache with the Vue Devtools (Pinia tab); read-only */ const data = computed(() => cache) @@ -81,6 +99,8 @@ export const useDataStore return { data, - get + get, + tagsCache, + tags } })