feat: implements rectangle controls

This commit is contained in:
Mark Mankarious
2023-08-18 14:27:38 +01:00
parent e1515a7409
commit 94995f099c
10 changed files with 134 additions and 124 deletions

View File

@@ -8,15 +8,21 @@ interface Props {
activeColor: string;
}
export const ColorSelector = ({ colors, onChange, activeColor }: Props) => (
<Box>
{colors.map((color) => (
<ColorSwatch
key={color}
hex={color}
onClick={() => onChange(color)}
isActive={activeColor === color}
/>
))}
</Box>
);
export const ColorSelector = ({ colors, onChange, activeColor }: Props) => {
return (
<Box>
{colors.map((color) => {
return (
<ColorSwatch
key={color}
hex={color}
onClick={() => {
return onChange(color);
}}
isActive={activeColor === color}
/>
);
})}
</Box>
);
};

View File

@@ -3,7 +3,7 @@ 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';
import { RectangleControls } from './RectangleControls/RectangleControls';
export const ItemControlsManager = () => {
const itemControls = useUiStateStore((state) => {
@@ -13,11 +13,11 @@ export const ItemControlsManager = () => {
const Controls = useMemo(() => {
switch (itemControls?.type) {
case 'SINGLE_NODE':
return <NodeControls nodeId={itemControls.nodeId} />;
case 'PROJECT_SETTINGS':
return <ProjectControls />;
case 'PLACE_ELEMENT':
case 'NODE':
return <NodeControls id={itemControls.id} />;
case 'RECTANGLE':
return <RectangleControls id={itemControls.id} />;
case 'ADD_ITEM':
return <IconSelection />;
default:
return null;

View File

@@ -9,10 +9,10 @@ import { Header } from '../components/Header';
import { NodeSettings } from './NodeSettings/NodeSettings';
interface Props {
nodeId: string;
id: string;
}
export const NodeControls = ({ nodeId }: Props) => {
export const NodeControls = ({ id }: Props) => {
const [tab, setTab] = useState(0);
const icons = useSceneStore((state) => {
return state.icons;
@@ -20,7 +20,7 @@ export const NodeControls = ({ nodeId }: Props) => {
const sceneActions = useSceneStore((state) => {
return state.actions;
});
const node = useNode(nodeId);
const node = useNode(id);
const onTabChanged = (event: React.SyntheticEvent, newValue: number) => {
setTab(newValue);
@@ -28,13 +28,11 @@ export const NodeControls = ({ nodeId }: Props) => {
const onNodeUpdated = useCallback(
(updates: Partial<Node>) => {
sceneActions.updateNode(nodeId, updates);
sceneActions.updateNode(id, updates);
},
[sceneActions, nodeId]
[sceneActions, id]
);
if (!node) return null;
return (
<ControlsContainer
header={

View File

@@ -1,49 +0,0 @@
import React, { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import TextField from '@mui/material/TextField';
import Grid from '@mui/material/Grid';
import { Header } from '../components/Header';
import { Section } from '../components/Section';
import { ControlsContainer } from '../components/ControlsContainer';
interface Values {
name?: string;
notes?: string;
}
export const ProjectControls = () => {
const { register, handleSubmit } = useForm<Values>({
defaultValues: {
name: '',
notes: ''
}
});
const onSubmit = useCallback((values: Values) => {
console.log(values);
}, []);
return (
<ControlsContainer header={<Header title="Project settings" />}>
<Section>
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={4}>
<Grid item xs={12}>
<TextField {...register('name')} label="Name" fullWidth />
</Grid>
<Grid item xs={12}>
<TextField
{...register('notes')}
label="Notes"
variant="outlined"
rows={12}
multiline
fullWidth
/>
</Grid>
</Grid>
</form>
</Section>
</ControlsContainer>
);
};

View File

@@ -0,0 +1,42 @@
import React, { useCallback } from 'react';
import { Rectangle } from 'src/types';
import { useTheme } from '@mui/material';
import { useSceneStore } from 'src/stores/sceneStore';
import { useRectangle } from 'src/hooks/useRectangle';
import { ColorSelector } from 'src/components/ColorSelector/ColorSelector';
import { ControlsContainer } from '../components/ControlsContainer';
import { Section } from '../components/Section';
import { Header } from '../components/Header';
interface Props {
id: string;
}
export const RectangleControls = ({ id }: Props) => {
const theme = useTheme();
const sceneActions = useSceneStore((state) => {
return state.actions;
});
const rectangle = useRectangle(id);
const onRectangleUpdated = useCallback(
(updates: Partial<Rectangle>) => {
sceneActions.updateRectangle(id, updates);
},
[sceneActions, id]
);
return (
<ControlsContainer header={<Header title="Rectangle settings" />}>
<Section>
<ColorSelector
colors={Object.values(theme.customVars.diagramPalette)}
onChange={(color) => {
return onRectangleUpdated({ color });
}}
activeColor={rectangle.color}
/>
</Section>
</ControlsContainer>
);
};

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Card, useTheme } from '@mui/material';
import { ItemControlsTypeEnum } from 'src/types';
import {
PanToolOutlined as PanToolIcon,
ZoomInOutlined as ZoomInIcon,
@@ -44,7 +43,7 @@ export const ToolMenu = () => {
Icon={<AddIcon />}
onClick={() => {
uiStateStoreActions.setItemControls({
type: ItemControlsTypeEnum.PLACE_ELEMENT
type: 'ADD_ITEM'
});
uiStateStoreActions.setMode({
type: 'PLACE_ELEMENT',

View File

@@ -1,16 +1,15 @@
import { useMemo } from 'react';
import { useSceneStore } from 'src/stores/sceneStore';
import { getItemById } from 'src/utils';
export const useNode = (nodeId: string) => {
export const useNode = (id: string) => {
const nodes = useSceneStore((state) => {
return state.nodes;
});
const node = useMemo(() => {
return nodes.find((n) => {
return n.id === nodeId;
});
}, [nodes, nodeId]);
return getItemById(nodes, id).item;
}, [nodes, id]);
return node;
};

15
src/hooks/useRectangle.ts Normal file
View File

@@ -0,0 +1,15 @@
import { useMemo } from 'react';
import { useSceneStore } from 'src/stores/sceneStore';
import { getItemById } from 'src/utils';
export const useRectangle = (id: string) => {
const rectangles = useSceneStore((state) => {
return state.rectangles;
});
const node = useMemo(() => {
return getItemById(rectangles, id).item;
}, [rectangles, id]);
return node;
};

View File

@@ -1,13 +1,12 @@
import { produce } from 'immer';
import { ItemControlsTypeEnum, ModeActions } from 'src/types';
import { getItemAtTile, getItemById } from 'src/utils';
import { ModeActions } from 'src/types';
import { getItemAtTile, hasMovedTile } from 'src/utils';
export const Cursor: ModeActions = {
mousemove: ({ uiState }) => {
if (uiState.mode.type !== 'CURSOR') return;
if (uiState.mode.type !== 'CURSOR' || !hasMovedTile(uiState.mouse)) return;
if (uiState.mode.mousedownItem) {
// User is in dragging mode
uiState.actions.setMode({
type: 'DRAG_ITEMS',
showCursor: true,
@@ -23,42 +22,42 @@ export const Cursor: ModeActions = {
scene
});
const newMode = produce(uiState.mode, (draftState) => {
draftState.mousedownItem = itemAtTile;
});
uiState.actions.setMode(newMode);
uiState.actions.setMode(
produce(uiState.mode, (draftState) => {
if (itemAtTile) {
draftState.mousedownItem = {
type: itemAtTile.type,
id: itemAtTile.id
};
} else {
draftState.mousedownItem = null;
}
})
);
},
mouseup: ({ uiState, scene, isRendererInteraction }) => {
mouseup: ({ uiState, isRendererInteraction }) => {
if (uiState.mode.type !== 'CURSOR' || !isRendererInteraction) return;
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
type: 'NODE',
id: uiState.mode.mousedownItem.id
});
} else if (uiState.mode.mousedownItem.type === 'RECTANGLE') {
uiState.actions.setItemControls({
type: 'RECTANGLE',
id: uiState.mode.mousedownItem.id
});
}
} else {
// Empty tile selected
uiState.actions.setContextMenu({
type: 'EMPTY_TILE',
position: uiState.mouse.position.tile
});
uiState.actions.setItemControls(null);
}
const newMode = produce(uiState.mode, (draftState) => {
draftState.mousedownItem = null;
});
uiState.actions.setMode(newMode);
uiState.actions.setMode(
produce(uiState.mode, (draftState) => {
draftState.mousedownItem = null;
})
);
}
};

View File

@@ -2,23 +2,24 @@ import { Coords, Size } from './common';
import { SceneItem, Connector, SceneItemReference } from './scene';
import { IconInput } from './inputs';
export enum ItemControlsTypeEnum {
SINGLE_NODE = 'SINGLE_NODE',
PROJECT_SETTINGS = 'PROJECT_SETTINGS',
PLACE_ELEMENT = 'PLACE_ELEMENT'
interface NodeControls {
type: 'NODE';
id: string;
}
interface RectangleControls {
type: 'RECTANGLE';
id: string;
}
interface AddItemControls {
type: 'ADD_ITEM';
}
export type ItemControls =
| {
type: ItemControlsTypeEnum.SINGLE_NODE;
nodeId: string;
}
| {
type: ItemControlsTypeEnum.PROJECT_SETTINGS;
}
| {
type: ItemControlsTypeEnum.PLACE_ELEMENT;
}
| NodeControls
| RectangleControls
| AddItemControls
| null;
export interface Mouse {