mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
Manage app.ts litegraph keybindings (#1151)
* Manage app.ts litegraph keybindings * nit
This commit is contained in:
@@ -30,7 +30,7 @@ app.registerExtension({
|
|||||||
const keybindingStore = useKeybindingStore()
|
const keybindingStore = useKeybindingStore()
|
||||||
const commandStore = useCommandStore()
|
const commandStore = useCommandStore()
|
||||||
const keybinding = keybindingStore.getKeybinding(keyCombo)
|
const keybinding = keybindingStore.getKeybinding(keyCombo)
|
||||||
if (keybinding) {
|
if (keybinding && keybinding.targetSelector !== '#graph-canvas') {
|
||||||
await commandStore.execute(keybinding.commandId)
|
await commandStore.execute(keybinding.commandId)
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ import { useWorkspaceStore } from '@/stores/workspaceStateStore'
|
|||||||
import { useExecutionStore } from '@/stores/executionStore'
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
import { IWidget } from '@comfyorg/litegraph'
|
import { IWidget } from '@comfyorg/litegraph'
|
||||||
import { useExtensionStore } from '@/stores/extensionStore'
|
import { useExtensionStore } from '@/stores/extensionStore'
|
||||||
|
import { KeyComboImpl, useKeybindingStore } from '@/stores/keybindingStore'
|
||||||
|
import { useCommandStore } from '@/stores/commandStore'
|
||||||
|
|
||||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
||||||
|
|
||||||
@@ -1278,13 +1280,10 @@ export class ComfyApp {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle keypress
|
* Handle keypress
|
||||||
*
|
|
||||||
* Ctrl + M mute/unmute selected nodes
|
|
||||||
*/
|
*/
|
||||||
#addProcessKeyHandler() {
|
#addProcessKeyHandler() {
|
||||||
const self = this
|
|
||||||
const origProcessKey = LGraphCanvas.prototype.processKey
|
const origProcessKey = LGraphCanvas.prototype.processKey
|
||||||
LGraphCanvas.prototype.processKey = function (e) {
|
LGraphCanvas.prototype.processKey = function (e: KeyboardEvent) {
|
||||||
if (!this.graph) {
|
if (!this.graph) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1296,54 +1295,11 @@ export class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.type == 'keydown' && !e.repeat) {
|
if (e.type == 'keydown' && !e.repeat) {
|
||||||
// Ctrl + M mute/unmute
|
const keyCombo = KeyComboImpl.fromEvent(e)
|
||||||
if (e.key === 'm' && (e.metaKey || e.ctrlKey)) {
|
const keybindingStore = useKeybindingStore()
|
||||||
if (this.selected_nodes) {
|
const keybinding = keybindingStore.getKeybinding(keyCombo)
|
||||||
for (var i in this.selected_nodes) {
|
if (keybinding && keybinding.targetSelector === '#graph-canvas') {
|
||||||
if (this.selected_nodes[i].mode === 2) {
|
useCommandStore().execute(keybinding.commandId)
|
||||||
// never
|
|
||||||
this.selected_nodes[i].mode = 0 // always
|
|
||||||
} else {
|
|
||||||
this.selected_nodes[i].mode = 2 // never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ctrl + B bypass
|
|
||||||
if (e.key === 'b' && (e.metaKey || e.ctrlKey)) {
|
|
||||||
if (this.selected_nodes) {
|
|
||||||
for (var i in this.selected_nodes) {
|
|
||||||
if (this.selected_nodes[i].mode === 4) {
|
|
||||||
// never
|
|
||||||
this.selected_nodes[i].mode = 0 // always
|
|
||||||
} else {
|
|
||||||
this.selected_nodes[i].mode = 4 // never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// p pin/unpin
|
|
||||||
if (e.key === 'p') {
|
|
||||||
if (this.selected_nodes) {
|
|
||||||
for (const i in this.selected_nodes) {
|
|
||||||
const node = this.selected_nodes[i]
|
|
||||||
node.pin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alt + C collapse/uncollapse
|
|
||||||
if (e.key === 'c' && e.altKey) {
|
|
||||||
if (this.selected_nodes) {
|
|
||||||
for (var i in this.selected_nodes) {
|
|
||||||
this.selected_nodes[i].collapse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_default = true
|
block_default = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,26 +1318,6 @@ export class ComfyApp {
|
|||||||
// Trigger onPaste
|
// Trigger onPaste
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === '+' && e.altKey) {
|
|
||||||
block_default = true
|
|
||||||
let scale = this.ds.scale * 1.1
|
|
||||||
this.ds.changeScale(scale, [
|
|
||||||
this.ds.element.width / 2,
|
|
||||||
this.ds.element.height / 2
|
|
||||||
])
|
|
||||||
this.graph.change()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === '-' && e.altKey) {
|
|
||||||
block_default = true
|
|
||||||
let scale = (this.ds.scale * 1) / 1.1
|
|
||||||
this.ds.changeScale(scale, [
|
|
||||||
this.ds.element.width / 2,
|
|
||||||
this.ds.element.height / 2
|
|
||||||
])
|
|
||||||
this.graph.change()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.graph.change()
|
this.graph.change()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { useTitleEditorStore } from './graphStore'
|
|||||||
import { useErrorHandling } from '@/hooks/errorHooks'
|
import { useErrorHandling } from '@/hooks/errorHooks'
|
||||||
import { useWorkflowStore } from './workflowStore'
|
import { useWorkflowStore } from './workflowStore'
|
||||||
import { type KeybindingImpl, useKeybindingStore } from './keybindingStore'
|
import { type KeybindingImpl, useKeybindingStore } from './keybindingStore'
|
||||||
|
import { LGraphNode } from '@comfyorg/litegraph'
|
||||||
|
|
||||||
export interface ComfyCommand {
|
export interface ComfyCommand {
|
||||||
id: string
|
id: string
|
||||||
@@ -75,6 +76,28 @@ export class ComfyCommandImpl implements ComfyCommand {
|
|||||||
const getTracker = () =>
|
const getTracker = () =>
|
||||||
app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker
|
app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker
|
||||||
|
|
||||||
|
const getSelectedNodes = (): LGraphNode[] => {
|
||||||
|
const selectedNodes = app.canvas.selected_nodes
|
||||||
|
const result: LGraphNode[] = []
|
||||||
|
if (selectedNodes) {
|
||||||
|
for (const i in selectedNodes) {
|
||||||
|
const node = selectedNodes[i]
|
||||||
|
result.push(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSelectedNodesMode = (mode: number) => {
|
||||||
|
getSelectedNodes().forEach((node) => {
|
||||||
|
if (node.mode === mode) {
|
||||||
|
node.mode = 0 // always
|
||||||
|
} else {
|
||||||
|
node.mode = mode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const useCommandStore = defineStore('command', () => {
|
export const useCommandStore = defineStore('command', () => {
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
|
|
||||||
@@ -248,7 +271,11 @@ export const useCommandStore = defineStore('command', () => {
|
|||||||
icon: 'pi pi-plus',
|
icon: 'pi pi-plus',
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
function: () => {
|
function: () => {
|
||||||
app.canvas.ds.changeScale(app.canvas.ds.scale + 0.1)
|
const ds = app.canvas.ds
|
||||||
|
ds.changeScale(ds.scale * 1.1, [
|
||||||
|
ds.element.width / 2,
|
||||||
|
ds.element.height / 2
|
||||||
|
])
|
||||||
app.canvas.setDirty(true, true)
|
app.canvas.setDirty(true, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -257,7 +284,11 @@ export const useCommandStore = defineStore('command', () => {
|
|||||||
icon: 'pi pi-minus',
|
icon: 'pi pi-minus',
|
||||||
label: 'Zoom Out',
|
label: 'Zoom Out',
|
||||||
function: () => {
|
function: () => {
|
||||||
app.canvas.ds.changeScale(app.canvas.ds.scale - 0.1)
|
const ds = app.canvas.ds
|
||||||
|
ds.changeScale(ds.scale / 1.1, [
|
||||||
|
ds.element.width / 2,
|
||||||
|
ds.element.height / 2
|
||||||
|
])
|
||||||
app.canvas.setDirty(true, true)
|
app.canvas.setDirty(true, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -365,6 +396,46 @@ export const useCommandStore = defineStore('command', () => {
|
|||||||
function: () => {
|
function: () => {
|
||||||
useWorkflowStore().loadPreviousOpenedWorkflow()
|
useWorkflowStore().loadPreviousOpenedWorkflow()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.Canvas.ToggleSelectedNodes.Mute',
|
||||||
|
icon: 'pi pi-volume-off',
|
||||||
|
label: 'Mute/Unmute Selected Nodes',
|
||||||
|
versionAdded: '1.3.11',
|
||||||
|
function: () => {
|
||||||
|
toggleSelectedNodesMode(2) // muted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.Canvas.ToggleSelectedNodes.Bypass',
|
||||||
|
icon: 'pi pi-shield',
|
||||||
|
label: 'Bypass/Unbypass Selected Nodes',
|
||||||
|
versionAdded: '1.3.11',
|
||||||
|
function: () => {
|
||||||
|
toggleSelectedNodesMode(4) // bypassed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.Canvas.ToggleSelectedNodes.Pin',
|
||||||
|
icon: 'pi pi-pin',
|
||||||
|
label: 'Pin/Unpin Selected Nodes',
|
||||||
|
versionAdded: '1.3.11',
|
||||||
|
function: () => {
|
||||||
|
getSelectedNodes().forEach((node) => {
|
||||||
|
node.pin(!node.pinned)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.Canvas.ToggleSelectedNodes.Collapse',
|
||||||
|
icon: 'pi pi-minus',
|
||||||
|
label: 'Collapse/Expand Selected Nodes',
|
||||||
|
versionAdded: '1.3.11',
|
||||||
|
function: () => {
|
||||||
|
getSelectedNodes().forEach((node) => {
|
||||||
|
node.collapse()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -94,5 +94,71 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
|||||||
ctrl: true
|
ctrl: true
|
||||||
},
|
},
|
||||||
commandId: 'Comfy.ShowSettingsDialog'
|
commandId: 'Comfy.ShowSettingsDialog'
|
||||||
|
},
|
||||||
|
// For '=' both holding shift and not holding shift
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: '=',
|
||||||
|
alt: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ZoomIn',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: '+',
|
||||||
|
alt: true,
|
||||||
|
shift: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ZoomIn',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
// For number pad '+'
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: '+',
|
||||||
|
alt: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ZoomIn',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: '-',
|
||||||
|
alt: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ZoomOut',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: 'p'
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Pin',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: 'c',
|
||||||
|
alt: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Collapse',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: 'b',
|
||||||
|
ctrl: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Bypass',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combo: {
|
||||||
|
key: 'm',
|
||||||
|
ctrl: true
|
||||||
|
},
|
||||||
|
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Mute',
|
||||||
|
targetSelector: '#graph-canvas'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,16 +8,20 @@ import type { ComfyExtension } from '@/types/comfy'
|
|||||||
export class KeybindingImpl implements Keybinding {
|
export class KeybindingImpl implements Keybinding {
|
||||||
commandId: string
|
commandId: string
|
||||||
combo: KeyComboImpl
|
combo: KeyComboImpl
|
||||||
|
targetSelector?: string
|
||||||
|
|
||||||
constructor(obj: Keybinding) {
|
constructor(obj: Keybinding) {
|
||||||
this.commandId = obj.commandId
|
this.commandId = obj.commandId
|
||||||
this.combo = new KeyComboImpl(obj.combo)
|
this.combo = new KeyComboImpl(obj.combo)
|
||||||
|
this.targetSelector = obj.targetSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: any): boolean {
|
equals(other: any): boolean {
|
||||||
if (toRaw(other) instanceof KeybindingImpl) {
|
if (toRaw(other) instanceof KeybindingImpl) {
|
||||||
return (
|
return (
|
||||||
this.commandId === other.commandId && this.combo.equals(other.combo)
|
this.commandId === other.commandId &&
|
||||||
|
this.combo.equals(other.combo) &&
|
||||||
|
this.targetSelector === other.targetSelector
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ export const zKeyCombo = z.object({
|
|||||||
// Keybinding schema
|
// Keybinding schema
|
||||||
export const zKeybinding = z.object({
|
export const zKeybinding = z.object({
|
||||||
commandId: z.string(),
|
commandId: z.string(),
|
||||||
combo: zKeyCombo
|
combo: zKeyCombo,
|
||||||
|
// Optional target element CSS selector to limit keybinding to.
|
||||||
|
// Note: Currently only used to distinguish between global keybindings
|
||||||
|
// and litegraph canvas keybindings.
|
||||||
|
// Do NOT use this field in extensions as it has no effect.
|
||||||
|
targetSelector: z.string().optional()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Infer types from schemas
|
// Infer types from schemas
|
||||||
|
|||||||
Reference in New Issue
Block a user