feat: allows main menu to be customised

This commit is contained in:
Mark Mankarious 2023-10-13 14:28:48 +01:00
parent 5d51aab722
commit 46ce637cd0
8 changed files with 130 additions and 39 deletions

View file

@ -7,4 +7,5 @@
| `height` | `number` \| `string` | Height of the Isoflow renderer as a CSS value. | `100%` |
| `onSceneUpdate` | `function` | A callback that is triggered whenever an item is added, updated or removed from the scene. The callback is called with the updated scene as the first argument. | `undefined` |
| `enableDebugTools` | `boolean` | Enables extra tools for debugging purposes. | `false` |
| `editorMode` | `EXPLORABLE_READONLY` \| `NON_INTERACTIVE` \| `EDITABLE` | Enables / disables editor features. | `EDITABLE` |
| `editorMode` | `EXPLORABLE_READONLY` \| `NON_INTERACTIVE` \| `EDITABLE` | Enables / disables editor features. | `EDITABLE` |
| `mainMenuOptions` | `(OPEN \| SAVE_JSON \| CLEAR \| GITHUB \| DISCORD \| VERSION)[]` | Shows / hides options in the main menu. If `[]` is passed, the menu is hidden. | `['OPEN', 'SAVE_JSON', 'CLEAR', 'GITHUB', 'DISCORD', 'VERSION']` |

View file

@ -20,11 +20,12 @@ import { useWindowUtils } from 'src/hooks/useWindowUtils';
import { sceneInput as sceneValidationSchema } from 'src/validation/scene';
import { UiOverlay } from 'src/components/UiOverlay/UiOverlay';
import { UiStateProvider, useUiStateStore } from 'src/stores/uiStateStore';
import { INITIAL_SCENE } from 'src/config';
import { INITIAL_SCENE, MAIN_MENU_OPTIONS } from 'src/config';
import { useIconCategories } from './hooks/useIconCategories';
const App = ({
initialScene,
mainMenuOptions = MAIN_MENU_OPTIONS,
width = '100%',
height = '100%',
onSceneUpdated,
@ -51,7 +52,14 @@ const App = ({
useEffect(() => {
uiActions.setZoom(initialScene?.zoom ?? 1);
uiActions.setEditorMode(editorMode);
}, [initialScene?.zoom, editorMode, sceneActions, uiActions]);
uiActions.setMainMenuOptions(mainMenuOptions);
}, [
initialScene?.zoom,
editorMode,
sceneActions,
uiActions,
mainMenuOptions
]);
useEffect(() => {
return () => {

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import { Menu, Typography, Divider, Card } from '@mui/material';
import {
Menu as MenuIcon,
@ -22,6 +22,9 @@ export const MainMenu = () => {
const isMainMenuOpen = useUiStateStore((state) => {
return state.isMainMenuOpen;
});
const mainMenuOptions = useUiStateStore((state) => {
return state.mainMenuOptions;
});
const scene = useSceneStore((state) => {
return {
title: state.title,
@ -95,6 +98,25 @@ export const MainMenu = () => {
uiStateActions.setIsMainMenuOpen(false);
}, [uiStateActions, setScene]);
const sectionVisibility = useMemo(() => {
return {
actions: Boolean(
mainMenuOptions.includes('OPEN') ||
mainMenuOptions.includes('SAVE_JSON') ||
mainMenuOptions.includes('CLEAR')
),
links: Boolean(
mainMenuOptions.includes('GITHUB') ||
mainMenuOptions.includes('DISCORD')
),
version: Boolean(mainMenuOptions.includes('VERSION'))
};
}, [mainMenuOptions]);
if (mainMenuOptions.length === 0) {
return null;
}
return (
<UiElement>
<IconButton Icon={<MenuIcon />} name="Main menu" onClick={onToggleMenu} />
@ -117,38 +139,65 @@ export const MainMenu = () => {
}}
>
<Card sx={{ py: 1 }}>
<MenuItem onClick={onOpenScene} Icon={<FolderOpenIcon />}>
Open
</MenuItem>
<MenuItem onClick={onSaveAs} Icon={<DownloadIcon />}>
Download diagram
</MenuItem>
<MenuItem onClick={onClearCanvas} Icon={<DeleteOutlineIcon />}>
Clear the canvas
</MenuItem>
<Divider />
<MenuItem
onClick={() => {
return gotoUrl(`${REPOSITORY_URL}`);
}}
Icon={<GitHubIcon />}
>
GitHub
</MenuItem>
<MenuItem
onClick={() => {
return gotoUrl('https://discord.gg/QYPkvZth7D');
}}
Icon={<QuestionAnswerIcon />}
>
Discord
</MenuItem>
<Divider />
<MenuItem>
<Typography variant="body2" color="text.secondary">
Isoflow v{PACKAGE_VERSION}
</Typography>
</MenuItem>
{mainMenuOptions.includes('OPEN') && (
<MenuItem onClick={onOpenScene} Icon={<FolderOpenIcon />}>
Open
</MenuItem>
)}
{mainMenuOptions.includes('SAVE_JSON') && (
<MenuItem onClick={onSaveAs} Icon={<DownloadIcon />}>
Download diagram
</MenuItem>
)}
{mainMenuOptions.includes('CLEAR') && (
<MenuItem onClick={onClearCanvas} Icon={<DeleteOutlineIcon />}>
Clear the canvas
</MenuItem>
)}
{sectionVisibility.links && (
<>
<Divider />
{mainMenuOptions.includes('GITHUB') && (
<MenuItem
onClick={() => {
return gotoUrl(`${REPOSITORY_URL}`);
}}
Icon={<GitHubIcon />}
>
GitHub
</MenuItem>
)}
{mainMenuOptions.includes('DISCORD') && (
<MenuItem
onClick={() => {
return gotoUrl('https://discord.gg/QYPkvZth7D');
}}
Icon={<QuestionAnswerIcon />}
>
Discord
</MenuItem>
)}
</>
)}
{sectionVisibility.version && (
<>
<Divider />
{mainMenuOptions.includes('VERSION') && (
<MenuItem>
<Typography variant="body2" color="text.secondary">
Isoflow v{PACKAGE_VERSION}
</Typography>
</MenuItem>
)}
</>
)}
</Card>
</Menu>
</UiElement>

View file

@ -1,4 +1,10 @@
import { Size, Coords, SceneInput, Connector } from 'src/types';
import {
Size,
Coords,
SceneInput,
Connector,
MainMenuOptions
} from 'src/types';
import { customVars } from './styles/theme';
// TODO: This file could do with better organisation and convention for easier reading.
@ -53,3 +59,12 @@ export const INITIAL_SCENE: SceneInput = {
textBoxes: [],
rectangles: []
};
export const MAIN_MENU_OPTIONS: MainMenuOptions = [
'CLEAR',
'OPEN',
'SAVE_JSON',
'CLEAR',
'DISCORD',
'GITHUB',
'VERSION'
];

View file

@ -11,6 +11,7 @@ import { UiStateStore } from 'src/types';
const initialState = () => {
return createStore<UiStateStore>((set, get) => {
return {
mainMenuOptions: [],
editorMode: 'EXPLORABLE_READONLY',
mode: getStartingMode('EXPLORABLE_READONLY'),
iconCategoriesState: [],
@ -31,6 +32,9 @@ const initialState = () => {
zoom: 1,
rendererSize: { width: 0, height: 0 },
actions: {
setMainMenuOptions: (mainMenuOptions) => {
set({ mainMenuOptions });
},
setEditorMode: (mode) => {
set({ editorMode: mode, mode: getStartingMode(mode) });
},

View file

@ -30,3 +30,14 @@ export const EditorModeEnum = {
EXPLORABLE_READONLY: 'EXPLORABLE_READONLY',
EDITABLE: 'EDITABLE'
} as const;
export const MainMenuOptionsEnum = {
OPEN: 'OPEN',
SAVE_JSON: 'SAVE_JSON',
CLEAR: 'CLEAR',
GITHUB: 'GITHUB',
DISCORD: 'DISCORD',
VERSION: 'VERSION'
} as const;
export type MainMenuOptions = (keyof typeof MainMenuOptionsEnum)[];

View file

@ -9,7 +9,7 @@ import {
connectorStyleEnum
} from 'src/validation/sceneItems';
import { sceneInput } from 'src/validation/scene';
import type { EditorModeEnum } from './common';
import type { EditorModeEnum, MainMenuOptions } from './common';
export type ConnectorStyleEnum = z.infer<typeof connectorStyleEnum>;
export type IconInput = z.infer<typeof iconInput>;
@ -26,6 +26,7 @@ export type InitialScene = Partial<SceneInput> & {
export interface IsoflowProps {
initialScene?: InitialScene;
mainMenuOptions?: MainMenuOptions;
onSceneUpdated?: (scene: SceneInput) => void;
width?: number | string;
height?: number | string;

View file

@ -1,4 +1,4 @@
import { Coords, Size, EditorModeEnum } from './common';
import { Coords, Size, EditorModeEnum, MainMenuOptions } from './common';
import { SceneItem, Connector, SceneItemReference } from './scene';
import { IconInput } from './inputs';
@ -154,6 +154,7 @@ export type IconCollectionStateWithIcons = IconCollectionState & {
};
export interface UiState {
mainMenuOptions: MainMenuOptions;
editorMode: keyof typeof EditorModeEnum;
iconCategoriesState: IconCollectionState[];
mode: Mode;
@ -168,6 +169,7 @@ export interface UiState {
}
export interface UiStateActions {
setMainMenuOptions: (options: MainMenuOptions) => void;
setEditorMode: (mode: keyof typeof EditorModeEnum) => void;
setIconCategoriesState: (iconCategoriesState: IconCollectionState[]) => void;
resetUiState: () => void;