feat(front): [WIP] Couple modal state with Url query
This commit is contained in:
parent
a2824e1f43
commit
0e4cef36a0
|
@ -15,7 +15,7 @@ const props = defineProps<{
|
||||||
cancel?: string
|
cancel?: string
|
||||||
} & (ColorProps | DefaultProps)>()
|
} & (ColorProps | DefaultProps)>()
|
||||||
|
|
||||||
const isOpen = defineModel<boolean>({ default:false })
|
const isOpen = defineModel<boolean>({ default: false })
|
||||||
|
|
||||||
const previouslyFocusedElement = ref();
|
const previouslyFocusedElement = ref();
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { useRoute } from 'vue-router'
|
||||||
import useThemeList from '~/composables/useThemeList'
|
import useThemeList from '~/composables/useThemeList'
|
||||||
import useTheme from '~/composables/useTheme'
|
import useTheme from '~/composables/useTheme'
|
||||||
|
|
||||||
|
import { useModal } from '~/ui/composables/useModal.ts'
|
||||||
|
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Popover from '~/components/ui/Popover.vue'
|
import Popover from '~/components/ui/Popover.vue'
|
||||||
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
|
@ -89,9 +91,7 @@ const labels = computed(() => ({
|
||||||
{{ labels.settings }}
|
{{ labels.settings }}
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
<hr v-if="store.state.auth.authenticated"/>
|
<hr v-if="store.state.auth.authenticated"/>
|
||||||
<PopoverItem @click="store.commit('ui/toggleModal', 'languages')"
|
<PopoverItem :to="useModal('languages').to">
|
||||||
:aria-pressed="store.state.ui.modalsOpen.has('languages') || undefined"
|
|
||||||
>
|
|
||||||
<i class="bi bi-translate" />
|
<i class="bi bi-translate" />
|
||||||
{{ labels.language }}...
|
{{ labels.language }}...
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
|
@ -130,9 +130,7 @@ const labels = computed(() => ({
|
||||||
<i class="bi bi-book" />
|
<i class="bi bi-book" />
|
||||||
{{ labels.docs }}
|
{{ labels.docs }}
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
<PopoverItem @click="store.commit('ui/toggleModal', 'shortcuts')"
|
<PopoverItem :to="useModal('shortcuts').to">
|
||||||
:aria-pressed="store.state.ui.modalsOpen.has('shortcuts') || undefined"
|
|
||||||
>
|
|
||||||
<i class="bi bi-keyboard" />
|
<i class="bi bi-keyboard" />
|
||||||
{{ labels.shortcuts }}
|
{{ labels.shortcuts }}
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
|
@ -1,27 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SUPPORTED_LOCALES, setI18nLanguage } from '~/init/locale'
|
import { SUPPORTED_LOCALES, setI18nLanguage } from '~/init/locale'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useStore } from '~/store'
|
|
||||||
|
import { useModal } from '~/ui/composables/useModal.ts'
|
||||||
|
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,29 +1,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useStore } from '~/store'
|
|
||||||
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
|
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
|
||||||
|
|
||||||
|
import { useModal } from '~/ui/composables/useModal.ts'
|
||||||
|
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const modalName = 'shortcuts'
|
const isOpen = useModal('shortcuts').isOpen
|
||||||
|
|
||||||
const isOpen = computed({
|
onKeyboardShortcut('h', useModal('shortcuts').toggle)
|
||||||
get() {
|
|
||||||
return store.state.ui.modalsOpen.has(modalName);
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
store.commit('ui/setModal', [modalName, value]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onKeyboardShortcut('h', () => store.commit('ui/toggleModal', modalName))
|
|
||||||
|
|
||||||
const general = computed(() => [
|
const general = computed(() => [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue