Compare commits

..

1 Commits

Author SHA1 Message Date
Alexis Rolland
80ff6d8469 Fix side bar 2026-06-13 21:08:00 +08:00
3 changed files with 145 additions and 107 deletions

View File

@@ -2,7 +2,7 @@
<div
class="pointer-events-none absolute top-0 left-0 z-999 flex size-full flex-col"
>
<slot name="workflow-tabs" />
<slot v-if="!toolbarFullHeight" name="workflow-tabs" />
<div
class="pointer-events-none flex flex-1 overflow-hidden"
@@ -15,108 +15,114 @@
<slot name="side-toolbar" />
</div>
<Splitter
:key="splitterRefreshKey"
class="pointer-events-none flex-1 overflow-hidden border-none bg-transparent"
:state-key="
isSelectMode
? sidebarLocation === 'left'
? 'builder-splitter'
: 'builder-splitter-right'
: sidebarStateKey
"
state-storage="local"
@resizestart="onResizestart"
@resizeend="normalizeSavedSizes"
>
<!-- First panel: sidebar when left, properties when right -->
<SplitterPanel
v-if="firstPanelVisible"
:class="
sidebarLocation === 'left'
? cn(
'side-bar-panel pointer-events-auto bg-comfy-menu-bg',
sidebarPanelVisible && 'min-w-78'
)
: 'pointer-events-auto bg-comfy-menu-bg'
"
:min-size="
sidebarLocation === 'left' ? SIDEBAR_MIN_SIZE : BUILDER_MIN_SIZE
"
:size="SIDE_PANEL_SIZE"
:style="firstPanelStyle"
:role="sidebarLocation === 'left' ? 'complementary' : undefined"
:aria-label="
sidebarLocation === 'left' ? t('sideToolbar.sidebar') : undefined
<div class="pointer-events-none flex flex-1 flex-col overflow-hidden">
<slot v-if="toolbarFullHeight" name="workflow-tabs" />
<Splitter
:key="splitterRefreshKey"
class="pointer-events-none flex-1 overflow-hidden border-none bg-transparent"
:state-key="
isSelectMode
? sidebarLocation === 'left'
? 'builder-splitter'
: 'builder-splitter-right'
: sidebarStateKey
"
state-storage="local"
@resizestart="onResizestart"
@resizeend="normalizeSavedSizes"
>
<slot
v-if="sidebarLocation === 'left' && sidebarPanelVisible"
name="side-bar-panel"
/>
<slot
v-else-if="sidebarLocation === 'right'"
name="right-side-panel"
/>
</SplitterPanel>
<!-- Main panel (always present) -->
<SplitterPanel :size="centerPanelDefaultSize" class="flex flex-col">
<slot name="topmenu" :sidebar-panel-visible />
<Splitter
class="splitter-overlay-bottom pointer-events-none mx-1 mb-1 flex-1 border-none bg-transparent"
layout="vertical"
:pt:gutter="
cn(
'rounded-t-lg',
!(bottomPanelVisible && !focusMode) && 'hidden'
)
<!-- First panel: sidebar when left, properties when right -->
<SplitterPanel
v-if="firstPanelVisible"
:class="
sidebarLocation === 'left'
? cn(
'side-bar-panel pointer-events-auto bg-comfy-menu-bg',
sidebarPanelVisible && 'min-w-78'
)
: 'pointer-events-auto bg-comfy-menu-bg'
"
:min-size="
sidebarLocation === 'left' ? SIDEBAR_MIN_SIZE : BUILDER_MIN_SIZE
"
:size="SIDE_PANEL_SIZE"
:style="firstPanelStyle"
:role="sidebarLocation === 'left' ? 'complementary' : undefined"
:aria-label="
sidebarLocation === 'left' ? t('sideToolbar.sidebar') : undefined
"
state-key="bottom-panel-splitter"
state-storage="local"
@resizestart="onResizestart"
>
<SplitterPanel class="graph-canvas-panel relative overflow-visible">
<slot name="graph-canvas-panel" />
</SplitterPanel>
<SplitterPanel
v-show="bottomPanelVisible && !focusMode"
class="bottom-panel pointer-events-auto max-w-full overflow-x-auto rounded-lg border border-(--p-panel-border-color) bg-comfy-menu-bg"
>
<slot name="bottom-panel" />
</SplitterPanel>
</Splitter>
</SplitterPanel>
<slot
v-if="sidebarLocation === 'left' && sidebarPanelVisible"
name="side-bar-panel"
/>
<slot
v-else-if="sidebarLocation === 'right'"
name="right-side-panel"
/>
</SplitterPanel>
<!-- Last panel: properties when left, sidebar when right -->
<SplitterPanel
v-if="lastPanelVisible"
:class="
sidebarLocation === 'right'
? cn(
'side-bar-panel pointer-events-auto bg-comfy-menu-bg',
sidebarPanelVisible && 'min-w-78'
<!-- Main panel (always present) -->
<SplitterPanel :size="centerPanelDefaultSize" class="flex flex-col">
<slot name="topmenu" :sidebar-panel-visible />
<Splitter
class="splitter-overlay-bottom pointer-events-none mx-1 mb-1 flex-1 border-none bg-transparent"
layout="vertical"
:pt:gutter="
cn(
'rounded-t-lg',
!(bottomPanelVisible && !focusMode) && 'hidden'
)
: 'pointer-events-auto bg-comfy-menu-bg'
"
:min-size="
sidebarLocation === 'right' ? SIDEBAR_MIN_SIZE : BUILDER_MIN_SIZE
"
:size="SIDE_PANEL_SIZE"
:style="lastPanelStyle"
:role="sidebarLocation === 'right' ? 'complementary' : undefined"
:aria-label="
sidebarLocation === 'right' ? t('sideToolbar.sidebar') : undefined
"
>
<slot v-if="sidebarLocation === 'left'" name="right-side-panel" />
<slot
v-else-if="sidebarLocation === 'right' && sidebarPanelVisible"
name="side-bar-panel"
/>
</SplitterPanel>
</Splitter>
"
state-key="bottom-panel-splitter"
state-storage="local"
@resizestart="onResizestart"
>
<SplitterPanel
class="graph-canvas-panel relative overflow-visible"
>
<slot name="graph-canvas-panel" />
</SplitterPanel>
<SplitterPanel
v-show="bottomPanelVisible && !focusMode"
class="bottom-panel pointer-events-auto max-w-full overflow-x-auto rounded-lg border border-(--p-panel-border-color) bg-comfy-menu-bg"
>
<slot name="bottom-panel" />
</SplitterPanel>
</Splitter>
</SplitterPanel>
<!-- Last panel: properties when left, sidebar when right -->
<SplitterPanel
v-if="lastPanelVisible"
:class="
sidebarLocation === 'right'
? cn(
'side-bar-panel pointer-events-auto bg-comfy-menu-bg',
sidebarPanelVisible && 'min-w-78'
)
: 'pointer-events-auto bg-comfy-menu-bg'
"
:min-size="
sidebarLocation === 'right' ? SIDEBAR_MIN_SIZE : BUILDER_MIN_SIZE
"
:size="SIDE_PANEL_SIZE"
:style="lastPanelStyle"
:role="sidebarLocation === 'right' ? 'complementary' : undefined"
:aria-label="
sidebarLocation === 'right' ? t('sideToolbar.sidebar') : undefined
"
>
<slot v-if="sidebarLocation === 'left'" name="right-side-panel" />
<slot
v-else-if="sidebarLocation === 'right' && sidebarPanelVisible"
name="side-bar-panel"
/>
</SplitterPanel>
</Splitter>
</div>
</div>
</div>
</template>
@@ -131,6 +137,7 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppMode } from '@/composables/useAppMode'
import { useSideToolbarState } from '@/composables/useSideToolbarState'
import {
BUILDER_MIN_SIZE,
CENTER_PANEL_SIZE,
@@ -152,6 +159,13 @@ const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const { isConnected } = useSideToolbarState()
// Full-height (beside the tabs) only when docked left and connected; otherwise below the tabs
const toolbarFullHeight = computed(
() => sidebarLocation.value === 'left' && isConnected.value
)
const unifiedWidth = computed(() =>
settingStore.get('Comfy.Sidebar.UnifiedWidth')
)
@@ -301,6 +315,12 @@ const lastPanelStyle = computed(() => {
display: none;
}
/* Hide the sidebar resize gutter when the sidebar is on the right (a gutter only
* precedes .side-bar-panel when it is the last panel) */
:deep(.p-splitter-gutter:has(+ .side-bar-panel)) {
display: none;
}
.splitter-overlay-bottom :deep(.p-splitter-gutter) {
transform: translateY(5px);
}

View File

@@ -2,13 +2,16 @@
<nav
ref="sideToolbarRef"
data-testid="side-toolbar"
class="side-tool-bar-container flex h-full flex-col items-center bg-transparent [.floating-sidebar]:-mr-2"
class="side-tool-bar-container flex h-full flex-col items-center bg-transparent"
:class="{
'small-sidebar': isSmall,
'connected-sidebar pointer-events-auto': isConnected,
'floating-sidebar': !isConnected,
'overflowing-sidebar': isOverflowing,
'border-r border-(--interface-stroke) shadow-interface': isConnected
'border-(--interface-stroke) shadow-interface': isConnected,
'border-r': isConnected && sidebarLocation === 'left',
'border-l': isConnected && sidebarLocation === 'right',
'-mr-2': !isConnected && sidebarLocation === 'left'
}"
>
<div
@@ -73,6 +76,7 @@ import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
import { useSideToolbarState } from '@/composables/useSideToolbarState'
import { isCloud, isDesktop, isNightly } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
@@ -112,13 +116,7 @@ const isSmall = computed(
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style'))
const isConnected = computed(
() =>
selectedTab.value ||
isOverflowing.value ||
sidebarStyle.value === 'connected'
)
const { isConnected, isOverflowing } = useSideToolbarState()
const tabs = computed(() => workspaceStore.getSidebarTabs())
const selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
@@ -170,7 +168,6 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
return shortcut ? t('g.shortcutSuffix', { shortcut }) : ''
}
const isOverflowing = ref(false)
const groupClasses = computed(() =>
cn(
'sidebar-item-group flex shrink-0 flex-col items-center overflow-hidden',

View File

@@ -0,0 +1,21 @@
import { createSharedComposable } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
// Shared so the layout can react to the toolbar's connected/floating state.
// `isOverflowing` is written by SideToolbar after it measures its content.
export const useSideToolbarState = createSharedComposable(() => {
const settingStore = useSettingStore()
const { activeSidebarTab } = storeToRefs(useSidebarTabStore())
const isOverflowing = ref(false)
const isConnected = computed(
() =>
activeSidebarTab.value !== null ||
isOverflowing.value ||
settingStore.get('Comfy.Sidebar.Style') === 'connected'
)
return { isConnected, isOverflowing }
})