From b55a0eb41280e0539a5677688db4de259750cce7 Mon Sep 17 00:00:00 2001 From: asif-choudhari Date: Thu, 28 Mar 2024 15:18:46 +0530 Subject: [PATCH 1/3] addded rename, duplicate and delete for page hierarchy --- .../AppEditor/HierarchyExplorer/index.tsx | 114 ++++++++++++++++-- 1 file changed, 106 insertions(+), 8 deletions(-) diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx index caa9ae91871..643081c401e 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx @@ -1,19 +1,50 @@ import * as React from 'react'; +import clsx from 'clsx'; import { NodeId } from '@toolpad/studio-runtime'; -import { Box, Typography } from '@mui/material'; -import { TreeView, TreeItem, TreeItemProps } from '@mui/x-tree-view'; +import { Box, Typography, IconButton, SxProps, styled } from '@mui/material'; +import { TreeView, TreeItem, TreeItemProps, treeItemClasses } from '@mui/x-tree-view'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import useBoolean from '@toolpad/utils/hooks/useBoolean'; import * as appDom from '@toolpad/studio-runtime/appDom'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import invariant from 'invariant'; import { useAppState, useDomApi, useAppStateApi } from '../../AppState'; import { ComponentIcon } from '../PageEditor/ComponentCatalog/ComponentCatalogItem'; import { DomView } from '../../../utils/domView'; import { removePageLayoutNode } from '../pageLayout'; -import EditableTreeItem from '../../../components/EditableTreeItem'; +import EditableTreeItem, { EditableTreeItemProps } from '../../../components/EditableTreeItem'; import ExplorerHeader from '../ExplorerHeader'; +import NodeMenu from '../NodeMenu'; -export interface CustomTreeItemProps extends TreeItemProps { +const classes = { + treeItemMenuButton: 'Toolpad__HierarchyListItem', + treeItemMenuOpen: 'Toolpad__HierarchyListItemMenuOpen', +}; + +const StyledTreeItem = styled(EditableTreeItem)({ + [`& .${classes.treeItemMenuButton}`]: { + visibility: 'hidden', + }, + [` + & .${treeItemClasses.content}:hover .${classes.treeItemMenuButton}, + & .${classes.treeItemMenuOpen} + `]: { + visibility: 'visible', + }, +}); + +interface StyledTreeItemProps extends TreeItemProps, EditableTreeItemProps { + labelTextSx?: SxProps; + labelIconId?: string; + labelIconSx?: SxProps; + createLabelText?: string; + deleteLabelText?: string; + renameLabelText?: string; + duplicateLabelText?: string; +} + +export interface CustomTreeItemProps extends StyledTreeItemProps { ref?: React.RefObject; node: appDom.ElementNode; } @@ -23,9 +54,20 @@ function CustomTreeItem(props: CustomTreeItemProps) { const { dom } = useAppState(); const appStateApi = useAppStateApi(); - const { label, node, ...other } = props; + const { + label, + node, + renameLabelText = 'Rename', + duplicateLabelText = 'Duplicate', + deleteLabelText = 'Delete', + ...other + } = props; - const { value: domNodeEditing, setFalse: stopDomNodeEditing } = useBoolean(false); + const { + value: domNodeEditing, + setTrue: startDomNodeEditing, + setFalse: stopDomNodeEditing, + } = useBoolean(false); const existingNames = React.useMemo(() => appDom.getExistingNamesForNode(dom, node), [dom, node]); @@ -47,8 +89,41 @@ function CustomTreeItem(props: CustomTreeItemProps) { const handleNameSave = React.useCallback( (newName: string) => { domApi.setNodeName(node.id, newName); + stopDomNodeEditing(); + }, + [domApi, node.id, stopDomNodeEditing], + ); + + const handleNodeDelete = React.useCallback( + (nodeId: NodeId) => { + domApi.update((draft) => { + const toRemove = appDom.getNode(draft, nodeId); + if (appDom.isElement(toRemove)) { + draft = removePageLayoutNode(draft, toRemove); + } + + return draft; + }); + }, + [domApi], + ); + + const handleNodeDuplicate = React.useCallback( + (nodeId: NodeId) => { + const currentNode = appDom.getNode(dom, nodeId); + + invariant( + node.parentId && node.parentProp, + 'Duplication should never be called on nodes that are not placed in the dom', + ); + + domApi.update((draft) => { + draft = appDom.duplicateNode(draft, currentNode); + + return draft; + }); }, - [domApi, node.id], + [dom, domApi, node.parentId, node.parentProp], ); const handleNodeHover = React.useCallback( @@ -63,7 +138,7 @@ function CustomTreeItem(props: CustomTreeItemProps) { }, [appStateApi]); return ( - ( @@ -80,6 +155,29 @@ function CustomTreeItem(props: CustomTreeItemProps) { sx={{ marginRight: 1, fontSize: 18, opacity: 0.5 }} /> {children} + {node.id ? ( + ( + + + + )} + nodeId={node.id} + renameLabelText={renameLabelText} + duplicateLabelText={duplicateLabelText} + deleteLabelText={deleteLabelText} + onRenameNode={startDomNodeEditing} + onDuplicateNode={handleNodeDuplicate} + onDeleteNode={handleNodeDelete} + /> + ) : null} )} isEditing={domNodeEditing} From db2c77a71059253b24989706aac476832dfd04ed Mon Sep 17 00:00:00 2001 From: asif-choudhari Date: Thu, 28 Mar 2024 16:09:28 +0530 Subject: [PATCH 2/3] updated imports --- .../src/toolpad/AppEditor/HierarchyExplorer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx index e5f50d4c186..45c3db360be 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import clsx from 'clsx'; import { NodeId } from '@toolpad/studio-runtime'; -import { Box, Typography, styled, IconButton, SxProps, styled } from '@mui/material'; +import { Box, Typography, styled, IconButton, SxProps } from '@mui/material'; import { SimpleTreeView, TreeItem, TreeItemProps, treeItemClasses } from '@mui/x-tree-view'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; From e1e698cdab74d6c9c4b7e8cf3e0b96dca25cf044 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:25:03 +0100 Subject: [PATCH 3/3] Simplify a bit --- .../AppEditor/HierarchyExplorer/index.tsx | 36 ++++--------------- .../src/toolpad/AppEditor/NodeMenu.tsx | 6 ++-- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx index 45c3db360be..185400e6f36 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/HierarchyExplorer/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import clsx from 'clsx'; import { NodeId } from '@toolpad/studio-runtime'; -import { Box, Typography, styled, IconButton, SxProps } from '@mui/material'; +import { Box, Typography, styled, IconButton } from '@mui/material'; import { SimpleTreeView, TreeItem, TreeItemProps, treeItemClasses } from '@mui/x-tree-view'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; @@ -29,25 +29,13 @@ const StyledTreeItem = styled(EditableTreeItem)({ [`& .${classes.treeItemMenuButton}`]: { visibility: 'hidden', }, - [` - & .${treeItemClasses.content}:hover .${classes.treeItemMenuButton}, - & .${classes.treeItemMenuOpen} - `]: { - visibility: 'visible', - }, + [`& .${treeItemClasses.content}:hover .${classes.treeItemMenuButton}, & .${classes.treeItemMenuOpen}`]: + { + visibility: 'visible', + }, }); -interface StyledTreeItemProps extends TreeItemProps, EditableTreeItemProps { - labelTextSx?: SxProps; - labelIconId?: string; - labelIconSx?: SxProps; - createLabelText?: string; - deleteLabelText?: string; - renameLabelText?: string; - duplicateLabelText?: string; -} - -export interface CustomTreeItemProps extends StyledTreeItemProps { +interface CustomTreeItemProps extends TreeItemProps, EditableTreeItemProps { ref?: React.RefObject; node: appDom.ElementNode; } @@ -57,14 +45,7 @@ function CustomTreeItem(props: CustomTreeItemProps) { const { dom } = useAppState(); const appStateApi = useAppStateApi(); - const { - label, - node, - renameLabelText = 'Rename', - duplicateLabelText = 'Duplicate', - deleteLabelText = 'Delete', - ...other - } = props; + const { label, node, ...other } = props; const { value: domNodeEditing, @@ -173,9 +154,6 @@ function CustomTreeItem(props: CustomTreeItemProps) { )} nodeId={node.id} - renameLabelText={renameLabelText} - duplicateLabelText={duplicateLabelText} - deleteLabelText={deleteLabelText} onRenameNode={startDomNodeEditing} onDuplicateNode={handleNodeDuplicate} onDeleteNode={handleNodeDelete} diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/NodeMenu.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/NodeMenu.tsx index dad1c75f768..b62a00a1cba 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/NodeMenu.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/NodeMenu.tsx @@ -24,9 +24,9 @@ export interface NodeMenuProps { export default function NodeMenu({ nodeId, renderButton, - renameLabelText, - deleteLabelText, - duplicateLabelText, + renameLabelText = 'Rename', + deleteLabelText = 'Delete', + duplicateLabelText = 'Duplicate', onRenameNode, onDeleteNode, onDuplicateNode,