mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-27 08:29:17 -05:00
refactor: unifies various ui states into single enum
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
NodeInput,
|
||||
ConnectorInput,
|
||||
RectangleInput,
|
||||
IsoflowProps,
|
||||
InitialScene
|
||||
} from 'src/types';
|
||||
import { sceneToSceneInput } from 'src/utils';
|
||||
@@ -22,25 +23,15 @@ import { UiStateProvider, useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { INITIAL_SCENE } from 'src/config';
|
||||
import { useIconCategories } from './hooks/useIconCategories';
|
||||
|
||||
interface Props {
|
||||
initialScene?: InitialScene;
|
||||
disableInteractions?: boolean;
|
||||
onSceneUpdated?: (scene: SceneInput) => void;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
debugMode?: boolean;
|
||||
hideMainMenu?: boolean;
|
||||
}
|
||||
|
||||
const App = ({
|
||||
initialScene,
|
||||
width = '100%',
|
||||
height = '100%',
|
||||
disableInteractions: disableInteractionsProp,
|
||||
onSceneUpdated,
|
||||
debugMode = false,
|
||||
hideMainMenu = false
|
||||
}: Props) => {
|
||||
hideMainMenu = false,
|
||||
editorMode = 'EDITABLE'
|
||||
}: IsoflowProps) => {
|
||||
const prevInitialScene = useRef<SceneInput>(INITIAL_SCENE);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
useWindowUtils();
|
||||
@@ -59,16 +50,9 @@ const App = ({
|
||||
const { setIconCategoriesState } = useIconCategories();
|
||||
|
||||
useEffect(() => {
|
||||
uiActions.setHideMainMenu(hideMainMenu);
|
||||
uiActions.setZoom(initialScene?.zoom ?? 1);
|
||||
uiActions.setDisableInteractions(Boolean(disableInteractionsProp));
|
||||
}, [
|
||||
initialScene?.zoom,
|
||||
disableInteractionsProp,
|
||||
sceneActions,
|
||||
uiActions,
|
||||
hideMainMenu
|
||||
]);
|
||||
uiActions.setEditorMode(editorMode);
|
||||
}, [initialScene?.zoom, editorMode, sceneActions, uiActions, hideMainMenu]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialScene || prevInitialScene.current === initialScene) return;
|
||||
@@ -114,7 +98,7 @@ const App = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const Isoflow = (props: Props) => {
|
||||
export const Isoflow = (props: IsoflowProps) => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<SceneProvider>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, useTheme, Typography } from '@mui/material';
|
||||
import { EditorModeEnum } from 'src/types';
|
||||
import { UiElement } from 'components/UiElement/UiElement';
|
||||
import { toPx } from 'src/utils';
|
||||
import { SceneLayer } from 'src/components/SceneLayer/SceneLayer';
|
||||
@@ -12,6 +13,34 @@ import { ZoomControls } from 'src/components/ZoomControls/ZoomControls';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { DebugUtils } from 'src/components/DebugUtils/DebugUtils';
|
||||
|
||||
const ToolsEnum = {
|
||||
MAIN_MENU: 'MAIN_MENU',
|
||||
ZOOM_CONTROLS: 'ZOOM_CONTROLS',
|
||||
TOOL_MENU: 'TOOL_MENU',
|
||||
ITEM_CONTROLS: 'ITEM_CONTROLS'
|
||||
} as const;
|
||||
|
||||
interface EditorModeMapping {
|
||||
[k: string]: (keyof typeof ToolsEnum)[];
|
||||
}
|
||||
|
||||
const EDITOR_MODE_MAPPING: EditorModeMapping = {
|
||||
[EditorModeEnum.EDITABLE]: [
|
||||
'ITEM_CONTROLS',
|
||||
'ZOOM_CONTROLS',
|
||||
'TOOL_MENU',
|
||||
'MAIN_MENU'
|
||||
],
|
||||
[EditorModeEnum.EXPLORABLE_READONLY]: ['ZOOM_CONTROLS'],
|
||||
[EditorModeEnum.NON_INTERACTIVE]: []
|
||||
};
|
||||
|
||||
const getEditorModeMapping = (editorMode: keyof typeof EditorModeEnum) => {
|
||||
const availableUiFeatures = EDITOR_MODE_MAPPING[editorMode];
|
||||
|
||||
return availableUiFeatures;
|
||||
};
|
||||
|
||||
export const UiOverlay = () => {
|
||||
const theme = useTheme();
|
||||
const { appPadding } = theme.customVars;
|
||||
@@ -21,9 +50,6 @@ export const UiOverlay = () => {
|
||||
},
|
||||
[theme]
|
||||
);
|
||||
const disableInteractions = useUiStateStore((state) => {
|
||||
return state.disableInteractions;
|
||||
});
|
||||
const debugMode = useUiStateStore((state) => {
|
||||
return state.debugMode;
|
||||
});
|
||||
@@ -39,15 +65,16 @@ export const UiOverlay = () => {
|
||||
const sceneTitle = useSceneStore((state) => {
|
||||
return state.title;
|
||||
});
|
||||
const hideMainMenu = useUiStateStore((state) => {
|
||||
return state.hideMainMenu;
|
||||
const editorMode = useUiStateStore((state) => {
|
||||
return state.editorMode;
|
||||
});
|
||||
|
||||
if (disableInteractions) return null;
|
||||
const availableTools = useMemo(() => {
|
||||
return getEditorModeMapping(editorMode);
|
||||
}, [editorMode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{itemControls && (
|
||||
{availableTools.includes('ITEM_CONTROLS') && itemControls && (
|
||||
<UiElement
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
@@ -65,33 +92,38 @@ export const UiOverlay = () => {
|
||||
</UiElement>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: appPadding.x,
|
||||
top: appPadding.y
|
||||
}}
|
||||
>
|
||||
<ToolMenu />
|
||||
</Box>
|
||||
|
||||
{mode.type === 'PLACE_ELEMENT' && mode.icon && (
|
||||
<SceneLayer>
|
||||
<DragAndDrop icon={mode.icon} tile={mouse.position.tile} />
|
||||
</SceneLayer>
|
||||
{availableTools.includes('TOOL_MENU') && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: appPadding.x,
|
||||
top: appPadding.y
|
||||
}}
|
||||
>
|
||||
<ToolMenu />
|
||||
</Box>
|
||||
{mode.type === 'PLACE_ELEMENT' && mode.icon && (
|
||||
<SceneLayer>
|
||||
<DragAndDrop icon={mode.icon} tile={mouse.position.tile} />
|
||||
</SceneLayer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: appPadding.x,
|
||||
bottom: appPadding.y
|
||||
}}
|
||||
>
|
||||
<ZoomControls />
|
||||
</Box>
|
||||
{availableTools.includes('ZOOM_CONTROLS') && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: appPadding.x,
|
||||
bottom: appPadding.y
|
||||
}}
|
||||
>
|
||||
<ZoomControls />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!hideMainMenu && (
|
||||
{availableTools.includes('MAIN_MENU') && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -49,7 +49,3 @@ export const INITIAL_SCENE: SceneInput = {
|
||||
textBoxes: [],
|
||||
rectangles: []
|
||||
};
|
||||
export const STARTING_MODE: Mode = {
|
||||
type: 'PAN',
|
||||
showCursor: false
|
||||
};
|
||||
|
||||
@@ -49,7 +49,8 @@ export const useInteractionManager = () => {
|
||||
|
||||
const onMouseEvent = useCallback(
|
||||
(e: SlimMouseEvent) => {
|
||||
if (!rendererRef.current || uiState.disableInteractions) return;
|
||||
if (!rendererRef.current || uiState.editorMode === 'NON_INTERACTIVE')
|
||||
return;
|
||||
|
||||
const mode = modes[uiState.mode.type];
|
||||
const modeFunction = getModeFunction(mode, e);
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import React, { createContext, useContext, useRef } from 'react';
|
||||
import { createStore, useStore } from 'zustand';
|
||||
import { CoordsUtils, incrementZoom, decrementZoom } from 'src/utils';
|
||||
import {
|
||||
CoordsUtils,
|
||||
incrementZoom,
|
||||
decrementZoom,
|
||||
getStartingMode
|
||||
} from 'src/utils';
|
||||
import { UiStateStore } from 'src/types';
|
||||
import { STARTING_MODE } from 'src/config';
|
||||
|
||||
const initialState = () => {
|
||||
return createStore<UiStateStore>((set, get) => {
|
||||
return {
|
||||
editorMode: 'EXPLORABLE_READONLY',
|
||||
mode: getStartingMode('EXPLORABLE_READONLY'),
|
||||
iconCategoriesState: [],
|
||||
disableInteractions: false,
|
||||
hideMainMenu: false,
|
||||
mode: STARTING_MODE,
|
||||
isMainMenuOpen: false,
|
||||
mouse: {
|
||||
position: { screen: CoordsUtils.zero(), tile: CoordsUtils.zero() },
|
||||
@@ -27,12 +32,15 @@ const initialState = () => {
|
||||
zoom: 1,
|
||||
rendererSize: { width: 0, height: 0 },
|
||||
actions: {
|
||||
setEditorMode: (mode) => {
|
||||
set({ editorMode: mode, mode: getStartingMode(mode) });
|
||||
},
|
||||
setIconCategoriesState: (iconCategoriesState) => {
|
||||
set({ iconCategoriesState });
|
||||
},
|
||||
resetUiState: () => {
|
||||
set({
|
||||
mode: STARTING_MODE,
|
||||
mode: getStartingMode(get().editorMode),
|
||||
scroll: {
|
||||
position: CoordsUtils.zero(),
|
||||
offset: CoordsUtils.zero()
|
||||
@@ -45,9 +53,6 @@ const initialState = () => {
|
||||
setMode: (mode) => {
|
||||
set({ mode });
|
||||
},
|
||||
setHideMainMenu: (state) => {
|
||||
set({ hideMainMenu: state });
|
||||
},
|
||||
setIsMainMenuOpen: (isMainMenuOpen) => {
|
||||
set({ isMainMenuOpen, itemControls: null });
|
||||
},
|
||||
@@ -77,17 +82,6 @@ const initialState = () => {
|
||||
setRendererSize: (rendererSize) => {
|
||||
set({ rendererSize });
|
||||
},
|
||||
setDisableInteractions: (isDisabled) => {
|
||||
set({ disableInteractions: isDisabled });
|
||||
|
||||
if (isDisabled) {
|
||||
set({ mode: { type: 'INTERACTIONS_DISABLED', showCursor: false } });
|
||||
} else {
|
||||
set({
|
||||
mode: STARTING_MODE
|
||||
});
|
||||
}
|
||||
},
|
||||
setDebugMode: (debugMode) => {
|
||||
set({ debugMode });
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import type { SceneInput } from './inputs';
|
||||
|
||||
export type InitialScene = Partial<SceneInput> & {
|
||||
zoom?: number;
|
||||
};
|
||||
|
||||
export interface Coords {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -30,3 +24,9 @@ export type SlimMouseEvent = Pick<
|
||||
MouseEvent,
|
||||
'clientX' | 'clientY' | 'target' | 'type'
|
||||
>;
|
||||
|
||||
export const EditorModeEnum = {
|
||||
NON_INTERACTIVE: 'NON_INTERACTIVE',
|
||||
EXPLORABLE_READONLY: 'EXPLORABLE_READONLY',
|
||||
EDITABLE: 'EDITABLE'
|
||||
} as const;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
connectorStyleEnum
|
||||
} from 'src/validation/sceneItems';
|
||||
import { sceneInput } from 'src/validation/scene';
|
||||
import type { EditorModeEnum } from './common';
|
||||
|
||||
export type ConnectorStyleEnum = z.infer<typeof connectorStyleEnum>;
|
||||
export type IconInput = z.infer<typeof iconInput>;
|
||||
@@ -18,3 +19,17 @@ export type ConnectorInput = z.infer<typeof connectorInput>;
|
||||
export type TextBoxInput = z.infer<typeof textBoxInput>;
|
||||
export type RectangleInput = z.infer<typeof rectangleInput>;
|
||||
export type SceneInput = z.infer<typeof sceneInput>;
|
||||
|
||||
export type InitialScene = Partial<SceneInput> & {
|
||||
zoom?: number;
|
||||
};
|
||||
|
||||
export interface IsoflowProps {
|
||||
initialScene?: InitialScene;
|
||||
onSceneUpdated?: (scene: SceneInput) => void;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
debugMode?: boolean;
|
||||
hideMainMenu?: boolean;
|
||||
editorMode?: keyof typeof EditorModeEnum;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Coords, Size } from './common';
|
||||
import { Coords, Size, EditorModeEnum } from './common';
|
||||
import { SceneItem, Connector, SceneItemReference } from './scene';
|
||||
import { IconInput } from './inputs';
|
||||
|
||||
@@ -154,9 +154,8 @@ export type IconCollectionStateWithIcons = IconCollectionState & {
|
||||
};
|
||||
|
||||
export interface UiState {
|
||||
editorMode: keyof typeof EditorModeEnum;
|
||||
iconCategoriesState: IconCollectionState[];
|
||||
disableInteractions: boolean;
|
||||
hideMainMenu: boolean;
|
||||
mode: Mode;
|
||||
isMainMenuOpen: boolean;
|
||||
itemControls: ItemControls;
|
||||
@@ -169,10 +168,10 @@ export interface UiState {
|
||||
}
|
||||
|
||||
export interface UiStateActions {
|
||||
setEditorMode: (mode: keyof typeof EditorModeEnum) => void;
|
||||
setIconCategoriesState: (iconCategoriesState: IconCollectionState[]) => void;
|
||||
resetUiState: () => void;
|
||||
setMode: (mode: Mode) => void;
|
||||
setHideMainMenu: (state: boolean) => void;
|
||||
incrementZoom: () => void;
|
||||
decrementZoom: () => void;
|
||||
setIsMainMenuOpen: (isOpen: boolean) => void;
|
||||
@@ -182,7 +181,6 @@ export interface UiStateActions {
|
||||
setContextMenu: (contextMenu: ContextMenu) => void;
|
||||
setMouse: (mouse: Mouse) => void;
|
||||
setRendererSize: (rendererSize: Size) => void;
|
||||
setDisableInteractions: (isDisabled: boolean) => void;
|
||||
setDebugMode: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import chroma from 'chroma-js';
|
||||
import { Icon } from 'src/types';
|
||||
import { Icon, EditorModeEnum, Mode } from 'src/types';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const generateId = () => {
|
||||
@@ -63,3 +63,18 @@ export const categoriseIcons = (icons: Icon[]) => {
|
||||
|
||||
return categories;
|
||||
};
|
||||
|
||||
export const getStartingMode = (
|
||||
editorMode: keyof typeof EditorModeEnum
|
||||
): Mode => {
|
||||
switch (editorMode) {
|
||||
case 'EDITABLE':
|
||||
return { type: 'CURSOR', showCursor: true, mousedownItem: null };
|
||||
case 'EXPLORABLE_READONLY':
|
||||
return { type: 'PAN', showCursor: false };
|
||||
case 'NON_INTERACTIVE':
|
||||
return { type: 'INTERACTIONS_DISABLED', showCursor: false };
|
||||
default:
|
||||
throw new Error('Invalid editor mode.');
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user