feat(front): Implement cache composable

2490-experimental-use-simple-data-store
Flupsi 2025-09-14 11:21:15 +02:00
rodzic 66d36f26a6
commit 81f1816ebb
3 zmienionych plików z 59 dodań i 23 usunięć

Wyświetl plik

@ -0,0 +1,20 @@
import { expect, test } from 'vitest'
import { useCache } from './useCache'
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
const retention = 10
test('Cache sets value synchronously', () => {
const cache = useCache({ retention })
cache('K').value = ('V')
expect(cache('K').value).toBe('V')
})
test('Cache forgets value after a set time', async () => {
const cache = useCache({ retention })
cache('K').value = ('V')
await wait(retention);
expect(cache('K').value).toBe(undefined)
})

Wyświetl plik

@ -0,0 +1,24 @@
import { computed, ref } from 'vue'
export const allCaches = ref<Map<unknown, unknown>[]>([]);
/**
* Forgetful, reactive key-value store.
*
* @param retention: Milliseconds until a datum expires
*/
export const useCache = <K, V>({ retention }: { retention: number }) => {
const cache = new Map<K, V>()
allCaches.value.push(cache);
return (key: K) => computed({
get() {
return cache.get(key)
},
set(value: V) {
cache.set(key, value);
setTimeout(() => cache.delete(key), retention)
}
})
}

Wyświetl plik

@ -1,16 +1,15 @@
<script setup lang="ts">
import { ref, onMounted, watch, computed, nextTick } from 'vue'
import { ref, onMounted, watch, computed } from 'vue'
import { useUploadsStore } from '../stores/upload'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
import { useModal } from '~/ui/composables/useModal.ts'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
import { defineAsyncComponent } from 'vue'
import Logo from '~/components/Logo.vue'
import Input from '~/components/ui/Input.vue'
import Link from '~/components/ui/Link.vue'
import UserMenu from './UserMenu.vue'
import Link from '~/components/ui/Link.vue'
import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
import Button from '~/components/ui/Button.vue'
@ -18,6 +17,11 @@ import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import { useRoute } from 'vue-router'
// simple usage
const SearchModal = defineAsyncComponent({
loader: () => import('~/ui/modals/Search.vue')
})
const isCollapsed = ref(true)
const route = useRoute()
@ -29,7 +33,6 @@ onMounted(() => {
})
const { t } = useI18n()
const { value: searchParameter } = useModal('search')
const store = useStore()
const uploads = useUploadsStore()
@ -37,19 +40,6 @@ const logoUrl = computed(() => store.state.auth.authenticated ? 'library.index'
const isOpen = ref(false)
// Search bar focus
const isFocusingSearch = ref<true | undefined>(undefined)
const focusSearch = () => {
isFocusingSearch.value = undefined
nextTick(() => {
isFocusingSearch.value = true
})
}
onKeyboardShortcut(['shift', 'f'], focusSearch, true)
onKeyboardShortcut(['ctrl', 'k'], focusSearch, true)
onKeyboardShortcut(['/'], focusSearch, true)
// Admin notifications
const moderationNotifications = computed(() =>
@ -209,16 +199,18 @@ const moderationNotifications = computed(() =>
stack
:class="[$style['menu-links'], isCollapsed && 'hide-on-mobile']"
>
<Input
:key="isFocusingSearch ? 1 : 0"
<!-- The search input will live next to the search modal. It needs -->
<!-- <Input
ref="globalSearchInput"
v-model="searchParameter"
:autofocus="isFocusingSearch"
raised
autocomplete="search"
type="search"
icon="bi-search"
:placeholder="t('components.audio.SearchBar.placeholder.search')"
/>
/> -->
<SearchModal />
<Spacer />