mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-13 09:00:16 +00:00
feat: add collapse/expand all toggle to right panel tabs (#9333)
## Summary Adds a collapse/expand all toggle button to all parameter and error tabs in the right side panel, letting users quickly collapse or expand all accordion sections at once. ## Changes - **New component**: `CollapseToggleButton.vue` — a reusable icon button (list-collapse / list-tree icon) with tooltip, bound via `v-model` - **Error tab**: Toggle collapses/expands all error groups; per-group state is now managed through `isSectionCollapsed` / `setSectionCollapsed` helpers - **Nodes tab** (`TabNodes.vue`): Per-node `collapseMap`; toggle overrides per-section state; defaults to collapsed (nodes tab default was already collapsed) - **Normal inputs tab** (`TabNormalInputs.vue`): Per-node `collapseMap` + `advancedCollapsed`; toggle covers both normal and advanced sections; defaults to collapsed when multiple nodes selected - **Subgraph inputs tab** (`TabSubgraphInputs.vue`): Toggle covers both main and advanced inputs sections - **Global parameters tab** (`TabGlobalParameters.vue`): Toggle bound to single `SectionWidgets` - **i18n**: Added `g.collapseAll` and `g.expandAll` keys ## Review Focus - `isAllCollapsed` getter in each tab: reads from the same per-section state so the toggle accurately reflects current state rather than being independently tracked - `TabNormalInputs`: multi-node selection default collapse behaviour is preserved through `isSectionCollapsed` fallback logic ## Screenshots <img width="778" height="643" alt="image" src="https://github.com/user-attachments/assets/04d07f32-5135-47f9-b029-78ca78a996fb" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9333-feat-add-collapse-expand-all-toggle-to-right-panel-tabs-3176d73d36508123ba22d6e81983bb1b) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -1,38 +1,43 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full min-w-0">
|
||||
<!-- Search bar -->
|
||||
<!-- Search bar + collapse toggle -->
|
||||
<div
|
||||
class="px-4 pt-1 pb-4 flex gap-2 border-b border-interface-stroke shrink-0 min-w-0"
|
||||
class="px-4 pt-1 pb-4 flex items-center border-b border-interface-stroke shrink-0 min-w-0"
|
||||
>
|
||||
<FormSearchInput v-model="searchQuery" />
|
||||
<FormSearchInput v-model="searchQuery" class="flex-1" />
|
||||
<CollapseToggleButton
|
||||
v-model="isAllCollapsed"
|
||||
:show="!isSearching && tabErrorGroups.length > 1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
<div class="flex-1 overflow-y-auto min-w-0">
|
||||
<div
|
||||
v-if="filteredGroups.length === 0"
|
||||
class="text-sm text-muted-foreground px-4 text-center pt-5 pb-15"
|
||||
>
|
||||
{{
|
||||
searchQuery.trim()
|
||||
? t('rightSidePanel.noneSearchDesc')
|
||||
: t('rightSidePanel.noErrors')
|
||||
}}
|
||||
</div>
|
||||
<TransitionGroup tag="div" name="list-scale" class="relative">
|
||||
<div
|
||||
v-if="filteredGroups.length === 0"
|
||||
key="empty"
|
||||
class="text-sm text-muted-foreground px-4 text-center pt-5 pb-15"
|
||||
>
|
||||
{{
|
||||
searchQuery.trim()
|
||||
? t('rightSidePanel.noneSearchDesc')
|
||||
: t('rightSidePanel.noErrors')
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- Group by Class Type -->
|
||||
<PropertiesAccordionItem
|
||||
v-for="group in filteredGroups"
|
||||
:key="group.title"
|
||||
:collapse="collapseState[group.title] ?? false"
|
||||
:collapse="isSectionCollapsed(group.title) && !isSearching"
|
||||
class="border-b border-interface-stroke"
|
||||
:size="
|
||||
group.type === 'missing_node' || group.type === 'swap_nodes'
|
||||
? 'lg'
|
||||
: 'default'
|
||||
"
|
||||
@update:collapse="collapseState[group.title] = $event"
|
||||
@update:collapse="setSectionCollapsed(group.title, $event)"
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||
@@ -126,7 +131,7 @@
|
||||
/>
|
||||
</div>
|
||||
</PropertiesAccordionItem>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Footer: Help Links -->
|
||||
@@ -177,6 +182,7 @@ import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTyp
|
||||
import { NodeBadgeMode } from '@/types/nodeSource'
|
||||
|
||||
import PropertiesAccordionItem from '../layout/PropertiesAccordionItem.vue'
|
||||
import CollapseToggleButton from '../layout/CollapseToggleButton.vue'
|
||||
import FormSearchInput from '@/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue'
|
||||
import ErrorNodeCard from './ErrorNodeCard.vue'
|
||||
import MissingNodeCard from './MissingNodeCard.vue'
|
||||
@@ -203,6 +209,7 @@ const { isInstalling: isInstallingAll, installAllPacks: installAll } =
|
||||
const { replaceGroup, replaceAllGroups } = useNodeReplacement()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const isSearching = computed(() => searchQuery.value.trim() !== '')
|
||||
|
||||
const showNodeIdBadge = computed(
|
||||
() =>
|
||||
@@ -212,6 +219,7 @@ const showNodeIdBadge = computed(
|
||||
|
||||
const {
|
||||
allErrorGroups,
|
||||
tabErrorGroups,
|
||||
filteredGroups,
|
||||
collapseState,
|
||||
isSingleNodeSelected,
|
||||
@@ -221,6 +229,26 @@ const {
|
||||
swapNodeGroups
|
||||
} = useErrorGroups(searchQuery, t)
|
||||
|
||||
const isAllCollapsed = computed({
|
||||
get() {
|
||||
return filteredGroups.value.every((g) => isSectionCollapsed(g.title))
|
||||
},
|
||||
set(collapse: boolean) {
|
||||
for (const group of tabErrorGroups.value) {
|
||||
setSectionCollapsed(group.title, collapse)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function isSectionCollapsed(title: string): boolean {
|
||||
// Defaults to expanded when not explicitly set by the user
|
||||
return collapseState[title] ?? false
|
||||
}
|
||||
|
||||
function setSectionCollapsed(title: string, collapsed: boolean) {
|
||||
collapseState[title] = collapsed
|
||||
}
|
||||
|
||||
/**
|
||||
* When an external trigger (e.g. "See Error" button in SectionWidgets)
|
||||
* sets focusedErrorNodeId, expand only the group containing the target
|
||||
@@ -240,7 +268,7 @@ watch(
|
||||
card.graphNodeId === graphNodeId ||
|
||||
(card.nodeId?.startsWith(prefix) ?? false)
|
||||
)
|
||||
collapseState[group.title] = !hasMatch
|
||||
setSectionCollapsed(group.title, !hasMatch)
|
||||
}
|
||||
rightSidePanelStore.focusedErrorNodeId = null
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user