Move keybinds to coreKeybindings (#1078)

* Refactor core keybinds

* Prevent default

* Add playwright test
This commit is contained in:
Chenlei Hu
2024-10-03 11:25:53 -04:00
committed by GitHub
parent 65cad74eba
commit 142882a8ff
9 changed files with 259 additions and 97 deletions

View File

@@ -6,7 +6,8 @@ import dotenv from 'dotenv'
dotenv.config()
import * as fs from 'fs'
import { NodeBadgeMode } from '../src/types/nodeSource'
import { NodeId } from '../src/types/comfyWorkflow'
import type { NodeId } from '../src/types/comfyWorkflow'
import type { KeyCombo } from '../src/types/keyBindingTypes'
import { ManageGroupNode } from './helpers/manageGroupNode'
import { ComfyTemplates } from './helpers/templates'
@@ -488,6 +489,34 @@ export class ComfyPage {
return `./browser_tests/assets/${fileName}`
}
async registerKeybinding(keyCombo: KeyCombo, command: () => void) {
await this.page.evaluate(
({ keyCombo, commandStr }) => {
const app = window['app']
const randomSuffix = Math.random().toString(36).substring(2, 8)
const extensionName = `TestExtension_${randomSuffix}`
const commandId = `TestCommand_${randomSuffix}`
app.registerExtension({
name: extensionName,
keybindings: [
{
combo: keyCombo,
commandId: commandId
}
],
commands: [
{
id: commandId,
function: eval(commandStr)
}
]
})
},
{ keyCombo, commandStr: command.toString() }
)
}
async setSetting(settingId: string, settingValue: any) {
return await this.page.evaluate(
async ({ id, value }) => {

View File

@@ -0,0 +1,37 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Keybindings', () => {
test('Should not trigger non-modifier keybinding when typing in input fields', async ({
comfyPage
}) => {
await comfyPage.registerKeybinding({ key: 'k' }, () => {
window['TestCommand'] = true
})
const textBox = comfyPage.widgetTextBox
await textBox.click()
await textBox.fill('k')
await expect(textBox).toHaveValue('k')
expect(await comfyPage.page.evaluate(() => window['TestCommand'])).toBe(
undefined
)
})
test('Should not trigger modifier keybinding when typing in input fields', async ({
comfyPage
}) => {
await comfyPage.registerKeybinding({ key: 'k', ctrl: true }, () => {
window['TestCommand'] = true
})
const textBox = comfyPage.widgetTextBox
await textBox.click()
await textBox.fill('q')
await textBox.press('Control+k')
await expect(textBox).toHaveValue('q')
expect(await comfyPage.page.evaluate(() => window['TestCommand'])).toBe(
true
)
})
})

View File

@@ -91,7 +91,7 @@ import { debounce, clamp } from 'lodash'
const settingsStore = useSettingStore()
const commandStore = useCommandStore()
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { batchCount, mode: queueMode } = storeToRefs(useQueueSettingsStore())
const { mode: queueMode } = storeToRefs(useQueueSettingsStore())
const visible = computed(
() => settingsStore.get('Comfy.UseNewMenu') === 'Floating'
@@ -139,7 +139,8 @@ const executingPrompt = computed(() => !!queueCountStore.count.value)
const hasPendingTasks = computed(() => queueCountStore.count.value > 1)
const queuePrompt = (e: MouseEvent) => {
app.queuePrompt(e.shiftKey ? -1 : 0, batchCount.value)
const commandId = e.shiftKey ? 'Comfy.QueuePromptFront' : 'Comfy.QueuePrompt'
commandStore.getCommandFunction(commandId)()
}
const panelRef = ref<HTMLElement | null>(null)

View File

@@ -1,6 +1,4 @@
import { app } from '../../scripts/app'
import { api } from '../../scripts/api'
import { useToastStore } from '@/stores/toastStore'
import { KeyComboImpl, useKeybindingStore } from '@/stores/keybindingStore'
import { useCommandStore } from '@/stores/commandStore'
@@ -13,59 +11,28 @@ app.registerExtension({
if (!app.vueAppReady) return
const keyCombo = KeyComboImpl.fromEvent(event)
if (keyCombo.isModifier) {
return
}
// Ignore non-modifier keybindings if typing in input fields
const target = event.composedPath()[0] as HTMLElement
if (
!keyCombo.hasModifier &&
(target.tagName === 'TEXTAREA' ||
target.tagName === 'INPUT' ||
(target.tagName === 'SPAN' &&
target.classList.contains('property_value')))
) {
return
}
const keybindingStore = useKeybindingStore()
const commandStore = useCommandStore()
const keybinding = keybindingStore.getKeybinding(keyCombo)
if (keybinding) {
await commandStore.getCommandFunction(keybinding.commandId)()
return
}
const modifierPressed = event.ctrlKey || event.metaKey
// Queue prompt using (ctrl or command) + enter
if (modifierPressed && event.key === 'Enter') {
// Cancel current prompt using (ctrl or command) + alt + enter
if (event.altKey) {
await api.interrupt()
useToastStore().add({
severity: 'info',
summary: 'Interrupted',
detail: 'Execution has been interrupted',
life: 1000
})
return
}
// Queue prompt as first for generation using (ctrl or command) + shift + enter
app.queuePrompt(event.shiftKey ? -1 : 0).then()
return
}
const target = event.composedPath()[0] as HTMLElement
if (
target.tagName === 'TEXTAREA' ||
target.tagName === 'INPUT' ||
(target.tagName === 'SPAN' &&
target.classList.contains('property_value'))
) {
return
}
const modifierKeyIdMap = {
s: '#comfy-save-button',
o: '#comfy-file-input',
Backspace: '#comfy-clear-button',
d: '#comfy-load-default-button',
g: '#comfy-group-selected-nodes-button',
',': '.comfy-settings-btn'
}
const modifierKeybindId = modifierKeyIdMap[event.key]
if (modifierPressed && modifierKeybindId) {
event.preventDefault()
const elem = document.querySelector(modifierKeybindId)
elem.click()
return
}
@@ -90,18 +57,6 @@ app.registerExtension({
d.close()
})
}
const keyIdMap = {
q: '.queue-tab-button.side-bar-button',
h: '.queue-tab-button.side-bar-button',
r: '#comfy-refresh-button'
}
const buttonId = keyIdMap[event.key]
if (buttonId) {
const button = document.querySelector(buttonId)
button.click()
}
}
window.addEventListener('keydown', keybindListener, true)

View File

@@ -5,10 +5,7 @@ import { ComfySettingsDialog } from './ui/settings'
import { ComfyApp, app } from './app'
import { TaskItem } from '@/types/apiTypes'
import { showSettingsDialog } from '@/services/dialogService'
import { useToastStore } from '@/stores/toastStore'
import { LGraphGroup } from '@comfyorg/litegraph'
import { useSettingStore } from '@/stores/settingStore'
import { useTitleEditorStore } from '@/stores/graphStore'
export const ComfyDialog = _ComfyDialog
@@ -695,32 +692,6 @@ export class ComfyUI {
onclick: async () => {
app.resetView()
}
}),
$el('button', {
id: 'comfy-group-selected-nodes-button',
textContent: 'Group',
hidden: true,
onclick: () => {
if (
!app.canvas.selected_nodes ||
Object.keys(app.canvas.selected_nodes).length === 0
) {
useToastStore().add({
severity: 'error',
summary: 'No nodes selected',
detail: 'Please select nodes to group',
life: 3000
})
return
}
const group = new LGraphGroup()
const padding = useSettingStore().get(
'Comfy.GroupSelectedNodes.Padding'
)
group.addNodes(Object.values(app.canvas.selected_nodes), padding)
app.canvas.graph.add(group)
useTitleEditorStore().titleEditorTarget = group
}
})
]) as HTMLDivElement

View File

@@ -5,10 +5,16 @@ import { ref } from 'vue'
import { globalTracker } from '@/scripts/changeTracker'
import { useSettingStore } from '@/stores/settingStore'
import { useToastStore } from '@/stores/toastStore'
import { showTemplateWorkflowsDialog } from '@/services/dialogService'
import { useQueueStore } from './queueStore'
import {
showSettingsDialog,
showTemplateWorkflowsDialog
} from '@/services/dialogService'
import { useQueueSettingsStore, useQueueStore } from './queueStore'
import { LiteGraph } from '@comfyorg/litegraph'
import { ComfyExtension } from '@/types/comfy'
import { useWorkspaceStore } from './workspaceStateStore'
import { LGraphGroup } from '@comfyorg/litegraph'
import { useTitleEditorStore } from './graphStore'
export interface ComfyCommand {
id: string
@@ -231,6 +237,75 @@ export const useCommandStore = defineStore('command', () => {
}
}
})()
},
{
id: 'Comfy.QueuePrompt',
icon: 'pi pi-play',
label: 'Queue Prompt',
versionAdded: '1.3.7',
function: () => {
const batchCount = useQueueSettingsStore().batchCount
app.queuePrompt(0, batchCount)
}
},
{
id: 'Comfy.QueuePromptFront',
icon: 'pi pi-play',
label: 'Queue Prompt (Front)',
versionAdded: '1.3.7',
function: () => {
const batchCount = useQueueSettingsStore().batchCount
app.queuePrompt(-1, batchCount)
}
},
{
id: 'Comfy.ToggleQueueSidebarTab',
icon: 'pi pi-history',
label: 'Queue',
versionAdded: '1.3.7',
function: () => {
const tabId = 'queue'
const workspaceStore = useWorkspaceStore()
workspaceStore.updateActiveSidebarTab(
workspaceStore.activeSidebarTab === tabId ? null : tabId
)
}
},
{
id: 'Comfy.ShowSettingsDialog',
icon: 'pi pi-cog',
label: 'Settings',
versionAdded: '1.3.7',
function: () => {
showSettingsDialog()
}
},
{
id: 'Comfy.Graph.GroupSelectedNodes',
icon: 'pi pi-sitemap',
label: 'Group Selected Nodes',
versionAdded: '1.3.7',
function: () => {
if (
!app.canvas.selected_nodes ||
Object.keys(app.canvas.selected_nodes).length === 0
) {
useToastStore().add({
severity: 'error',
summary: 'No nodes selected',
detail: 'Please select nodes to group',
life: 3000
})
return
}
const group = new LGraphGroup()
const padding = useSettingStore().get(
'Comfy.GroupSelectedNodes.Padding'
)
group.addNodes(Object.values(app.canvas.selected_nodes), padding)
app.canvas.graph.add(group)
useTitleEditorStore().titleEditorTarget = group
}
}
]

View File

@@ -1,3 +1,86 @@
import type { Keybinding } from '@/types/keyBindingTypes'
export const CORE_KEYBINDINGS: Keybinding[] = []
export const CORE_KEYBINDINGS: Keybinding[] = [
{
combo: {
ctrl: true,
key: 'Enter'
},
commandId: 'Comfy.QueuePrompt'
},
{
combo: {
ctrl: true,
shift: true,
key: 'Enter'
},
commandId: 'Comfy.QueuePromptFront'
},
{
combo: {
ctrl: true,
alt: true,
key: 'Enter'
},
commandId: 'Comfy.Interrupt'
},
{
combo: {
key: 'r'
},
commandId: 'Comfy.RefreshNodeDefinitions'
},
{
combo: {
key: 'q'
},
commandId: 'Comfy.ToggleQueueSidebarTab'
},
{
combo: {
key: 'h'
},
commandId: 'Comfy.ToggleQueueSidebarTab'
},
{
combo: {
key: 's',
ctrl: true
},
commandId: 'Comfy.ExportWorkflow'
},
{
combo: {
key: 'o',
ctrl: true
},
commandId: 'Comfy.OpenWorkflow'
},
{
combo: {
key: 'Backspace'
},
commandId: 'Comfy.ClearWorkflow'
},
{
combo: {
key: 'd',
ctrl: true
},
commandId: 'Comfy.LoadDefaultWorkflow'
},
{
combo: {
key: 'g',
ctrl: true
},
commandId: 'Comfy.Graph.GroupSelectedNodes'
},
{
combo: {
key: ',',
ctrl: true
},
commandId: 'Comfy.ShowSettingsDialog'
}
]

View File

@@ -41,7 +41,7 @@ export class KeyComboImpl implements KeyCombo {
static fromEvent(event: KeyboardEvent) {
return new KeyComboImpl({
key: event.key,
ctrl: event.ctrlKey,
ctrl: event.ctrlKey || event.metaKey,
alt: event.altKey,
shift: event.shiftKey
})
@@ -76,6 +76,14 @@ export class KeyComboImpl implements KeyCombo {
toString(): string {
return `${this.key} + ${this.ctrl ? 'Ctrl' : ''}${this.alt ? 'Alt' : ''}${this.shift ? 'Shift' : ''}`
}
get hasModifier(): boolean {
return this.ctrl || this.alt || this.shift
}
get isModifier(): boolean {
return ['Control', 'Meta', 'Alt', 'Shift'].includes(this.key)
}
}
export const useKeybindingStore = defineStore('keybinding', () => {

View File

@@ -45,6 +45,7 @@ import AppMenu from '@/components/appMenu/AppMenu.vue'
import WorkflowsSidebarTab from '@/components/sidebar/tabs/WorkflowsSidebarTab.vue'
import TopMenubar from '@/components/topbar/TopMenubar.vue'
import { setupAutoQueueHandler } from '@/services/autoQueueService'
import { useKeybindingStore } from '@/stores/keybindingStore'
setupAutoQueueHandler()
@@ -104,6 +105,8 @@ watchEffect(() => {
const init = () => {
settingStore.addSettings(app.ui.settings)
useKeybindingStore().loadCoreKeybindings()
app.extensionManager = useWorkspaceStore()
app.extensionManager.registerSidebarTab({
id: 'queue',