diff --git a/.github/workflows/release-biweekly-comfyui.yaml b/.github/workflows/release-biweekly-comfyui.yaml index c0f57822bc..6eb45a00e0 100644 --- a/.github/workflows/release-biweekly-comfyui.yaml +++ b/.github/workflows/release-biweekly-comfyui.yaml @@ -69,7 +69,7 @@ jobs: - name: Checkout ComfyUI (sparse) uses: actions/checkout@v5 with: - repository: comfyanonymous/ComfyUI + repository: Comfy-Org/ComfyUI sparse-checkout: | requirements.txt path: comfyui @@ -184,7 +184,7 @@ jobs: # Note: This only affects the local checkout, NOT the fork's master branch # We only push the automation branch, leaving the fork's master untouched echo "Fetching upstream master..." - if ! git fetch https://github.com/comfyanonymous/ComfyUI.git master; then + if ! git fetch https://github.com/Comfy-Org/ComfyUI.git master; then echo "Failed to fetch upstream master" exit 1 fi @@ -257,7 +257,7 @@ jobs: # Extract fork owner from repository name FORK_OWNER=$(echo "$COMFYUI_FORK" | cut -d'/' -f1) - echo "Creating PR from ${COMFYUI_FORK} to comfyanonymous/ComfyUI" + echo "Creating PR from ${COMFYUI_FORK} to Comfy-Org/ComfyUI" # Configure git git config user.name "github-actions[bot]" @@ -288,7 +288,7 @@ jobs: # Try to create PR, ignore error if it already exists if ! gh pr create \ - --repo comfyanonymous/ComfyUI \ + --repo Comfy-Org/ComfyUI \ --head "${FORK_OWNER}:${BRANCH}" \ --base master \ --title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \ @@ -297,7 +297,7 @@ jobs: # Check if PR already exists set +e - EXISTING_PR=$(gh pr list --repo comfyanonymous/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1) + EXISTING_PR=$(gh pr list --repo Comfy-Org/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1) PR_LIST_EXIT=$? set -e @@ -318,7 +318,7 @@ jobs: run: | echo "## ComfyUI PR Created" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Draft PR created in comfyanonymous/ComfyUI" >> $GITHUB_STEP_SUMMARY + echo "Draft PR created in Comfy-Org/ComfyUI" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### PR Body:" >> $GITHUB_STEP_SUMMARY cat pr-body.txt >> $GITHUB_STEP_SUMMARY diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index 08cdd5ccea..2f51533ce4 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -3,7 +3,7 @@ import { test as base, expect } from '@playwright/test' import dotenv from 'dotenv' import * as fs from 'fs' -import type { LGraphNode } from '../../src/lib/litegraph/src/litegraph' +import type { LGraphNode, LGraph } from '../../src/lib/litegraph/src/litegraph' import type { NodeId } from '../../src/platform/workflow/validation/schemas/workflowSchema' import type { KeyCombo } from '../../src/schemas/keyBindingSchema' import type { useWorkspaceStore } from '../../src/stores/workspaceStore' @@ -1591,14 +1591,29 @@ export class ComfyPage { return window['app'].graph.nodes }) } - async getNodeRefsByType(type: string): Promise { + async waitForGraphNodes(count: number) { + await this.page.waitForFunction((count) => { + return window['app']?.canvas.graph?.nodes?.length === count + }, count) + } + async getNodeRefsByType( + type: string, + includeSubgraph: boolean = false + ): Promise { return Promise.all( ( - await this.page.evaluate((type) => { - return window['app'].graph.nodes - .filter((n: LGraphNode) => n.type === type) - .map((n: LGraphNode) => n.id) - }, type) + await this.page.evaluate( + ({ type, includeSubgraph }) => { + const graph = ( + includeSubgraph ? window['app'].canvas.graph : window['app'].graph + ) as LGraph + const nodes = graph.nodes + return nodes + .filter((n: LGraphNode) => n.type === type) + .map((n: LGraphNode) => n.id) + }, + { type, includeSubgraph } + ) ).map((id: NodeId) => this.getNodeRefById(id)) ) } diff --git a/browser_tests/fixtures/VueNodeHelpers.ts b/browser_tests/fixtures/VueNodeHelpers.ts index 6c84af9cd5..3c11cfda22 100644 --- a/browser_tests/fixtures/VueNodeHelpers.ts +++ b/browser_tests/fixtures/VueNodeHelpers.ts @@ -163,4 +163,14 @@ export class VueNodeHelpers { incrementButton: widget.getByTestId('increment') } } + + /** + * Enter the subgraph of a node. + * @param nodeId - The ID of the node to enter the subgraph of. If not provided, the first matched subgraph will be entered. + */ + async enterSubgraph(nodeId?: string): Promise { + const locator = nodeId ? this.getNodeLocator(nodeId) : this.page + const editButton = locator.getByTestId('subgraph-enter-button') + await editButton.click() + } } diff --git a/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts b/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts index 9ff32c8a48..ff452b54ae 100644 --- a/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts +++ b/browser_tests/tests/propertiesPanel/propertiesPanel.spec.ts @@ -8,13 +8,11 @@ test.describe('Properties panel', () => { const { propertiesPanel } = comfyPage.menu - await expect(propertiesPanel.panelTitle).toContainText( - 'No node(s) selected' - ) + await expect(propertiesPanel.panelTitle).toContainText('Workflow Overview') await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)']) - await expect(propertiesPanel.panelTitle).toContainText('3 nodes selected') + await expect(propertiesPanel.panelTitle).toContainText('3 items selected') await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1) await expect( propertiesPanel.root.getByText('CLIP Text Encode (Prompt)') diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png index 07ff7a0968..885e463568 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-node-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png index 7ecb4123e7..39e3ade033 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-pinned-node-chromium-linux.png differ diff --git a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png index 07ff7a0968..885e463568 100644 Binary files a/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png and b/browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts index 09a78fb6ea..315758f5ce 100644 --- a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts @@ -102,7 +102,7 @@ test.describe('Vue Node Link Interaction', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) - await comfyPage.setup() + // await comfyPage.setup() await comfyPage.loadWorkflow('vueNodes/simple-triple') await comfyPage.vueNodes.waitForNodes() await fitToViewInstant(comfyPage) @@ -993,4 +993,51 @@ test.describe('Vue Node Link Interaction', () => { expect(linked).toBe(true) }) }) + + test('Dragging from subgraph input connects to correct slot', async ({ + comfyPage, + comfyMouse + }) => { + // Setup workflow with a KSampler node + await comfyPage.executeCommand('Comfy.NewBlankWorkflow') + await comfyPage.waitForGraphNodes(0) + await comfyPage.executeCommand('Workspace.SearchBox.Toggle') + await comfyPage.nextFrame() + await comfyPage.searchBox.fillAndSelectFirstNode('KSampler') + await comfyPage.waitForGraphNodes(1) + + // Convert the KSampler node to a subgraph + let ksamplerNode = (await comfyPage.getNodeRefsByType('KSampler'))?.[0] + await comfyPage.vueNodes.selectNode(String(ksamplerNode.id)) + await comfyPage.executeCommand('Comfy.Graph.ConvertToSubgraph') + + // Enter the subgraph + await comfyPage.vueNodes.enterSubgraph() + await fitToViewInstant(comfyPage) + + // Get the KSampler node inside the subgraph + ksamplerNode = (await comfyPage.getNodeRefsByType('KSampler', true))?.[0] + const positiveInput = await ksamplerNode.getInput(1) + const negativeInput = await ksamplerNode.getInput(2) + + const positiveInputPos = await getSlotCenter( + comfyPage.page, + ksamplerNode.id, + 1, + true + ) + + const sourceSlot = await comfyPage.getSubgraphInputSlot() + const calculatedSourcePos = await sourceSlot.getOpenSlotPosition() + + await comfyMouse.move(calculatedSourcePos) + await comfyMouse.drag(positiveInputPos) + await comfyMouse.drop() + + // Verify connection went to the correct slot + const positiveLinks = await positiveInput.getLinkCount() + const negativeLinks = await negativeInput.getLinkCount() + expect(positiveLinks).toBe(1) + expect(negativeLinks).toBe(0) + }) }) diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 78ddf2836b..16a48ace3b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 804e16c0d0..56f48867cd 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/package.json b/package.json index 8672b0cd2e..ed29f1fe3a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.38.0", + "version": "1.38.1", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index 551e26c62e..df8fe4a238 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -247,6 +247,7 @@ --inverted-background-hover: var(--color-charcoal-600); --warning-background: var(--color-gold-400); --warning-background-hover: var(--color-gold-500); + --success-background: var(--color-jade-600); --border-default: var(--color-smoke-600); --border-subtle: var(--color-smoke-400); --muted-background: var(--color-smoke-700); @@ -372,6 +373,7 @@ --inverted-background-hover: var(--color-smoke-200); --warning-background: var(--color-gold-600); --warning-background-hover: var(--color-gold-500); + --success-background: var(--color-jade-600); --border-default: var(--color-charcoal-200); --border-subtle: var(--color-charcoal-300); --muted-background: var(--color-charcoal-100); @@ -516,6 +518,7 @@ --color-inverted-background-hover: var(--inverted-background-hover); --color-warning-background: var(--warning-background); --color-warning-background-hover: var(--warning-background-hover); + --color-success-background: var(--success-background); --color-border-default: var(--border-default); --color-border-subtle: var(--border-subtle); --color-muted-background: var(--muted-background); diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 627b129e40..301161e5d4 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -1 +1,21 @@ -@import '@comfyorg/design-system/css/style.css'; \ No newline at end of file +@import '@comfyorg/design-system/css/style.css'; + +@media (prefers-reduced-motion: no-preference) { + /* List transition animations */ + .list-scale-move, + .list-scale-enter-active, + .list-scale-leave-active { + transition: opacity 150ms ease, transform 150ms ease; + } + + .list-scale-enter-from, + .list-scale-leave-to { + opacity: 0; + transform: scale(70%); + } + + .list-scale-leave-active { + position: absolute; + width: 100%; + } +} diff --git a/src/components/button/MoreButton.vue b/src/components/button/MoreButton.vue index d192efb90c..0ffea76ce5 100644 --- a/src/components/button/MoreButton.vue +++ b/src/components/button/MoreButton.vue @@ -1,6 +1,11 @@ diff --git a/src/platform/assets/components/MediaAssetContextMenu.vue b/src/platform/assets/components/MediaAssetContextMenu.vue index 9325f7a172..0b54ce33b7 100644 --- a/src/platform/assets/components/MediaAssetContextMenu.vue +++ b/src/platform/assets/components/MediaAssetContextMenu.vue @@ -11,6 +11,7 @@ ) } }" + @hide="emit('hide')" > @@ -148,6 +165,7 @@ import { LiteGraph, RenderShape } from '@/lib/litegraph/src/litegraph' +import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' import { TitleMode } from '@/lib/litegraph/src/types/globalEnums' import { useSettingStore } from '@/platform/settings/settingStore' import { useTelemetry } from '@/platform/telemetry' @@ -168,7 +186,7 @@ import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeS import { app } from '@/scripts/app' import { useExecutionStore } from '@/stores/executionStore' import { useNodeOutputStore } from '@/stores/imagePreviewStore' -import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' +import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import { isTransparent } from '@/utils/colorUtil' import { getLocatorIdFromNodeData, @@ -177,6 +195,7 @@ import { import { cn } from '@/utils/tailwindUtil' import { useNodeResize } from '../interactions/resize/useNodeResize' +import { WidgetInputBaseClass } from '../widgets/components/layout' import LivePreview from './LivePreview.vue' import NodeContent from './NodeContent.vue' import NodeHeader from './NodeHeader.vue' @@ -228,19 +247,6 @@ const bypassed = computed( ) const muted = computed((): boolean => nodeData.mode === LGraphEventMode.NEVER) -const nodeBodyBackgroundColor = computed(() => { - const colorPaletteStore = useColorPaletteStore() - - if (!nodeData.bgcolor) { - return '' - } - - return applyLightThemeColor( - nodeData.bgcolor, - Boolean(colorPaletteStore.completedActivePalette.light_theme) - ) -}) - const nodeOpacity = computed(() => { const globalOpacity = useSettingStore().get('Comfy.Node.Opacity') ?? 1 @@ -474,6 +480,22 @@ const lgraphNode = computed(() => { return getNodeByLocatorId(app.rootGraph, locatorId) }) +const showAdvancedInputsButton = computed(() => { + const node = lgraphNode.value + if (!node || !(node instanceof SubgraphNode)) return false + + // Check if there are hidden inputs (widgets not promoted) + const interiorNodes = node.subgraph.nodes + const allInteriorWidgets = interiorNodes.flatMap((n) => n.widgets ?? []) + + return allInteriorWidgets.some((w) => !w.computedDisabled && !w.promoted) +}) + +function handleShowAdvancedInputs() { + const rightSidePanelStore = useRightSidePanelStore() + rightSidePanelStore.focusSection('advanced-inputs') +} + const nodeMedia = computed(() => { const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value] const node = lgraphNode.value diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index fe99c5d620..0f24cf873a 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -11,7 +11,10 @@ headerShapeClass ) " - :style="headerStyle" + :style="{ + backgroundColor: applyLightThemeColor(nodeData?.color), + opacity: useSettingStore().get('Comfy.Node.Opacity') ?? 1 + }" :data-testid="`node-header-${nodeData?.id || ''}`" @dblclick="handleDoubleClick" > @@ -104,7 +107,6 @@ import NodeBadge from '@/renderer/extensions/vueNodes/components/NodeBadge.vue' import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips' import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils' import { app } from '@/scripts/app' -import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { normalizeI18nKey } from '@/utils/formatUtil' import { getLocatorIdFromNodeData, @@ -156,23 +158,6 @@ const enterSubgraphTooltipConfig = computed(() => { return createTooltipConfig(st('enterSubgraph', 'Enter Subgraph')) }) -const headerStyle = computed(() => { - const colorPaletteStore = useColorPaletteStore() - - const opacity = useSettingStore().get('Comfy.Node.Opacity') ?? 1 - - if (!nodeData?.color) { - return { backgroundColor: '', opacity } - } - - const headerColor = applyLightThemeColor( - nodeData.color, - Boolean(colorPaletteStore.completedActivePalette.light_theme) - ) - - return { backgroundColor: headerColor, opacity } -}) - const resolveTitle = (info: VueNodeData | undefined) => { const title = (info?.title ?? '').trim() if (title.length > 0) return title diff --git a/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts b/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts index 143684d137..57a0b97177 100644 --- a/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts +++ b/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts @@ -1,14 +1,13 @@ +import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { adjustColor } from '@/utils/colorUtil' /** * Applies light theme color adjustments to a color */ -export function applyLightThemeColor( - color: string, - isLightTheme: boolean -): string { - if (!color || !isLightTheme) { - return color - } +export function applyLightThemeColor(color?: string): string { + if (!color) return '' + + if (!useColorPaletteStore().completedActivePalette.light_theme) return color + return adjustColor(color, { lightness: 0.5 }) } diff --git a/src/components/rightSidePanel/layout/SidePanelSearch.vue b/src/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue similarity index 51% rename from src/components/rightSidePanel/layout/SidePanelSearch.vue rename to src/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue index efbe22159f..da91367996 100644 --- a/src/components/rightSidePanel/layout/SidePanelSearch.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue @@ -1,23 +1,30 @@ diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue index 670c054a43..22d6db5ff5 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue @@ -1,7 +1,6 @@