Skip to content

Commit

Permalink
[feat] Add page structure explorer (#2246)
Browse files Browse the repository at this point in the history
  • Loading branch information
bharatkashyap authored Jul 22, 2023
1 parent 35a810b commit a701954
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 17 deletions.
1 change: 0 additions & 1 deletion packages/toolpad-app/src/appDom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,6 @@ export function getApp(dom: AppDom): AppNode {

export type NodeChildren<N extends AppDomNode = any> = ChildNodesOf<N>;

// TODO: memoize the result of this function per dom in a WeakMap
const childrenMemo = new WeakMap<AppDom, Map<NodeId, NodeChildren<any>>>();
export function getChildNodes<N extends AppDomNode>(dom: AppDom, parent: N): NodeChildren<N> {
let domChildrenMemo = childrenMemo.get(dom);
Expand Down
8 changes: 6 additions & 2 deletions packages/toolpad-app/src/components/EditableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface EditableTextProps {
onChange?: (newValue: string) => void;
onSave?: (newValue: string) => void;
onClose?: () => void;
onDoubleClick?: () => void;
size?: 'small' | 'medium';
sx?: SxProps;
value?: string;
Expand All @@ -34,6 +35,7 @@ const EditableText = React.forwardRef<HTMLInputElement, EditableTextProps>(
error,
onChange,
onClose,
onDoubleClick,
onSave,
size,
sx,
Expand Down Expand Up @@ -113,17 +115,19 @@ const EditableText = React.forwardRef<HTMLInputElement, EditableTextProps>(
error={error}
helperText={helperText}
ref={ref}
onDoubleClick={onDoubleClick}
inputRef={appTitleInput}
inputProps={{
tabIndex: editable ? 0 : -1,
// tabIndex: editable ? 0 : -1,
'aria-readonly': !editable,
sx: (theme: Theme) => ({
// Handle overflow
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
fontSize: theme.typography[typographyVariant].fontSize,
height: `1.1em`,
height: theme.typography.pxToRem(8),
cursor: 'pointer',
}),
}}
onKeyUp={handleInput}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { TreeView } from '@mui/lab';
import { Typography, styled, Box, IconButton } from '@mui/material';
import * as React from 'react';
import TreeItem, { treeItemClasses, TreeItemProps } from '@mui/lab/TreeItem';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import AddIcon from '@mui/icons-material/Add';
import { NodeId } from '@mui/toolpad-core';
Expand Down Expand Up @@ -68,14 +68,14 @@ function HierarchyTreeItem(props: StyledTreeItemProps) {
return (
<StyledTreeItem
label={
<Box sx={{ display: 'flex', alignItems: 'center', p: 0.5, pr: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', p: 0.1, pr: 0 }}>
{labelIcon}
<Typography variant="body2" sx={{ fontWeight: 'inherit', flexGrow: 1 }} noWrap>
{labelText}
</Typography>
{onCreate ? (
<IconButton aria-label={createLabelText} onClick={onCreate}>
<AddIcon />
<IconButton aria-label={createLabelText} onClick={onCreate} size="small">
<AddIcon fontSize="inherit" />
</IconButton>
) : null}
{toolpadNodeId ? (
Expand All @@ -86,9 +86,10 @@ function HierarchyTreeItem(props: StyledTreeItemProps) {
[classes.treeItemMenuOpen]: menuProps.open,
})}
aria-label="Open hierarchy menu"
size="small"
{...buttonProps}
>
<MoreVertIcon />
<MoreVertIcon fontSize="inherit" />
</IconButton>
)}
nodeId={toolpadNodeId}
Expand Down Expand Up @@ -234,8 +235,8 @@ export default function HierarchyExplorer({ className }: HierarchyExplorerProps)
expanded={expanded}
onNodeToggle={handleToggle}
multiSelect
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
defaultCollapseIcon={<ExpandMoreIcon sx={{ fontSize: '0.9rem', opacity: 0.5 }} />}
defaultExpandIcon={<ChevronRightIcon sx={{ fontSize: '0.9rem', opacity: 0.5 }} />}
>
<HierarchyTreeItem
nodeId=":pages"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import PlaceIcon from '@mui/icons-material/Place';
import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
import MoodIcon from '@mui/icons-material/Mood';
import HtmlIcon from '@mui/icons-material/Html';
import TableRowsIcon from '@mui/icons-material/TableRows';
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
import TagIcon from '@mui/icons-material/Tag';
import { ButtonBase } from '@mui/material';
import { ButtonBase, SxProps } from '@mui/material';

const iconMap = new Map<string, React.ComponentType<SvgIconProps>>([
['Autocomplete', ManageSearchIcon],
Expand Down Expand Up @@ -59,6 +61,8 @@ const iconMap = new Map<string, React.ComponentType<SvgIconProps>>([
['Drawer', ViewSidebarIcon],
['Icon', MoodIcon],
['Html', HtmlIcon],
['PageRow', TableRowsIcon],
['PageColumn', ViewColumnIcon],
['Metric', TagIcon],
]);

Expand All @@ -67,11 +71,12 @@ type ComponentItemKind = 'future' | 'builtIn' | 'create' | 'custom';
interface ComponentIconProps {
id: string;
kind?: ComponentItemKind;
sx?: SxProps;
}

function ComponentIcon({ id: componentId, kind }: ComponentIconProps) {
export function ComponentIcon({ id: componentId, kind, sx }: ComponentIconProps) {
const Icon = iconMap.get(kind === 'custom' ? 'CodeComponent' : componentId);
return Icon ? <Icon fontSize="medium" opacity={kind === 'future' ? 0.75 : 1} /> : null;
return Icon ? <Icon sx={{ fontSize: 24, opacity: kind === 'future' ? 0.75 : 1, ...sx }} /> : null;
}

interface ComponentCatalogItemProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function stopPropagationHandler(event: React.SyntheticEvent) {

const nodeHudClasses = {
selected: 'NodeHud_Selected',
hovered: 'NodeHud_Hovered',
selectionHint: 'NodeHud_SelectionHint',
};

Expand All @@ -41,7 +42,7 @@ const NodeHudWrapper = styled('div', {
userSelect: 'none',
outline: `1px dotted ${isOutlineVisible ? theme.palette.primary[500] : 'transparent'}`,
zIndex: 80,
'&:hover': {
[`&:hover, &.${nodeHudClasses.hovered}`]: {
outline: `2px dashed ${isHoverable ? theme.palette.primary[500] : 'transparent'}`,
},
[`.${nodeHudClasses.selected}`]: {
Expand Down Expand Up @@ -150,6 +151,7 @@ interface NodeHudProps {
onDuplicate?: (event: React.MouseEvent) => void;
isOutlineVisible?: boolean;
isHoverable?: boolean;
isHovered?: boolean;
}

export default function NodeHud({
Expand All @@ -167,6 +169,7 @@ export default function NodeHud({
onDuplicate,
isOutlineVisible = false,
isHoverable = true,
isHovered = false,
}: NodeHudProps) {
const hintPosition = rect.y > HUD_HEIGHT ? HINT_POSITION_TOP : HINT_POSITION_BOTTOM;

Expand Down Expand Up @@ -205,6 +208,7 @@ export default function NodeHud({
}
: {}),
}}
className={isHovered ? nodeHudClasses.hovered : ''}
isOutlineVisible={isOutlineVisible}
isHoverable={isHoverable}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
const { dom } = useDom();
const { currentView } = useAppState();
const selectedNodeId = currentView.kind === 'page' ? currentView.selectedNodeId : null;
const hoveredNodeId = currentView.kind === 'page' ? currentView.hoveredNodeId : null;

const domApi = useDomApi();
const appStateApi = useAppStateApi();
Expand Down Expand Up @@ -1586,6 +1587,7 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
const isPageColumnChild = parent ? appDom.isElement(parent) && isPageColumn(parent) : false;

const isSelected = selectedNode && !newNode ? selectedNode.id === node.id : false;
const isHovered = hoveredNodeId === node.id;

const isHorizontallyResizable = isSelected && (isPageRowChild || isPageColumnChild);
const isVerticallyResizable =
Expand Down Expand Up @@ -1625,6 +1627,7 @@ export default function RenderOverlay({ bridge }: RenderOverlayProps) {
onDelete={handleNodeDelete(node.id)}
isResizing={isResizingNode}
resizePreviewElementRef={resizePreviewElementRef}
isHovered={isHovered}
isHoverable={!isResizing && !isDraggingOver}
isOutlineVisible={isDraggingOver}
/>
Expand Down
9 changes: 7 additions & 2 deletions packages/toolpad-app/src/toolpad/AppEditor/PagePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from 'react';
import { styled, SxProps, Box, Divider, Typography } from '@mui/material';
import HierarchyExplorer from './HierarchyExplorer';
import PagesHierarchyExplorer from './HierarchyExplorer';
import PageStructureExplorer from './StructureExplorer';
import SplitPane from '../../components/SplitPane';
import { useDom } from '../AppState';
import AppOptions from '../AppOptions';
import config from '../../config';
Expand Down Expand Up @@ -36,7 +38,10 @@ export default function PagePanel({ className, sx }: ComponentPanelProps) {
<AppOptions dom={dom} />
</Box>
<Divider />
<HierarchyExplorer />
<SplitPane sx={{ flex: 1 }} split="horizontal" defaultSize={200} minSize={100} maxSize={400}>
<PagesHierarchyExplorer />
<PageStructureExplorer />
</SplitPane>
</PagePanelRoot>
);
}
Loading

0 comments on commit a701954

Please sign in to comment.