feat(front): [WIP] useTags composable

This commit is contained in:
upsiflu 2025-03-25 23:18:52 +01:00
parent 0570d5c3a5
commit 1911aa799d
3 changed files with 83 additions and 9 deletions

View File

@ -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<Props>(), {
const page = usePage()
const tags = useRouteQuery<string[]>('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 ?? '')

View File

@ -233,6 +233,9 @@ const store: Module<State, RootState> = {
},
window: (state, value) => {
state.window = value
},
tags: (state, value) => {
state.tags = value
}
},
actions: {

View File

@ -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 = <T> (current = ref<string[]>([])) => {
export const useTags = <T> () => {
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<number>(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<paths['/api/v2/tags/']['get']['responses']['200']['content']['application/json']>(
'/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