Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c199d3d
wip v3 ui refactor
pythongosssss Sep 27, 2025
a1eaea2
Fix breadrumb collapsing
pythongosssss Sep 28, 2025
af050f1
Combine menu sections on small height
pythongosssss Sep 28, 2025
f46af7b
Remove terminal background
pythongosssss Oct 1, 2025
b9a44b1
Update styling of single tab tab-list to be a label
pythongosssss Oct 1, 2025
1ae877a
Update sidebar button to always be secondary
pythongosssss Oct 1, 2025
1218986
Refactor connected/floating resizing
pythongosssss Oct 2, 2025
3fb42de
Redesigned C menu
pythongosssss Oct 3, 2025
9be09c4
Make button hover indicator small in small mode
pythongosssss Oct 3, 2025
29bb027
add missing entry
pythongosssss Oct 3, 2025
daba827
Revert "Make button hover indicator small in small mode"
pythongosssss Oct 5, 2025
28d76c1
- change light menu to white
pythongosssss Oct 5, 2025
2877a59
Move FPS info drawing based on sidebar position
pythongosssss Oct 5, 2025
d32fe1d
fix tests
pythongosssss Oct 5, 2025
5f3d4b8
rework resize handling
pythongosssss Oct 8, 2025
719a47a
Make menu sticky so it is aligned with scrollable items
pythongosssss Oct 8, 2025
bc91aee
Add support for legacy custom actions
pythongosssss Oct 8, 2025
8720e46
[automated] Apply ESLint and Prettier fixes
actions-user Oct 8, 2025
7d82a78
hide login in focus mode
pythongosssss Oct 8, 2025
154577f
Merge branch 'pysssss/v3-floating-ui' of https://github.com/Comfy-Org…
pythongosssss Oct 8, 2025
c8b7582
- move top menu to own component
pythongosssss Oct 9, 2025
b8a6b2c
Relocate comfy legacy menu to inside graph container so z-index works
pythongosssss Oct 9, 2025
9f1d8e7
- fix removed provided element
pythongosssss Oct 9, 2025
2ebec8e
Merge remote-tracking branch 'origin/main' into pysssss/v3-floating-ui
pythongosssss Oct 9, 2025
78bde9d
[automated] Apply ESLint and Prettier fixes
actions-user Oct 9, 2025
1fffcbe
- add support for forced connected sidebar setting
pythongosssss Oct 11, 2025
d312459
Merge remote-tracking branch 'origin/main' into pysssss/v3-floating-ui
pythongosssss Oct 16, 2025
38023af
[automated] Update test expectations
invalid-email-address Oct 16, 2025
633ebf5
Fix test
pythongosssss Oct 16, 2025
ef11cee
Fix tests, library panel covers the node
pythongosssss Oct 16, 2025
50d79c3
Move click position, 10,10 is now tabs
pythongosssss Oct 16, 2025
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
4 changes: 4 additions & 0 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class ComfyMenu {
.nth(0)
}

get buttons() {
return this.sideToolbar.locator('.side-bar-button')
}

get nodeLibraryTab() {
this._nodeLibraryTab ??= new NodeLibrarySidebarTab(this.page)
return this._nodeLibraryTab
Expand Down
4 changes: 2 additions & 2 deletions browser_tests/fixtures/components/Topbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class Topbar {

constructor(public readonly page: Page) {
this.menuLocator = page.locator('.comfy-command-menu')
this.menuTrigger = page.locator('.comfyui-logo-wrapper')
this.menuTrigger = page.locator('.comfy-menu-button-wrapper')
}

async getTabNames(): Promise<string[]> {
Expand Down Expand Up @@ -105,7 +105,7 @@ export class Topbar {
* Close the topbar menu by clicking outside
*/
async closeTopbarMenu() {
await this.page.locator('body').click({ position: { x: 10, y: 10 } })
await this.page.locator('body').click({ position: { x: 300, y: 10 } })
await expect(this.menuLocator).not.toBeVisible()
}

Expand Down
5 changes: 3 additions & 2 deletions browser_tests/tests/actionbar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,10 @@ test.describe('Actionbar', () => {
test('Can dock actionbar into top menu', async ({ comfyPage }) => {
await comfyPage.page.dragAndDrop(
'.actionbar .drag-handle',
'.comfyui-menu',
'.actionbar-container',
{
targetPosition: { x: 0, y: 0 }
targetPosition: { x: 50, y: 20 },
force: true
}
)
expect(await comfyPage.actionbar.isDocked()).toBe(true)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions browser_tests/tests/groupNode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ test.describe('Group Node', () => {
}

const isRegisteredNodeDefStore = async (comfyPage: ComfyPage) => {
await comfyPage.menu.nodeLibraryTab.open()
const groupNodesFolderCt = await comfyPage.menu.nodeLibraryTab
.getFolder(GROUP_NODE_CATEGORY)
.count()
Expand All @@ -253,8 +254,6 @@ test.describe('Group Node', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.loadWorkflow(WORKFLOW_NAME)
await comfyPage.menu.nodeLibraryTab.open()

groupNode = await comfyPage.getFirstNodeRef()
if (!groupNode)
throw new Error(`Group node not found in workflow ${WORKFLOW_NAME}`)
Expand Down
14 changes: 10 additions & 4 deletions browser_tests/tests/interaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,10 +792,19 @@ test.describe('Viewport settings', () => {

await comfyPage.menu.topbar.saveWorkflow('Workflow A')
await comfyPage.nextFrame()
const screenshotA = (await comfyPage.canvas.screenshot()).toString('base64')

// Save workflow as a new file, then zoom out before screen shot
await comfyPage.menu.topbar.saveWorkflowAs('Workflow B')

await comfyPage.nextFrame()
const tabA = comfyPage.menu.topbar.getWorkflowTab('Workflow A')
await changeTab(tabA)

const screenshotA = (await comfyPage.canvas.screenshot()).toString('base64')

const tabB = comfyPage.menu.topbar.getWorkflowTab('Workflow B')
await changeTab(tabB)

await comfyMouse.move(comfyPage.emptySpace)
for (let i = 0; i < 4; i++) {
await comfyMouse.wheel(0, 60)
Expand All @@ -807,9 +816,6 @@ test.describe('Viewport settings', () => {
// Ensure that the screenshots are different due to zoom level
expect(screenshotB).not.toBe(screenshotA)

const tabA = comfyPage.menu.topbar.getWorkflowTab('Workflow A')
const tabB = comfyPage.menu.topbar.getWorkflowTab('Workflow B')

// Go back to Workflow A
await changeTab(tabA)
expect((await comfyPage.canvas.screenshot()).toString('base64')).toBe(
Expand Down
8 changes: 2 additions & 6 deletions browser_tests/tests/menu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ test.describe('Menu', () => {
})

test('Can register sidebar tab', async ({ comfyPage }) => {
const initialChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
(el) => el.children.length
)
const initialChildrenCount = await comfyPage.menu.buttons.count()

await comfyPage.page.evaluate(async () => {
window['app'].extensionManager.registerSidebarTab({
Expand All @@ -26,9 +24,7 @@ test.describe('Menu', () => {
})
await comfyPage.nextFrame()

const newChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
(el) => el.children.length
)
const newChildrenCount = await comfyPage.menu.buttons.count()
expect(newChildrenCount).toBe(initialChildrenCount + 1)
})

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/palettes/light.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"comfy_base": {
"fg-color": "#222",
"bg-color": "#DDD",
"comfy-menu-bg": "#F5F5F5",
"comfy-menu-bg": "#FFFFFF",
"comfy-menu-hover-bg": "#ccc",
"comfy-menu-secondary-bg": "#EEE",
"comfy-input-bg": "#C9C9C9",
Expand Down
129 changes: 89 additions & 40 deletions src/components/LiteGraphCanvasSplitterOverlay.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,88 @@
<template>
<Splitter
:key="sidebarStateKey"
class="splitter-overlay-root splitter-overlay"
:pt:gutter="sidebarPanelVisible ? '' : 'hidden'"
:state-key="sidebarStateKey"
state-storage="local"
>
<SplitterPanel
v-show="sidebarPanelVisible"
v-if="sidebarLocation === 'left'"
class="side-bar-panel"
:min-size="10"
:size="20"
<div class="splitter-overlay-root pointer-events-none flex flex-col">
<slot name="workflow-tabs" />

<div
class="pointer-events-none flex flex-1 overflow-hidden"
:class="{
'flex-row': sidebarLocation === 'left',
'flex-row-reverse': sidebarLocation === 'right'
}"
>
<slot name="side-bar-panel" />
</SplitterPanel>
<div class="side-toolbar-container pointer-events-auto">
<slot name="side-toolbar" />
</div>

<SplitterPanel :size="100">
<Splitter
class="splitter-overlay max-w-full"
layout="vertical"
:pt:gutter="bottomPanelVisible ? '' : 'hidden'"
state-key="bottom-panel-splitter"
key="main-splitter-stable"
class="splitter-overlay flex-1 overflow-hidden"
:pt:gutter="sidebarPanelVisible ? '' : 'hidden'"
:state-key="sidebarStateKey || 'main-splitter'"
state-storage="local"
>
<SplitterPanel class="graph-canvas-panel relative">
<slot name="graph-canvas-panel" />
<SplitterPanel
v-if="sidebarLocation === 'left'"
class="side-bar-panel pointer-events-auto"
:min-size="10"
:size="20"
:style="{
display:
sidebarPanelVisible && sidebarLocation === 'left'
? 'flex'
: 'none'
}"
>
<slot
v-if="sidebarPanelVisible && sidebarLocation === 'left'"
name="side-bar-panel"
/>
</SplitterPanel>
<SplitterPanel v-show="bottomPanelVisible" class="bottom-panel">
<slot name="bottom-panel" />

<SplitterPanel :size="80" class="flex flex-col">
<slot name="topmenu" :sidebar-panel-visible="sidebarPanelVisible" />

<Splitter
class="splitter-overlay splitter-overlay-bottom mr-2 mb-2 ml-2 flex-1"
layout="vertical"
:pt:gutter="
'rounded-tl-lg rounded-tr-lg ' +
(bottomPanelVisible ? '' : 'hidden')
"
state-key="bottom-panel-splitter"
state-storage="local"
>
<SplitterPanel class="graph-canvas-panel relative">
<slot name="graph-canvas-panel" />
</SplitterPanel>
<SplitterPanel
v-show="bottomPanelVisible"
class="bottom-panel pointer-events-auto rounded-lg"
>
<slot name="bottom-panel" />
</SplitterPanel>
</Splitter>
</SplitterPanel>

<SplitterPanel
v-if="sidebarLocation === 'right'"
class="side-bar-panel pointer-events-auto"
:min-size="10"
:size="20"
:style="{
display:
sidebarPanelVisible && sidebarLocation === 'right'
? 'flex'
: 'none'
}"
>
<slot
v-if="sidebarPanelVisible && sidebarLocation === 'right'"
name="side-bar-panel"
/>
</SplitterPanel>
</Splitter>
</SplitterPanel>

<SplitterPanel
v-show="sidebarPanelVisible"
v-if="sidebarLocation === 'right'"
class="side-bar-panel"
:min-size="10"
:size="20"
>
<slot name="side-bar-panel" />
</SplitterPanel>
</Splitter>
</div>
</div>
</template>

<script setup lang="ts">
Expand Down Expand Up @@ -74,7 +114,11 @@ const activeSidebarTabId = computed(
)

const sidebarStateKey = computed(() => {
return unifiedWidth.value ? 'unified-sidebar' : activeSidebarTabId.value ?? ''
if (unifiedWidth.value) {
return 'unified-sidebar'
}
// When no tab is active, use a default key to maintain state
return activeSidebarTabId.value ?? 'default-sidebar'
})
</script>

Expand All @@ -93,12 +137,17 @@ const sidebarStateKey = computed(() => {

.side-bar-panel {
background-color: var(--bg-color);
pointer-events: auto;
}

.bottom-panel {
background-color: var(--bg-color);
pointer-events: auto;
background-color: var(--comfy-menu-bg);
border: 1px solid var(--p-panel-border-color);
max-width: 100%;
overflow-x: auto;
}

.splitter-overlay-bottom :deep(.p-splitter-gutter) {
transform: translateY(5px);
}

.splitter-overlay {
Expand Down
17 changes: 3 additions & 14 deletions src/components/MenuHamburger.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<template>
<div
v-show="workspaceState.focusMode"
class="comfy-menu-hamburger no-drag"
:style="positionCSS"
class="comfy-menu-hamburger no-drag top-0 right-0"
>
<Button
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
Expand All @@ -15,14 +14,13 @@
@click="exitFocusMode"
@contextmenu="showNativeSystemMenu"
/>
<div v-show="menuSetting !== 'Bottom'" class="window-actions-spacer" />
<div class="window-actions-spacer" />
</div>
</template>

<script setup lang="ts">
import Button from 'primevue/button'
import type { CSSProperties } from 'vue'
import { computed, watchEffect } from 'vue'
import { watchEffect } from 'vue'

import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
Expand All @@ -45,15 +43,6 @@ watchEffect(() => {
app.ui.menuContainer.style.display = 'block'
}
})

const menuSetting = computed(() => settingStore.get('Comfy.UseNewMenu'))
const positionCSS = computed<CSSProperties>(() =>
// 'Bottom' menuSetting shows the hamburger button in the bottom right corner
// 'Disabled', 'Top' menuSetting shows the hamburger button in the top right corner
menuSetting.value === 'Bottom'
? { bottom: '0px', right: '0px' }
: { top: '0px', right: '0px' }
)
</script>

<style scoped>
Expand Down
54 changes: 54 additions & 0 deletions src/components/TopMenuSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<template>
<div
v-if="!workspaceStore.focusMode"
class="pointer-events-none ml-2 flex pt-2"
>
<div class="pointer-events-auto min-w-0 flex-1">
<SubgraphBreadcrumb />
</div>

<div
class="actionbar-container pointer-events-auto mx-2 flex h-12 items-center rounded-lg px-2 shadow-md"
>
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div
ref="legacyCommandsContainerRef"
class="[&:not(:has(*>*:not(:empty)))]:hidden"
></div>
<ComfyActionbar />
<LoginButton v-if="!isLoggedIn" />
<CurrentUserButton v-else class="shrink-0" />
</div>
</div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'

import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { app } from '@/scripts/app'
import { useWorkspaceStore } from '@/stores/workspaceStore'

const workspaceStore = useWorkspaceStore()
const { isLoggedIn } = useCurrentUser()

// Maintain support for legacy topbar elements attached by custom scripts
const legacyCommandsContainerRef = ref<HTMLElement>()
onMounted(() => {
if (legacyCommandsContainerRef.value) {
app.menu.element.style.width = 'fit-content'
legacyCommandsContainerRef.value.appendChild(app.menu.element)
}
})
</script>

<style scoped>
.actionbar-container {
background-color: var(--comfy-menu-bg);
border: 1px solid var(--p-panel-border-color);
}
</style>
Loading
Loading