Skip to content

Commit d121663

Browse files
committed
tree nav refactor
1 parent 8046a83 commit d121663

File tree

2 files changed

+164
-163
lines changed

2 files changed

+164
-163
lines changed

apps/sensenet/src/components/tree/StyledTreeItem.tsx

Lines changed: 89 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,39 @@ import { ExpandItemsContext } from './Contexts/ExpandedItemsProvider'
1313
import { useTreeLoading } from './Contexts/TreeLoadingProvider'
1414
import StyledTreeItemProps from './Props/StyledTreeItemProps'
1515

16-
export const StyledTreeItem = (props: StyledTreeItemProps) => {
16+
export const StyledTreeItem = ({
17+
contentvalue,
18+
activeitempath,
19+
navigate,
20+
editMode,
21+
...restProps
22+
}: StyledTreeItemProps) => {
1723
const { setIsTreeLoading, enabledPath } = useTreeLoading()
18-
const [hasChildren, setHasChildren] = useState<boolean>(true)
1924
const [innerElements, setInnerElements] = useState<React.JSX.Element[]>()
20-
const expContext = useContext(ExpandItemsContext)
21-
const currentPath = useQuery().get('path')
22-
if (!expContext) {
23-
throw new Error('MyComponent must be used within a ExpandItemsProvider')
24-
}
25-
const [expandItems, setExpandItems, _expandOriginalItems, _setExpandOriginalItems, loadChildren] = expContext
2625
const [contextMenuItem, setContextMenuItem] = useState<GenericContent | null>(null)
2726
const [isContextMenuOpened, setIsContextMenuOpened] = useState(false)
2827
const [contextMenuAnchorPos, setContextMenuAnchorPos] = useState<{ top: number; left: number }>({
2928
top: 0,
3029
left: 0,
3130
})
32-
const path = props.contentvalue.Path
33-
const isDisabled = !path.includes(enabledPath)
31+
32+
const expContext = useContext(ExpandItemsContext)
33+
if (!expContext) throw new Error('StyledTreeItem must be used within ExpandItemsProvider')
34+
35+
const [expandItems, setExpandItems, , , loadChildren] = expContext
3436
const history = useHistory()
3537
const repository = useRepository()
36-
const { location } = history
3738
const snRoute = useSnRoute()
3839
const uiSettings = useContext(ResponsivePersonalSettings)
39-
const { navigate, editMode, ...restProps } = props
40+
41+
const currentPath = useQuery().get('path')
4042
const mountedRef = useRef(true)
4143

44+
const path = contentvalue.Path
45+
const isDisabled = !path.includes(enabledPath)
46+
const itemId = String(contentvalue.Id)
47+
48+
// Track mount state
4249
useEffect(() => {
4350
mountedRef.current = true
4451
return () => {
@@ -47,162 +54,151 @@ export const StyledTreeItem = (props: StyledTreeItemProps) => {
4754
}, [])
4855

4956
const loadCollectionCB = useCallback(
50-
async (contentPath: string): Promise<void> => {
57+
async (contentPath: string) => {
5158
try {
5259
const children = await loadChildren(contentPath)
53-
if (!mountedRef.current) {
54-
return
55-
}
56-
children?.sort((a, b) => {
60+
if (!mountedRef.current) return
61+
62+
const sorted = children?.sort((a, b) => {
5763
const isAFolder = a.Type.toLowerCase().includes('folder') ? 0 : 1
5864
const isBFolder = b.Type.toLowerCase().includes('folder') ? 0 : 1
59-
if (isAFolder !== isBFolder) {
60-
return isAFolder - isBFolder
61-
}
62-
return a.Name.localeCompare(b.Name)
65+
return isAFolder - isBFolder || a.Name.localeCompare(b.Name)
6366
})
6467

65-
const elements = children?.map((innerChild: GenericContent) => (
68+
const elements = sorted?.map((child) => (
6669
<StyledTreeItem
67-
id={String(innerChild.Id)}
68-
key={innerChild.Id}
69-
data-id={innerChild.Id}
70-
activeitempath={props.activeitempath}
71-
nodeId={innerChild.Id.toString()}
72-
contentvalue={innerChild}
73-
navigate={props.navigate}
70+
key={child.Id}
71+
id={String(child.Id)}
72+
data-id={child.Id}
73+
activeitempath={activeitempath}
74+
nodeId={child.Id.toString()}
75+
contentvalue={child}
76+
navigate={navigate}
7477
editMode={editMode}
7578
onContextMenu={(event) => {
7679
event.preventDefault()
7780
event.stopPropagation()
7881
}}
7982
/>
8083
))
81-
82-
if (elements) {
83-
setHasChildren(elements.length > 0)
84-
setInnerElements(elements)
85-
}
84+
setInnerElements(elements)
8685
} finally {
8786
//
8887
}
8988
},
90-
[loadChildren, props.activeitempath, props.navigate, editMode],
89+
[loadChildren, activeitempath, navigate, editMode],
9190
)
9291

92+
// Load children if expanded
9393
useEffect(() => {
94-
const itemId = String(props.contentvalue.Id)
9594
if (expandItems.has(itemId)) {
96-
loadCollectionCB(props.contentvalue.Path)
95+
loadCollectionCB(contentvalue.Path)
9796
}
98-
}, [props, expandItems, loadCollectionCB, currentPath])
97+
}, [expandItems, itemId, contentvalue.Path, currentPath, loadCollectionCB])
9998

99+
// Collapse if outside enabledPath
100100
useEffect(() => {
101-
const itemId = String(props.contentvalue.Id)
102-
const itemPath = props.contentvalue.Path
103-
101+
const itemPath = contentvalue.Path
104102
if (!enabledPath.startsWith(itemPath) && !itemPath.startsWith(enabledPath) && expandItems.has(itemId)) {
105103
setExpandItems((prev) => {
106104
const updated = new Set(prev)
107105
updated.delete(itemId)
108106
return updated
109107
})
110108
}
111-
}, [enabledPath, expandItems, props.contentvalue.Id, props.contentvalue.Path, setExpandItems])
112-
113-
const getLabel = () => {
114-
return (
115-
<>
116-
<ListItemIcon key={props.contentvalue.Id}>
117-
<Icon item={props.contentvalue} />
118-
</ListItemIcon>
119-
<ListItemText
120-
style={{ fontSize: '11px!important', color: isDisabled ? 'grey' : '' }}
121-
primary={`${props.contentvalue.Name}`}
122-
/>
123-
</>
124-
)
125-
}
109+
}, [enabledPath, expandItems, itemId, contentvalue.Path, setExpandItems])
110+
111+
const getLabel = () => (
112+
<>
113+
<ListItemIcon>
114+
<Icon item={contentvalue} style={{ height: 20, width: 20, fontSize: 18 }} />
115+
</ListItemIcon>
116+
<ListItemText style={{ fontSize: '11px', color: isDisabled ? 'grey' : undefined }} primary={contentvalue.Name} />
117+
</>
118+
)
126119

127120
const onIconClick: MouseEventHandler = (event) => {
128121
if (isDisabled) return
129122
event.preventDefault()
130123
event.stopPropagation()
131-
setExpandItems((eItems) => {
132-
const updatedItems = new Set(eItems)
133-
const itemId = props.contentvalue.Id.toString()
134-
if (updatedItems.has(itemId)) {
135-
if (document.activeElement) {
136-
;(document.activeElement as HTMLElement).blur()
137-
}
138-
updatedItems.delete(itemId)
124+
125+
setExpandItems((items) => {
126+
const updated = new Set(items)
127+
if (updated.has(itemId)) {
128+
;(document.activeElement as HTMLElement | null)?.blur()
129+
updated.delete(itemId)
139130
} else {
140131
setIsTreeLoading(true)
141-
updatedItems.add(itemId)
142-
loadCollectionCB(props.contentvalue.Path).finally(() => setIsTreeLoading(false))
132+
updated.add(itemId)
133+
loadCollectionCB(contentvalue.Path).finally(() => setIsTreeLoading(false))
143134
}
144-
return updatedItems
135+
return updated
145136
})
146137
}
147138

148139
const onLabelClick: MouseEventHandler = (event) => {
149140
if (isDisabled) return
150-
const displayName = props.contentvalue.DisplayName
141+
const displayName = contentvalue.DisplayName
142+
151143
if (displayName?.endsWith('.settings') || displayName?.endsWith('.xml')) {
152-
history.push(getPrimaryActionUrl({ content: props.contentvalue, repository, uiSettings, location, snRoute }))
144+
history.push(
145+
getPrimaryActionUrl({ content: contentvalue, repository, uiSettings, location: history.location, snRoute }),
146+
)
153147
return
154148
}
149+
155150
if (editMode) {
156151
navigateToAction({
157152
history,
158153
routeMatch: snRoute.match!,
159154
action: 'edit',
160-
queryParams: { content: props.contentvalue.Path.replace(snRoute.path, '') },
155+
queryParams: { content: contentvalue.Path.replace(snRoute.path, '') },
161156
})
162157
} else {
163158
const itemPath = (event.target as HTMLElement).closest('[data-path]')?.getAttribute('data-path')
164-
const itemId = props.contentvalue.Id.toString()
165-
setExpandItems((prevItems) => {
166-
const updatedItems = new Set(prevItems)
159+
setExpandItems((prev) => {
160+
const updated = new Set(prev)
167161
if (!expandItems.has(itemId)) {
168162
setIsTreeLoading(true)
169-
updatedItems.add(itemId)
170-
loadCollectionCB(props.contentvalue.Path).finally(() => setIsTreeLoading(false))
171-
} else {
172-
if (itemPath === props.activeitempath) {
173-
updatedItems.delete(itemId)
174-
}
163+
updated.add(itemId)
164+
loadCollectionCB(contentvalue.Path).finally(() => setIsTreeLoading(false))
165+
} else if (itemPath === activeitempath) {
166+
updated.delete(itemId)
175167
}
176-
return updatedItems
168+
return updated
177169
})
178-
props.navigate(props.contentvalue)
170+
navigate(contentvalue)
179171
}
180172
}
181173

182-
const onContextMenu = (event: React.MouseEvent, data: GenericContent) => {
183-
if (isDisabled) return
184-
event.preventDefault()
185-
event.stopPropagation()
186-
setContextMenuItem(data)
187-
setContextMenuAnchorPos({ top: event.clientY, left: event.clientX })
188-
setIsContextMenuOpened(true)
189-
}
174+
const onContextMenu = useCallback(
175+
(event: React.MouseEvent) => {
176+
if (isDisabled) return
177+
event.preventDefault()
178+
event.stopPropagation()
179+
setContextMenuItem(contentvalue)
180+
setContextMenuAnchorPos({ top: event.clientY, left: event.clientX })
181+
setIsContextMenuOpened(true)
182+
},
183+
[contentvalue, isDisabled],
184+
)
190185

191186
return (
192187
<>
193188
<TreeItem
194189
{...restProps}
195190
label={getLabel()}
196-
id={props.contentvalue.Id.toString()}
197-
data-path={props.contentvalue.Path}
191+
id={itemId}
192+
data-path={path}
198193
onIconClick={onIconClick}
199194
onLabelClick={onLabelClick}
200-
onContextMenu={(event) => onContextMenu(event, props.contentvalue)}
195+
onContextMenu={onContextMenu}
201196
expandIcon={isDisabled ? <></> : undefined}
202-
collapseIcon={(!hasChildren || isDisabled) && <></>}>
197+
collapseIcon={(!innerElements?.length || isDisabled) && <></>}>
203198
{innerElements}
204199
<></>
205200
</TreeItem>
201+
206202
{contextMenuItem && (
207203
<ContentContextMenu
208204
isOpened={isContextMenuOpened}

0 commit comments

Comments
 (0)