mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-25 07:28:55 -05:00
refactor: moves state to context
keeping state in context prevents shared state between multiple instances of isoflow
This commit is contained in:
@@ -11,14 +11,14 @@ import {
|
||||
GroupInput,
|
||||
Scene
|
||||
} from 'src/types';
|
||||
import { useSceneStore } from 'src/stores/useSceneStore';
|
||||
import { useSceneStore, SceneProvider } from 'src/stores/sceneStore';
|
||||
import { GlobalStyles } from 'src/styles/GlobalStyles';
|
||||
import { Renderer } from 'src/components/Renderer/Renderer';
|
||||
import { sceneToSceneInput } from 'src/utils';
|
||||
import { LabelContainer } from 'src/components/Node/LabelContainer';
|
||||
import { useWindowUtils } from 'src/hooks/useWindowUtils';
|
||||
import { ItemControlsManager } from './components/ItemControls/ItemControlsManager';
|
||||
import { useUiStateStore } from './stores/useUiStateStore';
|
||||
import { UiStateProvider, useUiStateStore } from './stores/uiStateStore';
|
||||
|
||||
interface Props {
|
||||
initialScene: SceneInput & {
|
||||
@@ -30,12 +30,7 @@ interface Props {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
const Isoflow = ({
|
||||
initialScene,
|
||||
width,
|
||||
height = 500,
|
||||
onSceneUpdated
|
||||
}: Props) => {
|
||||
const App = ({ initialScene, width, height = 500, onSceneUpdated }: Props) => {
|
||||
useWindowUtils();
|
||||
const sceneActions = useSceneStore((state) => {
|
||||
return state.actions;
|
||||
@@ -61,14 +56,8 @@ const Isoflow = ({
|
||||
sceneActions.setScene(initialScene);
|
||||
}, [initialScene, sceneActions]);
|
||||
|
||||
useSceneStore.subscribe((scene, prevScene) => {
|
||||
if (!onSceneUpdated) return;
|
||||
|
||||
onSceneUpdated(sceneToSceneInput(scene), sceneToSceneInput(prevScene));
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<>
|
||||
<GlobalStyles />
|
||||
<Box
|
||||
sx={{
|
||||
@@ -82,6 +71,18 @@ const Isoflow = ({
|
||||
{isToolbarVisible && <ItemControlsManager />}
|
||||
<ToolMenu />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Isoflow = (props: Props) => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<SceneProvider>
|
||||
<UiStateProvider>
|
||||
<App {...props} />
|
||||
</UiStateProvider>
|
||||
</SceneProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { UNPROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { pathfinder, getBoundingBox, getBoundingBoxSize } from 'src/utils';
|
||||
import { IsoTileArea } from 'src/components/IsoTileArea/IsoTileArea';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
interface Props {
|
||||
// connector: ConnectorI;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
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/useSceneStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
|
||||
export const ContextMenuLayer = () => {
|
||||
const contextMenu = useUiStateStore((state) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
ArrowRightAlt as ConnectIcon,
|
||||
Delete as DeleteIcon
|
||||
} from '@mui/icons-material';
|
||||
import { useNodeHooks } from 'src/stores/useSceneStore';
|
||||
import { useNode } from 'src/hooks/useNode';
|
||||
import { ContextMenu } from './components/ContextMenu';
|
||||
import { ContextMenuItem } from './components/ContextMenuItem';
|
||||
|
||||
@@ -12,8 +12,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const NodeContextMenu = ({ nodeId }: Props) => {
|
||||
const { useGetNodeById } = useNodeHooks();
|
||||
const node = useGetNodeById(nodeId);
|
||||
const node = useNode(nodeId);
|
||||
|
||||
if (!node) return null;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
interface Props {
|
||||
onClick: () => void;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, useTheme } from '@mui/material';
|
||||
import gsap from 'gsap';
|
||||
import { Coords, TileOriginEnum } from 'src/types';
|
||||
import { IsoTileArea } from 'src/components/IsoTileArea/IsoTileArea';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Node, TileOriginEnum, Group as GroupI } from 'src/types';
|
||||
import { getBoundingBox, getBoundingBoxSize } from 'src/utils';
|
||||
import { IsoTileArea } from 'src/components/IsoTileArea/IsoTileArea';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
interface Props {
|
||||
nodes: Node[];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Card, useTheme } from '@mui/material';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { NodeControls } from './NodeControls/NodeControls';
|
||||
import { ProjectControls } from './ProjectControls/ProjectControls';
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Tabs, Tab, Box } from '@mui/material';
|
||||
import { Node } from 'src/types';
|
||||
import { useSceneStore, useNodeHooks } from 'src/stores/useSceneStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useNode } from 'src/hooks/useNode';
|
||||
import { ControlsContainer } from '../components/ControlsContainer';
|
||||
import { Icons } from './IconSelection/IconSelection';
|
||||
import { Header } from '../components/Header';
|
||||
@@ -19,8 +20,7 @@ export const NodeControls = ({ nodeId }: Props) => {
|
||||
const sceneActions = useSceneStore((state) => {
|
||||
return state.actions;
|
||||
});
|
||||
const { useGetNodeById } = useNodeHooks();
|
||||
const node = useGetNodeById(nodeId);
|
||||
const node = useNode(nodeId);
|
||||
|
||||
const onTabChanged = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setTab(newValue);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Size, Coords, TileOriginEnum, Node as NodeI } from 'src/types';
|
||||
import { getProjectedTileSize, getColorVariant } from 'src/utils';
|
||||
import { useResizeObserver } from 'src/hooks/useResizeObserver';
|
||||
import { IsoTileArea } from 'src/components/IsoTileArea/IsoTileArea';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { LabelContainer } from './LabelContainer';
|
||||
import { MarkdownLabel } from './LabelTypes/MarkdownLabel';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { Node as NodeI } from 'src/types';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useSceneStore } from 'src/stores/useSceneStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useInteractionManager } from 'src/interaction/useInteractionManager';
|
||||
import { Grid } from 'src/components/Grid/Grid';
|
||||
import { Cursor } from 'src/components/Cursor/Cursor';
|
||||
|
||||
@@ -7,12 +7,9 @@ import {
|
||||
NearMe as NearMeIcon,
|
||||
CenterFocusStrong as CenterFocusStrongIcon
|
||||
} from '@mui/icons-material';
|
||||
import {
|
||||
useUiStateStore,
|
||||
MIN_ZOOM,
|
||||
MAX_ZOOM
|
||||
} from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useDiagramUtils } from 'src/hooks/useDiagramUtils';
|
||||
import { MAX_ZOOM, MIN_ZOOM } from 'src/config';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
|
||||
export const ToolMenu = () => {
|
||||
|
||||
@@ -15,3 +15,6 @@ export const NODE_DEFAULTS = {
|
||||
labelHeight: 100,
|
||||
color: DEFAULT_COLOR
|
||||
};
|
||||
export const ZOOM_INCREMENT = 0.2;
|
||||
export const MIN_ZOOM = 0.2;
|
||||
export const MAX_ZOOM = 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSceneStore } from 'src/stores/useSceneStore';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { Size, Coords } from 'src/types';
|
||||
import { getBoundingBox, getBoundingBoxSize, sortByPosition } from 'src/utils';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { Coords, TileOriginEnum } from 'src/types';
|
||||
import { getTilePosition as getTilePositionUtil } from 'src/utils';
|
||||
|
||||
|
||||
16
src/hooks/useNode.ts
Normal file
16
src/hooks/useNode.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
|
||||
export const useNode = (nodeId: string) => {
|
||||
const nodes = useSceneStore((state) => {
|
||||
return state.nodes;
|
||||
});
|
||||
|
||||
const node = useMemo(() => {
|
||||
return nodes.find((n) => {
|
||||
return n.id === nodeId;
|
||||
});
|
||||
}, [nodes, nodeId]);
|
||||
|
||||
return node;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSceneStore } from 'src/stores/useSceneStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useDiagramUtils } from 'src/hooks/useDiagramUtils';
|
||||
|
||||
export const useWindowUtils = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { produce } from 'immer';
|
||||
import { useSceneStore } from 'src/stores/useSceneStore';
|
||||
import { useUiStateStore } from 'src/stores/useUiStateStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { CoordsUtils, screenToIso } from 'src/utils';
|
||||
import { InteractionReducer, Mouse, State, Coords } from 'src/types';
|
||||
import { DragItems } from './reducers/DragItems';
|
||||
|
||||
98
src/stores/sceneStore.tsx
Normal file
98
src/stores/sceneStore.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
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 { sceneInput } from 'src/validation/scene';
|
||||
import { sceneInputtoScene } from 'src/utils';
|
||||
|
||||
interface Actions {
|
||||
actions: SceneActions;
|
||||
}
|
||||
|
||||
type SceneStore = Scene & Actions;
|
||||
|
||||
const initialState = () => {
|
||||
return createStore<SceneStore>((set, get) => {
|
||||
return {
|
||||
nodes: [],
|
||||
connectors: [],
|
||||
groups: [],
|
||||
icons: [],
|
||||
actions: {
|
||||
setScene: (scene) => {
|
||||
sceneInput.parse(scene);
|
||||
|
||||
const newScene = sceneInputtoScene(scene);
|
||||
|
||||
set(newScene);
|
||||
},
|
||||
updateNode: (id, updates) => {
|
||||
const { nodes } = get();
|
||||
const nodeIndex = nodes.findIndex((node) => {
|
||||
return node.id === id;
|
||||
});
|
||||
|
||||
if (nodeIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newNodes = produce(nodes, (draftState) => {
|
||||
draftState[nodeIndex] = { ...draftState[nodeIndex], ...updates };
|
||||
});
|
||||
|
||||
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] });
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const SceneContext = createContext<ReturnType<typeof initialState> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
interface ProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// TODO: Typings below are pretty gnarly due to the way Zustand works.
|
||||
// see https://github.com/pmndrs/zustand/discussions/1180#discussioncomment-3439061
|
||||
export const SceneProvider = ({ children }: ProviderProps) => {
|
||||
const storeRef = useRef<ReturnType<typeof initialState>>();
|
||||
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = initialState();
|
||||
}
|
||||
|
||||
return (
|
||||
<SceneContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</SceneContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useSceneStore<T>(selector: (state: SceneStore) => T) {
|
||||
const store = useContext(SceneContext);
|
||||
|
||||
if (store === null) {
|
||||
throw new Error('Missing provider in the tree');
|
||||
}
|
||||
|
||||
const value = useStore(store, selector);
|
||||
return value;
|
||||
}
|
||||
105
src/stores/uiStateStore.tsx
Normal file
105
src/stores/uiStateStore.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { createContext, useContext, useRef } from 'react';
|
||||
import { createStore, useStore } from 'zustand';
|
||||
import { CoordsUtils, incrementZoom, decrementZoom } from 'src/utils';
|
||||
import { UiState, UiStateActions } from 'src/types';
|
||||
|
||||
interface Actions {
|
||||
actions: UiStateActions;
|
||||
}
|
||||
|
||||
type UiStateStore = UiState & Actions;
|
||||
|
||||
const initialState = () => {
|
||||
return createStore<UiStateStore>((set, get) => {
|
||||
return {
|
||||
isToolbarVisible: true,
|
||||
mode: {
|
||||
type: 'CURSOR',
|
||||
showCursor: true,
|
||||
mousedown: null
|
||||
},
|
||||
mouse: {
|
||||
position: { screen: CoordsUtils.zero(), tile: CoordsUtils.zero() },
|
||||
mousedown: null,
|
||||
delta: null
|
||||
},
|
||||
itemControls: null,
|
||||
contextMenu: null,
|
||||
scroll: {
|
||||
position: { x: 0, y: 0 },
|
||||
offset: { x: 0, y: 0 }
|
||||
},
|
||||
zoom: 1,
|
||||
rendererSize: { width: 0, height: 0 },
|
||||
actions: {
|
||||
setMode: (mode) => {
|
||||
set({ mode });
|
||||
},
|
||||
incrementZoom: () => {
|
||||
const { zoom } = get();
|
||||
set({ zoom: incrementZoom(zoom) });
|
||||
},
|
||||
decrementZoom: () => {
|
||||
const { zoom } = get();
|
||||
set({ zoom: decrementZoom(zoom) });
|
||||
},
|
||||
setZoom: (zoom) => {
|
||||
set({ zoom });
|
||||
},
|
||||
setScroll: ({ position, offset }) => {
|
||||
set({ scroll: { position, offset: offset ?? get().scroll.offset } });
|
||||
},
|
||||
setSidebar: (itemControls) => {
|
||||
set({ itemControls });
|
||||
},
|
||||
setContextMenu: (contextMenu) => {
|
||||
set({ contextMenu });
|
||||
},
|
||||
setMouse: (mouse) => {
|
||||
set({ mouse });
|
||||
},
|
||||
setRendererSize: (rendererSize) => {
|
||||
set({ rendererSize });
|
||||
},
|
||||
setToolbarVisibility: (visible) => {
|
||||
set({ isToolbarVisible: visible });
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const UiStateContext = createContext<ReturnType<typeof initialState> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
interface ProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// TODO: Typings below are pretty gnarly due to the way Zustand works.
|
||||
// see https://github.com/pmndrs/zustand/discussions/1180#discussioncomment-3439061
|
||||
export const UiStateProvider = ({ children }: ProviderProps) => {
|
||||
const storeRef = useRef<ReturnType<typeof initialState>>();
|
||||
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = initialState();
|
||||
}
|
||||
|
||||
return (
|
||||
<UiStateContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</UiStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useUiStateStore<T>(selector: (state: UiStateStore) => T) {
|
||||
const store = useContext(UiStateContext);
|
||||
|
||||
if (store === null) {
|
||||
throw new Error('Missing provider in the tree');
|
||||
}
|
||||
|
||||
const value = useStore(store, selector);
|
||||
return value;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { create } 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 { sceneInput } from 'src/validation/scene';
|
||||
import { sceneInputtoScene } from 'src/utils';
|
||||
|
||||
export type UseSceneStore = Scene & {
|
||||
actions: SceneActions;
|
||||
};
|
||||
|
||||
// TODO: Optimise lookup time by having a store of tile coords and what items they contain
|
||||
export const useSceneStore = create<UseSceneStore>((set, get) => {
|
||||
return {
|
||||
nodes: [],
|
||||
connectors: [],
|
||||
groups: [],
|
||||
icons: [],
|
||||
actions: {
|
||||
setScene: (scene) => {
|
||||
sceneInput.parse(scene);
|
||||
|
||||
const newScene = sceneInputtoScene(scene);
|
||||
|
||||
set(newScene);
|
||||
},
|
||||
updateNode: (id, updates) => {
|
||||
const { nodes } = get();
|
||||
const nodeIndex = nodes.findIndex((node) => {
|
||||
return node.id === id;
|
||||
});
|
||||
|
||||
if (nodeIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newNodes = produce(nodes, (draftState) => {
|
||||
draftState[nodeIndex] = { ...draftState[nodeIndex], ...updates };
|
||||
});
|
||||
|
||||
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] });
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const useNodeHooks = () => {
|
||||
const nodes = useSceneStore((state) => {
|
||||
return state.nodes;
|
||||
});
|
||||
|
||||
const useGetNodeById = useCallback(
|
||||
(id: string) => {
|
||||
return nodes.find((node) => {
|
||||
return node.id === id;
|
||||
});
|
||||
},
|
||||
[nodes]
|
||||
);
|
||||
|
||||
return { useGetNodeById };
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { clamp, roundToOneDecimalPlace, CoordsUtils } from 'src/utils';
|
||||
import { UiState, UiStateActions } from 'src/types';
|
||||
|
||||
// TODO: Move into the defaults file
|
||||
const ZOOM_INCREMENT = 0.2;
|
||||
export const MIN_ZOOM = 0.2;
|
||||
export const MAX_ZOOM = 1;
|
||||
|
||||
export type UseUiStateStore = UiState & {
|
||||
actions: UiStateActions;
|
||||
};
|
||||
|
||||
export const useUiStateStore = create<UseUiStateStore>((set, get) => {
|
||||
return {
|
||||
isToolbarVisible: true,
|
||||
mode: {
|
||||
type: 'CURSOR',
|
||||
showCursor: true,
|
||||
mousedown: null
|
||||
},
|
||||
mouse: {
|
||||
position: { screen: CoordsUtils.zero(), tile: CoordsUtils.zero() },
|
||||
mousedown: null,
|
||||
delta: null
|
||||
},
|
||||
itemControls: null,
|
||||
contextMenu: null,
|
||||
scroll: {
|
||||
position: { x: 0, y: 0 },
|
||||
offset: { x: 0, y: 0 }
|
||||
},
|
||||
zoom: 1,
|
||||
rendererSize: { width: 0, height: 0 },
|
||||
actions: {
|
||||
setMode: (mode) => {
|
||||
set({ mode });
|
||||
},
|
||||
incrementZoom: () => {
|
||||
const { zoom } = get();
|
||||
const targetZoom = clamp(zoom + ZOOM_INCREMENT, MIN_ZOOM, MAX_ZOOM);
|
||||
set({ zoom: roundToOneDecimalPlace(targetZoom) });
|
||||
},
|
||||
decrementZoom: () => {
|
||||
const { zoom } = get();
|
||||
const targetZoom = clamp(zoom - ZOOM_INCREMENT, MIN_ZOOM, MAX_ZOOM);
|
||||
set({ zoom: roundToOneDecimalPlace(targetZoom) });
|
||||
},
|
||||
setZoom: (zoom) => {
|
||||
set({ zoom });
|
||||
},
|
||||
setScroll: ({ position, offset }) => {
|
||||
set({ scroll: { position, offset: offset ?? get().scroll.offset } });
|
||||
},
|
||||
setSidebar: (itemControls) => {
|
||||
set({ itemControls });
|
||||
},
|
||||
setContextMenu: (contextMenu) => {
|
||||
set({ contextMenu });
|
||||
},
|
||||
setMouse: (mouse) => {
|
||||
set({ mouse });
|
||||
},
|
||||
setRendererSize: (rendererSize) => {
|
||||
set({ rendererSize });
|
||||
},
|
||||
setToolbarVisibility: (visible) => {
|
||||
set({ isToolbarVisible: visible });
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,6 +1,12 @@
|
||||
import { TILE_PROJECTION_MULTIPLIERS, UNPROJECTED_TILE_SIZE } from 'src/config';
|
||||
import {
|
||||
TILE_PROJECTION_MULTIPLIERS,
|
||||
UNPROJECTED_TILE_SIZE,
|
||||
ZOOM_INCREMENT,
|
||||
MAX_ZOOM,
|
||||
MIN_ZOOM
|
||||
} from 'src/config';
|
||||
import { Coords, TileOriginEnum, Node, Size, Scroll } from 'src/types';
|
||||
import { CoordsUtils } from 'src/utils';
|
||||
import { CoordsUtils, clamp, roundToOneDecimalPlace } from 'src/utils';
|
||||
|
||||
interface GetProjectedTileSize {
|
||||
zoom: number;
|
||||
@@ -194,3 +200,13 @@ export const filterNodesByTile = ({ tile, nodes }: GetNodesByTile): Node[] => {
|
||||
return CoordsUtils.isEqual(node.position, tile);
|
||||
});
|
||||
};
|
||||
|
||||
export const incrementZoom = (zoom: number) => {
|
||||
const newZoom = clamp(zoom + ZOOM_INCREMENT, MIN_ZOOM, MAX_ZOOM);
|
||||
return roundToOneDecimalPlace(newZoom);
|
||||
};
|
||||
|
||||
export const decrementZoom = (zoom: number) => {
|
||||
const newZoom = clamp(zoom - ZOOM_INCREMENT, MIN_ZOOM, MAX_ZOOM);
|
||||
return roundToOneDecimalPlace(newZoom);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user