From a87bd0eb37994efc5ad45e95ff06f3bad1369138 Mon Sep 17 00:00:00 2001 From: Csongor Czezar <44126075+csongorczezar@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:58:38 -0800 Subject: [PATCH] feat: position properties panel opposite to sidebar (#7647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When sidebar is positioned on the right, the properties panel also appears on the right, causing both panels to compete for space and creating a poor layout. ## Solution Properties panel now dynamically positions itself opposite to the sidebar: - Sidebar left → Properties panel right (default) - Sidebar right → Properties panel left ## Changes - Modified `LiteGraphCanvasSplitterOverlay.vue` to conditionally render properties panel based on sidebar location - Updated splitter refresh key to recalculate layout when sidebar position changes - Added dynamic close button icon in `RightSidePanel.vue` that points in the correct direction ## Testing - Created E2E tests to verify positioning behavior - Manually verified visual behavior in browser ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7647-feat-position-properties-panel-opposite-to-sidebar-2ce6d73d365081049683e74c8d03dbdd) by [Unito](https://www.unito.io) --- .../propertiesPanelPosition.spec.ts | 86 +++++++++++++++ .../LiteGraphCanvasSplitterOverlay.vue | 103 +++++++++++------- .../rightSidePanel/RightSidePanel.vue | 15 ++- src/locales/en/main.json | 1 + 4 files changed, 162 insertions(+), 43 deletions(-) create mode 100644 browser_tests/tests/propertiesPanel/propertiesPanelPosition.spec.ts diff --git a/browser_tests/tests/propertiesPanel/propertiesPanelPosition.spec.ts b/browser_tests/tests/propertiesPanel/propertiesPanelPosition.spec.ts new file mode 100644 index 000000000..ee2c85eda --- /dev/null +++ b/browser_tests/tests/propertiesPanel/propertiesPanelPosition.spec.ts @@ -0,0 +1,86 @@ +import { expect } from '@playwright/test' + +import { comfyPageFixture as test } from '../../fixtures/ComfyPage' + +test.describe('Properties panel position', () => { + test.beforeEach(async ({ comfyPage }) => { + // Open a sidebar tab to ensure sidebar is visible + await comfyPage.menu.nodeLibraryTab.open() + await comfyPage.actionbar.propertiesButton.click() + }) + + test('positions on the right when sidebar is on the left', async ({ + comfyPage + }) => { + await comfyPage.setSetting('Comfy.Sidebar.Location', 'left') + await comfyPage.nextFrame() + + const propertiesPanel = comfyPage.page.getByTestId('properties-panel') + const sidebar = comfyPage.page.locator('.side-bar-panel').first() + + await expect(propertiesPanel).toBeVisible() + await expect(sidebar).toBeVisible() + + const propsBoundingBox = await propertiesPanel.boundingBox() + const sidebarBoundingBox = await sidebar.boundingBox() + + expect(propsBoundingBox).not.toBeNull() + expect(sidebarBoundingBox).not.toBeNull() + + // Properties panel should be to the right of the sidebar + expect(propsBoundingBox!.x).toBeGreaterThan( + sidebarBoundingBox!.x + sidebarBoundingBox!.width + ) + }) + + test('positions on the left when sidebar is on the right', async ({ + comfyPage + }) => { + await comfyPage.setSetting('Comfy.Sidebar.Location', 'right') + await comfyPage.nextFrame() + + const propertiesPanel = comfyPage.page.getByTestId('properties-panel') + const sidebar = comfyPage.page.locator('.side-bar-panel').first() + + await expect(propertiesPanel).toBeVisible() + await expect(sidebar).toBeVisible() + + const propsBoundingBox = await propertiesPanel.boundingBox() + const sidebarBoundingBox = await sidebar.boundingBox() + + expect(propsBoundingBox).not.toBeNull() + expect(sidebarBoundingBox).not.toBeNull() + + // Properties panel should be to the left of the sidebar + expect(propsBoundingBox!.x + propsBoundingBox!.width).toBeLessThan( + sidebarBoundingBox!.x + ) + }) + + test('close button icon updates based on sidebar location', async ({ + comfyPage + }) => { + const propertiesPanel = comfyPage.page.getByTestId('properties-panel') + + // When sidebar is on the left, panel is on the right + await comfyPage.setSetting('Comfy.Sidebar.Location', 'left') + await comfyPage.nextFrame() + + await expect(propertiesPanel).toBeVisible() + const closeButtonLeft = propertiesPanel + .locator('button[aria-pressed]') + .locator('i') + await expect(closeButtonLeft).toBeVisible() + await expect(closeButtonLeft).toHaveClass(/lucide--panel-right/) + + // When sidebar is on the right, panel is on the left + await comfyPage.setSetting('Comfy.Sidebar.Location', 'right') + await comfyPage.nextFrame() + + const closeButtonRight = propertiesPanel + .locator('button[aria-pressed]') + .locator('i') + await expect(closeButtonRight).toBeVisible() + await expect(closeButtonRight).toHaveClass(/lucide--panel-left/) + }) +}) diff --git a/src/components/LiteGraphCanvasSplitterOverlay.vue b/src/components/LiteGraphCanvasSplitterOverlay.vue index 4d56e623b..4a7695d18 100644 --- a/src/components/LiteGraphCanvasSplitterOverlay.vue +++ b/src/components/LiteGraphCanvasSplitterOverlay.vue @@ -22,29 +22,38 @@ state-storage="local" @resizestart="onResizestart" > + + + @@ -73,38 +82,33 @@ + + - - - - - @@ -117,6 +121,7 @@ import Splitter from 'primevue/splitter' import type { SplitterResizeStartEvent } from 'primevue/splitter' import SplitterPanel from 'primevue/splitterpanel' import { computed } from 'vue' +import { useI18n } from 'vue-i18n' import { useSettingStore } from '@/platform/settings/settingStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' @@ -128,6 +133,7 @@ const workspaceStore = useWorkspaceStore() const settingStore = useSettingStore() const rightSidePanelStore = useRightSidePanelStore() const sidebarTabStore = useSidebarTabStore() +const { t } = useI18n() const sidebarLocation = computed<'left' | 'right'>(() => settingStore.get('Comfy.Sidebar.Location') ) @@ -159,12 +165,25 @@ function onResizestart({ originalEvent: event }: SplitterResizeStartEvent) { } /* - * Force refresh the splitter when right panel visibility changes to recalculate the width + * Force refresh the splitter when right panel visibility or sidebar location changes + * to recalculate the width and panel order */ const splitterRefreshKey = computed(() => { - return rightSidePanelVisible.value - ? 'main-splitter-with-right-panel' - : 'main-splitter' + return `main-splitter${rightSidePanelVisible.value ? '-with-right-panel' : ''}-${sidebarLocation.value}` +}) + +const firstPanelStyle = computed(() => { + if (sidebarLocation.value === 'left') { + return { display: sidebarPanelVisible.value ? 'flex' : 'none' } + } + return undefined +}) + +const lastPanelStyle = computed(() => { + if (sidebarLocation.value === 'right') { + return { display: sidebarPanelVisible.value ? 'flex' : 'none' } + } + return undefined }) diff --git a/src/components/rightSidePanel/RightSidePanel.vue b/src/components/rightSidePanel/RightSidePanel.vue index 50dc9de33..4eb2ae31b 100644 --- a/src/components/rightSidePanel/RightSidePanel.vue +++ b/src/components/rightSidePanel/RightSidePanel.vue @@ -9,6 +9,7 @@ import TabList from '@/components/tab/TabList.vue' import Button from '@/components/ui/button/Button.vue' import { SubgraphNode } from '@/lib/litegraph/src/litegraph' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import { useSettingStore } from '@/platform/settings/settingStore' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import type { RightSidePanelTab } from '@/stores/workspace/rightSidePanelStore' @@ -22,11 +23,23 @@ import SubgraphEditor from './subgraph/SubgraphEditor.vue' const canvasStore = useCanvasStore() const rightSidePanelStore = useRightSidePanelStore() +const settingStore = useSettingStore() const { t } = useI18n() const { selectedItems } = storeToRefs(canvasStore) const { activeTab, isEditingSubgraph } = storeToRefs(rightSidePanelStore) +const sidebarLocation = computed<'left' | 'right'>(() => + settingStore.get('Comfy.Sidebar.Location') +) + +// Panel is on the left when sidebar is on the right, and vice versa +const panelIcon = computed(() => + sidebarLocation.value === 'right' + ? 'icon-[lucide--panel-left]' + : 'icon-[lucide--panel-right]' +) + const hasSelection = computed(() => selectedItems.value.length > 0) const selectedNodes = computed((): LGraphNode[] => { @@ -160,7 +173,7 @@ function handleTitleCancel() { :aria-label="t('rightSidePanel.togglePanel')" @click="closePanel" > - + diff --git a/src/locales/en/main.json b/src/locales/en/main.json index af70861a3..a1f15efe8 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -648,6 +648,7 @@ "box": "Box" }, "sideToolbar": { + "sidebar": "Sidebar", "themeToggle": "Toggle Theme", "helpCenter": "Help Center", "logout": "Logout",