@samitouri / QOSami-HFS / commits / daafa0b8

fix: (regression 0.57.15) admin/shared: collapse-all button not working

Massimo Melina committed Sep 28, 2025 at 19:28 UTC daafa0b8864651bf55251ee94505626dcc48fbf4
2 files changed +17 -11
admin/src/VfsPage.ts
+2 -2
@@ -159,7 +159,7 @@ export default function VfsPage({ setTitleSide }: PageProps) {
159 const scrollProps = { height: '100%', display: 'flex', flexDirection: 'column', overflow: 'auto' } as const
160 return h(Grid, { container: true, rowSpacing: 1, columnSpacing: 2, top: 0, flex: '1 1 auto', height: 0 },
161 h(Grid, { item: true, xs: 12, [sideBreakpoint]: 5, lg: 6, xl: 5, ...scrollProps },
162 - h(VfsTree, { id2node, statusApi }) ),
162 + id2node.size > 0 && h(VfsTree, { id2node, statusApi }) ),
163 isSideBreakpoint && sideContent && h(Grid, { item: true, [sideBreakpoint]: true, maxWidth: '100%', ...scrollProps },
164 h(Card, { sx: { overflow: 'initial' } }, // overflow is incompatible with stickyBar
165 h(CardContent, {}, sideContent)) )
@@ -181,7 +181,7 @@ export async function deleteFiles() {
181 && _.findLastIndex(uris, y => x.startsWith(y), i - 1) !== -1) // search backward among previous elements, as they array is sorted
182 _.pull(uris, '/')
183 const { errors } = await apiCall('del_vfs', { uris })
184 - const urisThatFailed = uris.filter((uri, idx) => errors[idx])
184 + const urisThatFailed = uris.filter((_uri, idx) => errors[idx])
185 if (urisThatFailed.length)
186 return alertDialog("Following elements couldn't be deleted: " + urisThatFailed.join(', '), 'error')
187 reloadVfs()
admin/src/VfsTree.ts
+15 -9
@@ -9,7 +9,7 @@ import {
9 } from '@mui/icons-material'
10 import { Box, Typography } from '@mui/material'
11 import { reloadVfs, VfsNode } from './VfsPage'
12 -import { onlyTruthy, toMutable, useEffectOnce, Who, with_ } from './misc'
12 +import { onlyTruthy, toMutable, Who, with_ } from './misc'
13 import { Flex, iconTooltip, useToggleButton } from './mui'
14 import VfsMenuBar from './VfsMenuBar'
15 import { apiCall, ApiObject } from './api'
@@ -19,6 +19,8 @@ import _ from 'lodash'
19 export const FolderIcon = Folder
20 export const FileIcon = InsertDriveFileOutlined
21
22 +let once = true
23 +
24 export default function VfsTree({ id2node, statusApi }:{ id2node: Map<string, VfsNode>, statusApi: ApiObject }) {
25 const { vfs, selectedFiles, expanded } = useSnapState()
26 const dragging = useRef<string>()
@@ -106,15 +108,19 @@ export default function VfsTree({ id2node, statusApi }:{ id2node: Map<string, Vf
108 }
109 }, [statusApi.data])
110 const ref = useRef<HTMLUListElement>(null)
109 - const [expandAll, toggleBtn] = useToggleButton("Collapse all", "Expand all", exp => ({
111 + const allExpanded = expanded.length === id2node.size
112 + const initialExpansion = ['/', ...vfs?.children?.length === 1 ? [vfs.children[0].id] : []] // in case there's only one child, expand that too
113 + if (once) {
114 + once = false
115 + state.expanded = initialExpansion
116 + }
117 + const [_expandAll, toggleBtn] = useToggleButton("Collapse all", "Expand all", exp => ({
118 icon: exp ? UnfoldLess : UnfoldMore,
119 sx: { rotate: exp ? 0 : '180deg' },
112 - }), expanded.length === id2node.size)
113 - useEffectOnce(() => { // this is also resetting the state at each mount
114 - state.expanded = expandAll ? Array.from(id2node.keys())
115 - : state.expanded.length ? state.expanded // keep the previous state
116 - : ['/', ...vfs?.children?.length === 1 ? [vfs.children[0].id] : []] // in case there's only one child, expand that too
117 - }, [expandAll, Boolean(vfs)]) // vfs is undefined on the first render, we want to be called again as soon as it is loaded first time and not at reloads
120 + onClick() {
121 + state.expanded = allExpanded ? initialExpansion : Array.from(id2node.keys())
122 + }
123 + }), allExpanded)
124 useEffect(() => {
125 state.expanded = _.uniq(state.expanded.concat(state.selectedFiles.map(x => x.parent?.id || '')))
126 }, [state.vfs])
@@ -140,7 +146,7 @@ export default function VfsTree({ id2node, statusApi }:{ id2node: Map<string, Vf
146 maxWidth: ref.current && `calc(100vw - ${16 + ref.current.offsetLeft}px)`, // limit possible horizontal scrolling to this element
147 '& ul': { borderLeft: '1px dashed #444', marginLeft: '15px', paddingLeft: '15px' },
148 },
143 - onNodeSelect(ev, ids) {
149 + onNodeSelect(_ev, ids) {
150 if (typeof ids === 'string') return // shut up ts
151 state.selectedFiles = onlyTruthy(ids.map(id => id2node.get(id)))
152 }