refactor(command): use dialog (#352)

This commit is contained in:
QiroNT 2022-12-10 05:18:21 +08:00 committed by GitHub
parent f249087a95
commit 462e85dad0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 110 additions and 131 deletions

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
setupPageHeader() setupPageHeader()
await setupI18n() await setupI18n()
provideGlobalCommands()
// We want to trigger rerendering the page when account changes // We want to trigger rerendering the page when account changes
const key = computed(() => `${currentServer.value}:${currentUser.value?.account.id || ''}`) const key = computed(() => `${currentServer.value}:${currentUser.value?.account.id || ''}`)

View file

@ -1,31 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CommandScope, QueryIndexedCommand } from '@/composables/command' import type { CommandScope, QueryIndexedCommand } from '@/composables/command'
const isMac = useIsMac() const emit = defineEmits<{
(event: 'close'): void
}>()
const registry = useCommandRegistry() const registry = useCommandRegistry()
const inputEl = $ref<HTMLInputElement>() const inputEl = $ref<HTMLInputElement>()
const resultEl = $ref<HTMLDivElement>() const resultEl = $ref<HTMLDivElement>()
let show = $ref(false) const scopes = $ref<CommandScope[]>([])
let scopes = $ref<CommandScope[]>([]) let input = $(commandPanelInput)
let input = $ref('')
// listen to ctrl+/ on windows/linux or cmd+/ on mac onMounted(() => {
useEventListener('keydown', async (e: KeyboardEvent) => { inputEl?.focus()
if (e.key === '/' && (isMac.value ? e.metaKey : e.ctrlKey)) {
e.preventDefault()
show = true
scopes = []
input = '>'
await nextTick()
inputEl?.focus()
}
}) })
onKeyStroke('Escape', (e) => {
e.preventDefault()
show = false
}, { target: document })
const commandMode = $computed(() => input.startsWith('>')) const commandMode = $computed(() => input.startsWith('>'))
const result = $computed(() => commandMode const result = $computed(() => commandMode
@ -42,7 +32,7 @@ const findItemEl = (index: number) =>
const onCommandActivate = (item: QueryIndexedCommand) => { const onCommandActivate = (item: QueryIndexedCommand) => {
if (item.onActivate) { if (item.onActivate) {
item.onActivate() item.onActivate()
show = false emit('close')
} }
else if (item.onComplete) { else if (item.onComplete) {
scopes.push(item.onComplete()) scopes.push(item.onComplete())
@ -137,117 +127,86 @@ const onKeyDown = (e: KeyboardEvent) => {
</script> </script>
<template> <template>
<!-- Overlay --> <div class="flex flex-col w-50vw max-w-180 h-50vh max-h-120">
<Transition <!-- Input -->
enter-active-class="transition duration-200 ease-out" <label class="flex mx-3 my-1 items-center">
enter-from-class="transform opacity-0" <div mx-1 i-ri:search-line />
enter-to-class="transform opacity-100"
leave-active-class="transition duration-100 ease-in"
leave-from-class="transform opacity-100"
leave-to-class="transform opacity-0"
>
<div
v-if="show"
class="z-100 fixed inset-0 opacity-70 bg-base"
@click="show = false"
/>
</Transition>
<!-- Panel --> <div v-for="scope in scopes" :key="scope.id" class="flex items-center mx-1 gap-2">
<Transition <div class="text-sm">{{ scope.display }}</div>
enter-active-class="transition duration-65 ease-out" <span class="text-secondary">/</span>
enter-from-class="transform scale-95" </div>
enter-to-class="transform scale-100"
leave-active-class="transition duration-100 ease-in" <input
leave-from-class="transform scale-100" ref="inputEl"
leave-to-class="transform scale-95" v-model="input"
> class="focus:outline-none flex-1 p-2 rounded bg-base"
<div v-if="show" class="z-100 fixed inset-0 grid place-items-center pointer-events-none"> placeholder="Search"
<div @keydown="onKeyDown"
class="flex flex-col w-50vw h-50vh rounded-md bg-base shadow-lg pointer-events-auto"
border="1 base"
> >
<!-- Input -->
<label class="flex mx-3 my-1 items-center">
<div mx-1 i-ri:search-line />
<div v-for="scope in scopes" :key="scope.id" class="flex items-center mx-1 gap-2"> <CommandKey name="Escape" />
<div class="text-sm">{{ scope.display }}</div> </label>
<span class="text-secondary">/</span>
</div>
<input <div class="w-full border-b-1 border-base" />
ref="inputEl"
v-model="input" <!-- Results -->
class="focus:outline-none flex-1 p-2 rounded bg-base" <div ref="resultEl" class="flex-1 mx-1 overflow-y-auto">
placeholder="Search" <template v-for="[scope, group] in result.grouped" :key="scope">
@keydown="onKeyDown" <div class="mt-2 px-2 py-1 text-sm text-secondary">
{{ scope }}
</div>
<template v-for="cmd in group" :key="cmd.index">
<div
class="flex px-3 py-2 my-1 items-center rounded-lg hover:bg-active transition-all duration-65 ease-in-out cursor-pointer scroll-m-10"
:class="{ 'bg-active': active === cmd.index }"
:data-index="cmd.index"
@click="onCommandActivate(cmd)"
> >
<div v-if="cmd.icon" mr-2 :class="cmd.icon" />
<CommandKey name="Escape" /> <div class="flex-1 flex items-baseline gap-2">
</label> <div :class="{ 'font-medium': active === cmd.index }">
{{ cmd.name }}
<div class="w-full border-b-1 border-base" /> </div>
<div v-if="cmd.description" class="text-xs text-secondary">
<!-- Results --> {{ cmd.description }}
<div ref="resultEl" class="flex-1 mx-1 overflow-y-auto"> </div>
<template v-for="[scope, group] in result.grouped" :key="scope">
<div class="mt-2 px-2 py-1 text-sm text-secondary">
{{ scope }}
</div> </div>
<template v-for="cmd in group" :key="cmd.index"> <div
<div v-if="cmd.onComplete"
class="flex px-3 py-2 my-1 items-center rounded-lg hover:bg-active transition-all duration-65 ease-in-out cursor-pointer scroll-m-10" class="flex items-center gap-1 transition-all duration-65 ease-in-out"
:class="{ 'bg-active': active === cmd.index }" :class="active === cmd.index ? 'opacity-100' : 'opacity-0'"
:data-index="cmd.index" >
@click="onCommandActivate(cmd)" <div class="text-xs text-secondary">
> {{ $t('command.complete') }}
<div v-if="cmd.icon" mr-2 :class="cmd.icon" />
<div class="flex-1 flex items-baseline gap-2">
<div :class="{ 'font-medium': active === cmd.index }">
{{ cmd.name }}
</div>
<div v-if="cmd.description" class="text-xs text-secondary">
{{ cmd.description }}
</div>
</div>
<div
v-if="cmd.onComplete"
class="flex items-center gap-1 transition-all duration-65 ease-in-out"
:class="active === cmd.index ? 'opacity-100' : 'opacity-0'"
>
<div class="text-xs text-secondary">
{{ $t('command.complete') }}
</div>
<CommandKey name="Tab" />
</div>
<div
v-if="cmd.onActivate"
class="flex items-center gap-1 transition-all duration-65 ease-in-out"
:class="active === cmd.index ? 'opacity-100' : 'opacity-0'"
>
<div class="text-xs text-secondary">
{{ $t('command.activate') }}
</div>
<CommandKey name="Enter" />
</div>
</div> </div>
</template> <CommandKey name="Tab" />
</template> </div>
</div> <div
v-if="cmd.onActivate"
<div class="w-full border-b-1 border-base" /> class="flex items-center gap-1 transition-all duration-65 ease-in-out"
:class="active === cmd.index ? 'opacity-100' : 'opacity-0'"
<!-- Footer --> >
<div class="flex items-center px-3 py-1 text-xs"> <div class="text-xs text-secondary">
<div i-ri:lightbulb-flash-line /> Tip: Use {{ $t('command.activate') }}
<!-- <CommandKey name="Ctrl+K" /> to search, --> </div>
<CommandKey name="Ctrl+/" /> to activate command mode. <CommandKey name="Enter" />
</div> </div>
</div> </div>
</template>
</template>
</div> </div>
</Transition>
<div class="w-full border-b-1 border-base" />
<!-- Footer -->
<div class="flex items-center px-3 py-1 text-xs">
<div i-ri:lightbulb-flash-line /> Tip: Use
<!-- <CommandKey name="Ctrl+K" /> to search, -->
<CommandKey name="Ctrl+/" /> to activate command mode.
</div>
</div>
</template> </template>

View file

@ -1,7 +0,0 @@
<script setup lang="ts">
provideGlobalCommands()
</script>
<template>
<CommandPanel />
</template>

View file

@ -1,11 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
isCommandPanelOpen,
isEditHistoryDialogOpen, isEditHistoryDialogOpen,
isMediaPreviewOpen, isMediaPreviewOpen,
isPreviewHelpOpen, isPreviewHelpOpen,
isPublishDialogOpen, isPublishDialogOpen,
isSigninDialogOpen, isSigninDialogOpen,
} from '~/composables/dialog' } from '~/composables/dialog'
const isMac = useIsMac()
// TODO: temporary, await for keybind system
// listen to ctrl+/ on windows/linux or cmd+/ on mac
useEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === '/' && (isMac.value ? e.metaKey : e.ctrlKey)) {
e.preventDefault()
openCommandPanel(true)
}
})
</script> </script>
<template> <template>
@ -30,4 +42,7 @@ import {
<ModalDialog v-model="isEditHistoryDialogOpen"> <ModalDialog v-model="isEditHistoryDialogOpen">
<StatusEditPreview :edit="statusEdit" /> <StatusEditPreview :edit="statusEdit" />
</ModalDialog> </ModalDialog>
<ModalDialog v-model="isCommandPanelOpen" max-w-fit flex>
<CommandPanel @close="closeCommandPanel()" />
</ModalDialog>
</template> </template>

View file

@ -281,7 +281,7 @@ export const provideGlobalCommands = () => {
visible: () => users.value.length > 1, visible: () => users.value.length > 1,
name: () => t('action.switch_account'), name: () => t('action.switch_account'),
description: t('command.switch_account_desc'), description: () => t('command.switch_account_desc'),
icon: 'i-ri:user-shared-line', icon: 'i-ri:user-shared-line',
onComplete: () => ({ onComplete: () => ({

View file

@ -8,6 +8,8 @@ export const mediaPreviewIndex = ref(0)
export const statusEdit = ref<StatusEdit>() export const statusEdit = ref<StatusEdit>()
export const dialogDraftKey = ref<string>() export const dialogDraftKey = ref<string>()
export const commandPanelInput = ref('')
export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, !process.mock) export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, !process.mock)
export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false) export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false)
@ -16,6 +18,7 @@ export const isPublishDialogOpen = ref(false)
export const isMediaPreviewOpen = ref(false) export const isMediaPreviewOpen = ref(false)
export const isEditHistoryDialogOpen = ref(false) export const isEditHistoryDialogOpen = ref(false)
export const isPreviewHelpOpen = ref(isFirstVisit.value) export const isPreviewHelpOpen = ref(isFirstVisit.value)
export const isCommandPanelOpen = ref(false)
export const toggleZenMode = useToggle(isZenMode) export const toggleZenMode = useToggle(isZenMode)
@ -72,3 +75,12 @@ export function openPreviewHelp() {
export function closePreviewHelp() { export function closePreviewHelp() {
isPreviewHelpOpen.value = false isPreviewHelpOpen.value = false
} }
export function openCommandPanel(isCommandMode = false) {
commandPanelInput.value = isCommandMode ? '>' : ''
isCommandPanelOpen.value = true
}
export function closeCommandPanel() {
isCommandPanelOpen.value = false
}

View file

@ -51,6 +51,5 @@
</aside> </aside>
</main> </main>
<ModalContainer /> <ModalContainer />
<CommandRoot />
</div> </div>
</template> </template>