feat(ui): enable and style shortcuts modal
This commit is contained in:
parent
74958dcf34
commit
1740fa7c11
|
@ -1,12 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// import LegacyLayout from '~/LegacyLayout.vue'
|
// import LegacyLayout from '~/LegacyLayout.vue'
|
||||||
// TODO: Check if we ever need LegacyLayout
|
// TODO: see below (isUIv2)
|
||||||
import UiApp from '~/ui/App.vue'
|
import UiApp from '~/ui/App.vue'
|
||||||
|
|
||||||
import type { QueueTrack } from '~/composables/audio/queue'
|
import type { QueueTrack } from '~/composables/audio/queue'
|
||||||
|
|
||||||
import { watchEffect, computed, onMounted, nextTick } from 'vue'
|
import { watchEffect, computed, onMounted, nextTick } from 'vue'
|
||||||
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
|
|
||||||
|
|
||||||
import { useQueue } from '~/composables/audio/queue'
|
import { useQueue } from '~/composables/audio/queue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
@ -24,9 +23,9 @@ const ShortcutsModal = defineAsyncComponent(() => import('~/components/Shortcuts
|
||||||
const AudioPlayer = defineAsyncComponent(() => import('~/components/audio/Player.vue'))
|
const AudioPlayer = defineAsyncComponent(() => import('~/components/audio/Player.vue'))
|
||||||
const Sidebar = defineAsyncComponent(() => import('~/components/Sidebar.vue'))
|
const Sidebar = defineAsyncComponent(() => import('~/components/Sidebar.vue'))
|
||||||
const Queue = defineAsyncComponent(() => import('~/components/Queue.vue'))
|
const Queue = defineAsyncComponent(() => import('~/components/Queue.vue'))
|
||||||
import { useLocalStorage } from '@vueuse/core'
|
|
||||||
import { useLocalStorage, useStyleTag, useIntervalFn, useToggle, useWindowSize } from '@vueuse/core'
|
import { useLocalStorage, useStyleTag, useIntervalFn, useToggle, useWindowSize } from '@vueuse/core'
|
||||||
import { useLocalStorage, useStyleTag, useIntervalFn, useToggle } from '@vueuse/core'
|
|
||||||
|
|
||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
logger.debug('App setup()')
|
logger.debug('App setup()')
|
||||||
|
@ -70,10 +69,6 @@ useIntervalFn(() => {
|
||||||
store.commit('ui/computeLastDate')
|
store.commit('ui/computeLastDate')
|
||||||
}, 1000 * 60)
|
}, 1000 * 60)
|
||||||
|
|
||||||
// Shortcuts
|
|
||||||
const [_, toggleShortcutsModal] = useToggle(false)
|
|
||||||
onKeyboardShortcut('h', () => toggleShortcutsModal())
|
|
||||||
|
|
||||||
// Fetch user data on startup
|
// Fetch user data on startup
|
||||||
// NOTE: We're not checking if we're authenticated in the store,
|
// NOTE: We're not checking if we're authenticated in the store,
|
||||||
// because we want to learn if we are authenticated at all
|
// because we want to learn if we are authenticated at all
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Modal from '~/components/ui/Modal.vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
|
||||||
const model = defineModel<boolean>()
|
const model = defineModel<boolean>()
|
||||||
|
|
||||||
|
@ -98,52 +99,57 @@ const player = computed(() => [
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal
|
<Modal
|
||||||
|
overPopover
|
||||||
:title="t('components.ShortcutsModal.header.modal')"
|
:title="t('components.ShortcutsModal.header.modal')"
|
||||||
v-model="model">
|
v-model="model">
|
||||||
<section class="scrolling content">
|
<section class="scrolling content">
|
||||||
<div class="ui stackable two column grid">
|
<Layout flex>
|
||||||
<div class="column">
|
<div class="column" :class="$style.withPadding">
|
||||||
<table
|
<table
|
||||||
v-for="section in player"
|
v-for="section in player"
|
||||||
:key="section.title"
|
:key="section.title"
|
||||||
class="ui compact basic table"
|
class="ui compact basic table"
|
||||||
>
|
>
|
||||||
<caption>{{ section.title }}</caption>
|
<h3>{{ section.title }}</h3>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="shortcut in section.shortcuts"
|
v-for="shortcut in section.shortcuts"
|
||||||
:key="shortcut.summary"
|
:key="shortcut.summary"
|
||||||
>
|
>
|
||||||
<td>{{ shortcut.summary }}</td>
|
<td :class="$style.description">{{ shortcut.summary }}</td>
|
||||||
<td><span class="ui label">{{ shortcut.key }}</span></td>
|
<td> <Button style="pointer-events:none;" class="is-icon-only">{{ shortcut.key }}</Button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column" :class="$style.withPadding">
|
||||||
<table
|
<table
|
||||||
v-for="section in general"
|
v-for="section in general"
|
||||||
:key="section.title"
|
:key="section.title"
|
||||||
class="ui compact basic table"
|
class="ui compact basic table"
|
||||||
>
|
>
|
||||||
<caption>{{ section.title }}</caption>
|
<h3>{{ section.title }}</h3>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="shortcut in section.shortcuts"
|
v-for="shortcut in section.shortcuts"
|
||||||
:key="shortcut.summary"
|
:key="shortcut.summary"
|
||||||
>
|
>
|
||||||
<td>{{ shortcut.summary }}</td>
|
<td :class="$style.description">{{ shortcut.summary }}</td>
|
||||||
<td><span class="ui label">{{ shortcut.key }}</span></td>
|
<td> <Button style="pointer-events:none;" class="is-icon-only">{{ shortcut.key }}</Button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Layout>
|
||||||
</section>
|
</section>
|
||||||
<footer class="actions">
|
|
||||||
<Button color="secondary">
|
|
||||||
{{ t('components.ShortcutsModal.button.close') }}
|
|
||||||
</Button>
|
|
||||||
</footer>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.withPadding {
|
||||||
|
padding:8px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Spacer from '~/components/ui/layout/Spacer.vue'
|
||||||
|
|
||||||
const { title, overPopover = false } = defineProps<{ title:string, overPopover?:true }>()
|
const { title, overPopover = false } = defineProps<{ title:string, overPopover?:true }>()
|
||||||
const isOpen = defineModel<boolean>({ default:false })
|
const isOpen = defineModel<boolean>({ default:false })
|
||||||
|
@ -34,6 +35,7 @@ const isOpen = defineModel<boolean>({ default:false })
|
||||||
<div v-if="$slots.actions" class="modal-actions">
|
<div v-if="$slots.actions" class="modal-actions">
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
|
<Spacer :size="128" v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
box-shadow: 0 2px 4px 2px rgba(#000, 0.2);
|
box-shadow: 0 2px 4px 2px rgba(#000, 0.2);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
max-width: min(90vw, 40rem);
|
max-width: min(90vw, 55rem);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, nextTick } from 'vue'
|
import { onMounted, nextTick, ref, defineAsyncComponent } from 'vue'
|
||||||
|
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
|
||||||
import Sidebar from '~/ui/components/Sidebar.vue'
|
import Sidebar from '~/ui/components/Sidebar.vue'
|
||||||
|
import ShortcutsModal from '~/components/ShortcutsModal.vue'
|
||||||
|
|
||||||
// Fake content
|
// Fake content
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
document.getElementById('fake-app')?.remove()
|
document.getElementById('fake-app')?.remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isShortcutsModalOpen = ref(false)
|
||||||
|
onKeyboardShortcut('h', () => isShortcutsModalOpen.value = !isShortcutsModalOpen.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="funkwhale grid">
|
<div class="funkwhale grid">
|
||||||
<Sidebar />
|
<Sidebar :openShortcutsModal = "() => isShortcutsModalOpen=true" />
|
||||||
<main>
|
<main>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
|
<ShortcutsModal v-model="isShortcutsModalOpen" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ 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/layout/Spacer.vue'
|
import Spacer from '~/components/ui/layout/Spacer.vue'
|
||||||
|
|
||||||
|
const { openShortcutsModal } = defineProps<{ openShortcutsModal: ()=> void }>()
|
||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
// Hide the fake app when the real one is loaded
|
// Hide the fake app when the real one is loaded
|
||||||
|
@ -62,7 +64,7 @@ const uploads = useUploadsStore()
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<UserMenu />
|
<UserMenu :showShortcutsModal="openShortcutsModal" />
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
@ -129,7 +131,7 @@ const uploads = useUploadsStore()
|
||||||
{{ t('components.Sidebar.link.favorites') }}
|
{{ t('components.Sidebar.link.favorites') }}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
<Spacer grow />
|
<Spacer />
|
||||||
<h3>{{ t('components.Sidebar.link.channels') }}</h3>
|
<h3>{{ t('components.Sidebar.link.channels') }}</h3>
|
||||||
<Spacer grow />
|
<Spacer grow />
|
||||||
<nav>
|
<nav>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<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 { computed, ref } from 'vue'
|
import { computed, ref, watch, watchEffect } from 'vue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
@ -16,16 +16,17 @@ import PopoverRadio from '~/components/ui/popover/PopoverRadio.vue'
|
||||||
import PopoverRadioItem from '~/components/ui/popover/PopoverRadioItem.vue'
|
import PopoverRadioItem from '~/components/ui/popover/PopoverRadioItem.vue'
|
||||||
import PopoverSubmenu from '~/components/ui/popover/PopoverSubmenu.vue'
|
import PopoverSubmenu from '~/components/ui/popover/PopoverSubmenu.vue'
|
||||||
import Link from '~/components/ui/Link.vue'
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
|
|
||||||
|
const { showShortcutsModal } = defineProps<{
|
||||||
|
showShortcutsModal : () => void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
|
||||||
interface Events {
|
|
||||||
(e: 'show:shortcuts-modal'): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const emit = defineEmits<Events>()
|
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
|
|
||||||
const themes = useThemeList()
|
const themes = useThemeList()
|
||||||
|
@ -33,6 +34,8 @@ const { theme } = useTheme()
|
||||||
|
|
||||||
const openUserMenu = ref(false)
|
const openUserMenu = ref(false)
|
||||||
|
|
||||||
|
const isLanguageModelOpen = ref(false)
|
||||||
|
|
||||||
const labels = computed(() => ({
|
const labels = computed(() => ({
|
||||||
profile: t('components.common.UserMenu.link.profile'),
|
profile: t('components.common.UserMenu.link.profile'),
|
||||||
settings: t('components.common.UserMenu.link.settings'),
|
settings: t('components.common.UserMenu.link.settings'),
|
||||||
|
@ -50,6 +53,10 @@ const labels = computed(() => ({
|
||||||
signup: t('components.common.UserMenu.link.signup'),
|
signup: t('components.common.UserMenu.link.signup'),
|
||||||
notifications: t('components.common.UserMenu.link.notifications')
|
notifications: t('components.common.UserMenu.link.notifications')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
watchEffect(()=> {
|
||||||
|
isLanguageModelOpen.value = isLanguageModelOpen.value && openUserMenu.value
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -57,7 +64,7 @@ const labels = computed(() => ({
|
||||||
<Button
|
<Button
|
||||||
@click="openUserMenu = !openUserMenu"
|
@click="openUserMenu = !openUserMenu"
|
||||||
round
|
round
|
||||||
icon
|
icon=""
|
||||||
ghost
|
ghost
|
||||||
class="is-icon-only"
|
class="is-icon-only"
|
||||||
>
|
>
|
||||||
|
@ -69,11 +76,11 @@ const labels = computed(() => ({
|
||||||
<i v-else class="bi bi-gear-fill" />
|
<i v-else class="bi bi-gear-fill" />
|
||||||
</Button>
|
</Button>
|
||||||
<template #items>
|
<template #items>
|
||||||
<PopoverItem v-if="store.state.ui.notifications.inbox + additionalNotifications > 0">
|
<PopoverItem v-if="store.state.ui.notifications.inbox /* TODO: Check: + additionalNotifications */ > 0">
|
||||||
<Link :to="{name: 'notifications'}">
|
<Link :to="{name: 'notifications'}">
|
||||||
<i class="bi bi-inbox-fill" />
|
<i class="bi bi-inbox-fill" />
|
||||||
>
|
>
|
||||||
{{ store.state.ui.notifications.inbox + additionalNotifications }}
|
{{ store.state.ui.notifications.inbox /* TODO: Check: + additionalNotifications */ }}
|
||||||
{{ labels.notifications }}
|
{{ labels.notifications }}
|
||||||
</Link>
|
</Link>
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
|
@ -94,6 +101,17 @@ const labels = computed(() => ({
|
||||||
</Link>
|
</Link>
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
<hr v-if="store.state.auth.authenticated"/>
|
<hr v-if="store.state.auth.authenticated"/>
|
||||||
|
<PopoverItem @click="isLanguageModelOpen = true">
|
||||||
|
<i class="bi bi-translate" />
|
||||||
|
{{ labels.language }}...
|
||||||
|
<Modal v-model="isLanguageModelOpen" :title="labels.language" overPopover>
|
||||||
|
<Button ghost v-for="(language, key) in SUPPORTED_LOCALES"
|
||||||
|
:key="key"
|
||||||
|
@click="setI18nLanguage(key)" >
|
||||||
|
{{ language }}
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
</PopoverItem>
|
||||||
<PopoverSubmenu>
|
<PopoverSubmenu>
|
||||||
<i class="bi bi-translate" />
|
<i class="bi bi-translate" />
|
||||||
{{ labels.language }}
|
{{ labels.language }}
|
||||||
|
@ -161,7 +179,7 @@ const labels = computed(() => ({
|
||||||
</nav>
|
</nav>
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
<PopoverItem
|
<PopoverItem
|
||||||
@click.prevent="emit('show:shortcuts-modal')"
|
@click.prevent="showShortcutsModal ()"
|
||||||
>
|
>
|
||||||
<i class="bi bi-keyboard" />
|
<i class="bi bi-keyboard" />
|
||||||
{{ labels.shortcuts }}
|
{{ labels.shortcuts }}
|
||||||
|
|
Loading…
Reference in New Issue