Render app builder in arrange mode (#9260)

## Summary

Adds app builder in arrange/preview mode with re-orderable widgets,
maintaining size (as much as possible) between the select + preview
steps

## Changes

- **What**: 
- Extract sidebar size constants for sharing between canvas splitter +
app mode
- Add widget list using DraggableList and render inert WidgetItems

## Screenshots (if applicable)

<img width="1391" height="923" alt="image"
src="https://github.com/user-attachments/assets/3e17eafe-db1e-40a3-83b5-15a7d0672909"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9260-Render-app-builder-in-arrange-mode-3136d73d365081ef875acab683d01d9e)
by [Unito](https://www.unito.io)
This commit is contained in:
pythongosssss
2026-02-27 10:32:44 +00:00
committed by GitHub
parent 7901e14318
commit c090d189f0
6 changed files with 183 additions and 28 deletions

View File

@@ -6,6 +6,7 @@ import { storeToRefs } from 'pinia'
import { computed, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import AppBuilder from '@/components/builder/AppBuilder.vue'
import AppModeToolbar from '@/components/appMode/AppModeToolbar.vue'
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
import ModeToggle from '@/components/sidebar/ModeToggle.vue'
@@ -20,13 +21,20 @@ import LinearProgressBar from '@/renderer/extensions/linearMode/LinearProgressBa
import MobileMenu from '@/renderer/extensions/linearMode/MobileMenu.vue'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { useAppMode } from '@/composables/useAppMode'
import {
BUILDER_MIN_SIZE,
CENTER_PANEL_SIZE,
SIDEBAR_MIN_SIZE,
SIDE_PANEL_SIZE
} from '@/constants/splitterConstants'
import { useAppModeStore } from '@/stores/appModeStore'
const { t } = useI18n()
const settingStore = useSettingStore()
const workspaceStore = useWorkspaceStore()
const { isBuilderMode } = useAppMode()
const { hasOutputs } = storeToRefs(useAppModeStore())
const { isBuilderMode, isArrangeMode } = useAppMode()
const appModeStore = useAppModeStore()
const { hasOutputs } = storeToRefs(appModeStore)
const mobileDisplay = useBreakpoints(breakpointsTailwind).smaller('md')
@@ -34,17 +42,34 @@ const activeTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
const sidebarOnLeft = computed(
() => settingStore.get('Comfy.Sidebar.Location') === 'left'
)
// Builder panel is always on the opposite side of the sidebar.
// In arrange mode we render 3 panels to match the overlay structure,
// so the same stateKey percentage maps to the same pixel width.
const showLeftBuilder = computed(
() => !sidebarOnLeft.value && isArrangeMode.value
)
const showRightBuilder = computed(
() => sidebarOnLeft.value && isArrangeMode.value
)
const hasLeftPanel = computed(
() =>
isArrangeMode.value ||
(sidebarOnLeft.value && activeTab.value) ||
(!sidebarOnLeft.value && !isBuilderMode.value && hasOutputs.value)
)
const hasRightPanel = computed(
() =>
isArrangeMode.value ||
(sidebarOnLeft.value && !isBuilderMode.value && hasOutputs.value) ||
(!sidebarOnLeft.value && activeTab.value)
)
function sidePanelMinSize(isBuilder: boolean, isHidden: boolean) {
if (isBuilder) return BUILDER_MIN_SIZE
if (isHidden) return undefined
return SIDEBAR_MIN_SIZE
}
const bottomLeftRef = useTemplateRef('bottomLeftRef')
const bottomRightRef = useTemplateRef('bottomRightRef')
const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
@@ -52,7 +77,7 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
<template>
<div class="absolute w-full h-full">
<div
class="workflow-tabs-container pointer-events-auto h-(--workflow-tabs-height) w-full"
class="workflow-tabs-container pointer-events-auto h-(--workflow-tabs-height) w-full border-b border-interface-stroke shadow-interface"
>
<div class="flex h-full items-center">
<WorkflowTabs />
@@ -82,31 +107,47 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
</div>
<Splitter
v-else
class="h-[calc(100%-38px)] w-full bg-comfy-menu-secondary-bg"
:pt="{ gutter: { class: 'bg-transparent w-4 -mx-1' } }"
:key="isArrangeMode ? 'arrange' : 'normal'"
class="h-[calc(100%-var(--workflow-tabs-height))] w-full border-none bg-comfy-menu-secondary-bg"
:state-key="isArrangeMode ? 'builder-splitter' : undefined"
:state-storage="isArrangeMode ? 'local' : undefined"
@resizestart="({ originalEvent }) => originalEvent.preventDefault()"
>
<SplitterPanel
v-if="hasLeftPanel"
id="linearLeftPanel"
:size="1"
class="min-w-min outline-none"
:size="isArrangeMode ? SIDE_PANEL_SIZE : 1"
:min-size="
sidePanelMinSize(showLeftBuilder, showRightBuilder && !activeTab)
"
:style="
showRightBuilder && !activeTab ? { display: 'none' } : undefined
"
:class="
cn(
'outline-none arrange-panel',
showLeftBuilder ? 'bg-comfy-menu-bg min-w-78' : 'min-w-min'
)
"
>
<div v-if="showLeftBuilder" class="h-full overflow-y-auto">
<AppBuilder />
</div>
<div
v-if="sidebarOnLeft && activeTab"
v-else-if="sidebarOnLeft && activeTab"
class="flex h-full border-border-subtle border-r"
>
<ExtensionSlot :extension="activeTab" />
</div>
<LinearControls
v-else
v-else-if="!isArrangeMode"
ref="linearWorkflowRef"
:toast-to="unrefElement(bottomLeftRef) ?? undefined"
/>
</SplitterPanel>
<SplitterPanel
id="linearCenterPanel"
:size="98"
:size="isArrangeMode ? CENTER_PANEL_SIZE : 98"
class="flex flex-col min-w-0 gap-4 px-10 pt-8 pb-4 relative text-muted-foreground outline-none"
>
<LinearProgressBar
@@ -139,11 +180,23 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
<SplitterPanel
v-if="hasRightPanel"
id="linearRightPanel"
:size="1"
class="min-w-min outline-none"
:size="isArrangeMode ? SIDE_PANEL_SIZE : 1"
:min-size="
sidePanelMinSize(showRightBuilder, showLeftBuilder && !activeTab)
"
:style="showLeftBuilder && !activeTab ? { display: 'none' } : undefined"
:class="
cn(
'outline-none arrange-panel',
showRightBuilder ? 'bg-comfy-menu-bg min-w-78' : 'min-w-min'
)
"
>
<div v-if="showRightBuilder" class="h-full overflow-y-auto">
<AppBuilder />
</div>
<LinearControls
v-if="sidebarOnLeft"
v-else-if="sidebarOnLeft && !isArrangeMode"
ref="linearWorkflowRef"
:toast-to="unrefElement(bottomRightRef) ?? undefined"
/>
@@ -157,3 +210,21 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
</Splitter>
</div>
</template>
<style scoped>
:deep(.p-splitter-gutter) {
pointer-events: auto;
}
:deep(.p-splitter-gutter:hover),
:deep(.p-splitter-gutter[data-p-gutter-resizing='true']) {
transition: background-color 0.2s ease 300ms;
background-color: var(--p-primary-color);
}
/* Hide gutter next to hidden arrange panels */
:deep(.arrange-panel[style*='display: none'] + .p-splitter-gutter),
:deep(.p-splitter-gutter + .arrange-panel[style*='display: none']) {
display: none;
}
</style>