mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 22:25:05 +00:00
*PR Created by the Glary-Bot Agent* --- ## Summary Consolidated backport of bug fixes from `origin/main` (which feeds `v1.44.16`) onto `cloud/1.43`. Cherry-picks every applicable `fix:` commit identified in the prior backport-gap audit so `cloud/1.43` doesn't keep falling further behind. - **35 fixes cherry-picked** (out of 49 audited; 14 dropped — see "Dropped commits" below). - Verification (run on this branch): `pnpm typecheck` clean, `pnpm knip` clean, `pnpm lint` 0 errors (warning count 2976 vs cloud/1.43 baseline 2877 — delta is from new test files brought in by the picks, not new lint errors), `pnpm test:unit` 7842 passed / 8 skipped. This is a single consolidated PR per request — not a per-fix backport stream. Each commit has the original SHA recorded via `cherry picked from commit ...` so individual reverts remain possible. ## Picked fixes (35) App / runtime stability - `#10995` auto fit-to-view on first subgraph entry - `#11240` re-sync collapsed node slot positions after subgraph fitView - `#10849` store promoted widget values per SubgraphNode instance - `#10361` place cloned node above original in Vue renderer - `#9935` trigger Vue reactivity on output slot type changes in matchType - `#11541` stop duplicate node creation when dropping image on Vue nodes - `#11779` ensure escape key/graph navigation cancels ghost node placement - `#11063` disable pointer events on non-visible DOM widget overlays - `#11295` include focusMode in splitter refresh key to prevent panel resize - `#11487` render edit pencil icon correctly in properties panel header - `#11296` remove hover dimming overlay on image nodes - `#11542` keep finished badge fully opaque in ProgressToastItem - `#11713` search bar layout and autocomplete clipping on Desktop at small sizes - `#11570` dedupe keybinding modifier display - `#11144` fix(vite): hide git rev-parse window on Windows Cloud / subscription / workspace - `#11130` track workspace subscription success on immediate subscribe - `#11622` dedupe pending checkout attempt construction - `#11463` show credits in legacy user popover on non-cloud distributions - `#11636` enable Chrome password autofill on signup form - `#11520` fix(manager): migrate 4 endpoints GET→POST for CSRF hardening (security) - `#11425` remove deleted workflow from search results in sidebar Assets / blueprints - `#11700` route context menu Download through downloadMultipleAssets - `#11502` render asset fixtures in AssetBrowserModal stories - `#11759` hide blueprint node id in search - `#11573` translate blueprint label Telemetry / progress / feature flags - `#11174` guard progress_text before canvas init - `#11384` route progress_text feature flag check through getDevOverride - `#11417` reset file input value after selection to allow same-file reupload Load3D - `#11265` fix(load3d): restore missed hover state when viewer init is async - `#11546` load3d used wrong i18n key, add test - `#11359` chain Load3D node lifecycle callbacks to preserve widget cleanup GLSL preview - `#11010` resolve incorrect GLSL live preview for non-primitive widget types - `#11517` add GLSL live update when custom size is changed Misc - `#10374` consolidate `--color-coral-red` variables into `--color-coral` - `#11283` reduce noise in coverage Slack notifications ## Dropped commits (14) — need separate manual backport These cherry-picks pulled in code that depends on infrastructure not present on `cloud/1.43`, or required permissions a bot account does not hold. Individual backport tickets recommended: | PR | Subject | Blocker | |---|---|---| | #11224 | fix(ci): resolve pnpm version in release workflow for frontend/ checkout path | touches `.github/workflows/release-biweekly-comfyui.yaml` — bot lacks `workflows` permission. Also depends on prerequisite #11223 (patch-release support) not on cloud/1.43. Needs human-authored backport. | | #11329 | prevent duplicate prepareForSave + conflicting is_new telemetry on self-overwrite Save As | depends on `ChangeTracker.prepareForSave` API added by #10816 / #11328 (refactors not on cloud/1.43) | | #11358 | render dates in Secrets panel for timestamps with >3 fractional-second digits | depends on `formatToISOWithSeconds` and other utils in `dateTimeUtil.ts` not on cloud/1.43 | | #11539 | cancel-subscription dialog renders Invalid Date for ISO fractional seconds | depends on `parseIsoDateSafe` from #11358 | | #11480 | avoid escaped secret date labels | builds on #11358's SecretListItem changes | | #11524 | localize secret date labels | builds on #11358's SecretListItem changes | | #11321 | show asset display names in bulk delete confirmation | brings in `useMediaAssetActions.test.ts` requiring `downloadAssets` API not on cloud/1.43 | | #11610 | naming strategy for multi-job asset exports | same `useMediaAssetActions` test infrastructure | | #11737 | report total file count, not job count, in ZIP export toast | same `useMediaAssetActions` test infrastructure | | #11538 | resolve mesh widget thumbnails via asset preview API | depends on `FormDropdownMenuItemProps` type, `useWidgetSelectItems` not on cloud/1.43 | | #11492 | use getAssetFilename in asset browser to avoid showing hashes | same `useWidgetSelectItems` infrastructure | | #11836 | fix(load3d): dispose THREE.Points GPU resources in clearModel() | brings in `SceneModelManager.test.ts` referencing `ModelAdapter` infrastructure not on cloud/1.43 (memory-leak fix — manual backport recommended) | | #11807 | fix(load3d): suppress error toast on 404 when loading output model file | brings in `LoaderManager.ts` rewritten on main with `ModelAdapter` not on cloud/1.43 | | #11164 | enable playwright/no-force-option lint rule | requires oxlint upgrade with playwright plugin not pinned in cloud/1.43 | ## Conflict resolution notes Most picks applied cleanly. Conflicts encountered: - **Test-only / snapshot-only conflicts** — resolved with `-X theirs` (cloud/1.43 just doesn't have those test files yet). - **Modify/delete conflicts** (test file deleted on cloud/1.43, modified by the fix on main) — accepted the incoming version. - **`src/components/searchbox/v2/NodeSearchListItem.test.ts`** — pulled in by #11759 but referenced `ComfyNodeDefImpl` shape only on main. Removed the test file from the backport commit so the runtime fix lands; test should be rewritten/backported separately if desired. - **`browser_tests/tests/vueNodes/glslPreview.spec.ts` and `browser_tests/tests/subgraph/subgraphBreadcrumb.spec.ts`** — pulled in by #11517 / #11573 but reference helpers (`@e2e/fixtures/helpers/ExecutionHelper`, `SubgraphBreadcrumbHelper`) not on cloud/1.43. Removed the spec files from the respective backport commits; runtime fixes preserved. ## Out-of-scope review note Code review flagged a behavioral concern in `subgraphNavigationStore.ts:142-153,173-177` (`restoreViewport()` may auto-fit on first root-graph load even when the workflow already had a viewport). This is **pre-existing on `main`** (the cherry-picked code is identical to `origin/main` for that file region) — not introduced by this PR. Filing a separate ticket is recommended; not in scope for a backport batch. ## How to validate locally ```bash git checkout glary/cloud-1.43-batch-backport-fixes pnpm install --frozen-lockfile pnpm typecheck # clean pnpm knip # clean pnpm lint # 0 errors pnpm test:unit # 7842 passed, 8 skipped ``` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11926-backport-cloud-1-43-consolidated-bug-fixes-from-main-v1-44-16-3566d73d3650812393d3e0ea79ab9a14) by [Unito](https://www.unito.io) --------- Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Terry Jia <terryjia88@gmail.com> Co-authored-by: Dante <bunggl@naver.com> Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> Co-authored-by: dante <dante@danteui-MacStudio.local> Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: guill <jacob.e.segal@gmail.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Co-authored-by: Simon Pinfold <synap5e@users.noreply.github.com> Co-authored-by: comfydesigner <alextov@comfy.org> Co-authored-by: Alex <alex@Mac.lan> Co-authored-by: Robin Huang <robin.j.huang@gmail.com> Co-authored-by: Kelly Yang <124ykl@gmail.com> Co-authored-by: jaeone94 <89377375+jaeone94@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
308 lines
10 KiB
Vue
308 lines
10 KiB
Vue
<template>
|
|
<div
|
|
class="pointer-events-none absolute top-0 left-0 z-999 flex size-full 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'
|
|
}"
|
|
>
|
|
<div class="side-toolbar-container">
|
|
<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
|
|
"
|
|
>
|
|
<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'
|
|
)
|
|
"
|
|
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>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { cn } from '@comfyorg/tailwind-utils'
|
|
import { storeToRefs } from 'pinia'
|
|
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 { useAppMode } from '@/composables/useAppMode'
|
|
import {
|
|
BUILDER_MIN_SIZE,
|
|
CENTER_PANEL_SIZE,
|
|
SIDEBAR_MIN_SIZE,
|
|
SIDE_PANEL_SIZE
|
|
} from '@/constants/splitterConstants'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
|
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
|
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
|
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
|
|
|
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')
|
|
)
|
|
|
|
const unifiedWidth = computed(() =>
|
|
settingStore.get('Comfy.Sidebar.UnifiedWidth')
|
|
)
|
|
|
|
const { focusMode } = storeToRefs(workspaceStore)
|
|
|
|
const { isSelectMode, isBuilderMode } = useAppMode()
|
|
const { activeSidebarTabId, activeSidebarTab } = storeToRefs(sidebarTabStore)
|
|
const { bottomPanelVisible } = storeToRefs(useBottomPanelStore())
|
|
const { isOpen: rightSidePanelVisible } = storeToRefs(rightSidePanelStore)
|
|
const showOffsideSplitter = computed(
|
|
() => rightSidePanelVisible.value || isSelectMode.value
|
|
)
|
|
|
|
const sidebarPanelVisible = computed(
|
|
() => activeSidebarTab.value !== null && !isBuilderMode.value
|
|
)
|
|
|
|
const firstPanelVisible = computed(
|
|
() => sidebarLocation.value === 'left' || showOffsideSplitter.value
|
|
)
|
|
const lastPanelVisible = computed(
|
|
() => sidebarLocation.value === 'right' || showOffsideSplitter.value
|
|
)
|
|
|
|
/**
|
|
* When both side panels are visible, reduce center panel default size so that
|
|
* initial sizes sum to 100%. This prevents PrimeVue Splitter from saving an
|
|
* inconsistent panelSizes array (where untouched panel values are prop-based
|
|
* while resized panels are pixel-derived), which caused one panel's width to
|
|
* drift when the other was resized.
|
|
*
|
|
* Uses runtime visibility (not just mount state) because the sidebar panel can
|
|
* be mounted but hidden via display:none when no tab is active.
|
|
*/
|
|
const bothSidePanelsVisible = computed(
|
|
() =>
|
|
!focusMode.value && sidebarPanelVisible.value && showOffsideSplitter.value
|
|
)
|
|
|
|
const centerPanelDefaultSize = computed(() =>
|
|
bothSidePanelsVisible.value ? 100 - 2 * SIDE_PANEL_SIZE : CENTER_PANEL_SIZE
|
|
)
|
|
|
|
const sidebarTabKey = computed(() => {
|
|
return unifiedWidth.value
|
|
? 'unified-sidebar'
|
|
: // When no tab is active, use a default key to maintain state
|
|
(activeSidebarTabId.value ?? 'default-sidebar')
|
|
})
|
|
|
|
const sidebarStateKey = computed(() => {
|
|
const base = sidebarTabKey.value
|
|
if (sidebarLocation.value === 'left' && !showOffsideSplitter.value) {
|
|
return base
|
|
}
|
|
const suffix = showOffsideSplitter.value ? '-with-offside' : ''
|
|
return `${base}-${sidebarLocation.value}${suffix}`
|
|
})
|
|
|
|
/**
|
|
* Avoid triggering default behaviors during drag-and-drop, such as text selection.
|
|
*/
|
|
function onResizestart({ originalEvent: event }: SplitterResizeStartEvent) {
|
|
event.preventDefault()
|
|
}
|
|
|
|
/**
|
|
* Normalize persisted panel sizes to sum to 100% after each resize.
|
|
*
|
|
* PrimeVue Splitter only updates the two panels adjacent to the dragged gutter,
|
|
* leaving the third panel at its initial prop value. Because that prop value
|
|
* doesn't account for CSS min-width or gutter offsets, the saved array can sum
|
|
* to more than 100%, causing the untouched panel's width to drift on restore.
|
|
*/
|
|
function normalizeSavedSizes() {
|
|
const stateKey = isSelectMode.value
|
|
? sidebarLocation.value === 'left'
|
|
? 'builder-splitter'
|
|
: 'builder-splitter-right'
|
|
: sidebarStateKey.value
|
|
const raw = localStorage.getItem(stateKey)
|
|
if (!raw) return
|
|
try {
|
|
const parsed: unknown = JSON.parse(raw)
|
|
if (
|
|
!Array.isArray(parsed) ||
|
|
parsed.length === 0 ||
|
|
parsed.some((s) => typeof s !== 'number' || !Number.isFinite(s))
|
|
) {
|
|
return
|
|
}
|
|
const sum = parsed.reduce((a, b) => a + b, 0)
|
|
if (sum <= 0 || Math.abs(sum - 100) <= 0.5) return
|
|
localStorage.setItem(
|
|
stateKey,
|
|
JSON.stringify(parsed.map((s) => (s / sum) * 100))
|
|
)
|
|
} catch {
|
|
return
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Force refresh the splitter when right panel visibility or sidebar location changes
|
|
* to recalculate the width and panel order
|
|
*/
|
|
const splitterRefreshKey = computed(() => {
|
|
return `main-splitter${rightSidePanelVisible.value ? '-with-right-panel' : ''}${isSelectMode.value ? '-builder' : ''}-${sidebarLocation.value}`
|
|
})
|
|
|
|
const firstPanelStyle = computed(() => {
|
|
if (focusMode.value) return { display: 'none' }
|
|
if (sidebarLocation.value === 'left') {
|
|
return { display: sidebarPanelVisible.value ? 'flex' : 'none' }
|
|
}
|
|
return undefined
|
|
})
|
|
|
|
const lastPanelStyle = computed(() => {
|
|
if (focusMode.value) return { display: 'none' }
|
|
if (sidebarLocation.value === 'right') {
|
|
return { display: sidebarPanelVisible.value ? 'flex' : 'none' }
|
|
}
|
|
return undefined
|
|
})
|
|
</script>
|
|
|
|
<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 when adjacent panel is not visible */
|
|
:deep(
|
|
[data-pc-name='splitterpanel'][style*='display: none'] + .p-splitter-gutter
|
|
),
|
|
:deep(
|
|
.p-splitter-gutter + [data-pc-name='splitterpanel'][style*='display: none']
|
|
) {
|
|
display: none;
|
|
}
|
|
|
|
.splitter-overlay-bottom :deep(.p-splitter-gutter) {
|
|
transform: translateY(5px);
|
|
}
|
|
</style>
|