diff --git a/CHANGELOG.md b/CHANGELOG.md index ea60573..94d6745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ - The console banner is one line, version plus tool count, instead of one `Loaded:` line per node. +### Custom Colors + +- The `🫶 Custom Colors` entry opens its Full / Title / BG submenu again in + Nodes 2.0. The Nodes 2.0 context menu converts litegraph items itself and + only renders a submenu for entries flagged `has_submenu`, so the entry was an + inert label there; the classic menu was unaffected. + ### Model Resolution Picker - New `Krea 2` family (Krea 2 Turbo, Raw and finetunes): 8x VAE, step 16, diff --git a/tests/browser/workflows.spec.mjs b/tests/browser/workflows.spec.mjs index 9bc0a3d..5f08f4d 100644 --- a/tests/browser/workflows.spec.mjs +++ b/tests/browser/workflows.spec.mjs @@ -1731,8 +1731,11 @@ test('custom colors handle absent settings, empty canvas menus, legacy groups an expect(await page.evaluate(() => { const extension = kikoHost.extensions.find(item => item.name === 'kikotools.customColors'); kikoHost.settings.delete('kikotools.custom_colors.enabled'); - return [extension.getCanvasMenuItems({}), extension.getCanvasMenuItems({ graph_mouse: [0, 0], graph: { getGroupOnPos: () => null } }), extension.getNodeMenuItems(kikoHost.app.graph._nodes[0])[1].submenu.options.length]; - })).toEqual([[], [], 3]); + const nodeMenu = extension.getNodeMenuItems(kikoHost.app.graph._nodes[0])[1]; + const groupMenu = extension.getCanvasMenuItems({ graph_mouse: [0, 0], graph: { getGroupOnPos: () => ({ color: '#000000' }) } })[1]; + // The Nodes 2.0 menu converter only renders submenu.options when has_submenu is set. + return [extension.getCanvasMenuItems({}), extension.getCanvasMenuItems({ graph_mouse: [0, 0], graph: { getGroupOnPos: () => null } }), nodeMenu.submenu.options.length, nodeMenu.has_submenu, groupMenu.has_submenu]; + })).toEqual([[], [], 3, true, true]); await page.evaluate(() => { const extension = kikoHost.extensions.find(item => item.name === 'kikotools.customColors'); window.testGroup = { color: '#000000' }; diff --git a/tests/comfyui/image-browser.spec.mjs b/tests/comfyui/image-browser.spec.mjs index 11ac07f..9b025ed 100644 --- a/tests/comfyui/image-browser.spec.mjs +++ b/tests/comfyui/image-browser.spec.mjs @@ -733,6 +733,45 @@ test('Custom Colors preserves node colors across renderers and workflow reload', await expect(page.locator('dialog.ca-color-dialog')).toHaveCount(0); }); +test('Custom Colors opens from the node context menu in both renderers', async ({ page }) => { + const errors = []; + page.on('pageerror', error => errors.push(error.message)); + for (const renderer of [false, true]) { + await page.evaluate(async renderer => { + const { app } = await import('/scripts/app.js'); + await app.ui.settings.setSettingValue('Comfy.VueNodes.Enabled', renderer); + }, renderer); + await expect.poll(() => page.evaluate(() => LiteGraph.vueNodesMode)).toBe(renderer); + const title = await page.evaluate(async () => { + const { app } = await import('/scripts/app.js'); + app.graph.clear(); + const node = LiteGraph.createNode('EmptyLatentImage'); + app.graph.add(node); + app.canvas.centerOnNode(node); + app.canvas.setDirty(true, true); + await new Promise(resolve => setTimeout(resolve, 300)); + const rect = app.canvas.canvas.getBoundingClientRect(); + const [x, y] = app.canvas.ds.convertOffsetToCanvas([node.pos[0] + node.size[0] / 2, node.pos[1] - 10]); + return { x: rect.left + x, y: rect.top + y }; + }); + await page.mouse.click(title.x, title.y, { button: 'right' }); + // The Nodes 2.0 menu converts litegraph items itself and only renders a + // submenu for entries flagged has_submenu; the classic menu opens + // submenu.options either way. Both must reach the picker. + const entry = page.getByText('🫶 Custom Colors', { exact: true }).first(); + const full = page.getByText('🫶 Custom Full', { exact: true }).first(); + await entry.hover(); + try { await full.waitFor({ state: 'visible', timeout: 2000 }); } // Nodes 2.0 opens on hover + catch { await entry.click(); } // the classic litegraph menu opens on click + await full.click(); + await expect(page.getByRole('dialog', { name: 'Custom full color', exact: true })).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(page.locator('ca-color-picker')).toHaveCount(0); + await page.keyboard.press('Escape'); + } + expect(errors).toEqual([]); +}); + test('Follow Execution honors live settings and host menus in both renderers', async ({ page }, testInfo) => { const errors = []; page.on('pageerror', error => errors.push(error.message)); diff --git a/web/kiko_custom_colors.js b/web/kiko_custom_colors.js index 32fc8d6..3dd2acd 100644 --- a/web/kiko_custom_colors.js +++ b/web/kiko_custom_colors.js @@ -86,10 +86,13 @@ const openPicker = (initialColor, apply, node, label) => { picker.dialog.showModal(); }; -// Wrap menu options in the "Custom Colors" submenu (or nothing if empty) +// Wrap menu options in the "Custom Colors" submenu (or nothing if empty). +// has_submenu is required as well as submenu: the Nodes 2.0 context menu +// converts litegraph items itself and only renders submenu.options for +// entries flagged has_submenu; without it the entry is an inert label. const customColorsMenu = (options) => options.length === 0 ? [] : [ null, // separator - { content: "🫶 Custom Colors", submenu: { options } }, + { content: "🫶 Custom Colors", has_submenu: true, submenu: { options } }, ]; app.registerExtension({