mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-27 00:19:14 -05:00
refactor: renames node.position to node.tile
This commit is contained in:
@@ -18,22 +18,22 @@ The `initialScene` object contains the following properties:
|
||||
name: string;
|
||||
url: string;
|
||||
collection?: string;
|
||||
isIsometric?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Notes on icons:**
|
||||
- `collection` is an optional property that can be used to group icons together in the icon picker. All icons with the same `collection` will be grouped together.
|
||||
- For a list of standard icon `id`s, see [Isopacks](/docs/isopacks#icon-ids).
|
||||
- `collection` is an optional property that can be used to group icons together in the icon picker. All icons with the same `collection` will be grouped together under a heading.
|
||||
|
||||
## `Node`
|
||||
|
||||
```js
|
||||
{
|
||||
id: string;
|
||||
iconId: string;
|
||||
icon: string;
|
||||
label?: string;
|
||||
labelHeight?: number;
|
||||
position: {
|
||||
tile: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
@@ -100,29 +100,29 @@ Open this example in [CodeSandbox](https://codesandbox.io/p/sandbox/github/markm
|
||||
nodes: [
|
||||
{
|
||||
id: 'database',
|
||||
iconId: 'storage',
|
||||
icon: 'storage',
|
||||
label: 'Database',
|
||||
position: {
|
||||
tile: {
|
||||
x: 2,
|
||||
y: -2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'server',
|
||||
iconId: 'server',
|
||||
icon: 'server',
|
||||
label: 'Server',
|
||||
labelHeight: 100,
|
||||
position: {
|
||||
tile: {
|
||||
x: 2,
|
||||
y: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'client',
|
||||
iconId: 'laptop',
|
||||
icon: 'laptop',
|
||||
label: 'Client',
|
||||
labelHeight: 100,
|
||||
position: {
|
||||
tile: {
|
||||
x: -1,
|
||||
y: 0
|
||||
}
|
||||
@@ -165,6 +165,6 @@ Open this example in [CodeSandbox](https://codesandbox.io/p/sandbox/github/markm
|
||||
|
||||
Examples of common errors are as follows:
|
||||
- A `connector` references a `nodeId` that does not exist in the `nodes` array.
|
||||
- A `node` references an `iconId` that does not exist in the `icons` array.
|
||||
- A `node` references an `icon` that does not exist in the `icons` array.
|
||||
- A `rectangle` has a `from` but not a `to` property.
|
||||
- A `connector` has less than 2 anchors.
|
||||
@@ -2,14 +2,14 @@ import React from 'react';
|
||||
import { Coords } from 'src/types';
|
||||
|
||||
interface Props {
|
||||
position: Coords;
|
||||
tile: Coords;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export const Circle = ({
|
||||
position,
|
||||
tile,
|
||||
radius,
|
||||
...rest
|
||||
}: Props & React.SVGProps<SVGCircleElement>) => {
|
||||
return <circle cx={position.x} cy={position.y} r={radius} {...rest} />;
|
||||
return <circle cx={tile.x} cy={tile.y} r={radius} {...rest} />;
|
||||
};
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { NodeContextMenu } from 'src/components/ContextMenu/NodeContextMenu';
|
||||
import { EmptyTileContextMenu } from 'src/components/ContextMenu/EmptyTileContextMenu';
|
||||
|
||||
export const ContextMenuLayer = () => {
|
||||
const contextMenu = useUiStateStore((state) => {
|
||||
return state.contextMenu;
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
}}
|
||||
>
|
||||
{contextMenu?.type === 'NODE' && (
|
||||
<NodeContextMenu key={contextMenu.id} nodeId={contextMenu.id} />
|
||||
)}
|
||||
{contextMenu?.type === 'EMPTY_TILE' && (
|
||||
<EmptyTileContextMenu
|
||||
key={contextMenu.position.toString()}
|
||||
onAddNode={() => {}}
|
||||
position={contextMenu.position}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Add as AddIcon } from '@mui/icons-material';
|
||||
import { Coords } from 'src/types';
|
||||
import { ContextMenu } from './components/ContextMenu';
|
||||
import { ContextMenuItem } from './components/ContextMenuItem';
|
||||
|
||||
interface Props {
|
||||
onAddNode: () => void;
|
||||
position: Coords;
|
||||
}
|
||||
|
||||
export const EmptyTileContextMenu = ({ onAddNode, position }: Props) => {
|
||||
return (
|
||||
<ContextMenu position={position}>
|
||||
<ContextMenuItem
|
||||
onClick={onAddNode}
|
||||
icon={<AddIcon />}
|
||||
label="Add node"
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ArrowRightAlt as ConnectIcon,
|
||||
Delete as DeleteIcon
|
||||
} from '@mui/icons-material';
|
||||
import { useNode } from 'src/hooks/useNode';
|
||||
import { ContextMenu } from './components/ContextMenu';
|
||||
import { ContextMenuItem } from './components/ContextMenuItem';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const NodeContextMenu = ({ nodeId }: Props) => {
|
||||
const node = useNode(nodeId);
|
||||
|
||||
if (!node) return null;
|
||||
|
||||
return (
|
||||
<ContextMenu position={node.position}>
|
||||
<ContextMenuItem
|
||||
onClick={() => {}}
|
||||
icon={<ConnectIcon />}
|
||||
label="Connect"
|
||||
/>
|
||||
<ContextMenuItem
|
||||
onClick={() => {}}
|
||||
icon={<DeleteIcon />}
|
||||
label="Remove"
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import { Coords } from 'src/types';
|
||||
import gsap from 'gsap';
|
||||
// import { List, Box, Card } from '@mui/material';
|
||||
// import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
// import { getTileScreenPosition } from 'src/renderer/utils/gridHelpers';
|
||||
// import { useSceneStore } from 'src/stores/useSceneStore';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
position: Coords;
|
||||
}
|
||||
|
||||
const COLOR = 'grey.900';
|
||||
const ARROW = {
|
||||
size: 11,
|
||||
top: 8
|
||||
};
|
||||
|
||||
export const ContextMenu = ({ position, children }: Props) => {
|
||||
return null;
|
||||
// const [firstDisplay, setFirstDisplay] = useState(false);
|
||||
// const container = useRef<HTMLDivElement>();
|
||||
// const scroll = useUiStateStore((state) => state.scroll);
|
||||
// const zoom = useUiStateStore((state) => state.zoom);
|
||||
// const gridSize = useSceneStore((state) => state.gridSize);
|
||||
|
||||
// const { position: scrollPosition } = scroll;
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!container.current) return;
|
||||
|
||||
// const screenPosition = getTileScreenPosition({
|
||||
// position,
|
||||
// scrollPosition,
|
||||
// zoom
|
||||
// });
|
||||
|
||||
// gsap.to(container.current, {
|
||||
// duration: firstDisplay ? 0.1 : 0,
|
||||
// x: screenPosition.x,
|
||||
// y: screenPosition.y
|
||||
// });
|
||||
|
||||
// if (firstDisplay) {
|
||||
// // The context menu subtly slides in from the left when it is first displayed.
|
||||
// gsap.to(container.current, {
|
||||
// duration: 0.2,
|
||||
// opacity: 1
|
||||
// });
|
||||
// }
|
||||
|
||||
// setFirstDisplay(true);
|
||||
// }, [position, scrollPosition, zoom, firstDisplay, gridSize]);
|
||||
|
||||
// return (
|
||||
// <Box
|
||||
// ref={container}
|
||||
// sx={{
|
||||
// position: 'absolute',
|
||||
// opacity: 0,
|
||||
// marginLeft: '15px',
|
||||
// marginTop: '-20px',
|
||||
// whiteSpace: 'nowrap'
|
||||
// }}
|
||||
// >
|
||||
// <Box
|
||||
// sx={{
|
||||
// position: 'absolute',
|
||||
// left: -(ARROW.size - 2),
|
||||
// top: ARROW.top,
|
||||
// width: 0,
|
||||
// height: 0,
|
||||
// borderTop: `${ARROW.size}px solid transparent`,
|
||||
// borderBottom: `${ARROW.size}px solid transparent`,
|
||||
// borderRight: `${ARROW.size}px solid`,
|
||||
// borderRightColor: COLOR
|
||||
// }}
|
||||
// />
|
||||
// <Card sx={{ borderRadius: 2 }}>
|
||||
// <List sx={{ p: 0 }}>{children}</List>
|
||||
// </Card>
|
||||
// </Box>
|
||||
// );
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
interface Props {
|
||||
onClick: () => void;
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const ContextMenuItem = ({ onClick, icon, label }: Props) => {
|
||||
const uiStateActions = useUiStateStore((state) => state.actions);
|
||||
|
||||
const onClickProxy = useCallback(() => {
|
||||
onClick();
|
||||
uiStateActions.setContextMenu(null);
|
||||
}, [onClick, uiStateActions]);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
sx={{
|
||||
p: 0,
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: '1px solid',
|
||||
borderBottomColor: 'grey.800'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemButton onClick={onClickProxy} sx={{ py: 0.5, px: 2 }}>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
pr: 1,
|
||||
minWidth: 'auto',
|
||||
color: 'grey.400',
|
||||
svg: {
|
||||
maxWidth: 18,
|
||||
maxHeight: 18
|
||||
}
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText secondary={label} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
@@ -65,7 +65,7 @@ export const NodeControls = ({ id }: Props) => {
|
||||
key={node.id}
|
||||
iconCategories={iconCategories}
|
||||
onClick={(icon) => {
|
||||
onNodeUpdated({ iconId: icon.id });
|
||||
onNodeUpdated({ icon: icon.id });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -123,13 +123,13 @@ export const Connector = ({ connector }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Circle
|
||||
position={CoordsUtils.add(anchor, drawOffset)}
|
||||
tile={CoordsUtils.add(anchor, drawOffset)}
|
||||
radius={18 * zoom}
|
||||
fill={theme.palette.common.white}
|
||||
fillOpacity={0.7}
|
||||
/>
|
||||
<Circle
|
||||
position={CoordsUtils.add(anchor, drawOffset)}
|
||||
tile={CoordsUtils.add(anchor, drawOffset)}
|
||||
radius={12 * zoom}
|
||||
stroke={theme.palette.common.black}
|
||||
fill={theme.palette.common.white}
|
||||
|
||||
@@ -15,14 +15,14 @@ interface Props {
|
||||
export const Node = ({ node, order }: Props) => {
|
||||
const { projectedTileSize } = useTileSize();
|
||||
const { getTilePosition } = useGetTilePosition();
|
||||
const { iconComponent } = useIcon(node.iconId);
|
||||
const { iconComponent } = useIcon(node.icon);
|
||||
|
||||
const position = useMemo(() => {
|
||||
return getTilePosition({
|
||||
tile: node.position,
|
||||
tile: node.tile,
|
||||
origin: TileOriginEnum.BOTTOM
|
||||
});
|
||||
}, [node.position, getTilePosition]);
|
||||
}, [node.tile, getTilePosition]);
|
||||
|
||||
const label = useMemo(() => {
|
||||
if (node.label === undefined || node.label === '<p><br></p>') return null;
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Nodes = () => {
|
||||
return (
|
||||
<Node
|
||||
key={node.id}
|
||||
order={-node.position.x - node.position.y}
|
||||
order={-node.tile.x - node.tile.y}
|
||||
node={node}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -9,5 +9,5 @@ interface Props {
|
||||
export const NodeTransformControls = ({ id }: Props) => {
|
||||
const node = useNode(id);
|
||||
|
||||
return <TransformControls from={node.position} to={node.position} />;
|
||||
return <TransformControls from={node.tile} to={node.tile} />;
|
||||
};
|
||||
|
||||
@@ -8,9 +8,9 @@ const initialScene: InitialScene = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'server',
|
||||
label: '<p>This is an example of tracking changes to the scene.</p>',
|
||||
iconId: 'server',
|
||||
position: {
|
||||
label: '<p>Changes to the scene are tracked bottom left.</p>',
|
||||
icon: 'server',
|
||||
tile: {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
@@ -20,29 +20,29 @@ export const initialScene: InitialScene = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'database',
|
||||
iconId: 'storage',
|
||||
icon: 'storage',
|
||||
label: 'Database',
|
||||
position: {
|
||||
tile: {
|
||||
x: 2,
|
||||
y: -2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'server',
|
||||
iconId: 'server',
|
||||
icon: 'server',
|
||||
label: 'Server',
|
||||
labelHeight: 100,
|
||||
position: {
|
||||
tile: {
|
||||
x: 2,
|
||||
y: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'client',
|
||||
iconId: 'laptop',
|
||||
icon: 'laptop',
|
||||
label: 'Client',
|
||||
labelHeight: 100,
|
||||
position: {
|
||||
tile: {
|
||||
x: -1,
|
||||
y: 0
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const useDiagramUtils = () => {
|
||||
const positions = items.reduce<Coords[]>((acc, item) => {
|
||||
switch (item.type) {
|
||||
case 'NODE':
|
||||
return [...acc, item.position];
|
||||
return [...acc, item.tile];
|
||||
case 'CONNECTOR':
|
||||
return [
|
||||
...acc,
|
||||
|
||||
@@ -12,7 +12,7 @@ const dragItems = (
|
||||
const node = getItemById(scene.nodes, item.id).item;
|
||||
|
||||
scene.actions.updateNode(item.id, {
|
||||
position: CoordsUtils.add(node.position, delta)
|
||||
tile: CoordsUtils.add(node.tile, delta)
|
||||
});
|
||||
} else if (item.type === 'RECTANGLE') {
|
||||
const rectangle = getItemById(scene.rectangles, item.id).item;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// if (!uiState.mode.isDragging) {
|
||||
// const { mousedown } = uiState.mouse;
|
||||
// const items = scene.nodes.filter((node) => {
|
||||
// return CoordsUtils.isEqual(node.position, mousedown.tile);
|
||||
// return CoordsUtils.isEqual(node.tile, mousedown.tile);
|
||||
// });
|
||||
|
||||
// // User is creating a selection
|
||||
|
||||
@@ -28,8 +28,8 @@ export const PlaceElement: ModeActions = {
|
||||
if (uiState.mode.icon !== null) {
|
||||
scene.actions.createNode({
|
||||
id: generateId(),
|
||||
iconId: uiState.mode.icon.id,
|
||||
position: uiState.mouse.position.tile
|
||||
icon: uiState.mode.icon.id,
|
||||
tile: uiState.mouse.position.tile
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
12
src/tests/fixtures/scene.ts
vendored
12
src/tests/fixtures/scene.ts
vendored
@@ -17,8 +17,8 @@ export const scene: SceneInput = {
|
||||
{
|
||||
id: 'node1',
|
||||
label: 'Node1',
|
||||
iconId: 'icon1',
|
||||
position: {
|
||||
icon: 'icon1',
|
||||
tile: {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
@@ -26,8 +26,8 @@ export const scene: SceneInput = {
|
||||
{
|
||||
id: 'node2',
|
||||
label: 'Node2',
|
||||
iconId: 'icon2',
|
||||
position: {
|
||||
icon: 'icon2',
|
||||
tile: {
|
||||
x: 1,
|
||||
y: 1
|
||||
}
|
||||
@@ -35,8 +35,8 @@ export const scene: SceneInput = {
|
||||
{
|
||||
id: 'node3',
|
||||
label: 'Node3',
|
||||
iconId: 'icon1',
|
||||
position: {
|
||||
icon: 'icon1',
|
||||
tile: {
|
||||
x: 2,
|
||||
y: 2
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ export enum SceneItemTypeEnum {
|
||||
export interface Node {
|
||||
id: string;
|
||||
type: SceneItemTypeEnum.NODE;
|
||||
iconId: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
labelHeight: number;
|
||||
position: Coords;
|
||||
tile: Coords;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ export const nodeInputToNode = (nodeInput: NodeInput): Node => {
|
||||
id: nodeInput.id,
|
||||
label: nodeInput.label ?? NODE_DEFAULTS.label,
|
||||
labelHeight: nodeInput.labelHeight ?? NODE_DEFAULTS.labelHeight,
|
||||
iconId: nodeInput.iconId,
|
||||
position: nodeInput.position,
|
||||
icon: nodeInput.icon,
|
||||
tile: nodeInput.tile,
|
||||
isSelected: false
|
||||
};
|
||||
};
|
||||
@@ -201,10 +201,10 @@ export const iconToIconInput = (icon: Icon): IconInput => {
|
||||
export const nodeToNodeInput = (node: Node): NodeInput => {
|
||||
return {
|
||||
id: node.id,
|
||||
position: node.position,
|
||||
tile: node.tile,
|
||||
label: node.label,
|
||||
labelHeight: node.labelHeight,
|
||||
iconId: node.iconId
|
||||
icon: node.icon
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ export const getAnchorPosition = ({
|
||||
}: GetAnchorPositions): Coords => {
|
||||
if (anchor.ref.type === 'NODE') {
|
||||
const { item: node } = getItemById(nodes, anchor.ref.id);
|
||||
return node.position;
|
||||
return node.tile;
|
||||
}
|
||||
|
||||
if (anchor.ref.type === 'ANCHOR') {
|
||||
@@ -481,8 +481,8 @@ export const getItemAtTile = ({
|
||||
tile,
|
||||
scene
|
||||
}: GetItemAtTile): SceneItem | null => {
|
||||
const node = scene.nodes.find(({ position }) => {
|
||||
return CoordsUtils.isEqual(position, tile);
|
||||
const node = scene.nodes.find((n) => {
|
||||
return CoordsUtils.isEqual(n.tile, tile);
|
||||
});
|
||||
|
||||
if (node) return node;
|
||||
|
||||
@@ -18,8 +18,8 @@ export const nodeInput = z.object({
|
||||
id: z.string(),
|
||||
label: z.string().optional(),
|
||||
labelHeight: z.number().optional(),
|
||||
iconId: z.string(),
|
||||
position: coords
|
||||
icon: z.string(),
|
||||
tile: coords
|
||||
});
|
||||
|
||||
export const connectorAnchorInput = z
|
||||
|
||||
@@ -13,11 +13,11 @@ describe('scene validation works correctly', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('node with invalid iconId fails validation', () => {
|
||||
test('node with invalid icon fails validation', () => {
|
||||
const invalidNode = {
|
||||
id: 'invalidNode',
|
||||
iconId: 'doesntExist',
|
||||
position: { x: -1, y: -1 }
|
||||
icon: 'doesntExist',
|
||||
tile: { x: -1, y: -1 }
|
||||
};
|
||||
const scene = produce(sceneFixture, (draft) => {
|
||||
draft.nodes.push(invalidNode);
|
||||
|
||||
@@ -9,10 +9,10 @@ import { getAllAnchorsFromInput, getItemById } from 'src/utils';
|
||||
|
||||
export const ensureValidNode = (node: NodeInput, scene: SceneInput) => {
|
||||
try {
|
||||
getItemById(scene.icons, node.iconId);
|
||||
getItemById(scene.icons, node.icon);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Found invalid node [id: "${node.id}"] referencing an invalid icon [id: "${node.iconId}"]`
|
||||
`Found invalid node [id: "${node.id}"] referencing an invalid icon [id: "${node.icon}"]`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user