fix: add ShortcutTooltip component and integrate it for keyboard shortcuts in CalendarEventPanel and Calendar pages
This commit is contained in:
parent
f2ce3165dd
commit
346643bc6d
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -254,6 +254,7 @@ declare module 'vue' {
|
||||
SettingsIcon: typeof import('./src/components/Icons/SettingsIcon.vue')['default']
|
||||
SettingsIcon2: typeof import('./src/components/Icons/SettingsIcon2.vue')['default']
|
||||
SettingsPage: typeof import('./src/components/Settings/SettingsPage.vue')['default']
|
||||
ShortcutTooltip: typeof import('./src/components/ShortcutTooltip.vue')['default']
|
||||
SidebarLink: typeof import('./src/components/SidebarLink.vue')['default']
|
||||
SidePanelLayout: typeof import('./src/components/SidePanelLayout.vue')['default']
|
||||
SidePanelLayoutEditor: typeof import('./src/components/SidePanelLayoutEditor.vue')['default']
|
||||
|
||||
@ -13,33 +13,31 @@
|
||||
{{ __(title) }}
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1">
|
||||
<Button
|
||||
<ShortcutTooltip
|
||||
v-if="mode == 'details'"
|
||||
:icon="EditIcon"
|
||||
variant="ghost"
|
||||
:tooltip="__('Edit event')"
|
||||
@click="editDetails"
|
||||
/>
|
||||
<Button
|
||||
:label="__('Edit event')"
|
||||
combo="Enter"
|
||||
>
|
||||
<Button :icon="EditIcon" variant="ghost" @click="editDetails" />
|
||||
</ShortcutTooltip>
|
||||
<ShortcutTooltip
|
||||
v-if="mode === 'edit' || mode === 'details'"
|
||||
icon="trash-2"
|
||||
variant="ghost"
|
||||
:tooltip="__('Delete event')"
|
||||
@click="deleteEvent"
|
||||
/>
|
||||
<Button
|
||||
:label="__('Delete event')"
|
||||
combo="Delete"
|
||||
:alt-combos="['Backspace']"
|
||||
>
|
||||
<Button icon="trash-2" variant="ghost" @click="deleteEvent" />
|
||||
</ShortcutTooltip>
|
||||
<ShortcutTooltip
|
||||
v-if="mode === 'edit' || mode === 'details'"
|
||||
icon="copy"
|
||||
variant="ghost"
|
||||
:tooltip="__('Duplicate event')"
|
||||
@click="duplicateEvent"
|
||||
/>
|
||||
<Button
|
||||
icon="x"
|
||||
variant="ghost"
|
||||
:tooltip="__('Close panel')"
|
||||
@click="close"
|
||||
/>
|
||||
:label="__('Duplicate event')"
|
||||
combo="Mod+D"
|
||||
>
|
||||
<Button icon="copy" variant="ghost" @click="duplicateEvent" />
|
||||
</ShortcutTooltip>
|
||||
<ShortcutTooltip :label="__('Close panel')" combo="Esc">
|
||||
<Button icon="x" variant="ghost" @click="close" />
|
||||
</ShortcutTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -377,6 +375,7 @@ import {
|
||||
CalendarActiveEvent as activeEvent,
|
||||
createDocumentResource,
|
||||
} from 'frappe-ui'
|
||||
import ShortcutTooltip from '@/components/ShortcutTooltip.vue'
|
||||
import { ref, computed, watch, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
|
||||
104
frontend/src/components/ShortcutTooltip.vue
Normal file
104
frontend/src/components/ShortcutTooltip.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<Tooltip v-if="!disabled">
|
||||
<template #body>
|
||||
<div
|
||||
class="rounded bg-surface-gray-7 py-1.5 px-2 text-xs text-ink-white shadow-xl"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<span>{{ label }}</span>
|
||||
<!-- Primary combos (one or many) -->
|
||||
<template
|
||||
v-for="(combo, idx) in primaryCombosDisplay"
|
||||
:key="'prim-' + idx + combo"
|
||||
>
|
||||
<KeyboardShortcut
|
||||
bg
|
||||
class="!bg-surface-gray-5 !text-ink-gray-2 px-1"
|
||||
:combo="combo"
|
||||
/>
|
||||
</template>
|
||||
<!-- Alternate combos / equivalents -->
|
||||
<template
|
||||
v-for="(alt, idx) in altCombosDisplay"
|
||||
:key="'alt-' + idx + alt"
|
||||
>
|
||||
<KeyboardShortcut
|
||||
bg
|
||||
class="!bg-surface-gray-5 !text-ink-gray-2 px-1"
|
||||
:combo="alt"
|
||||
/>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<slot />
|
||||
</Tooltip>
|
||||
<slot v-else />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Tooltip, KeyboardShortcut } from 'frappe-ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
combo?: string | string[]
|
||||
altCombos?: string[]
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
combo: '',
|
||||
altCombos: () => [],
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const isMac = computed(() => {
|
||||
if (typeof navigator === 'undefined') return false
|
||||
const platform =
|
||||
(navigator as any).userAgentData?.platform || navigator.platform || ''
|
||||
if (/Mac|iPod|iPhone|iPad/i.test(platform)) return true
|
||||
return /Mac OS X|Macintosh|iPhone|iPad|iPod/i.test(navigator.userAgent || '')
|
||||
})
|
||||
|
||||
function normalizeCombo(raw: string): string {
|
||||
if (!raw) return ''
|
||||
if (/^mod\+/i.test(raw)) {
|
||||
const rest = raw.split('+').slice(1).join('+')
|
||||
return (isMac.value ? 'Cmd' : 'Ctrl') + '+' + rest
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
function normalizeList(list: string | string[]): string[] {
|
||||
const arr = Array.isArray(list) ? list : list ? [list] : []
|
||||
return arr.map(normalizeCombo)
|
||||
}
|
||||
|
||||
// Dedupe Backspace/Delete (prefer Backspace) on macOS
|
||||
function dedupeMacDeleteVariants(primary: string[], alts: string[]) {
|
||||
if (!isMac.value) return { primary, alts }
|
||||
const all = [...primary, ...alts]
|
||||
if (all.includes('Delete') && all.includes('Backspace')) {
|
||||
return {
|
||||
primary: primary.filter((k) => k !== 'Delete'),
|
||||
alts: alts.filter((k) => k !== 'Delete'),
|
||||
}
|
||||
}
|
||||
return { primary, alts }
|
||||
}
|
||||
|
||||
// Base normalized lists
|
||||
const normalizedPrimary = computed(() => normalizeList(props.combo))
|
||||
const normalizedAlt = computed(() => props.altCombos.map(normalizeCombo))
|
||||
|
||||
// Apply dedupe once to both arrays to avoid circular dependency
|
||||
const deduped = computed(() =>
|
||||
dedupeMacDeleteVariants(normalizedPrimary.value, normalizedAlt.value),
|
||||
)
|
||||
|
||||
const primaryCombosDisplay = computed(() => deduped.value.primary)
|
||||
const altCombosDisplay = computed(() => deduped.value.alts)
|
||||
|
||||
defineOptions({ name: 'ShortcutTooltip' })
|
||||
</script>
|
||||
@ -4,14 +4,16 @@
|
||||
<ViewBreadcrumbs routeName="Calendar" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<Button
|
||||
variant="solid"
|
||||
:label="__('Create')"
|
||||
:disabled="isCreateDisabled"
|
||||
@click="newEvent"
|
||||
>
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
<ShortcutTooltip :label="__('Create event')" combo="Mod+E">
|
||||
<Button
|
||||
variant="solid"
|
||||
:label="__('Create')"
|
||||
:disabled="isCreateDisabled"
|
||||
@click="newEvent"
|
||||
>
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
</ShortcutTooltip>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
@ -129,6 +131,7 @@
|
||||
import CalendarEventPanel from '@/components/Calendar/CalendarEventPanel.vue'
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ShortcutTooltip from '@/components/ShortcutTooltip.vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user