feat: allows icons to be drag and dropped onto canvas

This commit is contained in:
Mark Mankarious
2023-08-15 00:08:25 +01:00
parent ca19a4b36b
commit 07dc1d163c
28 changed files with 428 additions and 137 deletions

View File

@@ -21,6 +21,8 @@ import { useWindowUtils } from 'src/hooks/useWindowUtils';
import { sceneInput as sceneValidationSchema } from 'src/validation/scene';
import { ItemControlsManager } from './components/ItemControls/ItemControlsManager';
import { UiStateProvider, useUiStateStore } from './stores/uiStateStore';
import { SceneLayer } from './components/SceneLayer/SceneLayer';
import { DragAndDrop } from './components/DragAndDrop/DragAndDrop';
interface Props {
initialScene?: SceneInput & {
@@ -55,6 +57,12 @@ const App = ({
const interactionsEnabled = useUiStateStore((state) => {
return state.interactionsEnabled;
});
const mode = useUiStateStore((state) => {
return state.mode;
});
const mouse = useUiStateStore((state) => {
return state.mouse;
});
useEffect(() => {
uiActions.setZoom(initialScene?.zoom ?? 1);
@@ -93,6 +101,11 @@ const App = ({
<Renderer />
<ItemControlsManager />
{interactionsEnabled && <ToolMenu />}
{mode.type === 'PLACE_ELEMENT' && mode.icon && (
<SceneLayer>
<DragAndDrop icon={mode.icon} tile={mouse.position.tile} />
</SceneLayer>
)}
</Box>
</>
);

View File

@@ -3,15 +3,11 @@ 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';
import { useSceneStore } from 'src/stores/sceneStore';
export const ContextMenuLayer = () => {
const contextMenu = useUiStateStore((state) => {
return state.contextMenu;
});
const sceneActions = useSceneStore((state) => {
return state.actions;
});
return (
<Box
@@ -29,9 +25,7 @@ export const ContextMenuLayer = () => {
{contextMenu?.type === 'EMPTY_TILE' && (
<EmptyTileContextMenu
key={contextMenu.position.toString()}
onAddNode={() => {
return sceneActions.createNode(contextMenu.position);
}}
onAddNode={() => {}}
position={contextMenu.position}
/>
)}

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { Box, Typography, useTheme } from '@mui/material';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { SizeIndicator } from './SizeIndicator';
import { Value } from './Value';
import { LineItem } from './LineItem';
export const DebugUtils = () => {
const {
@@ -10,6 +12,9 @@ export const DebugUtils = () => {
const uiState = useUiStateStore(({ scroll, mouse, zoom, rendererSize }) => {
return { scroll, mouse, zoom, rendererSize };
});
const mode = useUiStateStore((state) => {
return state.mode;
});
const { scroll, mouse, zoom, rendererSize } = uiState;
@@ -20,24 +25,32 @@ export const DebugUtils = () => {
sx={{
position: 'absolute',
left: appPadding.x,
top: appPadding.y,
bottom: appPadding.y,
bgcolor: 'common.white',
p: 2,
width: 350,
borderRadius: 1,
border: 1,
minWidth: 200
border: (theme) => {
return `1px solid ${theme.palette.grey[400]}`;
},
px: 2,
py: 1
}}
>
<Typography variant="body2">
Scroll: {scroll.position.x}, {scroll.position.y}
</Typography>
<Typography variant="body2">
Mouse: {mouse.position.tile.x}, {mouse.position.tile.y}
</Typography>
<Typography variant="body2">Zoom: {zoom}</Typography>
<Typography variant="body2">
Renderer size: {rendererSize.width} {rendererSize.height}
</Typography>
<LineItem
title="Mouse"
value={`${mouse.position.tile.x}, ${mouse.position.tile.y}`}
/>
<LineItem
title="Scroll"
value={`${scroll.position.x}, ${scroll.position.y}`}
/>
<LineItem title="Zoom" value={zoom} />
<LineItem
title="Size"
value={`${rendererSize.width}, ${rendererSize.height}`}
/>
<LineItem title="Mode" value={mode.type} />
<LineItem title="Mode data" value={JSON.stringify(mode)} />
</Box>
</>
);

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { Typography, Box } from '@mui/material';
import { Value } from './Value';
interface Props {
title: string;
value: string | number;
}
export const LineItem = ({ title, value }: Props) => {
return (
<Box
sx={{
display: 'flex',
width: '100%',
py: 1,
borderBottom: (theme) => {
return `1px solid ${theme.palette.grey[300]}`;
}
}}
>
<Box
sx={{
width: 100
}}
>
<Typography>{title}</Typography>
</Box>
<Box
sx={{
flexGrow: 1
}}
>
<Value value={value.toString()} />
</Box>
</Box>
);
};

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
interface Props {
value: string;
}
export const Value = ({ value }: Props) => {
return (
<Box
sx={{
display: 'inline-block',
bgcolor: 'grey.300',
wordWrap: 'break-word',
py: 0.25,
px: 0.5,
border: (theme) => {
return `1px solid ${theme.palette.grey[400]}`;
},
borderRadius: 2,
maxWidth: 200
}}
>
<Typography sx={{ fontSize: '0.8em' }} variant="body2">
{value}
</Typography>
</Box>
);
};

View File

@@ -0,0 +1,30 @@
import React, { useMemo } from 'react';
import { Box } from '@mui/material';
import { Coords, TileOriginEnum, IconInput } from 'src/types';
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
import { NodeIcon } from 'src/components/Node/NodeIcon';
interface Props {
icon: IconInput;
tile: Coords;
}
export const DragAndDrop = ({ icon, tile }: Props) => {
const { getTilePosition } = useGetTilePosition();
const tilePosition = useMemo(() => {
return getTilePosition({ tile, origin: TileOriginEnum.BOTTOM });
}, [tile, getTilePosition]);
return (
<Box
sx={{
position: 'absolute',
left: tilePosition.x,
top: tilePosition.y
}}
>
<NodeIcon icon={icon} />
</Box>
);
};

View File

@@ -6,12 +6,18 @@ import { Icon as IconI } from 'src/types';
interface Props {
icon: IconI;
onClick: () => void;
onClick?: () => void;
onMouseDown?: () => void;
}
export const Icon = ({ icon, onClick }: Props) => {
export const Icon = ({ icon, onClick, onMouseDown }: Props) => {
return (
<Button variant="text" onClick={onClick}>
<Button
variant="text"
onClick={onClick}
onMouseDown={onMouseDown}
sx={{ userSelect: 'none' }}
>
<Stack
justifyContent="center"
alignItems="center"
@@ -19,6 +25,7 @@ export const Icon = ({ icon, onClick }: Props) => {
>
<Box
component="img"
draggable={false}
src={icon.url}
alt={`Icon ${icon.name}`}
sx={{ width: '100%', height: 80 }}

View File

@@ -2,15 +2,16 @@ import React from 'react';
import Grid from '@mui/material/Grid';
import { Icon as IconI } from 'src/types';
import { Icon } from './Icon';
import { Section } from '../../components/Section';
import { Section } from '../components/Section';
interface Props {
name?: string;
icons: IconI[];
onClick: (icon: IconI) => void;
onClick?: (icon: IconI) => void;
onMouseDown?: (icon: IconI) => void;
}
export const IconCategory = ({ name, icons, onClick }: Props) => {
export const IconCategory = ({ name, icons, onClick, onMouseDown }: Props) => {
return (
<Section title={name}>
<Grid container spacing={2}>
@@ -20,8 +21,15 @@ export const IconCategory = ({ name, icons, onClick }: Props) => {
<Icon
icon={icon}
onClick={() => {
if (!onClick) return;
return onClick(icon);
}}
onMouseDown={() => {
if (!onMouseDown) return;
return onMouseDown(icon);
}}
/>
</Grid>
);

View File

@@ -0,0 +1,37 @@
import React, { useCallback } from 'react';
import { useSceneStore } from 'src/stores/sceneStore';
import { ControlsContainer } from 'src/components/ItemControls/components/ControlsContainer';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { Icon } from 'src/types';
import { Icons } from './Icons';
export const IconSelection = () => {
const uiStateActions = useUiStateStore((state) => {
return state.actions;
});
const mode = useUiStateStore((state) => {
return state.mode;
});
const icons = useSceneStore((state) => {
return state.icons;
});
const onMouseDown = useCallback(
(icon: Icon) => {
if (mode.type !== 'PLACE_ELEMENT') return;
uiStateActions.setMode({
type: 'PLACE_ELEMENT',
showCursor: true,
icon
});
},
[mode, uiStateActions]
);
return (
<ControlsContainer>
<Icons icons={icons} onMouseDown={onMouseDown} />
</ControlsContainer>
);
};

View File

@@ -5,10 +5,11 @@ import { IconCategory } from './IconCategory';
interface Props {
icons: Icon[];
onClick: (icon: Icon) => void;
onClick?: (icon: Icon) => void;
onMouseDown?: (icon: Icon) => void;
}
export const Icons = ({ icons, onClick }: Props) => {
export const Icons = ({ icons, onClick, onMouseDown }: Props) => {
const categorisedIcons = useMemo(() => {
const cats: { name?: string; icons: Icon[] }[] = [];
@@ -42,7 +43,11 @@ export const Icons = ({ icons, onClick }: Props) => {
{categorisedIcons.map((cat) => {
return (
<Grid item xs={12} key={`icon-category-${cat.name}`}>
<IconCategory {...cat} onClick={onClick} />
<IconCategory
{...cat}
onClick={onClick}
onMouseDown={onMouseDown}
/>
</Grid>
);
})}

View File

@@ -1,11 +1,14 @@
import React, { useMemo } from 'react';
import { Card, useTheme } from '@mui/material';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { IconSelection } from 'src/components/ItemControls/IconSelection/IconSelection';
import { NodeControls } from './NodeControls/NodeControls';
import { ProjectControls } from './ProjectControls/ProjectControls';
export const ItemControlsManager = () => {
const itemControls = useUiStateStore((state) => state.itemControls);
const itemControls = useUiStateStore((state) => {
return state.itemControls;
});
const theme = useTheme();
const Controls = useMemo(() => {
@@ -14,6 +17,8 @@ export const ItemControlsManager = () => {
return <NodeControls nodeId={itemControls.nodeId} />;
case 'PROJECT_SETTINGS':
return <ProjectControls />;
case 'PLACE_ELEMENT':
return <IconSelection />;
default:
return null;
}

View File

@@ -4,7 +4,7 @@ import { Node } from 'src/types';
import { useSceneStore } from 'src/stores/sceneStore';
import { useNode } from 'src/hooks/useNode';
import { ControlsContainer } from '../components/ControlsContainer';
import { Icons } from './IconSelection/IconSelection';
import { Icons } from '../IconSelection/Icons';
import { Header } from '../components/Header';
import { NodeSettings } from './NodeSettings/NodeSettings';

View File

@@ -7,27 +7,29 @@ interface Props {
isIn: boolean;
}
export const Transition = ({ children, isIn }: Props) => (
<Slide
direction="right"
in={isIn}
mountOnEnter
unmountOnExit
style={{
transitionDelay: isIn ? '150ms' : '0ms'
}}
>
<Card
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: 0
export const Transition = ({ children, isIn }: Props) => {
return (
<Slide
direction="right"
in={isIn}
mountOnEnter
unmountOnExit
style={{
transitionDelay: isIn ? '150ms' : '0ms'
}}
>
{children}
</Card>
</Slide>
);
<Card
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: 0
}}
>
{children}
</Card>
</Slide>
);
};

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { Box } from '@mui/material';
interface Props {
header: React.ReactNode;
header?: React.ReactNode;
children: React.ReactNode;
}
@@ -17,7 +17,9 @@ export const ControlsContainer = ({ header, children }: Props) => {
flexDirection: 'column'
}}
>
<Box sx={{ width: '100%', boxShadow: 6, zIndex: 1 }}>{header}</Box>
{header && (
<Box sx={{ width: '100%', boxShadow: 6, zIndex: 1 }}>{header}</Box>
)}
<Box
sx={{
width: '100%',

View File

@@ -1,39 +1,29 @@
import React, { useEffect, useRef, useCallback, useMemo } from 'react';
import React, { useEffect, useRef, useCallback } from 'react';
import { Box } from '@mui/material';
import gsap from 'gsap';
import { Size, Coords, TileOriginEnum, Node as NodeI } from 'src/types';
import { getProjectedTileSize, getColorVariant } from 'src/utils';
import { useResizeObserver } from 'src/hooks/useResizeObserver';
import { Coords, TileOriginEnum, Node as NodeI, IconInput } from 'src/types';
import { getColorVariant } from 'src/utils';
import { IsoTileArea } from 'src/components/IsoTileArea/IsoTileArea';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
import { useProjectedTileSize } from 'src/hooks/useProjectedTileSize';
import { LabelContainer } from './LabelContainer';
import { MarkdownLabel } from './LabelTypes/MarkdownLabel';
import { NodeIcon } from './NodeIcon';
interface Props {
node: NodeI;
iconUrl?: string;
icon?: IconInput;
order: number;
}
export const Node = ({ node, iconUrl, order }: Props) => {
export const Node = ({ node, icon, order }: Props) => {
const zoom = useUiStateStore((state) => {
return state.zoom;
});
const nodeRef = useRef<HTMLDivElement>();
const iconRef = useRef<HTMLImageElement>();
const { observe, size: iconSize } = useResizeObserver();
const { getTilePosition } = useGetTilePosition();
useEffect(() => {
if (!iconRef.current) return;
observe(iconRef.current);
}, [observe]);
const projectedTileSize = useMemo<Size>(() => {
return getProjectedTileSize({ zoom });
}, [zoom]);
const projectedTileSize = useProjectedTileSize();
const moveToTile = useCallback(
({
@@ -43,19 +33,13 @@ export const Node = ({ node, iconUrl, order }: Props) => {
tile: Coords;
animationDuration?: number;
}) => {
if (!nodeRef.current || !iconRef.current) return;
if (!nodeRef.current) return;
const position = getTilePosition({
tile,
origin: TileOriginEnum.BOTTOM
});
gsap.to(iconRef.current, {
duration: animationDuration,
x: -iconRef.current.width * 0.5,
y: -iconRef.current.height
});
gsap.to(nodeRef.current, {
duration: animationDuration,
x: position.x,
@@ -66,12 +50,10 @@ export const Node = ({ node, iconUrl, order }: Props) => {
);
const onImageLoaded = useCallback(() => {
if (!nodeRef.current || !iconRef.current) return;
if (!nodeRef.current) return;
gsap.killTweensOf(nodeRef.current);
moveToTile({ tile: node.position, animationDuration: 0 });
nodeRef.current.style.opacity = '1';
}, [node.position, moveToTile]);
}, []);
useEffect(() => {
moveToTile({ tile: node.position, animationDuration: 0 });
@@ -112,24 +94,22 @@ export const Node = ({ node, iconUrl, order }: Props) => {
/>
</Box>
<LabelContainer
labelHeight={node.labelHeight + iconSize.height}
labelHeight={node.labelHeight + 100}
tileSize={projectedTileSize}
connectorDotSize={5 * zoom}
>
{node.label && <MarkdownLabel label={node.label} />}
</LabelContainer>
</Box>
<Box
component="img"
ref={iconRef}
onLoad={onImageLoaded}
src={iconUrl}
sx={{
position: 'absolute',
width: projectedTileSize.width,
pointerEvents: 'none'
}}
/>
{icon && (
<Box
sx={{
position: 'absolute'
}}
>
<NodeIcon icon={icon} onImageLoaded={onImageLoaded} />
</Box>
)}
</Box>
);
};

View File

@@ -0,0 +1,40 @@
import React, { useRef, useEffect } from 'react';
import { Box } from '@mui/material';
import { useProjectedTileSize } from 'src/hooks/useProjectedTileSize';
import { useResizeObserver } from 'src/hooks/useResizeObserver';
import { IconInput } from 'src/types';
interface Props {
icon: IconInput;
onImageLoaded?: () => void;
}
export const NodeIcon = ({ icon, onImageLoaded }: Props) => {
const ref = useRef();
const projectedTileSize = useProjectedTileSize();
const { size, observe, disconnect } = useResizeObserver();
useEffect(() => {
if (!ref.current) return;
observe(ref.current);
return disconnect;
}, [observe, disconnect]);
return (
<Box
ref={ref}
component="img"
onLoad={onImageLoaded}
src={icon.url}
sx={{
position: 'absolute',
width: projectedTileSize.width,
pointerEvents: 'none',
top: -size.height,
left: -size.width / 2
}}
/>
);
};

View File

@@ -128,11 +128,9 @@ export const Renderer = () => {
key={node.id}
order={-node.position.x - node.position.y}
node={node}
iconUrl={
icons.find((icon) => {
return icon.id === node.iconId;
})?.url
}
icon={icons.find((icon) => {
return icon.id === node.iconId;
})}
/>
);
})}

View File

@@ -7,5 +7,18 @@ interface Props {
}
export const SceneLayer = ({ children, order = 0 }: Props) => {
return <Box sx={{ position: 'absolute', zIndex: order }}>{children}</Box>;
return (
<Box
sx={{
position: 'absolute',
zIndex: order,
top: 0,
left: 0,
width: '100%',
height: '100%'
}}
>
{children}
</Box>
);
};

View File

@@ -1,11 +1,13 @@
import React from 'react';
import { Card, useTheme } from '@mui/material';
import { ItemControlsTypeEnum } from 'src/types';
import {
PanTool as PanToolIcon,
ZoomIn as ZoomInIcon,
ZoomOut as ZoomOutIcon,
NearMe as NearMeIcon,
CenterFocusStrong as CenterFocusStrongIcon
CenterFocusStrong as CenterFocusStrongIcon,
Add as AddIcon
} from '@mui/icons-material';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { useDiagramUtils } from 'src/hooks/useDiagramUtils';
@@ -35,11 +37,26 @@ export const ToolMenu = () => {
borderRadius: 2
}}
>
<IconButton
name="Add element"
Icon={<AddIcon />}
onClick={() => {
uiStateStoreActions.setItemControls({
type: ItemControlsTypeEnum.PLACE_ELEMENT
});
uiStateStoreActions.setMode({
type: 'PLACE_ELEMENT',
showCursor: true,
icon: null
});
}}
size={theme.customVars.toolMenu.height}
/>
<IconButton
name="Select"
Icon={<NearMeIcon />}
onClick={() => {
return uiStateStoreActions.setMode({
uiStateStoreActions.setMode({
type: 'CURSOR',
showCursor: true,
mousedown: null
@@ -52,7 +69,7 @@ export const ToolMenu = () => {
name="Pan"
Icon={<PanToolIcon />}
onClick={() => {
return uiStateStoreActions.setMode({
uiStateStoreActions.setMode({
type: 'PAN',
showCursor: false
});

View File

@@ -0,0 +1,15 @@
import { useMemo } from 'react';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { getProjectedTileSize } from 'src/utils';
export const useProjectedTileSize = () => {
const zoom = useUiStateStore((state) => {
return state.zoom;
});
const projectedTileSize = useMemo(() => {
return getProjectedTileSize({ zoom });
}, [zoom]);
return projectedTileSize;
};

View File

@@ -1,4 +1,4 @@
import { SidebarTypeEnum, InteractionReducer } from 'src/types';
import { ItemControlsTypeEnum, InteractionReducer } from 'src/types';
import { CoordsUtils, filterNodesByTile } from 'src/utils';
export const Cursor: InteractionReducer = {
@@ -74,7 +74,7 @@ export const Cursor: InteractionReducer = {
draftState.contextMenu = draftState.scene.nodes[nodeIndex];
draftState.scene.nodes[nodeIndex].isSelected = true;
draftState.itemControls = {
type: SidebarTypeEnum.SINGLE_NODE,
type: ItemControlsTypeEnum.SINGLE_NODE,
nodeId: draftState.scene.nodes[nodeIndex].id
};
draftState.mode.mousedown = null;

View File

@@ -0,0 +1,44 @@
import { InteractionReducer } from 'src/types';
import { v4 as uuid } from 'uuid';
import { nodeInputToNode, filterNodesByTile } from 'src/utils';
export const PlaceElement: InteractionReducer = {
type: 'PLACE_ELEMENT',
mousemove: () => {},
mousedown: (draftState) => {
if (draftState.mode.type !== 'PLACE_ELEMENT') return;
if (!draftState.mode.icon) {
const itemsAtTile = filterNodesByTile({
tile: draftState.mouse.position.tile,
nodes: draftState.scene.nodes
});
draftState.mode = {
type: 'CURSOR',
mousedown: {
items: itemsAtTile,
tile: draftState.mouse.position.tile
},
showCursor: true
};
draftState.itemControls = null;
}
},
mouseup: (draftState) => {
if (draftState.mode.type !== 'PLACE_ELEMENT') return;
if (draftState.mode.icon !== null) {
const newNode = nodeInputToNode({
id: uuid(),
iconId: draftState.mode.icon.id,
label: 'New Node',
position: draftState.mouse.position.tile
});
draftState.mode.icon = null;
draftState.scene.nodes.push(newNode);
}
}
};

View File

@@ -8,12 +8,14 @@ import { DragItems } from './reducers/DragItems';
import { Pan } from './reducers/Pan';
import { Cursor } from './reducers/Cursor';
import { Lasso } from './reducers/Lasso';
import { PlaceElement } from './reducers/PlaceElement';
const reducers: { [k in string]: InteractionReducer } = {
CURSOR: Cursor,
DRAG_ITEMS: DragItems,
PAN: Pan,
LASSO: Lasso
LASSO: Lasso,
PLACE_ELEMENT: PlaceElement
};
export const useInteractionManager = () => {
@@ -88,7 +90,8 @@ export const useInteractionManager = () => {
scroll,
contextMenu,
itemControls,
rendererRef: rendererRef.current
rendererRef: rendererRef.current,
sceneActions
};
const getTransitionaryState = () => {
@@ -129,7 +132,7 @@ export const useInteractionManager = () => {
uiStateActions.setScroll(newState.scroll);
uiStateActions.setMode(newState.mode);
uiStateActions.setContextMenu(newState.contextMenu);
uiStateActions.setSidebar(newState.itemControls);
uiStateActions.setItemControls(newState.itemControls);
sceneActions.updateScene(newState.scene);
},
[
@@ -153,7 +156,7 @@ export const useInteractionManager = () => {
return;
}
const el = rendererRef.current;
const el = window;
el.addEventListener('mousemove', onMouseEvent);
el.addEventListener('mousedown', onMouseEvent);

View File

@@ -1,9 +1,7 @@
import React, { createContext, useRef, useContext } from 'react';
import { createStore, useStore } from 'zustand';
import { v4 as uuid } from 'uuid';
import { produce } from 'immer';
import { NODE_DEFAULTS } from 'src/config';
import { Scene, SceneActions, Node, SceneItemTypeEnum } from 'src/types';
import { Scene, SceneActions } from 'src/types';
import { sceneInput } from 'src/validation/scene';
import { sceneInputtoScene } from 'src/utils';
@@ -46,19 +44,6 @@ const initialState = () => {
});
set({ nodes: newNodes });
},
createNode: (position) => {
const { nodes, icons } = get();
const newNode: Node = {
...NODE_DEFAULTS,
id: uuid(),
type: SceneItemTypeEnum.NODE,
iconId: icons[0].id,
position,
isSelected: false
};
set({ nodes: [...nodes, newNode] });
}
}
};

View File

@@ -50,7 +50,7 @@ const initialState = () => {
setScroll: ({ position, offset }) => {
set({ scroll: { position, offset: offset ?? get().scroll.offset } });
},
setSidebar: (itemControls) => {
setItemControls: (itemControls) => {
set({ itemControls });
},
setContextMenu: (contextMenu) => {

View File

@@ -5,7 +5,8 @@ import {
ContextMenu,
ItemControls,
Mouse,
Scene
Scene,
SceneActions
} from 'src/types';
export interface State {
@@ -13,6 +14,7 @@ export interface State {
mouse: Mouse;
scroll: Scroll;
scene: Scene;
sceneActions: SceneActions;
contextMenu: ContextMenu;
itemControls: ItemControls;
rendererRef: HTMLElement;

View File

@@ -57,5 +57,4 @@ export interface SceneActions {
setScene: (scene: SceneInput) => void;
updateScene: (scene: Scene) => void;
updateNode: (id: string, updates: Partial<Node>) => void;
createNode: (position: Coords) => void;
}

View File

@@ -1,18 +1,23 @@
import { Coords, Size } from './common';
import { SceneItem } from './scene';
import { IconInput } from './inputs';
export enum SidebarTypeEnum {
export enum ItemControlsTypeEnum {
SINGLE_NODE = 'SINGLE_NODE',
PROJECT_SETTINGS = 'PROJECT_SETTINGS'
PROJECT_SETTINGS = 'PROJECT_SETTINGS',
PLACE_ELEMENT = 'PLACE_ELEMENT'
}
export type ItemControls =
| {
type: SidebarTypeEnum.SINGLE_NODE;
type: ItemControlsTypeEnum.SINGLE_NODE;
nodeId: string;
}
| {
type: SidebarTypeEnum.PROJECT_SETTINGS;
type: ItemControlsTypeEnum.PROJECT_SETTINGS;
}
| {
type: ItemControlsTypeEnum.PLACE_ELEMENT;
}
| null;
@@ -68,12 +73,19 @@ export interface DragItemsMode {
items: SceneItem[];
}
export interface PlaceElement {
type: 'PLACE_ELEMENT';
showCursor: boolean;
icon: IconInput | null;
}
export type Mode =
| InteractionsDisabled
| CursorMode
| PanMode
| DragItemsMode
| LassoMode;
| LassoMode
| PlaceElement;
// End mode types
export type ContextMenu =
@@ -107,7 +119,7 @@ export interface UiStateActions {
decrementZoom: () => void;
setZoom: (zoom: number) => void;
setScroll: (scroll: Scroll) => void;
setSidebar: (itemControls: ItemControls) => void;
setItemControls: (itemControls: ItemControls) => void;
setContextMenu: (contextMenu: ContextMenu) => void;
setMouse: (mouse: Mouse) => void;
setRendererSize: (rendererSize: Size) => void;