feat(front): [WIP] Couple modal state with Url query

This commit is contained in:
upsiflu 2025-02-10 10:02:46 +01:00
parent a2824e1f43
commit 0e4cef36a0
5 changed files with 94 additions and 34 deletions

View File

@ -15,7 +15,7 @@ const props = defineProps<{
cancel?: string
} & (ColorProps | DefaultProps)>()
const isOpen = defineModel<boolean>({ default:false })
const isOpen = defineModel<boolean>({ default: false })
const previouslyFocusedElement = ref();

View File

@ -7,6 +7,8 @@ import { useRoute } from 'vue-router'
import useThemeList from '~/composables/useThemeList'
import useTheme from '~/composables/useTheme'
import { useModal } from '~/ui/composables/useModal.ts'
import Button from '~/components/ui/Button.vue'
import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
@ -89,9 +91,7 @@ const labels = computed(() => ({
{{ labels.settings }}
</PopoverItem>
<hr v-if="store.state.auth.authenticated"/>
<PopoverItem @click="store.commit('ui/toggleModal', 'languages')"
:aria-pressed="store.state.ui.modalsOpen.has('languages') || undefined"
>
<PopoverItem :to="useModal('languages').to">
<i class="bi bi-translate" />
{{ labels.language }}...
</PopoverItem>
@ -130,9 +130,7 @@ const labels = computed(() => ({
<i class="bi bi-book" />
{{ labels.docs }}
</PopoverItem>
<PopoverItem @click="store.commit('ui/toggleModal', 'shortcuts')"
:aria-pressed="store.state.ui.modalsOpen.has('shortcuts') || undefined"
>
<PopoverItem :to="useModal('shortcuts').to">
<i class="bi bi-keyboard" />
{{ labels.shortcuts }}
</PopoverItem>

View File

@ -0,0 +1,82 @@
import { computed } from 'vue'
import { useRouter, type RouteLocationRaw } from 'vue-router'
/**
* Bind a modal to a single query parameter in the URL (and vice versa)
*
* @param flag query parameter to appear in the `url`, corresponding with `isOpen`
*
* This functionality completely independent from the `router` modules.
*/
export function useModal(flag: string) {
const router = useRouter()
const query = computed(() => router?.currentRoute.value ? router.currentRoute.value.query : {})
/**
* Visibility of this modal
*
* Use this function to bind a modal to a query parameter in the URL.
* Using this helper function will not change the routes.
*
* For example, if the modal flag is 'upload':
*
* ```vue
* <template>
* <Modal :open="isOpen('upload')">...</Modal>
* </template>
* ```
*
* Url: `https://funkwhale.audio/?upload`
*
* More information on query flags: https://router.vuejs.org/api/type-aliases/LocationQueryValue.html
*
* Use `asAttribute` instead to bind a modal to an on/off attribute such as `aria-expanded` or `aria-pressed`
*
* @param flag Identifier for the modal
* @returns a `ref`<boolean>
*/
const isOpen = computed({
get() {
const newValue = flag in query.value && query.value[flag] === null
// console.log("GET isOpen", flag, "in", query, newValue)
return newValue
},
set(newValue: boolean) {
// console.log("SET isOpen", query, "+", newValue, ", was", isOpen.value)
router.push({ query: { ...query.value, [flag]: newValue ? null : undefined }}).catch((err) => {
throw new Error(`Problem pushing route query: ${err}.`);
});
// console.log("DONE SET isOpen", query)
}
})
/**
*
* @param flag Identifier for the modal
* @returns a `RouteLocationRaw` object to use in the `to` prop of a RouterLink component
*/
const to: RouteLocationRaw = {
query: { ...query.value, [flag]: null }
}
/**
* Use this function to bind a modal to an on/off attribute such as `aria-expanded` or `aria-pressed`
*
* @param flag Identifier for the modal
* @returns a `ref`<true | undefined>
*/
const asAttribute = computed(() =>
isOpen.value || undefined
)
/**
* Toggle the visibility of this modal
*
*
* @param flag Identifier for the modal
*/
const toggle = () =>
isOpen.value = !isOpen.value
return { isOpen, to, asAttribute, toggle }
}

View File

@ -1,27 +1,16 @@
<script setup lang="ts">
import { SUPPORTED_LOCALES, setI18nLanguage } from '~/init/locale'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
import { useModal } from '~/ui/composables/useModal.ts'
import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue'
import Layout from '~/components/ui/Layout.vue'
import { computed } from 'vue'
const { t, locale } = useI18n()
const store = useStore()
const modalName = 'languages'
const isOpen = computed({
get() {
return store.state.ui.modalsOpen.has(modalName);
},
set(value) {
store.commit('ui/setModal', [modalName, value]);
}
})
const isOpen = useModal('languages').isOpen
</script>
<template>

View File

@ -1,29 +1,20 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
import { useModal } from '~/ui/composables/useModal.ts'
import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
const { t } = useI18n()
const store = useStore()
const modalName = 'shortcuts'
const isOpen = useModal('shortcuts').isOpen
const isOpen = computed({
get() {
return store.state.ui.modalsOpen.has(modalName);
},
set(value) {
store.commit('ui/setModal', [modalName, value]);
}
})
onKeyboardShortcut('h', () => store.commit('ui/toggleModal', modalName))
onKeyboardShortcut('h', useModal('shortcuts').toggle)
const general = computed(() => [
{