Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve visual editor UX #466

Merged
merged 68 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
aa43b2f
Remove current slot system
apedroferreira May 26, 2022
106dccc
Add drag preview to DOM elements
apedroferreira May 26, 2022
36fd699
Recreate dragOver event
apedroferreira May 26, 2022
af5e663
Use page editor context
apedroferreira May 26, 2022
e10e3f2
Add boundary highlighting
apedroferreira May 27, 2022
44d1711
Add center boundary
apedroferreira May 27, 2022
a43d0e2
Drag over behavior for center zones
apedroferreira May 31, 2022
5633752
Fix formatting discrepancies
apedroferreira May 31, 2022
bb7a86a
WIP
apedroferreira Jun 1, 2022
046674a
Fixes and improvements to drag over behavior and highlighting
apedroferreira Jun 2, 2022
9fa9efb
Show dropzone for empty containers
apedroferreira Jun 2, 2022
95d4caf
Automatic row/column creation
apedroferreira Jun 3, 2022
8e89f91
Finalize layout composition drop logic (without indices)
apedroferreira Jun 3, 2022
62386a8
Disable dropping on non-highlighted zones
apedroferreira Jun 3, 2022
169508d
Fix bug when dropping on page
apedroferreira Jun 3, 2022
2069ebc
Fix logic for moving components
apedroferreira Jun 6, 2022
6b5eb42
Place elements in the correct order
apedroferreira Jun 6, 2022
cfdbf69
Fix type errors
apedroferreira Jun 6, 2022
411158c
Fix some bugs that appeared during type fixes
apedroferreira Jun 7, 2022
44f9415
Support drop on empty space + allow dropping inside rows/columns only
apedroferreira Jun 7, 2022
c1d24c4
Fix bug when moving element to deleted container + delete columns wit…
apedroferreira Jun 7, 2022
b6576e1
Handle moving rows and columns
apedroferreira Jun 8, 2022
ae72a18
Refactor available drop zones
apedroferreira Jun 8, 2022
bfded4c
More bug fixes
apedroferreira Jun 8, 2022
d773a30
Disable dragging to column center, enable sides only
apedroferreira Jun 8, 2022
f383a5a
Consider container gap in node overlays
apedroferreira Jun 9, 2022
3f3c862
More uniform highlights and reduce layout frames
apedroferreira Jun 9, 2022
359e6bf
UX improvement for columns
apedroferreira Jun 9, 2022
8b44094
Make cursor size constant
apedroferreira Jun 9, 2022
7ff8765
Improvements and fixes to drag & drop logic
apedroferreira Jun 13, 2022
21d2b54
Fix adding elements vertically in horizontal container
apedroferreira Jun 14, 2022
715e891
Infinitely composable rows and columns
apedroferreira Jun 14, 2022
d435b76
Row/column composition improvements
apedroferreira Jun 15, 2022
3f2bfde
Fix more inconsistencies with dragging layout elements
apedroferreira Jun 15, 2022
9afc1cf
Exclude gap from element selection overlays
apedroferreira Jun 15, 2022
ea7fab1
Fix empty slot size
apedroferreira Jun 17, 2022
f115e5d
Small refactoring
apedroferreira Jun 17, 2022
7238d45
Fix custom components (WIP)
apedroferreira Jun 20, 2022
02ddbb5
Enable custom components and slot props
apedroferreira Jun 22, 2022
8a8ca72
Merge remote-tracking branch 'origin/master' into drag-and-drop-quadr…
apedroferreira Jun 22, 2022
04509d8
Show correct container borders
apedroferreira Jun 23, 2022
cc5902a
Improve selection areas
apedroferreira Jun 23, 2022
d99a648
Calculate gap automatically
apedroferreira Jun 23, 2022
060735e
Fixes to for components with multiple slots
apedroferreira Jun 23, 2022
210d61d
Allow dragging rows to column center
apedroferreira Jun 23, 2022
425b8be
Fix reverse containers
apedroferreira Jun 23, 2022
a68aea1
Merge remote-tracking branch 'origin/master' into drag-and-drop-quadr…
apedroferreira Jun 23, 2022
2639fe3
Merge remote-tracking branch 'origin/master' into drag-and-drop-quadr…
apedroferreira Jun 30, 2022
bcd3483
Fix untyped argument
apedroferreira Jun 30, 2022
ae7ab11
Allow placing components in page edges
apedroferreira Jul 4, 2022
1494a21
Make components interactive again
apedroferreira Jul 4, 2022
c675dfd
Merge remote-tracking branch 'origin/master' into drag-and-drop-quadr…
apedroferreira Jul 4, 2022
ab48716
Improve column behavior for now
apedroferreira Jul 4, 2022
a18b688
Fix wrong position when placing elements between column rows
apedroferreira Jul 4, 2022
f94f86a
Add implicit containers (Jan's PR)
apedroferreira Jul 5, 2022
0d8bc98
Fix column cursor + allow aligment in implicit element containers
apedroferreira Jul 5, 2022
4e27baf
Make column elements take full width
apedroferreira Jul 5, 2022
719a410
Fix empty slots
apedroferreira Jul 5, 2022
0e8de41
Re-implement CSS grid in page columns
apedroferreira Jul 5, 2022
6f3d138
Determine flow direction in grid elements
apedroferreira Jul 5, 2022
504315a
Add padding to Paper component
apedroferreira Jul 5, 2022
f6a496d
Remove Stack from component options
apedroferreira Jul 5, 2022
3873562
Replace enums for drop zones / rectangle edges
apedroferreira Jul 5, 2022
99b70aa
Merge remote-tracking branch 'origin/master' into drag-and-drop-quadr…
apedroferreira Jul 5, 2022
a42d069
Remove unnecessary stylings + only allow dropping in center of empty …
apedroferreira Jul 6, 2022
b72b718
Merge remote-tracking branch 'origin/master' into drag-and-drop-quadr…
apedroferreira Jul 6, 2022
3a64f6c
Disable dragging to inside of single slot elements
apedroferreira Jul 6, 2022
056131c
Fix dropping in empty slots in custom parent props
apedroferreira Jul 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 72 additions & 7 deletions packages/toolpad-app/src/appDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ export function createDom(): AppDom {
}

/**
* Creates a new DOM node representing aReact Element
* Creates a new DOM node representing a React Element
*/
export function createElement<P>(
dom: AppDom,
Expand Down Expand Up @@ -627,15 +627,14 @@ export function addNode<Parent extends AppDomNode, Child extends AppDomNode>(
return setNodeParent(dom, newNode, parent.id, parentProp, parentIndex);
}

export function moveNode(
export function moveNode<Parent extends AppDomNode, Child extends AppDomNode>(
dom: AppDom,
nodeId: NodeId,
parentId: NodeId,
parentProp: string,
node: Child,
parent: Parent,
parentProp: ParentPropOf<Child, Parent>,
parentIndex?: string,
) {
const node = getNode(dom, nodeId);
return setNodeParent(dom, node, parentId, parentProp, parentIndex);
return setNodeParent(dom, node, parent.id, parentProp, parentIndex);
}

export function saveNode(dom: AppDom, node: AppDomNode) {
Expand Down Expand Up @@ -703,6 +702,72 @@ export function getNodeIdByName(dom: AppDom, name: string): NodeId | null {
return index.get(name) ?? null;
}

export function getNewFirstParentIndexInNode(
dom: AppDom,
node: ElementNode | PageNode,
parentProp: string,
) {
const children = (getChildNodes(dom, node) as NodeChildren<ElementNode>)[parentProp] || [];
const firstChild = children.length > 0 ? children[0] : null;

return createFractionalIndex(null, firstChild?.parentIndex || null);
}

export function getNewLastParentIndexInNode(
dom: AppDom,
node: ElementNode | PageNode,
parentProp: string,
) {
const children = (getChildNodes(dom, node) as NodeChildren<ElementNode>)[parentProp] || [];
const lastChild = children.length > 0 ? children[children.length - 1] : null;

return createFractionalIndex(lastChild?.parentIndex || null, null);
}

export function getNewParentIndexBeforeNode(
dom: AppDom,
node: ElementNode | PageNode,
parentProp: string,
) {
const parent = getParent(dom, node);

if (!parent) {
throw new Error(`Invariant: Node: "${node.id}" has no parent`);
}

const parentChildren =
((isPage(parent) || isElement(parent)) &&
(getChildNodes(dom, parent) as NodeChildren<ElementNode>)[parentProp]) ||
[];

const nodeIndex = parentChildren.findIndex((child) => child.id === node.id);
const nodeBefore = nodeIndex > 0 ? parentChildren[nodeIndex - 1] : null;

return createFractionalIndex(nodeBefore?.parentIndex || null, node.parentIndex);
}

export function getNewParentIndexAfterNode(
dom: AppDom,
node: ElementNode | PageNode,
parentProp: string,
) {
const parent = getParent(dom, node);

if (!parent) {
throw new Error(`Invariant: Node: "${node.id}" has no parent`);
}

const parentChildren =
((isPage(parent) || isElement(parent)) &&
(getChildNodes(dom, parent) as NodeChildren<ElementNode>)[parentProp]) ||
[];

const nodeIndex = parentChildren.findIndex((child) => child.id === node.id);
const nodeAfter = nodeIndex < parentChildren.length - 1 ? parentChildren[nodeIndex + 1] : null;

return createFractionalIndex(node.parentIndex, nodeAfter?.parentIndex || null);
}

/**
* We need to make sure no secrets end up in the frontend html, so let's only send the
* nodes that we need to build frontend, and that we know don't contain secrets.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import * as appDom from '../../../appDom';
import { useDom } from '../../DomLoader';
import { usePageEditorApi } from './PageEditorProvider';
import { useToolpadComponents } from '../toolpadComponents';
import { PAGE_ROW_COMPONENT_ID } from '../../../toolpadComponents';
import {
PAGE_COLUMN_COMPONENT_ID,
PAGE_ROW_COMPONENT_ID,
STACK_COMPONENT_ID,
} from '../../../toolpadComponents';

const WIDTH_COLLAPSED = 50;

Expand Down Expand Up @@ -94,7 +98,12 @@ export default function ComponentCatalog({ className }: ComponentCatalogProps) {
<Box sx={{ width: 300, height: '100%', overflow: 'auto' }}>
<Box display="grid" gridTemplateColumns="1fr" gap={1} padding={1}>
{Object.entries(toolpadComponents)
.filter(([componentId]) => componentId !== PAGE_ROW_COMPONENT_ID)
.filter(
([componentId]) =>
![PAGE_ROW_COMPONENT_ID, PAGE_COLUMN_COMPONENT_ID, STACK_COMPONENT_ID].includes(
componentId,
),
)
.map(([componentId, componentType]) => {
if (!componentType) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { NodeId, LiveBindings } from '@mui/toolpad-core';
import * as React from 'react';
import * as appDom from '../../../appDom';
import { SlotLocation, PageViewState } from '../../../types';
import { update, updateOrCreate } from '../../../utils/immutability';
import { PageViewState } from '../../../types';
import { update } from '../../../utils/immutability';

export type ComponentPanelTab = 'component' | 'theme';

export const DROP_ZONE_TOP = 'top';
export const DROP_ZONE_BOTTOM = 'bottom';
export const DROP_ZONE_LEFT = 'left';
export const DROP_ZONE_RIGHT = 'right';
export const DROP_ZONE_CENTER = 'center';
export type DropZone =
| typeof DROP_ZONE_TOP
| typeof DROP_ZONE_BOTTOM
| typeof DROP_ZONE_LEFT
| typeof DROP_ZONE_RIGHT
| typeof DROP_ZONE_CENTER;

export interface PageEditorState {
readonly appId: string;
readonly type: 'page';
Expand All @@ -14,7 +26,9 @@ export interface PageEditorState {
readonly componentPanelTab: ComponentPanelTab;
readonly newNode: appDom.ElementNode | null;
readonly highlightLayout: boolean;
readonly highlightedSlot: SlotLocation | null;
readonly dragOverNodeId: NodeId | null;
readonly dragOverSlotParentProp: string | null;
readonly dragOverZone: DropZone | null;
readonly viewState: PageViewState;
readonly pageState: Record<string, unknown>;
readonly bindings: LiveBindings;
Expand Down Expand Up @@ -42,7 +56,11 @@ export type PageEditorAction =
}
| {
type: 'PAGE_NODE_DRAG_OVER';
slot: SlotLocation | null;
dragOverState: {
nodeId: NodeId | null;
parentProp: string | null;
zone: DropZone | null;
};
}
| {
type: 'PAGE_NODE_DRAG_END';
Expand All @@ -69,7 +87,9 @@ export function createPageEditorState(appId: string, nodeId: NodeId): PageEditor
componentPanelTab: 'component',
newNode: null,
highlightLayout: false,
highlightedSlot: null,
dragOverNodeId: null,
dragOverSlotParentProp: null,
dragOverZone: null,
viewState: { nodes: {} },
pageState: {},
bindings: {},
Expand Down Expand Up @@ -111,12 +131,16 @@ export function pageEditorReducer(
return update(state, {
newNode: null,
highlightLayout: false,
highlightedSlot: null,
dragOverNodeId: null,
});
case 'PAGE_NODE_DRAG_OVER': {
const { nodeId, parentProp, zone } = action.dragOverState;

return update(state, {
highlightLayout: true,
highlightedSlot: action.slot ? updateOrCreate(state.highlightedSlot, action.slot) : null,
dragOverNodeId: nodeId,
dragOverSlotParentProp: parentProp,
dragOverZone: zone,
});
}
case 'PAGE_VIEW_STATE_UPDATE': {
Expand Down Expand Up @@ -156,10 +180,18 @@ function createPageEditorApi(dispatch: React.Dispatch<PageEditorAction>) {
nodeDragEnd() {
dispatch({ type: 'PAGE_NODE_DRAG_END' });
},
nodeDragOver(slot: SlotLocation | null) {
nodeDragOver({
nodeId,
parentProp,
zone,
}: {
nodeId: NodeId | null;
parentProp: string | null;
zone: DropZone | null;
}) {
dispatch({
type: 'PAGE_NODE_DRAG_OVER',
slot,
dragOverState: { nodeId, parentProp, zone },
});
},
pageViewStateUpdate(viewState: PageViewState) {
Expand Down
Loading