Files
ComfyUI_frontend/src/stores/commandStore.ts
Chenlei Hu 1775d43d90 Support keybinding customization (#1081)
* Basic keybinding panel

nit

Make row selectable

Reduce padding

Better key seq render

Show actions on demand

Turn off autocomplete

nit

Persist keybindings

Autofocus

Fix set unsetted keybinding bug

Refactor

Add reset button

Add back default keybinding logic

Report key conflict error

Adjust style

fix bug

Highlight modified keybindings

* Set current editing command's id as dialog header
2024-10-03 16:58:56 -04:00

344 lines
8.9 KiB
TypeScript

import { app } from '@/scripts/app'
import { api } from '@/scripts/api'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { globalTracker } from '@/scripts/changeTracker'
import { useSettingStore } from '@/stores/settingStore'
import { useToastStore } from '@/stores/toastStore'
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
function: () => void | Promise<void>
label?: string | (() => string)
icon?: string | (() => string)
tooltip?: string | (() => string)
versionAdded?: string
}
const getTracker = () =>
app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker
export const useCommandStore = defineStore('command', () => {
const settingStore = useSettingStore()
const commandsById = ref<Record<string, ComfyCommand>>({})
const commands = computed(() => Object.values(commandsById.value))
const registerCommand = (command: ComfyCommand) => {
if (commandsById.value[command.id]) {
console.warn(`Command ${command.id} already registered`)
}
commandsById.value[command.id] = command
}
const commandDefinitions: ComfyCommand[] = [
{
id: 'Comfy.NewBlankWorkflow',
icon: 'pi pi-plus',
label: 'New Blank Workflow',
function: () => {
app.workflowManager.setWorkflow(null)
app.clean()
app.graph.clear()
app.workflowManager.activeWorkflow.track()
}
},
{
id: 'Comfy.OpenWorkflow',
icon: 'pi pi-folder-open',
label: 'Open Workflow',
function: () => {
app.ui.loadFile()
}
},
{
id: 'Comfy.LoadDefaultWorkflow',
icon: 'pi pi-code',
label: 'Load Default Workflow',
function: async () => {
await app.loadGraphData()
}
},
{
id: 'Comfy.SaveWorkflow',
icon: 'pi pi-save',
label: 'Save Workflow',
function: () => {
app.workflowManager.activeWorkflow.save()
}
},
{
id: 'Comfy.SaveWorkflowAs',
icon: 'pi pi-save',
label: 'Save Workflow As',
function: () => {
app.workflowManager.activeWorkflow.save(true)
}
},
{
id: 'Comfy.ExportWorkflow',
icon: 'pi pi-download',
label: 'Export Workflow',
function: () => {
app.menu.exportWorkflow('workflow', 'workflow')
}
},
{
id: 'Comfy.ExportWorkflowAPI',
icon: 'pi pi-download',
label: 'Export Workflow (API Format)',
function: () => {
app.menu.exportWorkflow('workflow_api', 'output')
}
},
{
id: 'Comfy.Undo',
icon: 'pi pi-undo',
label: 'Undo',
function: async () => {
await getTracker().undo()
}
},
{
id: 'Comfy.Redo',
icon: 'pi pi-refresh',
label: 'Redo',
function: async () => {
await getTracker().redo()
}
},
{
id: 'Comfy.ClearWorkflow',
icon: 'pi pi-trash',
label: 'Clear Workflow',
function: () => {
if (
!settingStore.get('Comfy.ComfirmClear') ||
confirm('Clear workflow?')
) {
app.clean()
app.graph.clear()
api.dispatchEvent(new CustomEvent('graphCleared'))
}
}
},
{
id: 'Comfy.Canvas.ResetView',
icon: 'pi pi-expand',
label: 'Reset View',
function: () => {
app.resetView()
}
},
{
id: 'Comfy.OpenClipspace',
icon: 'pi pi-clipboard',
label: 'Clipspace',
function: () => {
app['openClipspace']?.()
}
},
{
id: 'Comfy.RefreshNodeDefinitions',
icon: 'pi pi-refresh',
label: 'Refresh Node Definitions',
function: async () => {
await app.refreshComboInNodes()
}
},
{
id: 'Comfy.Interrupt',
icon: 'pi pi-stop',
label: 'Interrupt',
function: async () => {
await api.interrupt()
useToastStore().add({
severity: 'info',
summary: 'Interrupted',
detail: 'Execution has been interrupted',
life: 1000
})
}
},
{
id: 'Comfy.ClearPendingTasks',
icon: 'pi pi-stop',
label: 'Clear Pending Tasks',
function: async () => {
await useQueueStore().clear(['queue'])
useToastStore().add({
severity: 'info',
summary: 'Confirmed',
detail: 'Pending tasks deleted',
life: 3000
})
}
},
{
id: 'Comfy.BrowseTemplates',
icon: 'pi pi-folder-open',
label: 'Browse Templates',
function: showTemplateWorkflowsDialog
},
{
id: 'Comfy.Canvas.ZoomIn',
icon: 'pi pi-plus',
label: 'Zoom In',
function: () => {
app.canvas.ds.changeScale(app.canvas.ds.scale + 0.1)
app.canvas.setDirty(true, true)
}
},
{
id: 'Comfy.Canvas.ZoomOut',
icon: 'pi pi-minus',
label: 'Zoom Out',
function: () => {
app.canvas.ds.changeScale(app.canvas.ds.scale - 0.1)
app.canvas.setDirty(true, true)
}
},
{
id: 'Comfy.Canvas.ToggleLock',
icon: 'pi pi-lock',
label: 'Toggle Lock',
function: () => {
app.canvas['read_only'] = !app.canvas['read_only']
}
},
{
id: 'Comfy.Canvas.ToggleLinkVisibility',
icon: 'pi pi-eye',
label: 'Toggle Link Visibility',
versionAdded: '1.3.6',
function: (() => {
let lastLinksRenderMode = LiteGraph.SPLINE_LINK
return () => {
const currentMode = settingStore.get('Comfy.LinkRenderMode')
if (currentMode === LiteGraph.HIDDEN_LINK) {
// If links are hidden, restore the last positive value or default to spline mode
settingStore.set('Comfy.LinkRenderMode', lastLinksRenderMode)
} else {
// If links are visible, store the current mode and hide links
lastLinksRenderMode = currentMode
settingStore.set('Comfy.LinkRenderMode', LiteGraph.HIDDEN_LINK)
}
}
})()
},
{
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
}
}
]
commandDefinitions.forEach(registerCommand)
const getCommandFunction = (command: string) => {
return commandsById.value[command]?.function ?? (() => {})
}
const getCommand = (command: string) => {
return commandsById.value[command]
}
const isRegistered = (command: string) => {
return !!commandsById.value[command]
}
const loadExtensionCommands = (extension: ComfyExtension) => {
if (extension.commands) {
for (const command of extension.commands) {
registerCommand(command)
}
}
}
return {
commands,
getCommand,
getCommandFunction,
registerCommand,
isRegistered,
loadExtensionCommands
}
})