mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-24 06:58:48 -05:00
feat: implements connector controls
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Connector } from 'src/types';
|
||||
import { useTheme, Box } from '@mui/material';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useConnector } from 'src/hooks/useConnector';
|
||||
import { ColorSelector } from 'src/components/ColorSelector/ColorSelector';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { ControlsContainer } from '../components/ControlsContainer';
|
||||
import { Section } from '../components/Section';
|
||||
import { Header } from '../components/Header';
|
||||
import { DeleteButton } from '../components/DeleteButton';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const ConnectorControls = ({ id }: Props) => {
|
||||
const theme = useTheme();
|
||||
const sceneActions = useSceneStore((state) => {
|
||||
return state.actions;
|
||||
});
|
||||
const uiStateActions = useUiStateStore((state) => {
|
||||
return state.actions;
|
||||
});
|
||||
const rectangle = useConnector(id);
|
||||
|
||||
const onConnectorUpdated = useCallback(
|
||||
(updates: Partial<Connector>) => {
|
||||
sceneActions.updateConnector(id, updates);
|
||||
},
|
||||
[sceneActions, id]
|
||||
);
|
||||
|
||||
const onConnectorDeleted = useCallback(() => {
|
||||
uiStateActions.setItemControls(null);
|
||||
sceneActions.deleteConnector(id);
|
||||
}, [sceneActions, id, uiStateActions]);
|
||||
|
||||
return (
|
||||
<ControlsContainer header={<Header title="Connector settings" />}>
|
||||
<Section>
|
||||
<ColorSelector
|
||||
colors={Object.values(theme.customVars.diagramPalette)}
|
||||
onChange={(color) => {
|
||||
return onConnectorUpdated({ color });
|
||||
}}
|
||||
activeColor={rectangle.color}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<Box>
|
||||
<DeleteButton onClick={onConnectorDeleted} />
|
||||
</Box>
|
||||
</Section>
|
||||
</ControlsContainer>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +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 { ConnectorControls } from './ConnectorControls/ConnectorControls';
|
||||
import { RectangleControls } from './RectangleControls/RectangleControls';
|
||||
|
||||
export const ItemControlsManager = () => {
|
||||
@@ -15,6 +16,8 @@ export const ItemControlsManager = () => {
|
||||
switch (itemControls?.type) {
|
||||
case 'NODE':
|
||||
return <NodeControls key={itemControls.id} id={itemControls.id} />;
|
||||
case 'CONNECTOR':
|
||||
return <ConnectorControls key={itemControls.id} id={itemControls.id} />;
|
||||
case 'RECTANGLE':
|
||||
return <RectangleControls key={itemControls.id} id={itemControls.id} />;
|
||||
case 'ADD_ITEM':
|
||||
|
||||
15
src/hooks/useConnector.ts
Normal file
15
src/hooks/useConnector.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { getItemById } from 'src/utils';
|
||||
|
||||
export const useConnector = (id: string) => {
|
||||
const connectors = useSceneStore((state) => {
|
||||
return state.connectors;
|
||||
});
|
||||
|
||||
const node = useMemo(() => {
|
||||
return getItemById(connectors, id).item;
|
||||
}, [connectors, id]);
|
||||
|
||||
return node;
|
||||
};
|
||||
@@ -49,6 +49,11 @@ export const Cursor: ModeActions = {
|
||||
type: 'RECTANGLE',
|
||||
id: uiState.mode.mousedownItem.id
|
||||
});
|
||||
} else if (uiState.mode.mousedownItem.type === 'CONNECTOR') {
|
||||
uiState.actions.setItemControls({
|
||||
type: 'CONNECTOR',
|
||||
id: uiState.mode.mousedownItem.id
|
||||
});
|
||||
}
|
||||
} else {
|
||||
uiState.actions.setItemControls(null);
|
||||
|
||||
@@ -120,6 +120,16 @@ const initialState = () => {
|
||||
set({ rectangles: newScene.rectangles });
|
||||
},
|
||||
|
||||
deleteConnector: (id: string) => {
|
||||
const newScene = produce(get(), (draftState) => {
|
||||
const { index } = getItemById(draftState.connectors, id);
|
||||
|
||||
draftState.connectors.splice(index, 1);
|
||||
});
|
||||
|
||||
set({ connectors: newScene.connectors });
|
||||
},
|
||||
|
||||
deleteRectangle: (id: string) => {
|
||||
const newScene = produce(get(), (draftState) => {
|
||||
const { index } = getItemById(draftState.rectangles, id);
|
||||
|
||||
@@ -73,14 +73,15 @@ export type Icon = IconInput;
|
||||
export interface SceneActions {
|
||||
setScene: (scene: SceneInput) => void;
|
||||
updateScene: (scene: Scene) => void;
|
||||
updateNode: (id: string, updates: Partial<Node>) => void;
|
||||
deleteNode: (id: string) => void;
|
||||
updateConnector: (id: string, updates: Partial<Connector>) => void;
|
||||
updateRectangle: (id: string, updates: Partial<Rectangle>) => void;
|
||||
deleteRectangle: (id: string) => void;
|
||||
createNode: (node: NodeInput) => void;
|
||||
createConnector: (connector: ConnectorInput) => void;
|
||||
createRectangle: (rectangle: RectangleInput) => void;
|
||||
updateNode: (id: string, updates: Partial<Node>) => void;
|
||||
updateConnector: (id: string, updates: Partial<Connector>) => void;
|
||||
updateRectangle: (id: string, updates: Partial<Rectangle>) => void;
|
||||
deleteNode: (id: string) => void;
|
||||
deleteConnector: (id: string) => void;
|
||||
deleteRectangle: (id: string) => void;
|
||||
}
|
||||
|
||||
export type Scene = {
|
||||
|
||||
@@ -7,6 +7,11 @@ interface NodeControls {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface ConnectorControls {
|
||||
type: 'CONNECTOR';
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface RectangleControls {
|
||||
type: 'RECTANGLE';
|
||||
id: string;
|
||||
@@ -18,6 +23,7 @@ interface AddItemControls {
|
||||
|
||||
export type ItemControls =
|
||||
| NodeControls
|
||||
| ConnectorControls
|
||||
| RectangleControls
|
||||
| AddItemControls
|
||||
| null;
|
||||
|
||||
@@ -205,30 +205,6 @@ export const getTranslateCSS = (translate: Coords = { x: 0, y: 0 }) => {
|
||||
return `translate(${translate.x}px, ${translate.y}px)`;
|
||||
};
|
||||
|
||||
interface GetItemAtTile {
|
||||
tile: Coords;
|
||||
scene: Scene;
|
||||
}
|
||||
|
||||
export const getItemAtTile = ({
|
||||
tile,
|
||||
scene
|
||||
}: GetItemAtTile): SceneItem | null => {
|
||||
const node = scene.nodes.find(({ position }) => {
|
||||
return CoordsUtils.isEqual(position, tile);
|
||||
});
|
||||
|
||||
if (node) return node;
|
||||
|
||||
const rectangle = scene.rectangles.find(({ from, to }) => {
|
||||
return isWithinBounds(tile, [from, to]);
|
||||
});
|
||||
|
||||
if (rectangle) return rectangle;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const incrementZoom = (zoom: number) => {
|
||||
const newZoom = clamp(zoom + ZOOM_INCREMENT, MIN_ZOOM, MAX_ZOOM);
|
||||
return roundToOneDecimalPlace(newZoom);
|
||||
@@ -380,9 +356,10 @@ export const getConnectorPath = ({
|
||||
const positionsNormalisedFromSearchArea = anchorPositions.map((position) => {
|
||||
return normalisePositionFromOrigin({ position, origin });
|
||||
});
|
||||
|
||||
const tiles = positionsNormalisedFromSearchArea.reduce<Coords[]>(
|
||||
(acc, position, i) => {
|
||||
if (i === 0) return [position];
|
||||
if (i === 0) return acc;
|
||||
|
||||
const prev = positionsNormalisedFromSearchArea[i - 1];
|
||||
const path = findPath({
|
||||
@@ -416,3 +393,47 @@ export const hasMovedTile = (mouse: Mouse) => {
|
||||
|
||||
return !CoordsUtils.isEqual(mouse.delta.tile, CoordsUtils.zero());
|
||||
};
|
||||
|
||||
export const connectorPathTileToGlobal = (tile: Coords, origin: Coords) => {
|
||||
return CoordsUtils.subtract(
|
||||
CoordsUtils.subtract(origin, CONNECTOR_DEFAULTS.searchOffset),
|
||||
CoordsUtils.subtract(tile, CONNECTOR_DEFAULTS.searchOffset)
|
||||
);
|
||||
};
|
||||
|
||||
interface GetItemAtTile {
|
||||
tile: Coords;
|
||||
scene: Scene;
|
||||
}
|
||||
|
||||
export const getItemAtTile = ({
|
||||
tile,
|
||||
scene
|
||||
}: GetItemAtTile): SceneItem | null => {
|
||||
const node = scene.nodes.find(({ position }) => {
|
||||
return CoordsUtils.isEqual(position, tile);
|
||||
});
|
||||
|
||||
if (node) return node;
|
||||
|
||||
const rectangle = scene.rectangles.find(({ from, to }) => {
|
||||
return isWithinBounds(tile, [from, to]);
|
||||
});
|
||||
|
||||
if (rectangle) return rectangle;
|
||||
|
||||
const connector = scene.connectors.find((con) => {
|
||||
return con.path.tiles.find((pathTile) => {
|
||||
const globalPathTile = connectorPathTileToGlobal(
|
||||
pathTile,
|
||||
con.path.origin
|
||||
);
|
||||
|
||||
return CoordsUtils.isEqual(globalPathTile, tile);
|
||||
});
|
||||
});
|
||||
|
||||
if (connector) return connector;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user