feat(front): suggest previously used tags in `<Pills>` component #2448

This commit is contained in:
Flupsi 2025-06-06 20:38:22 +02:00
parent 65529429a6
commit a97a8d53d1
6 changed files with 35 additions and 15 deletions

View File

@ -5,6 +5,7 @@ import type { paths } from '~/generated/types'
import { slugify } from 'transliteration' import { slugify } from 'transliteration'
import { reactive, computed, ref, watchEffect, watch } from 'vue' import { reactive, computed, ref, watchEffect, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDataStore } from '~/ui/stores/data'
import axios from 'axios' import axios from 'axios'
import AttachmentInput from '~/components/common/AttachmentInput.vue' import AttachmentInput from '~/components/common/AttachmentInput.vue'
@ -35,6 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
}) })
const { t } = useI18n() const { t } = useI18n()
const dataStore = useDataStore()
const newValues = reactive({ const newValues = reactive({
name: props.object?.artist?.name ?? '', name: props.object?.artist?.name ?? '',
@ -261,7 +263,7 @@ defineExpose({
:get="model => { newValues.tags = model.currents.map(({ label }) => label) }" :get="model => { newValues.tags = model.currents.map(({ label }) => label) }"
:set="model => ({ :set="model => ({
currents: newValues.tags.map(tag => ({ type: 'custom' as const, label: tag })), 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')" :label="t('components.audio.ChannelForm.label.tags')"
/> />

View File

@ -11,6 +11,7 @@ import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useDataStore } from '~/ui/stores/data'
import { useModal } from '~/ui/composables/useModal.ts' import { useModal } from '~/ui/composables/useModal.ts'
import axios from 'axios' import axios from 'axios'
@ -101,6 +102,7 @@ const fetchData = async () => {
} }
const store = useStore() const store = useStore()
const dataStore = useDataStore()
watch(() => store.state.moderation.lastUpdate, fetchData) watch(() => store.state.moderation.lastUpdate, fetchData)
watch([page, tags, q, ordering, orderingDirection, () => props.scope], fetchData) watch([page, tags, q, ordering, orderingDirection, () => props.scope], fetchData)
fetchData() fetchData()
@ -153,9 +155,8 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
v-if="typeof tags === 'object'" v-if="typeof tags === 'object'"
:get="model => { tags = model.currents.map(({ label }) => label) }" :get="model => { tags = model.currents.map(({ label }) => label) }"
:set="model => ({ :set="model => ({
...model,
others: [],
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })), 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')" :label="t('components.library.Albums.label.tags')"
style="max-width: 150px;" style="max-width: 150px;"

View File

@ -10,6 +10,7 @@ import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useDataStore } from '~/ui/stores/data'
import { useModal } from '~/ui/composables/useModal.ts' import { useModal } from '~/ui/composables/useModal.ts'
import axios from 'axios' import axios from 'axios'
@ -101,6 +102,7 @@ const fetchData = async () => {
} }
const store = useStore() const store = useStore()
const dataStore = useDataStore()
watch([() => store.state.moderation.lastUpdate, excludeCompilation], fetchData) watch([() => store.state.moderation.lastUpdate, excludeCompilation], fetchData)
watch([page, tags, q, ordering, orderingDirection, () => props.scope], fetchData) watch([page, tags, q, ordering, orderingDirection, () => props.scope], fetchData)
fetchData() fetchData()
@ -153,9 +155,8 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
v-if="typeof tags === 'object'" v-if="typeof tags === 'object'"
:get="model => { tags = model.currents.map(({ label }) => label) }" :get="model => { tags = model.currents.map(({ label }) => label) }"
:set="model => ({ :set="model => ({
...model,
others: [],
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })), 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')" :label="t('components.library.Artists.label.tags')"
style="max-width: 150px;" style="max-width: 150px;"

View File

@ -318,8 +318,8 @@ const resetField = (fieldId: string) => {
:get="model => { values[fieldConfig.id] = model.currents.map(({ label }) => label) }" :get="model => { values[fieldConfig.id] = model.currents.map(({ label }) => label) }"
:set="model => ({ :set="model => ({
...model, ...model,
others: [],
currents: (values[fieldConfig.id] as string[]).map(tag => ({ type: 'custom' as const, label: tag })), 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" :label="fieldConfig.label"
:required="fieldConfig.required" :required="fieldConfig.required"

View File

@ -10,6 +10,7 @@ import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useDataStore } from '~/ui/stores/data'
import { useModal } from '~/ui/composables/useModal.ts' import { useModal } from '~/ui/composables/useModal.ts'
import axios from 'axios' import axios from 'axios'
@ -60,11 +61,6 @@ const submittable = ref(false)
const tags = useRouteQuery<string[]>('tag', []) const tags = useRouteQuery<string[]>('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 q = useRouteQuery('query', '')
const query = ref(q.value) const query = ref(q.value)
syncRef(q, query, { direction: 'ltr' }) syncRef(q, query, { direction: 'ltr' })
@ -116,6 +112,7 @@ const fetchData = async () => {
} }
const store = useStore() const store = useStore()
const dataStore = useDataStore()
watch(() => store.state.moderation.lastUpdate, fetchData) watch(() => store.state.moderation.lastUpdate, fetchData)
watch([page, tags, q, ordering, orderingDirection], fetchData) watch([page, tags, q, ordering, orderingDirection], fetchData)
fetchData() fetchData()
@ -179,9 +176,8 @@ const { to: upload } = useModal('upload')
v-if="typeof tags === 'object'" v-if="typeof tags === 'object'"
:get="model => { tags = model.currents.map(({ label }) => label) }" :get="model => { tags = model.currents.map(({ label }) => label) }"
:set="model => ({ :set="model => ({
...model,
others: [],
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })), 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')" :label="t('components.library.Podcasts.label.tags')"
style="max-width: 150px;" style="max-width: 150px;"

View File

@ -2,7 +2,8 @@ import { defineStore } from 'pinia'
import { ref, computed, type Ref } from 'vue' import { ref, computed, type Ref } from 'vue'
import axios from 'axios' 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. * Fetch an item from the API.
@ -51,6 +52,23 @@ export const useDataStore
track: {} track: {}
} }
const tagsCache = ref<Tag[]>([])
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<components['schemas']['PaginatedTagList']>('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 */ /** Inspect the cache with the Vue Devtools (Pinia tab); read-only */
const data = computed(() => cache) const data = computed(() => cache)
@ -81,6 +99,8 @@ export const useDataStore
return { return {
data, data,
get get,
tagsCache,
tags
} }
}) })