From 995f4825932461b4d9e99a20431a7979d18d5805 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Mon, 21 Jul 2025 08:23:39 -0700 Subject: [PATCH 01/65] [feat] Implement versioned defaults for link release actions (#4489) --- browser_tests/tests/nodeSearchBox.spec.ts | 53 ++++++++++++++++++++++- src/constants/coreSettings.ts | 10 ++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/browser_tests/tests/nodeSearchBox.spec.ts b/browser_tests/tests/nodeSearchBox.spec.ts index ddb67d84e..f455c926a 100644 --- a/browser_tests/tests/nodeSearchBox.spec.ts +++ b/browser_tests/tests/nodeSearchBox.spec.ts @@ -27,6 +27,21 @@ test.describe('Node search box', () => { await expect(comfyPage.searchBox.input).toHaveCount(1) }) + test('New user (1.24.1+) gets search box by default on link release', async ({ + comfyPage + }) => { + // Start fresh to test new user behavior + await comfyPage.setup({ clearStorage: true }) + // Simulate new user with 1.24.1+ installed version + await comfyPage.setSetting('Comfy.InstalledVersion', '1.24.1') + await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default') + // Don't set LinkRelease settings explicitly to test versioned defaults + + await comfyPage.disconnectEdge() + await expect(comfyPage.searchBox.input).toHaveCount(1) + await expect(comfyPage.searchBox.input).toBeVisible() + }) + test('Can add node', async ({ comfyPage }) => { await comfyPage.doubleClickCanvas() await expect(comfyPage.searchBox.input).toHaveCount(1) @@ -172,10 +187,10 @@ test.describe('Node search box', () => { await comfyPage.page.mouse.click(panelBounds!.x - 10, panelBounds!.y - 10) // Verify the filter selection panel is hidden - expect(panel.header).not.toBeVisible() + await expect(panel.header).not.toBeVisible() // Verify the node search dialog is still visible - expect(comfyPage.searchBox.input).toBeVisible() + await expect(comfyPage.searchBox.input).toBeVisible() }) test('Can add multiple filters', async ({ comfyPage }) => { @@ -264,4 +279,38 @@ test.describe('Release context menu', () => { 'link-context-menu-search.png' ) }) + + test('Existing user (pre-1.24.1) gets context menu by default on link release', async ({ + comfyPage + }) => { + // Start fresh to test existing user behavior + await comfyPage.setup({ clearStorage: true }) + // Simulate existing user with pre-1.24.1 version + await comfyPage.setSetting('Comfy.InstalledVersion', '1.23.0') + await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default') + // Don't set LinkRelease settings explicitly to test versioned defaults + + await comfyPage.disconnectEdge() + // Context menu should appear, search box should not + await expect(comfyPage.searchBox.input).toHaveCount(0) + const contextMenu = comfyPage.page.locator('.litecontextmenu') + await expect(contextMenu).toBeVisible() + }) + + test('Explicit setting overrides versioned defaults', async ({ + comfyPage + }) => { + // Start fresh and simulate new user who should get search box by default + await comfyPage.setup({ clearStorage: true }) + await comfyPage.setSetting('Comfy.InstalledVersion', '1.24.1') + // But explicitly set to context menu (overriding versioned default) + await comfyPage.setSetting('Comfy.LinkRelease.Action', 'context menu') + await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default') + + await comfyPage.disconnectEdge() + // Context menu should appear due to explicit setting, not search box + await expect(comfyPage.searchBox.input).toHaveCount(0) + const contextMenu = comfyPage.page.locator('.litecontextmenu') + await expect(contextMenu).toBeVisible() + }) }) diff --git a/src/constants/coreSettings.ts b/src/constants/coreSettings.ts index c3a9b30af..eafe5ba15 100644 --- a/src/constants/coreSettings.ts +++ b/src/constants/coreSettings.ts @@ -35,7 +35,10 @@ export const CORE_SETTINGS: SettingParams[] = [ name: 'Action on link release (No modifier)', type: 'combo', options: Object.values(LinkReleaseTriggerAction), - defaultValue: LinkReleaseTriggerAction.CONTEXT_MENU + defaultValue: LinkReleaseTriggerAction.CONTEXT_MENU, + defaultsByInstallVersion: { + '1.24.1': LinkReleaseTriggerAction.SEARCH_BOX + } }, { id: 'Comfy.LinkRelease.ActionShift', @@ -43,7 +46,10 @@ export const CORE_SETTINGS: SettingParams[] = [ name: 'Action on link release (Shift)', type: 'combo', options: Object.values(LinkReleaseTriggerAction), - defaultValue: LinkReleaseTriggerAction.SEARCH_BOX + defaultValue: LinkReleaseTriggerAction.SEARCH_BOX, + defaultsByInstallVersion: { + '1.24.1': LinkReleaseTriggerAction.CONTEXT_MENU + } }, { id: 'Comfy.NodeSearchBoxImpl.NodePreview', From a39f6e676364fa419b65a4e541b70be6643710c0 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Mon, 21 Jul 2025 11:52:54 -0700 Subject: [PATCH 02/65] [feat] DOM widget promotion for subgraph inputs (#4491) --- browser_tests/fixtures/ComfyPage.ts | 112 +++++++ .../tests/domWidgetPromotion.spec.ts | 286 ++++++++++++++++++ .../tests/subgraphBreadcrumb.spec.ts | 2 +- src/components/graph/DomWidgets.vue | 26 +- src/components/graph/widgets/DomWidget.vue | 54 +++- src/composables/useRefreshableSelection.ts | 27 +- src/scripts/domWidget.ts | 25 ++ src/services/litegraphService.ts | 33 ++ 8 files changed, 537 insertions(+), 28 deletions(-) create mode 100644 browser_tests/tests/domWidgetPromotion.spec.ts diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index e619e4e75..6722ec3e5 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -776,6 +776,118 @@ export class ComfyPage { await this.nextFrame() } + /** + * Clicks on a litegraph context menu item (uses .litemenu-entry selector). + * Use this for canvas/node context menus, not PrimeVue menus. + */ + async clickLitegraphContextMenuItem(name: string): Promise { + await this.page.locator(`.litemenu-entry:has-text("${name}")`).click() + await this.nextFrame() + } + + /** + * Right-clicks on a subgraph input slot to open the context menu. + * Must be called when inside a subgraph. + * + * This method uses the actual slot positions from the subgraph.inputs array, + * which contain the correct coordinates for each input slot. These positions + * are different from the visual node positions and are specifically where + * the slots are rendered on the input node. + * + * @param inputName Optional name of the specific input slot to target (e.g., 'text'). + * If not provided, tries all available input slots until one works. + * @returns Promise that resolves when the context menu appears + */ + async rightClickSubgraphInputSlot(inputName?: string): Promise { + const foundSlot = await this.page.evaluate(async (targetInputName) => { + const app = window['app'] + const currentGraph = app.canvas.graph + + // Check if we're in a subgraph + if (currentGraph.constructor.name !== 'Subgraph') { + throw new Error( + 'Not in a subgraph - this method only works inside subgraphs' + ) + } + + // Get the input node + const inputNode = currentGraph.inputNode + if (!inputNode) { + throw new Error('No input node found in subgraph') + } + + // Get available inputs + const inputs = currentGraph.inputs + if (!inputs || inputs.length === 0) { + throw new Error('No input slots found in subgraph') + } + + // Filter to specific input if requested + const inputsToTry = targetInputName + ? inputs.filter((inp) => inp.name === targetInputName) + : inputs + + if (inputsToTry.length === 0) { + throw new Error( + targetInputName + ? `Input slot '${targetInputName}' not found` + : 'No input slots available to try' + ) + } + + // Try right-clicking on each input slot position until one works + for (const input of inputsToTry) { + if (!input.pos) continue + + const testX = input.pos[0] + const testY = input.pos[1] + + // Create a right-click event at the input slot position + const rightClickEvent = { + canvasX: testX, + canvasY: testY, + button: 2, // Right mouse button + preventDefault: () => {}, + stopPropagation: () => {} + } + + // Trigger the input node's right-click handler + if (inputNode.onPointerDown) { + inputNode.onPointerDown( + rightClickEvent, + app.canvas.pointer, + app.canvas.linkConnector + ) + } + + // Wait briefly for menu to appear + await new Promise((resolve) => setTimeout(resolve, 100)) + + // Check if litegraph context menu appeared + const menuExists = document.querySelector('.litemenu-entry') + if (menuExists) { + return { success: true, inputName: input.name, x: testX, y: testY } + } + } + + return { success: false } + }, inputName) + + if (!foundSlot.success) { + throw new Error( + inputName + ? `Could not open context menu for input slot '${inputName}'` + : 'Could not find any input slot position to right-click' + ) + } + + // Wait for the litegraph context menu to be visible + await this.page.waitForSelector('.litemenu-entry', { + state: 'visible', + timeout: 5000 + }) + } + async doubleClickCanvas() { await this.page.mouse.dblclick(10, 10, { delay: 5 }) await this.nextFrame() diff --git a/browser_tests/tests/domWidgetPromotion.spec.ts b/browser_tests/tests/domWidgetPromotion.spec.ts new file mode 100644 index 000000000..df4aeee93 --- /dev/null +++ b/browser_tests/tests/domWidgetPromotion.spec.ts @@ -0,0 +1,286 @@ +import { expect } from '@playwright/test' + +import { comfyPageFixture as test } from '../fixtures/ComfyPage' +import type { ComfyPage } from '../fixtures/ComfyPage' +import type { NodeReference } from '../fixtures/litegraph' + +/** + * Helper to navigate into a subgraph with retry logic + */ +async function navigateIntoSubgraph( + comfyPage: ComfyPage, + subgraphNode: NodeReference +) { + const nodePos = await subgraphNode.getPosition() + const nodeSize = await subgraphNode.getSize() + + // Use simple navigation for tests without promoted widgets blocking + await comfyPage.canvas.dblclick({ + position: { + x: nodePos.x + nodeSize.width / 2, + y: nodePos.y + 10 // Click below the title + } + }) + await comfyPage.nextFrame() + await comfyPage.page.waitForTimeout(100) +} + +/** + * Helper to navigate into a subgraph when DOM widgets might interfere + * Uses retry logic with different click positions + */ +async function navigateIntoSubgraphWithRetry( + comfyPage: ComfyPage, + subgraphNode: NodeReference +) { + const nodePos = await subgraphNode.getPosition() + const nodeSize = await subgraphNode.getSize() + + let attempts = 0 + const maxAttempts = 3 + let isInSubgraph = false + + while (attempts < maxAttempts && !isInSubgraph) { + attempts++ + + // Clear any existing selection that might interfere + await comfyPage.canvas.click({ + position: { x: 50, y: 50 } + }) + await comfyPage.nextFrame() + + // Try different click positions to avoid DOM widget interference + const clickPositions = [ + { x: nodePos.x + nodeSize.width / 2, y: nodePos.y + 15 }, // Near top + { x: nodePos.x + nodeSize.width / 2, y: nodePos.y + nodeSize.height / 2 }, // Center + { x: nodePos.x + 20, y: nodePos.y + nodeSize.height / 2 } // Left side + ] + + const position = + clickPositions[Math.min(attempts - 1, clickPositions.length - 1)] + + await comfyPage.canvas.dblclick({ position }) + await comfyPage.nextFrame() + await comfyPage.page.waitForTimeout(300) + + // Check if we're now in the subgraph + isInSubgraph = await comfyPage.page.evaluate(() => { + const graph = window['app'].canvas.graph + return graph?.constructor?.name === 'Subgraph' + }) + + if (isInSubgraph) { + break + } + } + + if (!isInSubgraph) { + throw new Error( + `Failed to navigate into subgraph after ${maxAttempts} attempts` + ) + } +} + +test.describe.skip('DOM Widget Promotion', () => { + test('DOM widget visibility persists through subgraph navigation', async ({ + comfyPage + }) => { + // Load workflow with promoted text widget + await comfyPage.loadWorkflow('subgraph-with-promoted-text-widget') + await comfyPage.nextFrame() + + // Check that the promoted widget's DOM element is visible in parent graph + const parentTextarea = await comfyPage.page.locator( + '.comfy-multiline-input' + ) + await expect(parentTextarea).toBeVisible() + await expect(parentTextarea).toHaveCount(1) + + // Get subgraph node + const subgraphNode = await comfyPage.getNodeRefById('11') + if (!(await subgraphNode.exists())) { + throw new Error('Subgraph node with ID 11 not found') + } + + // Navigate into the subgraph + await navigateIntoSubgraph(comfyPage, subgraphNode) + + // Check that the original widget's DOM element is visible in subgraph + const subgraphTextarea = await comfyPage.page.locator( + '.comfy-multiline-input' + ) + await expect(subgraphTextarea).toBeVisible() + await expect(subgraphTextarea).toHaveCount(1) + + // Navigate back to parent graph + await comfyPage.page.keyboard.press('Escape') + await comfyPage.nextFrame() + + // Check that the promoted widget's DOM element is still visible + const backToParentTextarea = await comfyPage.page.locator( + '.comfy-multiline-input' + ) + await expect(backToParentTextarea).toBeVisible() + await expect(backToParentTextarea).toHaveCount(1) + }) + + test('DOM widget content is preserved through navigation', async ({ + comfyPage + }) => { + await comfyPage.loadWorkflow('subgraph-with-promoted-text-widget') + + // Type some text in the promoted widget + const textarea = await comfyPage.page.locator('.comfy-multiline-input') + await textarea.fill('Test content that should persist') + + // Get subgraph node + const subgraphNode = await comfyPage.getNodeRefById('11') + + // Navigate into subgraph + await navigateIntoSubgraph(comfyPage, subgraphNode) + + // Verify content is still there + const subgraphTextarea = await comfyPage.page.locator( + '.comfy-multiline-input' + ) + await expect(subgraphTextarea).toHaveValue( + 'Test content that should persist' + ) + + // Navigate back + await comfyPage.page.keyboard.press('Escape') + await comfyPage.nextFrame() + + // Verify content persisted + const parentTextarea = await comfyPage.page.locator( + '.comfy-multiline-input' + ) + await expect(parentTextarea).toHaveValue('Test content that should persist') + }) + + test('DOM elements are cleaned up when subgraph node is removed', async ({ + comfyPage + }) => { + await comfyPage.loadWorkflow('subgraph-with-promoted-text-widget') + + // Count initial DOM elements + const initialCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + expect(initialCount).toBe(1) + + // Get subgraph node + const subgraphNode = await comfyPage.getNodeRefById('11') + + // Select and delete the subgraph node + await subgraphNode.click('title') + await comfyPage.page.keyboard.press('Delete') + await comfyPage.nextFrame() + + // Verify DOM elements are cleaned up + const finalCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + expect(finalCount).toBe(0) + }) + + test('DOM elements are cleaned up when widget is disconnected from I/O', async ({ + comfyPage + }) => { + await comfyPage.loadWorkflow('subgraph-with-promoted-text-widget') + + // Verify initial state - promoted widget exists + const textareaCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + expect(textareaCount).toBe(1) + + // Get subgraph node + const subgraphNode = await comfyPage.getNodeRefById('11') + + // Navigate into subgraph with retry logic (DOM widget might interfere) + await navigateIntoSubgraphWithRetry(comfyPage, subgraphNode) + + // Count DOM widgets before removing the slot + const beforeRemovalCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + + // Right-click on the "text" input slot (the one connected to the DOM widget) + await comfyPage.rightClickSubgraphInputSlot('text') + + // Click "Remove Slot" in the litegraph context menu + await comfyPage.clickLitegraphContextMenuItem('Remove Slot') + + await comfyPage.page.waitForTimeout(200) + + // Navigate back to parent + await comfyPage.page.keyboard.press('Escape') + await comfyPage.nextFrame() + + await comfyPage.page.waitForTimeout(200) + + // Verify the promoted widget is actually removed from the subgraph node + const widgetRemoved = await comfyPage.page.evaluate(() => { + const subgraphNode = window['app'].canvas.graph.getNodeById(11) + if (!subgraphNode) { + throw new Error('Subgraph node not found') + } + + // Check if the subgraph node still has any promoted widgets + const hasPromotedWidgets = + subgraphNode.widgets && subgraphNode.widgets.length > 0 + + // Also check the subgraph's inputs to see if the text input was actually removed + const hasTextInput = subgraphNode.subgraph?.inputs?.some( + (input) => input.name === 'text' + ) + + return { + nodeWidgetCount: subgraphNode.widgets?.length || 0, + hasTextInput: !!hasTextInput, + inputCount: subgraphNode.subgraph?.inputs?.length || 0 + } + }) + + // The subgraph node should no longer have any promoted widgets + expect(widgetRemoved.nodeWidgetCount).toBe(0) + + // The text input should be removed from the subgraph + expect(widgetRemoved.hasTextInput).toBe(false) + }) + + test('Multiple promoted widgets are handled correctly', async ({ + comfyPage + }) => { + await comfyPage.loadWorkflow('subgraph-with-multiple-promoted-widgets') + + // Count widgets in parent view + const parentCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + expect(parentCount).toBeGreaterThan(1) // Should have multiple widgets + + // Get subgraph node + const subgraphNode = await comfyPage.getNodeRefById('11') + + // Navigate into subgraph + await navigateIntoSubgraph(comfyPage, subgraphNode) + + // Count should be the same in subgraph + const subgraphCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + expect(subgraphCount).toBe(parentCount) + + // Navigate back + await comfyPage.page.keyboard.press('Escape') + await comfyPage.nextFrame() + + // Count should still be the same + const finalCount = await comfyPage.page + .locator('.comfy-multiline-input') + .count() + expect(finalCount).toBe(parentCount) + }) +}) diff --git a/browser_tests/tests/subgraphBreadcrumb.spec.ts b/browser_tests/tests/subgraphBreadcrumb.spec.ts index d0bcb3360..f28f7067f 100644 --- a/browser_tests/tests/subgraphBreadcrumb.spec.ts +++ b/browser_tests/tests/subgraphBreadcrumb.spec.ts @@ -2,7 +2,7 @@ import { expect } from '@playwright/test' import { comfyPageFixture as test } from '../fixtures/ComfyPage' -test.describe('Subgraph Breadcrumb Title Sync', () => { +test.describe.skip('Subgraph Breadcrumb Title Sync', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') }) diff --git a/src/components/graph/DomWidgets.vue b/src/components/graph/DomWidgets.vue index 9f7117e00..7edca3f5e 100644 --- a/src/components/graph/DomWidgets.vue +++ b/src/components/graph/DomWidgets.vue @@ -11,7 +11,6 @@ + + diff --git a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue new file mode 100644 index 000000000..f541db432 --- /dev/null +++ b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index c6aa2aea7..fbb9500ae 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -16,7 +16,6 @@ - @@ -50,7 +49,6 @@ import { computed, onMounted, ref, watch, watchEffect } from 'vue' import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue' import BottomPanel from '@/components/bottomPanel/BottomPanel.vue' -import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue' import DomWidgets from '@/components/graph/DomWidgets.vue' import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue' import NodeTooltip from '@/components/graph/NodeTooltip.vue' diff --git a/src/components/sidebar/SideToolbar.vue b/src/components/sidebar/SideToolbar.vue index 4b7e6df00..e455ae74a 100644 --- a/src/components/sidebar/SideToolbar.vue +++ b/src/components/sidebar/SideToolbar.vue @@ -14,9 +14,8 @@ />
- - +
@@ -32,6 +31,7 @@ import { computed } from 'vue' import ExtensionSlot from '@/components/common/ExtensionSlot.vue' +import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue' import { useKeybindingStore } from '@/stores/keybindingStore' import { useSettingStore } from '@/stores/settingStore' import { useUserStore } from '@/stores/userStore' @@ -41,8 +41,6 @@ import type { SidebarTabExtension } from '@/types/extensionTypes' import SidebarHelpCenterIcon from './SidebarHelpCenterIcon.vue' import SidebarIcon from './SidebarIcon.vue' import SidebarLogoutIcon from './SidebarLogoutIcon.vue' -import SidebarSettingsToggleIcon from './SidebarSettingsToggleIcon.vue' -import SidebarThemeToggleIcon from './SidebarThemeToggleIcon.vue' const workspaceStore = useWorkspaceStore() const settingStore = useSettingStore() diff --git a/src/components/sidebar/SidebarBottomPanelToggleButton.vue b/src/components/sidebar/SidebarBottomPanelToggleButton.vue new file mode 100644 index 000000000..6ca891e6e --- /dev/null +++ b/src/components/sidebar/SidebarBottomPanelToggleButton.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/sidebar/SidebarIcon.vue b/src/components/sidebar/SidebarIcon.vue index 1c1dffc1b..5002d7724 100644 --- a/src/components/sidebar/SidebarIcon.vue +++ b/src/components/sidebar/SidebarIcon.vue @@ -19,10 +19,12 @@ @click="emit('click', $event)" > diff --git a/src/components/sidebar/SidebarSettingsToggleIcon.vue b/src/components/sidebar/SidebarSettingsToggleIcon.vue deleted file mode 100644 index 3b214fb43..000000000 --- a/src/components/sidebar/SidebarSettingsToggleIcon.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/components/sidebar/SidebarThemeToggleIcon.vue b/src/components/sidebar/SidebarThemeToggleIcon.vue deleted file mode 100644 index 8d64ff4ef..000000000 --- a/src/components/sidebar/SidebarThemeToggleIcon.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/src/components/topbar/BottomPanelToggleButton.vue b/src/components/topbar/BottomPanelToggleButton.vue deleted file mode 100644 index 8af15331b..000000000 --- a/src/components/topbar/BottomPanelToggleButton.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/components/topbar/CommandMenubar.vue b/src/components/topbar/CommandMenubar.vue index faf795b9b..02029d307 100644 --- a/src/components/topbar/CommandMenubar.vue +++ b/src/components/topbar/CommandMenubar.vue @@ -1,51 +1,116 @@ @@ -51,6 +57,7 @@ import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitter import BottomPanel from '@/components/bottomPanel/BottomPanel.vue' import DomWidgets from '@/components/graph/DomWidgets.vue' import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue' +import MiniMap from '@/components/graph/MiniMap.vue' import NodeTooltip from '@/components/graph/NodeTooltip.vue' import SelectionOverlay from '@/components/graph/SelectionOverlay.vue' import SelectionToolbox from '@/components/graph/SelectionToolbox.vue' @@ -65,6 +72,7 @@ import { useContextMenuTranslation } from '@/composables/useContextMenuTranslati import { useCopy } from '@/composables/useCopy' import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph' import { useLitegraphSettings } from '@/composables/useLitegraphSettings' +import { useMinimap } from '@/composables/useMinimap' import { usePaste } from '@/composables/usePaste' import { useWorkflowAutoSave } from '@/composables/useWorkflowAutoSave' import { useWorkflowPersistence } from '@/composables/useWorkflowPersistence' @@ -111,6 +119,10 @@ const selectionToolboxEnabled = computed(() => settingStore.get('Comfy.Canvas.SelectionToolbox') ) +const minimapRef = ref>() +const minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible')) +const minimap = useMinimap() + watchEffect(() => { nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated') }) @@ -346,6 +358,13 @@ onMounted(async () => { } ) + whenever( + () => minimapRef.value, + (ref) => { + minimap.setMinimapRef(ref) + } + ) + whenever( () => useCanvasStore().canvas, (canvas) => { diff --git a/src/components/graph/GraphCanvasMenu.vue b/src/components/graph/GraphCanvasMenu.vue index 8f5d036cb..f0b208ce4 100644 --- a/src/components/graph/GraphCanvasMenu.vue +++ b/src/components/graph/GraphCanvasMenu.vue @@ -56,6 +56,15 @@ data-testid="toggle-link-visibility-button" @click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')" /> +