mirror of
https://github.com/markmanx/isoflow.git
synced 2025-02-08 04:18:29 +00:00
feat: allows project to be centered
This commit is contained in:
parent
9cfef256e2
commit
c638bf015c
12 changed files with 179 additions and 34 deletions
|
@ -30,6 +30,7 @@ interface Props {
|
|||
onSceneUpdated?: (scene: SceneInput) => void;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
debugMode?: boolean;
|
||||
}
|
||||
|
||||
const App = ({
|
||||
|
@ -37,7 +38,8 @@ const App = ({
|
|||
width,
|
||||
height = 500,
|
||||
interactionsEnabled: interactionsEnabledProp = true,
|
||||
onSceneUpdated
|
||||
onSceneUpdated,
|
||||
debugMode = false
|
||||
}: Props) => {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
useWindowUtils();
|
||||
|
@ -73,6 +75,10 @@ const App = ({
|
|||
onSceneUpdated(sceneInput);
|
||||
}, [scene, onSceneUpdated, isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
uiActions.setDebugMode(debugMode);
|
||||
}, [debugMode, uiActions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalStyles />
|
||||
|
|
|
@ -1,6 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Box, Typography, useTheme } from '@mui/material';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { SizeIndicator } from './SizeIndicator';
|
||||
|
||||
export const DebugUtils = () => {
|
||||
return <SizeIndicator />;
|
||||
const {
|
||||
customVars: { appPadding }
|
||||
} = useTheme();
|
||||
const { scroll, mouse } = useUiStateStore(({ scroll, mouse }) => {
|
||||
return { scroll, mouse };
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<SizeIndicator />
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: appPadding.x,
|
||||
top: appPadding.y,
|
||||
bgcolor: 'common.white',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
border: 1,
|
||||
minWidth: 200
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Scroll: {scroll.position.x}, {scroll.position.y}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Mouse: {mouse.position.tile.x}, {mouse.position.tile.y}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,10 +3,10 @@ import { Box } from '@mui/material';
|
|||
import { useDiagramUtils } from 'src/hooks/useDiagramUtils';
|
||||
|
||||
export const SizeIndicator = () => {
|
||||
const { getDiagramBoundingBox } = useDiagramUtils();
|
||||
const { getUnprojectedBounds } = useDiagramUtils();
|
||||
const diagramBoundingBox = useMemo(() => {
|
||||
return getDiagramBoundingBox();
|
||||
}, [getDiagramBoundingBox]);
|
||||
return getUnprojectedBounds();
|
||||
}, [getUnprojectedBounds]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { Node as NodeI } from 'src/types';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
@ -14,8 +14,10 @@ import { useResizeObserver } from 'src/hooks/useResizeObserver';
|
|||
import { SceneLayer } from 'src/components/SceneLayer/SceneLayer';
|
||||
|
||||
export const Renderer = () => {
|
||||
const [isDebugModeOn] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const debugMode = useUiStateStore((state) => {
|
||||
return state.debugMode;
|
||||
});
|
||||
const nodes = useSceneStore((state) => {
|
||||
return state.nodes;
|
||||
});
|
||||
|
@ -135,7 +137,7 @@ export const Renderer = () => {
|
|||
);
|
||||
})}
|
||||
</SceneLayer>
|
||||
{isDebugModeOn && (
|
||||
{debugMode && (
|
||||
<SceneLayer>
|
||||
<DebugUtils />
|
||||
</SceneLayer>
|
||||
|
|
51
src/examples/DebugTools/DebugTools.tsx
Normal file
51
src/examples/DebugTools/DebugTools.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import Isoflow from 'src/Isoflow';
|
||||
import { icons } from '../icons';
|
||||
|
||||
export const DebugTools = () => {
|
||||
return (
|
||||
<Isoflow
|
||||
initialScene={{
|
||||
icons,
|
||||
connectors: [
|
||||
{
|
||||
id: 'connector1',
|
||||
from: 'database',
|
||||
to: 'server',
|
||||
label: 'connection'
|
||||
}
|
||||
],
|
||||
groups: [
|
||||
{
|
||||
id: 'group1',
|
||||
nodeIds: ['server', 'database']
|
||||
}
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
id: 'server',
|
||||
label: 'Requests per minute',
|
||||
labelHeight: 40,
|
||||
iconId: 'server',
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
label: 'Transactions',
|
||||
labelHeight: 40,
|
||||
iconId: 'server',
|
||||
position: {
|
||||
x: 5,
|
||||
y: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
debugMode
|
||||
height="100%"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -3,8 +3,10 @@ import { Box, Select, MenuItem, useTheme } from '@mui/material';
|
|||
import { BasicEditor } from './BasicEditor/BasicEditor';
|
||||
import { CustomNode } from './CustomNode/CustomNode';
|
||||
import { Callbacks } from './Callbacks/Callbacks';
|
||||
import { DebugTools } from './DebugTools/DebugTools';
|
||||
|
||||
const examples = [
|
||||
{ name: 'Debug tools', component: DebugTools },
|
||||
{ name: 'Live Diagrams', component: CustomNode },
|
||||
{ name: 'Callbacks', component: Callbacks },
|
||||
{ name: 'Basic Editor', component: BasicEditor }
|
||||
|
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
|
@ -3,7 +3,7 @@ import { Size, Coords, SceneInput } from 'src/types';
|
|||
declare global {
|
||||
interface Window {
|
||||
Isoflow: {
|
||||
getDiagramBoundingBox: () => Size & Coords;
|
||||
getUnprojectedBounds: () => Size & Coords;
|
||||
fitDiagramToScreen: () => void;
|
||||
setScene: (scene: SceneInput) => void;
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { Size, Coords } from 'src/types';
|
||||
import { Size, Coords, Node, TileOriginEnum } from 'src/types';
|
||||
import { getBoundingBox, getBoundingBoxSize, sortByPosition } from 'src/utils';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { useScroll } from 'src/hooks/useScroll';
|
||||
|
||||
const BOUNDING_BOX_PADDING = 4;
|
||||
const BOUNDING_BOX_PADDING = 0;
|
||||
|
||||
export const useDiagramUtils = () => {
|
||||
const scene = useSceneStore(({ nodes, groups, connectors, icons }) => {
|
||||
|
@ -16,15 +17,17 @@ export const useDiagramUtils = () => {
|
|||
icons
|
||||
};
|
||||
});
|
||||
const { scrollToTile } = useScroll();
|
||||
const uiStateActions = useUiStateStore((state) => {
|
||||
return state.actions;
|
||||
});
|
||||
const scroll = useUiStateStore((state) => {
|
||||
return state.scroll;
|
||||
});
|
||||
const { getTilePosition } = useGetTilePosition();
|
||||
|
||||
const getDiagramBoundingBox = useCallback((): Size & Coords => {
|
||||
if (scene.nodes.length === 0) return { width: 0, height: 0, x: 0, y: 0 };
|
||||
|
||||
const nodePositions = scene.nodes.map((node) => {
|
||||
const getProjectBounds = useCallback((nodes: Node[]): Coords[] => {
|
||||
const nodePositions = nodes.map((node) => {
|
||||
return node.position;
|
||||
});
|
||||
|
||||
|
@ -32,7 +35,14 @@ export const useDiagramUtils = () => {
|
|||
x: BOUNDING_BOX_PADDING,
|
||||
y: BOUNDING_BOX_PADDING
|
||||
});
|
||||
const cornerPositions = corners.map((corner) => {
|
||||
|
||||
return corners;
|
||||
}, []);
|
||||
|
||||
const getUnprojectedBounds = useCallback((): Size & Coords => {
|
||||
const projectBounds = getProjectBounds(scene.nodes);
|
||||
|
||||
const cornerPositions = projectBounds.map((corner) => {
|
||||
return getTilePosition({
|
||||
tile: corner
|
||||
});
|
||||
|
@ -47,30 +57,26 @@ export const useDiagramUtils = () => {
|
|||
x: topLeft.x,
|
||||
y: topLeft.y
|
||||
};
|
||||
}, [scene, getTilePosition]);
|
||||
}, [scene, getTilePosition, getProjectBounds]);
|
||||
|
||||
const fitDiagramToScreen = useCallback(() => {
|
||||
const boundingBox = getDiagramBoundingBox();
|
||||
const boundingBox = getProjectBounds(scene.nodes);
|
||||
const sortedCornerPositions = sortByPosition(boundingBox);
|
||||
const boundingBoxSize = getBoundingBoxSize(boundingBox);
|
||||
// const newZoom = Math.min(
|
||||
// window.innerWidth / boundingBox.width,
|
||||
// window.innerHeight / boundingBox.height
|
||||
// );
|
||||
|
||||
uiStateActions.setScroll({
|
||||
offset: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
position: {
|
||||
x: -(boundingBox.x + boundingBox.width / 2) + window.innerWidth / 2,
|
||||
y: -(boundingBox.y + boundingBox.height / 2) + window.innerHeight / 2
|
||||
}
|
||||
});
|
||||
// uiStateActions.setZoom(newZoom);
|
||||
}, [getDiagramBoundingBox, uiStateActions]);
|
||||
const centralTile: Coords = {
|
||||
x: sortedCornerPositions.lowX + Math.floor(boundingBoxSize.width / 2),
|
||||
y: sortedCornerPositions.lowY + Math.floor(boundingBoxSize.height / 2)
|
||||
};
|
||||
scrollToTile(centralTile);
|
||||
}, [getProjectBounds, scene, scrollToTile]);
|
||||
|
||||
return {
|
||||
getDiagramBoundingBox,
|
||||
getUnprojectedBounds,
|
||||
fitDiagramToScreen
|
||||
};
|
||||
};
|
||||
|
|
40
src/hooks/useScroll.ts
Normal file
40
src/hooks/useScroll.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { useCallback } from 'react';
|
||||
import { Coords, TileOriginEnum } from 'src/types';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
export const useScroll = () => {
|
||||
const { getTilePosition } = useGetTilePosition();
|
||||
const scroll = useUiStateStore((state) => {
|
||||
return state.scroll;
|
||||
});
|
||||
const uiStateActions = useUiStateStore((state) => {
|
||||
return state.actions;
|
||||
});
|
||||
const rendererSize = useUiStateStore((state) => {
|
||||
return state.rendererSize;
|
||||
});
|
||||
|
||||
const scrollToTile = useCallback(
|
||||
(tile: Coords, origin?: TileOriginEnum) => {
|
||||
const tilePosition = getTilePosition({ tile, origin });
|
||||
const scrollTo: Coords = {
|
||||
x: scroll.position.x - tilePosition.x + rendererSize.width / 2,
|
||||
y: scroll.position.y - tilePosition.y + rendererSize.height / 2
|
||||
};
|
||||
|
||||
uiStateActions.setScroll({
|
||||
offset: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
position: scrollTo
|
||||
});
|
||||
},
|
||||
[getTilePosition, scroll.position, uiStateActions, rendererSize]
|
||||
);
|
||||
|
||||
return {
|
||||
scrollToTile
|
||||
};
|
||||
};
|
|
@ -14,13 +14,13 @@ export const useWindowUtils = () => {
|
|||
const sceneActions = useSceneStore(({ actions }) => {
|
||||
return actions;
|
||||
});
|
||||
const { fitDiagramToScreen, getDiagramBoundingBox } = useDiagramUtils();
|
||||
const { fitDiagramToScreen, getUnprojectedBounds } = useDiagramUtils();
|
||||
|
||||
useEffect(() => {
|
||||
window.Isoflow = {
|
||||
getDiagramBoundingBox,
|
||||
getUnprojectedBounds,
|
||||
fitDiagramToScreen,
|
||||
setScene: sceneActions.setScene
|
||||
};
|
||||
}, [getDiagramBoundingBox, fitDiagramToScreen, scene, sceneActions]);
|
||||
}, [getUnprojectedBounds, fitDiagramToScreen, scene, sceneActions]);
|
||||
};
|
||||
|
|
|
@ -29,6 +29,7 @@ const initialState = () => {
|
|||
position: { x: 0, y: 0 },
|
||||
offset: { x: 0, y: 0 }
|
||||
},
|
||||
debugMode: false,
|
||||
zoom: 1,
|
||||
rendererSize: { width: 0, height: 0 },
|
||||
actions: {
|
||||
|
@ -71,6 +72,9 @@ const initialState = () => {
|
|||
mode: { type: 'CURSOR', showCursor: true, mousedown: null }
|
||||
});
|
||||
}
|
||||
},
|
||||
setDebugMode: (debugMode) => {
|
||||
set({ debugMode });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -98,6 +98,7 @@ export interface UiState {
|
|||
scroll: Scroll;
|
||||
mouse: Mouse;
|
||||
rendererSize: Size;
|
||||
debugMode: boolean;
|
||||
}
|
||||
|
||||
export interface UiStateActions {
|
||||
|
@ -111,4 +112,5 @@ export interface UiStateActions {
|
|||
setMouse: (mouse: Mouse) => void;
|
||||
setRendererSize: (rendererSize: Size) => void;
|
||||
setInteractionsEnabled: (enabled: boolean) => void;
|
||||
setDebugMode: (enabled: boolean) => void;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue