refactor: propagates all naming of groups to rectangles

This commit is contained in:
Mark Mankarious
2023-08-18 12:07:15 +01:00
parent 514dd7a6b6
commit cdaaea4c3e
24 changed files with 137 additions and 117 deletions

View File

@@ -16,19 +16,19 @@ MIT licence | Source available on [Github](https://github.com/markmanx/isoflow)
- **Real-time:** Display real-time data on diagrams.
- **Customizable:** Use your own isometric icon packs, or use our free set of networking icons (also under MIT).
- **Export options:** Export diagrams as images, JSON or YAML.
- **Powerful annotation tools:** Annotate nodes, groups and connectors.
- **Powerful annotation tools:** Annotate nodes, rectangles and connectors.
- **Step-by-step walkthroughs:** Create interactive tours of large diagrams to help viewers easily digest information.
## Roadmap
Migration to open-source: ██░░░░░░░░░
Version 1: ██████████░░
- [x] Set up automated publishing to NPM registry
- [ ] Migrate private JS project to public Typescript project
- [x] Pan / Select / Zoom modes
- [x] Display icons in itemControls
- [ ] Node controls
- [ ] Group controls
- [x] Node controls
- [ ] Rectangle controls
- [ ] Connector controls
- [ ] Publish icons as separate importable package
@@ -62,7 +62,7 @@ const scene = {
},
],
connectors: [],
groups: []
rectangles: []
}
const App = () => (

View File

@@ -9,7 +9,7 @@ import {
IconInput,
NodeInput,
ConnectorInput,
GroupInput,
RectangleInput,
Scene
} from 'src/types';
import { sceneToSceneInput } from 'src/utils';
@@ -47,8 +47,8 @@ const App = ({
const prevInitialScene = useRef<SceneInput>(EMPTY_SCENE);
const [isReady, setIsReady] = useState(false);
useWindowUtils();
const scene = useSceneStore(({ nodes, connectors, groups, icons }) => {
return { nodes, connectors, groups, icons };
const scene = useSceneStore(({ nodes, connectors, rectangles, icons }) => {
return { nodes, connectors, rectangles, icons };
}, shallow);
const sceneActions = useSceneStore((state) => {
return state.actions;
@@ -144,7 +144,7 @@ export {
SceneInput,
IconInput,
NodeInput,
GroupInput,
RectangleInput,
ConnectorInput,
useIsoflow,
LabelContainer,

View File

@@ -1,25 +0,0 @@
import React from 'react';
import { useSceneStore } from 'src/stores/sceneStore';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { DEFAULT_COLOR } from 'src/config';
import { Group } from './Group/Group';
export const Groups = () => {
const groups = useSceneStore((state) => {
return state.groups;
});
const mode = useUiStateStore((state) => {
return state.mode;
});
return (
<>
{groups.map((group) => {
return <Group key={group.id} {...group} />;
})}
{mode.type === 'RECTANGLE.DRAW' && mode.area && (
<Group from={mode.area.from} to={mode.area.to} color={DEFAULT_COLOR} />
)}
</>
);
};

View File

@@ -1,5 +1,5 @@
// import { useRef, useCallback } from 'react';
// import { Group, Shape } from 'paper';
// import { Rectangle, Shape } from 'paper';
// import gsap from 'gsap';
// import { Coords } from 'src/types';
// import { UNPROJECTED_TILE_SIZE, PIXEL_UNIT } from 'src/renderer/utils/constants';
@@ -11,7 +11,7 @@
// import { applyProjectionMatrix } from 'src/renderer/utils/projection';
// export const useLasso = () => {
// const containerRef = useRef(new Group());
// const containerRef = useRef(new Rectangle());
// const shapeRef = useRef<paper.Shape.Rectangle>();
// const setSelection = useCallback((startTile: Coords, endTile: Coords) => {

View File

@@ -10,7 +10,7 @@ interface Props {
color: string;
}
export const Group = ({ from, to, color }: Props) => {
export const Rectangle = ({ from, to, color }: Props) => {
const zoom = useUiStateStore((state) => {
return state.zoom;
});

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useSceneStore } from 'src/stores/sceneStore';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { DEFAULT_COLOR } from 'src/config';
import { Rectangle } from './Rectangle/Rectangle';
export const Rectangles = () => {
const rectangles = useSceneStore((state) => {
return state.rectangles;
});
const mode = useUiStateStore((state) => {
return state.mode;
});
return (
<>
{rectangles.map((rectangle) => {
return <Rectangle key={rectangle.id} {...rectangle} />;
})}
{mode.type === 'RECTANGLE.DRAW' && mode.area && (
<Rectangle
from={mode.area.from}
to={mode.area.to}
color={DEFAULT_COLOR}
/>
)}
</>
);
};

View File

@@ -5,7 +5,7 @@ import { useInteractionManager } from 'src/interaction/useInteractionManager';
import { Grid } from 'src/components/Grid/Grid';
import { Cursor } from 'src/components/Cursor/Cursor';
import { Nodes } from 'src/components/Nodes/Nodes';
import { Groups } from 'src/components/Groups/Groups';
import { Rectangles } from 'src/components/Rectangles/Rectangles';
import { Connectors } from 'src/components/Connectors/Connectors';
import { DebugUtils } from 'src/components/DebugUtils/DebugUtils';
import { useResizeObserver } from 'src/hooks/useResizeObserver';
@@ -59,7 +59,7 @@ export const Renderer = () => {
</SceneLayer>
)}
<SceneLayer>
<Groups />
<Rectangles />
</SceneLayer>
<SceneLayer>
<Connectors />

View File

@@ -31,5 +31,5 @@ export const EMPTY_SCENE: SceneInput = {
icons: [],
nodes: [],
connectors: [],
groups: []
rectangles: []
};

View File

@@ -9,7 +9,7 @@ export const BasicEditor = () => {
icons,
nodes: [],
connectors: [],
groups: []
rectangles: []
}}
height="100%"
/>

View File

@@ -27,7 +27,7 @@ export const Callbacks = () => {
}
],
connectors: [],
groups: []
rectangles: []
};
}, []);

View File

@@ -8,9 +8,9 @@ export const DebugTools = () => {
initialScene={{
icons,
connectors: [],
groups: [
rectangles: [
{
id: 'group1',
id: 'rectangle1',
from: {
x: 5,
y: 5

View File

@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useSceneStore } from 'src/stores/sceneStore';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { Size, Coords, Node, Group, Connector } from 'src/types';
import { Size, Coords, Node, Rectangle, Connector } from 'src/types';
import {
getBoundingBox,
getBoundingBoxSize,
@@ -16,10 +16,10 @@ import { MAX_ZOOM } from 'src/config';
const BOUNDING_BOX_PADDING = 3;
export const useDiagramUtils = () => {
const scene = useSceneStore(({ nodes, groups, connectors, icons }) => {
const scene = useSceneStore(({ nodes, rectangles, connectors, icons }) => {
return {
nodes,
groups,
rectangles,
connectors,
icons
};
@@ -34,7 +34,7 @@ export const useDiagramUtils = () => {
const { getTilePosition } = useGetTilePosition();
const getProjectBounds = useCallback(
(items: (Node | Group | Connector)[]): Coords[] => {
(items: (Node | Rectangle | Connector)[]): Coords[] => {
const positions = items.reduce<Coords[]>((acc, item) => {
switch (item.type) {
case 'NODE':
@@ -46,7 +46,7 @@ export const useDiagramUtils = () => {
return getAnchorPosition({ anchor, nodes: scene.nodes });
})
];
case 'GROUP':
case 'RECTANGLE':
return [...acc, item.from, item.to];
default:
return acc;
@@ -67,7 +67,7 @@ export const useDiagramUtils = () => {
const projectBounds = getProjectBounds([
...scene.nodes,
...scene.connectors,
...scene.groups
...scene.rectangles
]);
const cornerPositions = projectBounds.map((corner) => {

View File

@@ -3,10 +3,10 @@ import { useSceneStore } from 'src/stores/sceneStore';
import { useDiagramUtils } from 'src/hooks/useDiagramUtils';
export const useWindowUtils = () => {
const scene = useSceneStore(({ nodes, groups, connectors, icons }) => {
const scene = useSceneStore(({ nodes, rectangles, connectors, icons }) => {
return {
nodes,
groups,
rectangles,
connectors,
icons
};

View File

@@ -43,20 +43,19 @@ export const Cursor: ModeActions = {
mouseup: ({ uiState, scene, isRendererInteraction }) => {
if (uiState.mode.type !== 'CURSOR' || !isRendererInteraction) return;
if (
uiState.mode.mousedownItem &&
uiState.mode.mousedownItem.type === 'NODE'
) {
const { item: node } = getItemById(
scene.nodes,
uiState.mode.mousedownItem.id
);
if (uiState.mode.mousedownItem) {
if (uiState.mode.mousedownItem.type === 'NODE') {
const { item: node } = getItemById(
scene.nodes,
uiState.mode.mousedownItem.id
);
uiState.actions.setContextMenu(node);
uiState.actions.setItemControls({
type: ItemControlsTypeEnum.SINGLE_NODE,
nodeId: node.id
});
uiState.actions.setContextMenu(node);
uiState.actions.setItemControls({
type: ItemControlsTypeEnum.SINGLE_NODE,
nodeId: node.id
});
}
} else {
// Empty tile selected
uiState.actions.setContextMenu({

View File

@@ -34,11 +34,14 @@ export const DragItems: ModeActions = {
scene.actions.updateNode(item.id, {
position: uiState.mouse.position.tile
});
} else if (item.type === 'GROUP' && uiState.mouse.delta?.tile) {
const { item: group } = getItemById(scene.groups, item.id);
const newFrom = CoordsUtils.add(group.from, uiState.mouse.delta.tile);
const newTo = CoordsUtils.add(group.to, uiState.mouse.delta.tile);
// const bounds = getBoundingBox([group.from, group.to]);
} else if (item.type === 'RECTANGLE' && uiState.mouse.delta?.tile) {
const { item: rectangle } = getItemById(scene.rectangles, item.id);
const newFrom = CoordsUtils.add(
rectangle.from,
uiState.mouse.delta.tile
);
const newTo = CoordsUtils.add(rectangle.to, uiState.mouse.delta.tile);
// const bounds = getBoundingBox([rectangle.from, rectangle.to]);
// scene.nodes.forEach((node) => {
// if (isWithinBounds(node.position, bounds)) {
@@ -48,7 +51,7 @@ export const DragItems: ModeActions = {
// }
// });
scene.actions.updateGroup(item.id, { from: newFrom, to: newTo });
scene.actions.updateRectangle(item.id, { from: newFrom, to: newTo });
}
});

View File

@@ -47,7 +47,7 @@ export const DrawRectangle: ModeActions = {
)
return;
scene.actions.createGroup({
scene.actions.createRectangle({
id: generateId(),
color: DEFAULT_COLOR,
from: uiState.mode.area.from,

View File

@@ -6,7 +6,7 @@ import { sceneInput } from 'src/validation/scene';
import {
getItemById,
getConnectorPath,
groupInputToGroup,
rectangleInputToRectangle,
connectorInputToConnector,
sceneInputtoScene,
nodeInputToNode
@@ -17,7 +17,7 @@ const initialState = () => {
return {
nodes: [],
connectors: [],
groups: [],
rectangles: [],
icons: [],
actions: {
setScene: (scene) => {
@@ -32,7 +32,7 @@ const initialState = () => {
set({
nodes: scene.nodes,
connectors: scene.connectors,
groups: scene.groups
rectangles: scene.rectangles
});
},
@@ -86,17 +86,20 @@ const initialState = () => {
set({ connectors: newScene.connectors });
},
updateGroup: (id, updates) => {
updateRectangle: (id, updates) => {
const newScene = produce(get(), (draftState) => {
const { item: group, index } = getItemById(draftState.groups, id);
const { item: rectangle, index } = getItemById(
draftState.rectangles,
id
);
draftState.groups[index] = {
...group,
draftState.rectangles[index] = {
...rectangle,
...updates
};
});
set({ groups: newScene.groups });
set({ rectangles: newScene.rectangles });
},
createConnector: (connector) => {
@@ -109,12 +112,12 @@ const initialState = () => {
set({ connectors: newScene.connectors });
},
createGroup: (group) => {
createRectangle: (rectangle) => {
const newScene = produce(get(), (draftState) => {
draftState.groups.push(groupInputToGroup(group));
draftState.rectangles.push(rectangleInputToRectangle(rectangle));
});
set({ groups: newScene.groups });
set({ rectangles: newScene.rectangles });
}
}
};

View File

@@ -52,5 +52,5 @@ export const scene: SceneInput = {
anchors: [{ nodeId: 'node2' }, { nodeId: 'node3' }]
}
],
groups: [{ id: 'group1', from: { x: 0, y: 0 }, to: { x: 2, y: 2 } }]
rectangles: [{ id: 'rectangle1', from: { x: 0, y: 0 }, to: { x: 2, y: 2 } }]
};

View File

@@ -4,7 +4,7 @@ import {
nodeInput,
connectorAnchorInput,
connectorInput,
groupInput
rectangleInput
} from 'src/validation/sceneItems';
import { sceneInput } from 'src/validation/scene';
@@ -12,5 +12,5 @@ export type IconInput = z.infer<typeof iconInput>;
export type NodeInput = z.infer<typeof nodeInput>;
export type ConnectorAnchorInput = z.infer<typeof connectorAnchorInput>;
export type ConnectorInput = z.infer<typeof connectorInput>;
export type GroupInput = z.infer<typeof groupInput>;
export type RectangleInput = z.infer<typeof rectangleInput>;
export type SceneInput = z.infer<typeof sceneInput>;

View File

@@ -2,7 +2,7 @@ import { Coords, Size } from './common';
import {
IconInput,
SceneInput,
GroupInput,
RectangleInput,
ConnectorInput,
NodeInput
} from './inputs';
@@ -18,7 +18,7 @@ export enum TileOriginEnum {
export enum SceneItemTypeEnum {
NODE = 'NODE',
CONNECTOR = 'CONNECTOR',
GROUP = 'GROUP'
RECTANGLE = 'RECTANGLE'
}
export interface Node {
@@ -54,15 +54,15 @@ export interface Connector {
};
}
export interface Group {
type: SceneItemTypeEnum.GROUP;
export interface Rectangle {
type: SceneItemTypeEnum.RECTANGLE;
id: string;
color: string;
from: Coords;
to: Coords;
}
export type SceneItem = Node | Connector | Group;
export type SceneItem = Node | Connector | Rectangle;
export type SceneItemReference = {
type: SceneItemTypeEnum;
id: string;
@@ -75,16 +75,16 @@ export interface SceneActions {
updateScene: (scene: Scene) => void;
updateNode: (id: string, updates: Partial<Node>) => void;
updateConnector: (id: string, updates: Partial<Connector>) => void;
updateGroup: (id: string, updates: Partial<Group>) => void;
updateRectangle: (id: string, updates: Partial<Rectangle>) => void;
createNode: (node: NodeInput) => void;
createConnector: (connector: ConnectorInput) => void;
createGroup: (group: GroupInput) => void;
createRectangle: (rectangle: RectangleInput) => void;
}
export type Scene = {
nodes: Node[];
connectors: Connector[];
groups: Group[];
rectangles: Rectangle[];
icons: IconInput[];
};

View File

@@ -2,12 +2,12 @@ import {
SceneInput,
NodeInput,
ConnectorInput,
GroupInput,
RectangleInput,
SceneItemTypeEnum,
Scene,
Node,
Connector,
Group,
Rectangle,
ConnectorAnchorInput,
ConnectorAnchor,
Coords
@@ -28,13 +28,15 @@ export const nodeInputToNode = (nodeInput: NodeInput): Node => {
};
};
export const groupInputToGroup = (groupInput: GroupInput): Group => {
export const rectangleInputToRectangle = (
rectangleInput: RectangleInput
): Rectangle => {
return {
type: SceneItemTypeEnum.GROUP,
id: groupInput.id,
from: groupInput.from,
to: groupInput.to,
color: groupInput.color ?? DEFAULT_COLOR
type: SceneItemTypeEnum.RECTANGLE,
id: rectangleInput.id,
from: rectangleInput.from,
to: rectangleInput.to,
color: rectangleInput.color ?? DEFAULT_COLOR
};
};
@@ -80,8 +82,8 @@ export const sceneInputtoScene = (sceneInput: SceneInput): Scene => {
return nodeInputToNode(nodeInput);
});
const groups = sceneInput.groups.map((groupInput) => {
return groupInputToGroup(groupInput);
const rectangles = sceneInput.rectangles.map((rectangleInput) => {
return rectangleInputToRectangle(rectangleInput);
});
const connectors = sceneInput.connectors.map((connectorInput) => {
@@ -91,7 +93,7 @@ export const sceneInputtoScene = (sceneInput: SceneInput): Scene => {
return {
...sceneInput,
nodes,
groups,
rectangles,
connectors,
icons: sceneInput.icons
} as Scene;
@@ -143,12 +145,14 @@ export const connectorToConnectorInput = (
};
};
export const groupToGroupInput = (group: Group): GroupInput => {
export const rectangleToRectangleInput = (
rectangle: Rectangle
): RectangleInput => {
return {
id: group.id,
color: group.color,
from: group.from,
to: group.to
id: rectangle.id,
color: rectangle.color,
from: rectangle.from,
to: rectangle.to
};
};
@@ -158,12 +162,14 @@ export const sceneToSceneInput = (scene: Scene): SceneInput => {
connectorToConnectorInput,
nodes
);
const groups: GroupInput[] = scene.groups.map(groupToGroupInput);
const rectangles: RectangleInput[] = scene.rectangles.map(
rectangleToRectangleInput
);
return {
nodes,
connectors,
groups,
rectangles,
icons: scene.icons
} as SceneInput;
};

View File

@@ -220,11 +220,11 @@ export const getItemAtTile = ({
if (node) return node;
const group = scene.groups.find(({ from, to }) => {
const rectangle = scene.rectangles.find(({ from, to }) => {
return isWithinBounds(tile, [from, to]);
});
if (group) return group;
if (rectangle) return rectangle;
return null;
};

View File

@@ -1,6 +1,11 @@
// TODO: Split into individual files
import { z } from 'zod';
import { iconInput, nodeInput, connectorInput, groupInput } from './sceneItems';
import {
iconInput,
nodeInput,
connectorInput,
rectangleInput
} from './sceneItems';
import { findInvalidConnector, findInvalidNode } from './utils';
export const sceneInput = z
@@ -8,7 +13,7 @@ export const sceneInput = z
icons: z.array(iconInput),
nodes: z.array(nodeInput),
connectors: z.array(connectorInput),
groups: z.array(groupInput)
rectangles: z.array(rectangleInput)
})
.superRefine((scene, ctx) => {
const invalidNode = findInvalidNode(scene.nodes, scene.icons);

View File

@@ -43,7 +43,7 @@ export const connectorInput = z.object({
anchors: z.array(connectorAnchorInput)
});
export const groupInput = z.object({
export const rectangleInput = z.object({
id: z.string(),
color: z.string().optional(),
from: coords,