mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Show a non-blocking warning in the keybinding edit dialog when users try to bind shortcuts that browsers intercept (e.g. Ctrl+T, Ctrl+W, F12). ## Changes - **What**: Add `RESERVED_BY_BROWSER` set of known browser-intercepted shortcuts, `isBrowserReserved` getter on `KeyComboImpl`, and a warning `<Message>` in the keybinding edit dialog. Users can still save the binding. ## Review Focus Whether the list of browser-reserved shortcuts is comprehensive enough, and whether a non-blocking warning (vs blocking) is the right UX choice. ## Before https://github.com/user-attachments/assets/5abfc062-5ed1-4fcd-b394-ff98221d82a8 ## After https://github.com/user-attachments/assets/12a49e24-051f-4579-894a-164dbf1cb7b7 Fixes #1087 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9406-feat-Warn-when-binding-browser-reserved-shortcuts-31a6d73d36508162a021e88ab76914f6) by [Unito](https://www.unito.io) --------- Co-authored-by: Kelly Yang <124ykl@gmail.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: jaeone94 <89377375+jaeone94@users.noreply.github.com> Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com> Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: AustinMroz <austin@comfy.org> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Terry Jia <terryjia88@gmail.com> Co-authored-by: Alexander Brown <DrJKL0424@gmail.com> Co-authored-by: Dante <bunggl@naver.com> Co-authored-by: Deep Mehta <42841935+deepme987@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Jin Yi <jin12cc@gmail.com> Co-authored-by: Yourz <crazilou@vip.qq.com> Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
203 lines
6.0 KiB
Vue
203 lines
6.0 KiB
Vue
<template>
|
|
<div class="keybinding-panel flex flex-col gap-2">
|
|
<SearchInput
|
|
v-model="filters['global'].value"
|
|
:placeholder="$t('g.searchPlaceholder', { subject: $t('g.keybindings') })"
|
|
/>
|
|
|
|
<DataTable
|
|
v-model:selection="selectedCommandData"
|
|
:value="commandsData"
|
|
:global-filter-fields="['id', 'label']"
|
|
:filters="filters"
|
|
selection-mode="single"
|
|
striped-rows
|
|
:pt="{
|
|
header: 'px-0'
|
|
}"
|
|
@row-dblclick="editKeybinding($event.data)"
|
|
>
|
|
<Column field="actions" header="" :pt="{ bodyCell: 'p-1 min-h-8' }">
|
|
<template #body="slotProps">
|
|
<div class="actions flex flex-row">
|
|
<Button
|
|
variant="textonly"
|
|
size="icon"
|
|
:aria-label="$t('g.edit')"
|
|
@click="editKeybinding(slotProps.data)"
|
|
>
|
|
<i class="pi pi-pencil" />
|
|
</Button>
|
|
<Button
|
|
variant="textonly"
|
|
size="icon"
|
|
:aria-label="$t('g.reset')"
|
|
:disabled="
|
|
!keybindingStore.isCommandKeybindingModified(slotProps.data.id)
|
|
"
|
|
@click="resetKeybinding(slotProps.data)"
|
|
>
|
|
<i class="pi pi-replay" />
|
|
</Button>
|
|
<Button
|
|
variant="textonly"
|
|
size="icon"
|
|
:aria-label="$t('g.delete')"
|
|
:disabled="!slotProps.data.keybinding"
|
|
@click="removeKeybinding(slotProps.data)"
|
|
>
|
|
<i class="pi pi-trash" />
|
|
</Button>
|
|
</div>
|
|
</template>
|
|
</Column>
|
|
<Column
|
|
field="id"
|
|
:header="$t('g.command')"
|
|
sortable
|
|
class="max-w-64 2xl:max-w-full"
|
|
:pt="{ bodyCell: 'p-1 min-h-8' }"
|
|
>
|
|
<template #body="slotProps">
|
|
<div
|
|
class="flex items-center gap-1.5 truncate"
|
|
:title="slotProps.data.id"
|
|
>
|
|
<i
|
|
v-if="slotProps.data.keybinding?.combo.isBrowserReserved"
|
|
v-tooltip="$t('g.browserReservedKeybindingTooltip')"
|
|
class="icon-[lucide--triangle-alert] shrink-0 text-warning-background"
|
|
/>
|
|
{{ slotProps.data.label }}
|
|
</div>
|
|
</template>
|
|
</Column>
|
|
<Column
|
|
field="keybinding"
|
|
:header="$t('g.keybinding')"
|
|
:pt="{ bodyCell: 'p-1 min-h-8' }"
|
|
>
|
|
<template #body="slotProps">
|
|
<KeyComboDisplay
|
|
v-if="slotProps.data.keybinding"
|
|
:key-combo="slotProps.data.keybinding.combo"
|
|
:is-modified="
|
|
keybindingStore.isCommandKeybindingModified(slotProps.data.id)
|
|
"
|
|
/>
|
|
<span v-else>-</span>
|
|
</template>
|
|
</Column>
|
|
<Column
|
|
field="source"
|
|
:header="$t('g.source')"
|
|
:pt="{ bodyCell: 'p-1 min-h-8' }"
|
|
>
|
|
<template #body="slotProps">
|
|
<span class="overflow-hidden text-ellipsis">{{
|
|
slotProps.data.source || '-'
|
|
}}</span>
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
|
|
<Button
|
|
v-tooltip="$t('g.resetAllKeybindingsTooltip')"
|
|
class="mt-4 w-full"
|
|
variant="destructive-textonly"
|
|
@click="resetAllKeybindings"
|
|
>
|
|
<i class="pi pi-replay" />
|
|
{{ $t('g.resetAll') }}
|
|
</Button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { FilterMatchMode } from '@primevue/core/api'
|
|
import Column from 'primevue/column'
|
|
import DataTable from 'primevue/datatable'
|
|
import { useToast } from 'primevue/usetoast'
|
|
import { computed, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import SearchInput from '@/components/ui/search-input/SearchInput.vue'
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { useEditKeybindingDialog } from '@/composables/useEditKeybindingDialog'
|
|
import type { KeybindingImpl } from '@/platform/keybindings/keybinding'
|
|
import { useKeybindingService } from '@/platform/keybindings/keybindingService'
|
|
import { useKeybindingStore } from '@/platform/keybindings/keybindingStore'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { normalizeI18nKey } from '@/utils/formatUtil'
|
|
|
|
import KeyComboDisplay from './keybinding/KeyComboDisplay.vue'
|
|
|
|
const filters = ref({
|
|
global: { value: '', matchMode: FilterMatchMode.CONTAINS }
|
|
})
|
|
|
|
const keybindingStore = useKeybindingStore()
|
|
const keybindingService = useKeybindingService()
|
|
const commandStore = useCommandStore()
|
|
const { t } = useI18n()
|
|
|
|
interface ICommandData {
|
|
id: string
|
|
keybinding: KeybindingImpl | null
|
|
label: string
|
|
source?: string
|
|
}
|
|
|
|
const commandsData = computed<ICommandData[]>(() => {
|
|
return Object.values(commandStore.commands).map((command) => ({
|
|
id: command.id,
|
|
label: t(
|
|
`commands.${normalizeI18nKey(command.id)}.label`,
|
|
command.label ?? ''
|
|
),
|
|
keybinding: keybindingStore.getKeybindingByCommandId(command.id),
|
|
source: command.source
|
|
}))
|
|
})
|
|
|
|
const selectedCommandData = ref<ICommandData | null>(null)
|
|
const editKeybindingDialog = useEditKeybindingDialog()
|
|
|
|
function editKeybinding(commandData: ICommandData) {
|
|
editKeybindingDialog.show({
|
|
commandId: commandData.id,
|
|
commandLabel: commandData.label,
|
|
currentCombo: commandData.keybinding?.combo ?? null
|
|
})
|
|
}
|
|
|
|
async function removeKeybinding(commandData: ICommandData) {
|
|
if (commandData.keybinding) {
|
|
keybindingStore.unsetKeybinding(commandData.keybinding)
|
|
await keybindingService.persistUserKeybindings()
|
|
}
|
|
}
|
|
|
|
async function resetKeybinding(commandData: ICommandData) {
|
|
if (keybindingStore.resetKeybindingForCommand(commandData.id)) {
|
|
await keybindingService.persistUserKeybindings()
|
|
} else {
|
|
console.warn(
|
|
`No changes made when resetting keybinding for command: ${commandData.id}`
|
|
)
|
|
}
|
|
}
|
|
|
|
const toast = useToast()
|
|
async function resetAllKeybindings() {
|
|
keybindingStore.resetAllKeybindings()
|
|
await keybindingService.persistUserKeybindings()
|
|
toast.add({
|
|
severity: 'info',
|
|
summary: 'Info',
|
|
detail: 'All keybindings reset',
|
|
life: 3000
|
|
})
|
|
}
|
|
</script>
|