import React, { useCallback, useState, useEffect, useMemo, type RefObject, type RefCallback, type MouseEvent, type JSX, } from 'react'; import { useSWRxPageChildren } from '~/stores/page-listing'; import { usePageTreeDescCountMap } from '~/stores/ui'; import { ItemNode } from './ItemNode'; import { SimpleItemContent } from './SimpleItemContent'; import type { TreeItemProps, TreeItemToolProps } from './interfaces'; import styles from './TreeItemLayout.module.scss'; const moduleClass = styles['tree-item-layout'] ?? ''; type TreeItemLayoutProps = TreeItemProps & { className?: string, itemRef?: RefObject | RefCallback, indentSize?: number, } export const TreeItemLayout = (props: TreeItemLayoutProps): JSX.Element => { const { className, itemClassName, indentSize = 10, itemLevel: baseItemLevel = 1, itemNode, targetPath, targetPathOrId, isOpen: _isOpen = false, onRenamed, onClick, onClickDuplicateMenuItem, onClickDeleteMenuItem, onWheelClick, isEnableActions, isReadOnlyUser, isWipPageShown = true, itemRef, itemClass, showAlternativeContent, } = props; const { page } = itemNode; const [currentChildren, setCurrentChildren] = useState([]); const [isOpen, setIsOpen] = useState(_isOpen); const { data } = useSWRxPageChildren(isOpen ? page._id : null); const itemClickHandler = useCallback((e: MouseEvent) => { // DO NOT handle the event when e.currentTarget and e.target is different if (e.target !== e.currentTarget) { return; } onClick?.(page); }, [onClick, page]); const itemMouseupHandler = useCallback((e: MouseEvent) => { // DO NOT handle the event when e.currentTarget and e.target is different if (e.target !== e.currentTarget) { return; } if (e.button === 1) { e.preventDefault(); onWheelClick?.(page); } }, [onWheelClick, page]); // descendantCount const { getDescCount } = usePageTreeDescCountMap(); const descendantCount = getDescCount(page._id) || page.descendantCount || 0; // hasDescendants flag const isChildrenLoaded = currentChildren?.length > 0; const hasDescendants = descendantCount > 0 || isChildrenLoaded; const hasChildren = useCallback((): boolean => { return currentChildren != null && currentChildren.length > 0; }, [currentChildren]); const onClickLoadChildren = useCallback(() => { setIsOpen(!isOpen); }, [isOpen]); // didMount useEffect(() => { const isPathToTarget = page.path != null && targetPath.startsWith(page.path) && targetPath !== page.path; // Target Page does not need to be opened if (isPathToTarget) setIsOpen(true); }, [targetPath, page.path]); /* * When swr fetch succeeded */ useEffect(() => { if (isOpen && data != null) { const newChildren = ItemNode.generateNodesFromPages(data.children); setCurrentChildren(newChildren); } }, [data, isOpen, targetPathOrId]); const isSelected = useMemo(() => { return page._id === targetPathOrId || page.path === targetPathOrId; }, [page, targetPathOrId]); const ItemClassFixed = itemClass ?? TreeItemLayout; const baseProps: Omit = { isEnableActions, isReadOnlyUser, isOpen: false, isWipPageShown, targetPath, targetPathOrId, onRenamed, onClickDuplicateMenuItem, onClickDeleteMenuItem, }; const toolProps: TreeItemToolProps = { ...baseProps, itemLevel: baseItemLevel, itemNode, stateHandlers: { setIsOpen, }, }; const EndComponents = props.customEndComponents; const HoveredEndComponents = props.customHoveredEndComponents; const HeadObChildrenComponents = props.customHeadOfChildrenComponents; const AlternativeComponents = props.customAlternativeComponents; if (!isWipPageShown && page.wip) { return <>; } return (
1 ? indentSize : 0}px` }} >
  • {hasDescendants && ( )}
    { showAlternativeContent && AlternativeComponents != null ? ( AlternativeComponents.map((AlternativeContent, index) => ( // eslint-disable-next-line react/no-array-index-key () )) ) : ( <>
    {EndComponents?.map((EndComponent, index) => ( // eslint-disable-next-line react/no-array-index-key () ))}
    {HoveredEndComponents?.map((HoveredEndContent, index) => ( // eslint-disable-next-line react/no-array-index-key () ))}
    ) }
  • { isOpen && (
    {HeadObChildrenComponents?.map((HeadObChildrenContents, index) => ( // eslint-disable-next-line react/no-array-index-key () ))} { hasChildren() && currentChildren.map((node) => { const itemProps = { ...baseProps, className, itemLevel: baseItemLevel + 1, itemNode: node, itemClass, itemClassName, onClick, }; return ( ); }) }
    ) }
    ); };