From 1911aa799d7dc47d23add61e047eb8f61f49cd87 Mon Sep 17 00:00:00 2001 From: upsiflu Date: Tue, 25 Mar 2025 23:18:52 +0100 Subject: [PATCH] feat(front): [WIP] useTags composable --- front/src/components/library/Albums.vue | 7 +-- front/src/store/ui.ts | 3 + front/src/ui/composables/useTags.ts | 82 +++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue index be88d5589..86737e2d1 100644 --- a/front/src/components/library/Albums.vue +++ b/front/src/components/library/Albums.vue @@ -12,6 +12,7 @@ import { syncRef } from '@vueuse/core' import { sortedUniq } from 'lodash-es' import { useStore } from '~/store' import { useModal } from '~/ui/composables/useModal.ts' +import { useTags } from '~/ui/composables/useTags.ts' import axios from 'axios' @@ -48,11 +49,7 @@ const props = withDefaults(defineProps(), { const page = usePage() const tags = useRouteQuery('tag', []) - -const tagList = ref({ - currents: [].map(tag => ({ type: 'custom' as const, label: tag })), - others: tags.value.map(tag => ({ type: 'custom' as const, label: tag })) -}) +const tagList = useTags().model(tags.value) const q = useRouteQuery('query', '') const query = ref(q.value ?? '') diff --git a/front/src/store/ui.ts b/front/src/store/ui.ts index 9caf632bc..38eb21701 100644 --- a/front/src/store/ui.ts +++ b/front/src/store/ui.ts @@ -233,6 +233,9 @@ const store: Module = { }, window: (state, value) => { state.window = value + }, + tags: (state, value) => { + state.tags = value } }, actions: { diff --git a/front/src/ui/composables/useTags.ts b/front/src/ui/composables/useTags.ts index 1f90fb68b..ce1c4a31b 100644 --- a/front/src/ui/composables/useTags.ts +++ b/front/src/ui/composables/useTags.ts @@ -1,15 +1,89 @@ +import type { paths } from '~/generated/types.ts' + import { computed, ref } from 'vue' import { useStore } from '~/store' +import axios from 'axios' + +type Item = { type: 'custom' | 'preset', label: string } +type Model = { currents: Item[], others?: Item[] } /** * Load and cache all tags - * - * @param current */ -export const useTags = (current = ref([])) => { +export const useTags = () => { const store = useStore() - const tags = computed(() => store.state.ui.tags) + // Two-way bind with store + const tags = computed({ + get: () => store.state.ui.tags || [], + set: function(value) { + store.commit('ui/tags', value) + } + }) + + // Ignore quick successive fetch triggers + const ignorePeriod = 500 + + // Wait between consecutiv fetches + const waitInterval = 3000 + + // Wait after changing a tag before re-fetching + const refetchInterval = 6000 + + const maxTags = 100000 + + const lastFetched = ref(0) + + const fetch = async () => { + // Ignore subsequent fetch commands triggered in quick succession + if (lastFetched.value + ignorePeriod > Date.now()) + return + + // Always wait some milliseconds before re-fetching + if (lastFetched.value + waitInterval > Date.now()) { + window.setTimeout(fetch, lastFetched.value + waitInterval - Date.now()) + return + } + + const response = await axios.get( + '/tags', + { params: { page: 1, page_size: maxTags } } + ) + + tags.value = response.data.results + } + + return ({ + /** + * @param currents Initial selected tags + * @returns v-model for `Pills` component + */ + model: (currents: string[] = []) => { + fetch(); + return computed({ + + // Get currents initially from parameter. Get others from backend. + get: (): Model => ({ + currents: currents.map(tag => ({ + label: tag, + type: tags.value.some(({ name }) => tag === name) ? 'preset' : 'custom' + })), + others: tags.value + .filter(({ name }) => !currents.includes(name)) + .map(({ name }) => ({ + label: name, + type: 'preset' + })) + }), + + // Re-fetch after each new setting + set: function (_) { + window.setTimeout(fetch, refetchInterval) + // TODO: Broadcast new custom tags so that other pills components can use them + } + }) + } + }) } // alternative ways to generate tags