Skip to content

Commit

Permalink
Avoid single child layout containers (#2388)
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira authored Aug 21, 2023
1 parent 296ef4c commit faff5db
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 144 deletions.
2 changes: 1 addition & 1 deletion docs/data/toolpad/reference/components/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
| <span class="prop-name">options</span> | <span class="prop-type">array</span> | <span class="prop-default">[]</span> | The options available to search from. |
| <span class="prop-name">value</span> | <span class="prop-type">string</span> | <span class="prop-default">""</span> | The value of the autocomplete. |
| <span class="prop-name">label</span> | <span class="prop-type">string</span> | <span class="prop-default">"Search…"</span> | The label to display for the autocomplete. |
| <span class="prop-name">fullWidth</span> | <span class="prop-type">boolean</span> | <span class="prop-default">true</span> | If true, the autocomplete will take up the full width of its container. |
| <span class="prop-name">fullWidth</span> | <span class="prop-type">boolean</span> | | If true, the autocomplete will take up the full width of its container. |
| <span class="prop-name">size</span> | <span class="prop-type">string</span> | <span class="prop-default">"small"</span> | The size of the autocomplete. One of `small`, `medium`, or `large`. |
| <span class="prop-name">loading</span> | <span class="prop-type">boolean</span> | | If true, the autocomplete will display a loading indicator. |
| <span class="prop-name">disabled</span> | <span class="prop-type">boolean</span> | | If true, the autocomplete will be disabled. |
Expand Down
40 changes: 38 additions & 2 deletions packages/toolpad-app/src/server/localMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { VersionInfo, checkVersion } from './versionInfo';
import { VERSION_CHECK_INTERVAL } from '../constants';
import DataManager from './DataManager';
import type { RuntimeConfig } from '../config';
import { PAGE_COLUMN_COMPONENT_ID, PAGE_ROW_COMPONENT_ID } from '../runtime/toolpadComponents';

invariant(
isMainThread,
Expand Down Expand Up @@ -720,9 +721,44 @@ function mergePageIntoDom(dom: appDom.AppDom, pageName: string, pageFile: Page):
return dom;
}

function optimizePageElement(element: ElementType): ElementType {
const isLayoutElement = (possibleLayoutElement: ElementType): boolean =>
possibleLayoutElement.component === PAGE_ROW_COMPONENT_ID ||
possibleLayoutElement.component === PAGE_COLUMN_COMPONENT_ID;

if (isLayoutElement(element) && element.children?.length === 1) {
const onlyChild = element.children[0];

if (!isLayoutElement(onlyChild)) {
return optimizePageElement({
...onlyChild,
layout: {
...onlyChild.layout,
columnSize: 1,
},
});
}
}

return {
...element,
children: (element.children ?? []).map(optimizePageElement),
};
}

function optimizePage(page: Page): Page {
return {
...page,
spec: {
...page.spec,
content: page.spec.content?.map(optimizePageElement),
},
};
}

function mergePagesIntoDom(dom: appDom.AppDom, pages: PagesContent): appDom.AppDom {
for (const [name, page] of Object.entries(pages)) {
dom = mergePageIntoDom(dom, name, page);
dom = mergePageIntoDom(dom, name, optimizePage(page));
}
return dom;
}
Expand Down Expand Up @@ -768,7 +804,7 @@ async function writePagesToFiles(root: string, pages: PagesContent) {
await Promise.all(
Object.entries(pages).map(async ([name, page]) => {
const pageFileName = getPageFile(root, name);
await updateYamlFile(pageFileName, page);
await updateYamlFile(pageFileName, optimizePage(page));
}),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import { useDom, useDomApi, useAppState, useAppStateApi } from '../../AppState';
import EditableText from '../../../components/EditableText';
import { ComponentIcon } from '../PageEditor/ComponentCatalog/ComponentCatalogItem';
import { useNodeNameValidation } from '../PagesExplorer/validation';
import {
PAGE_ROW_COMPONENT_ID,
PAGE_COLUMN_COMPONENT_ID,
} from '../../../runtime/toolpadComponents';
import { DomView } from '../../../utils/domView';
import { removePageLayoutNode } from '../pageLayout';

Expand Down Expand Up @@ -104,16 +100,6 @@ function RecursiveSubTree({ dom, root }: { dom: appDom.AppDom; root: appDom.Elem
[dom, root],
);

if (
(root.attributes.component === PAGE_ROW_COMPONENT_ID ||
root.attributes.component === PAGE_COLUMN_COMPONENT_ID) &&
children.length === 1
) {
return children.map((childNode) => (
<RecursiveSubTree key={childNode.id} dom={dom} root={childNode} />
));
}

if (children.length > 0) {
return (
<CustomTreeItem nodeId={root.id} node={root}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ export default function NodeDropArea({

const isPageNode = appDom.isPage(node);
const isPageChild = dropAreaNodeParent ? appDom.isPage(dropAreaNodeParent) : false;

const isPageChildElement = isPageChild && appDom.isElement(node) && !isPageRow(node);
const isPageRowChild = dropAreaNodeParent
? appDom.isElement(dropAreaNodeParent) && isPageRow(dropAreaNodeParent)
: false;
Expand All @@ -198,13 +200,16 @@ export default function NodeDropArea({
if (isPageNode && parentProp && !isEmptySlot) {
return null;
}

const pageAwareParentProp = isPageChild ? 'children' : parentProp;

if (dragOverZone === DROP_ZONE_TOP) {
// Is dragging over page top and is slot
if (
dropAreaNodeParent &&
dropAreaNodeParent.id === dragOverNodeId &&
appDom.isPage(dropAreaNodeParent) &&
parentProp
pageAwareParentProp
) {
const pageFirstChild = appDom.getNodeFirstChild(dom, dropAreaNodeParent, 'children');

Expand Down Expand Up @@ -248,33 +253,41 @@ export default function NodeDropArea({
if (
dropAreaNodeParent &&
dropAreaNodeParent.id === dragOverNodeId &&
parentProp === dragOverSlotParentProp
pageAwareParentProp === dragOverSlotParentProp
) {
const parentLastChild =
parentProp && (appDom.isPage(dropAreaNodeParent) || appDom.isElement(dropAreaNodeParent))
? appDom.getNodeLastChild(dom, dropAreaNodeParent, parentProp)
pageAwareParentProp &&
(appDom.isPage(dropAreaNodeParent) || appDom.isElement(dropAreaNodeParent))
? appDom.getNodeLastChild(dom, dropAreaNodeParent, pageAwareParentProp)
: null;

const isParentLastChild = parentLastChild ? node.id === parentLastChild.id : false;

const parentSlots = dropAreaNodeParentInfo?.slots || null;

const parentFlowDirection =
parentSlots && parentProp && parentSlots[parentProp]?.flowDirection;
parentSlots && pageAwareParentProp && parentSlots[pageAwareParentProp]?.flowDirection;

return parentFlowDirection && isParentLastChild
? getChildNodeHighlightedZone(parentFlowDirection)
: null;
}

// Is dragging over slot center
if (node.id === dragOverNodeId && parentProp && parentProp === dragOverSlotParentProp) {
if (
node.id === dragOverNodeId &&
pageAwareParentProp &&
pageAwareParentProp === dragOverSlotParentProp
) {
if (isPageNode) {
return DROP_ZONE_CENTER;
}

const nodeChildren =
(parentProp && appDom.isElement(node) && dropAreaNodeChildNodes[parentProp]) || [];
(pageAwareParentProp &&
appDom.isElement(node) &&
dropAreaNodeChildNodes[pageAwareParentProp]) ||
[];
return nodeChildren.length === 0 ? DROP_ZONE_CENTER : null;
}
}
Expand Down Expand Up @@ -336,7 +349,21 @@ export default function NodeDropArea({

const isHighlightingCenter = highlightedZone === DROP_ZONE_CENTER;

const highlightRect = isHighlightingCenter && isEmptySlot && slotRect ? slotRect : dropAreaRect;
const highlightRect =
isHighlightingCenter && isEmptySlot && slotRect
? slotRect
: {
...dropAreaRect,
x: isPageChildElement ? dropAreaNodeRect.x : dropAreaRect.x,
width: isPageChildElement ? dropAreaNodeRect.width : dropAreaRect.width,
};

const highlightRelativeRect = {
x: isPageChildElement ? 0 : highlightRelativeX,
y: highlightRelativeY,
width: highlightWidth,
height: highlightHeight,
};

return (
<React.Fragment>
Expand All @@ -349,12 +376,7 @@ export default function NodeDropArea({
}
: {},
)}
highlightRelativeRect={{
x: highlightRelativeX,
y: highlightRelativeY,
width: highlightWidth,
height: highlightHeight,
}}
highlightRelativeRect={highlightRelativeRect}
/>
{isEmptySlot && slotRect ? (
<EmptySlot style={absolutePositionCss(slotRect)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,32 @@ const NodeHudWrapper = styled('div', {
})<{
isOutlineVisible: boolean;
isHoverable: boolean;
}>(({ isOutlineVisible, isHoverable, theme }) => ({
// capture mouse events
pointerEvents: 'initial',
position: 'absolute',
userSelect: 'none',
outline: `1px dotted ${isOutlineVisible ? theme.palette.primary[500] : 'transparent'}`,
zIndex: 80,
[`&:hover, &.${nodeHudClasses.hovered}`]: {
outline: `2px dashed ${isHoverable ? theme.palette.primary[500] : 'transparent'}`,
},
[`.${nodeHudClasses.selected}`]: {
}>(({ isOutlineVisible, isHoverable, theme }) => {
const defaultOutline = `1px dotted ${
isOutlineVisible ? theme.palette.primary[500] : 'transparent'
}`;

return {
// capture mouse events
pointerEvents: 'initial',
position: 'absolute',
height: '100%',
width: '100%',
outline: `2px solid ${theme.palette.primary[500]}`,
left: 0,
top: 0,
userSelect: 'none',
outline: defaultOutline,
zIndex: 80,
},
}));
[`&:hover, &.${nodeHudClasses.hovered}`]: {
outline: `2px dashed ${isHoverable ? theme.palette.primary[500] : defaultOutline}`,
},
[`.${nodeHudClasses.selected}`]: {
position: 'absolute',
height: '100%',
width: '100%',
outline: `2px solid ${theme.palette.primary[500]}`,
left: 0,
top: 0,
zIndex: 80,
},
};
});

const SelectionHintWrapper = styled('div', {
shouldForwardProp: (prop) => prop !== 'hintPosition',
Expand Down
Loading

0 comments on commit faff5db

Please sign in to comment.