Skip to content

[feat] Make hotkey for exiting subgraphs configurable in user keybindings #4818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions browser_tests/tests/subgraph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,103 @@ test.describe('Subgraph Operations', () => {
expect(finalCount).toBe(parentCount)
})
})

test.describe('Navigation Hotkeys', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})

test('Navigation hotkey can be customized', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('basic-subgraph')
await comfyPage.nextFrame()

// Change the Exit Subgraph keybinding from Escape to Alt+Q
await comfyPage.setSetting('Comfy.Keybinding.NewBindings', [
{
commandId: 'Comfy.Graph.ExitSubgraph',
combo: {
key: 'q',
ctrl: false,
alt: true,
shift: false
}
}
])

await comfyPage.setSetting('Comfy.Keybinding.UnsetBindings', [
{
commandId: 'Comfy.Graph.ExitSubgraph',
combo: {
key: 'Escape',
ctrl: false,
alt: false,
shift: false
}
}
])

// Reload the page
await comfyPage.page.reload()
await comfyPage.page.waitForTimeout(1024)

// Navigate into subgraph
const subgraphNode = await comfyPage.getNodeRefById('2')
await subgraphNode.navigateIntoSubgraph()
await comfyPage.page.waitForSelector(SELECTORS.breadcrumb)

// Verify we're in a subgraph
expect(await isInSubgraph(comfyPage)).toBe(true)

// Test that Escape no longer exits subgraph
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
if (!(await isInSubgraph(comfyPage))) {
throw new Error('Not in subgraph')
}

// Test that Alt+Q now exits subgraph
await comfyPage.page.keyboard.press('Alt+q')
await comfyPage.nextFrame()
expect(await isInSubgraph(comfyPage)).toBe(false)
})

test('Escape prioritizes closing dialogs over exiting subgraph', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('basic-subgraph')
await comfyPage.nextFrame()

const subgraphNode = await comfyPage.getNodeRefById('2')
await subgraphNode.navigateIntoSubgraph()
await comfyPage.page.waitForSelector(SELECTORS.breadcrumb)

// Verify we're in a subgraph
if (!(await isInSubgraph(comfyPage))) {
throw new Error('Not in subgraph')
}

// Open settings dialog using hotkey
await comfyPage.page.keyboard.press('Control+,')
await comfyPage.page.waitForSelector('.settings-container', {
state: 'visible'
})

// Press Escape - should close dialog, not exit subgraph
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()

// Dialog should be closed
await expect(
comfyPage.page.locator('.settings-container')
).not.toBeVisible()

// Should still be in subgraph
expect(await isInSubgraph(comfyPage)).toBe(true)

// Press Escape again - now should exit subgraph
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
expect(await isInSubgraph(comfyPage)).toBe(false)
})
})
})
13 changes: 0 additions & 13 deletions src/components/breadcrumb/SubgraphBreadcrumb.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
</template>

<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import Breadcrumb from 'primevue/breadcrumb'
import type { MenuItem } from 'primevue/menuitem'
import { computed, onUpdated, ref, watch } from 'vue'
Expand Down Expand Up @@ -98,18 +97,6 @@ const home = computed(() => ({
}
}))

// Escape exits from the current subgraph.
useEventListener(document, 'keydown', (event) => {
if (event.key === 'Escape') {
const canvas = useCanvasStore().getCanvas()
if (!canvas.graph) throw new TypeError('Canvas has no graph')

canvas.setGraph(
navigationStore.navigationStack.at(-2) ?? canvas.graph.rootGraph
)
}
})

// Check for overflow on breadcrumb items and collapse/expand the breadcrumb to fit
let overflowObserver: ReturnType<typeof useOverflowObserver> | undefined
watch(breadcrumbElement, (el) => {
Expand Down
16 changes: 16 additions & 0 deletions src/composables/useCoreCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useExecutionStore } from '@/stores/executionStore'
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
import { useSettingStore } from '@/stores/settingStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { useToastStore } from '@/stores/toastStore'
import { type ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
Expand Down Expand Up @@ -805,6 +806,21 @@ export function useCoreCommands(): ComfyCommand[] {
function: () => {
bottomPanelStore.togglePanel('shortcuts')
}
},
{
id: 'Comfy.Graph.ExitSubgraph',
icon: 'pi pi-arrow-up',
label: 'Exit Subgraph',
versionAdded: '1.20.1',
function: () => {
const canvas = useCanvasStore().getCanvas()
const navigationStore = useSubgraphNavigationStore()
if (!canvas.graph) return

canvas.setGraph(
navigationStore.navigationStack.at(-2) ?? canvas.graph.rootGraph
)
}
}
]

Expand Down
6 changes: 6 additions & 0 deletions src/constants/coreKeybindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,11 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
key: 'k'
},
commandId: 'Workspace.ToggleBottomPanel.Shortcuts'
},
{
combo: {
key: 'Escape'
},
commandId: 'Comfy.Graph.ExitSubgraph'
}
]
3 changes: 3 additions & 0 deletions src/locales/en/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "Convert Selection to Subgraph"
},
"Comfy_Graph_ExitSubgraph": {
"label": "Exit Subgraph"
},
"Comfy_Graph_FitGroupToContents": {
"label": "Fit Group To Contents"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,7 @@
"Export (API)": "Export (API)",
"Give Feedback": "Give Feedback",
"Convert Selection to Subgraph": "Convert Selection to Subgraph",
"Exit Subgraph": "Exit Subgraph",
"Fit Group To Contents": "Fit Group To Contents",
"Group Selected Nodes": "Group Selected Nodes",
"Convert selected nodes to group node": "Convert selected nodes to group node",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/es/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "Convertir selección en subgrafo"
},
"Comfy_Graph_ExitSubgraph": {
"label": "Salir de subgrafo"
},
"Comfy_Graph_FitGroupToContents": {
"label": "Ajustar grupo al contenido"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/es/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "Guía de usuario de escritorio",
"Duplicate Current Workflow": "Duplicar flujo de trabajo actual",
"Edit": "Editar",
"Exit Subgraph": "Salir de subgrafo",
"Export": "Exportar",
"Export (API)": "Exportar (API)",
"Fit Group To Contents": "Ajustar grupo a contenidos",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/fr/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "Convertir la sélection en sous-graphe"
},
"Comfy_Graph_ExitSubgraph": {
"label": "Quitter le sous-graphe"
},
"Comfy_Graph_FitGroupToContents": {
"label": "Ajuster le groupe au contenu"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "Guide de l'utilisateur de bureau",
"Duplicate Current Workflow": "Dupliquer le flux de travail actuel",
"Edit": "Éditer",
"Exit Subgraph": "Quitter le sous-graphe",
"Export": "Exporter",
"Export (API)": "Exporter (API)",
"Fit Group To Contents": "Ajuster le groupe au contenu",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ja/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "選択範囲をサブグラフに変換"
},
"Comfy_Graph_ExitSubgraph": {
"label": "サブグラフを終了"
},
"Comfy_Graph_FitGroupToContents": {
"label": "グループを内容に合わせて調整"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "デスクトップユーザーガイド",
"Duplicate Current Workflow": "現在のワークフローを複製",
"Edit": "編集",
"Exit Subgraph": "サブグラフを終了",
"Export": "エクスポート",
"Export (API)": "エクスポート (API)",
"Fit Group To Contents": "グループを内容に合わせる",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ko/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "선택 영역을 서브그래프로 변환"
},
"Comfy_Graph_ExitSubgraph": {
"label": "서브그래프 종료"
},
"Comfy_Graph_FitGroupToContents": {
"label": "그룹을 내용에 맞게 맞추기"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/ko/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "데스크톱 사용자 가이드",
"Duplicate Current Workflow": "현재 워크플로 복제",
"Edit": "편집",
"Exit Subgraph": "서브그래프 종료",
"Export": "내보내기",
"Export (API)": "내보내기 (API)",
"Fit Group To Contents": "그룹을 내용에 맞게 조정",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ru/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "Преобразовать выделенное в подграф"
},
"Comfy_Graph_ExitSubgraph": {
"label": "Выйти из подграфа"
},
"Comfy_Graph_FitGroupToContents": {
"label": "Подогнать группу к содержимому"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/ru/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "Руководство пользователя для настольных ПК",
"Duplicate Current Workflow": "Дублировать текущий рабочий процесс",
"Edit": "Редактировать",
"Exit Subgraph": "Выйти из подграфа",
"Export": "Экспортировать",
"Export (API)": "Экспорт (API)",
"Fit Group To Contents": "Подогнать группу под содержимое",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/zh-TW/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "將選取內容轉換為子圖"
},
"Comfy_Graph_ExitSubgraph": {
"label": "離開子圖"
},
"Comfy_Graph_FitGroupToContents": {
"label": "調整群組以符合內容"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh-TW/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "桌面應用程式使用指南",
"Duplicate Current Workflow": "複製目前工作流程",
"Edit": "編輯",
"Exit Subgraph": "離開子圖",
"Export": "匯出",
"Export (API)": "匯出(API)",
"Fit Group To Contents": "群組貼合內容",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/zh/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
"Comfy_Graph_ConvertToSubgraph": {
"label": "将选区转换为子图"
},
"Comfy_Graph_ExitSubgraph": {
"label": "退出子圖"
},
"Comfy_Graph_FitGroupToContents": {
"label": "适应节点框到内容"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
"Desktop User Guide": "桌面端用户指南",
"Duplicate Current Workflow": "复制当前工作流",
"Edit": "编辑",
"Exit Subgraph": "退出子圖",
"Export": "导出",
"Export (API)": "导出 (API)",
"Fit Group To Contents": "适应组内容",
Expand Down
15 changes: 15 additions & 0 deletions src/services/keybindingService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings'
import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
import {
KeyComboImpl,
KeybindingImpl,
Expand All @@ -11,6 +12,7 @@ export const useKeybindingService = () => {
const keybindingStore = useKeybindingStore()
const commandStore = useCommandStore()
const settingStore = useSettingStore()
const dialogStore = useDialogStore()

const keybindHandler = async function (event: KeyboardEvent) {
const keyCombo = KeyComboImpl.fromEvent(event)
Expand All @@ -32,6 +34,19 @@ export const useKeybindingService = () => {

const keybinding = keybindingStore.getKeybinding(keyCombo)
if (keybinding && keybinding.targetElementId !== 'graph-canvas') {
// Special handling for Escape key - let dialogs handle it first
if (
event.key === 'Escape' &&
!event.ctrlKey &&
!event.altKey &&
!event.metaKey
) {
// If dialogs are open, don't execute the keybinding - let the dialog handle it
if (dialogStore.dialogStack.length > 0) {
return
}
}

// Prevent default browser behavior first, then execute the command
event.preventDefault()
await commandStore.execute(keybinding.commandId)
Expand Down
Loading