Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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 @@ -47,6 +47,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
2 changes: 1 addition & 1 deletion 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
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
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
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