svg-layout-designer-react/src/Components/UI/UI.tsx
Eric Nguyen c256a76e01 Merged PR 212: Optimize FindChildrenById from O(n) to O(1)
Optimize FindChildrenById from O(n) to O(1):
- Deprecate FindContainerByIdDFS
- Container: Replace Children to string[]
- Add HashMap to IHistoryState that contains all containers

To access a container by id now cost O(1) without any additional cost

+ Implement CICD for SVGLibs
2022-10-12 09:39:54 +00:00

233 lines
7.5 KiB
TypeScript

import * as React from 'react';
import { ElementsList } from '../ElementsList/ElementsList';
import { History } from '../History/History';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { Bar } from '../Bar/Bar';
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
import { Symbols } from '../Symbols/Symbols';
import { SymbolsSidebar } from '../SymbolsList/SymbolsList';
import { PropertyType } from '../../Enums/PropertyType';
import { Messages } from '../Messages/Messages';
import { ICategory } from '../../Interfaces/ICategory';
import { Sidebar } from '../Sidebar/Sidebar';
import { Components } from '../Components/Components';
import { Viewer } from '../Viewer/Viewer';
import { Settings } from '../Settings/Settings';
import { IMessage } from '../../Interfaces/IMessage';
import { DISABLE_API } from '../../utils/default';
import { UseWorker, UseAsync } from './UseWorker';
import { FindContainerById } from '../../utils/itertools';
export interface IUIProps {
selectedContainer: IContainerModel | undefined
current: IHistoryState
history: IHistoryState[]
historyCurrentStep: number
availableContainers: IAvailableContainer[]
availableSymbols: IAvailableSymbol[]
categories: ICategory[]
selectContainer: (containerId: string) => void
deleteContainer: (containerId: string) => void
onPropertyChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
addContainer: (type: string) => void
addContainerAt: (index: number, type: string, parent: string) => void
addSymbol: (type: string) => void
onSymbolPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void
deleteSymbol: (symbolId: string) => void
saveEditorAsJSON: () => void
saveEditorAsSVG: () => void
loadState: (move: number) => void
}
export enum SidebarType {
None,
Components,
Symbols,
History,
Messages,
Settings
}
function UseSetOrToggleSidebar(
selectedSidebar: SidebarType,
setSelectedSidebar: React.Dispatch<React.SetStateAction<SidebarType>>
): (newSidebarType: SidebarType) => void {
return (newSidebarType) => {
if (newSidebarType === selectedSidebar) {
setSelectedSidebar(SidebarType.None);
return;
}
setSelectedSidebar(newSidebarType);
};
}
export function UI(props: IUIProps): JSX.Element {
const [selectedSidebar, setSelectedSidebar] = React.useState<SidebarType>(SidebarType.Components);
const [messages, setMessages] = React.useState<IMessage[]>([]);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (window.Worker && !DISABLE_API) {
UseWorker(
props.current,
setMessages
);
} else if (!DISABLE_API) {
UseAsync(
props.current,
setMessages
);
}
// Please use setOrToggleSidebar rather than setSelectedSidebar so we can close the sidebar
const setOrToggleSidebar = UseSetOrToggleSidebar(selectedSidebar, setSelectedSidebar);
let leftSidebarTitle = '';
let rightSidebarTitle = '';
let leftChildren: JSX.Element = (<></>);
let rightChildren: JSX.Element = (<></>);
const mainContainer = FindContainerById(props.current.containers, props.current.mainContainer)
if (mainContainer === undefined) {
throw new Error('Tried to initialized UI but there is no main container!');
}
switch (selectedSidebar) {
case SidebarType.Components:
leftSidebarTitle = 'Components';
leftChildren = <Components
selectedContainer={props.selectedContainer}
componentOptions={props.availableContainers}
categories={props.categories}
buttonOnClick={props.addContainer}
/>;
rightSidebarTitle = 'Elements';
rightChildren = <ElementsList
containers={props.current.containers}
mainContainer={mainContainer}
symbols={props.current.symbols}
selectedContainer={props.selectedContainer}
onPropertyChange={props.onPropertyChange}
selectContainer={props.selectContainer}
addContainer={props.addContainerAt}
/>;
break;
case SidebarType.Symbols:
leftSidebarTitle = 'Symbols';
leftChildren = <Symbols
componentOptions={props.availableSymbols}
buttonOnClick={props.addSymbol}
/>;
rightSidebarTitle = 'Symbols';
rightChildren = <SymbolsSidebar
selectedSymbolId={props.current.selectedSymbolId}
symbols={props.current.symbols}
onPropertyChange={props.onSymbolPropertyChange}
selectSymbol={props.selectSymbol}
/>;
break;
case SidebarType.History:
leftSidebarTitle = 'Timeline';
leftChildren = <History
history={props.history}
historyCurrentStep={props.historyCurrentStep}
jumpTo={props.loadState}
/>;
break;
case SidebarType.Messages:
leftSidebarTitle = 'Messages';
leftChildren = <Messages
historyState={props.current}
messages={messages}
clearMessage={() => setMessages([])}
/>;
break;
case SidebarType.Settings:
leftSidebarTitle = 'Settings';
leftChildren = <Settings
saveEditorAsJSON={props.saveEditorAsJSON}
saveEditorAsSVG={props.saveEditorAsSVG}
/>;
break;
}
const isLeftSidebarOpen = selectedSidebar !== SidebarType.None;
const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols;
let isLeftSidebarOpenClasses = new Set<string>([
'left-sidebar',
'left-16',
'-bottom-full',
'md:-left-64',
'md:bottom-0'
]);
let isRightSidebarOpenClasses = 'right-0 -bottom-full md:-right-80 md:bottom-0';
if (isLeftSidebarOpen) {
isLeftSidebarOpenClasses.delete('-bottom-full');
isLeftSidebarOpenClasses.delete('md:-left-64');
isLeftSidebarOpenClasses.delete('md:bottom-0');
}
if (isRightSidebarOpen) {
isRightSidebarOpenClasses = 'right-0';
} else {
isLeftSidebarOpenClasses.delete('left-sidebar');
isLeftSidebarOpenClasses.add('left-sidebar-single');
}
return (
<>
<Bar
isComponentsOpen={selectedSidebar === SidebarType.Components}
isSymbolsOpen={selectedSidebar === SidebarType.Symbols}
isHistoryOpen={selectedSidebar === SidebarType.History}
isMessagesOpen={selectedSidebar === SidebarType.Messages}
isSettingsOpen={selectedSidebar === SidebarType.Settings}
toggleComponents={() => {
setOrToggleSidebar(SidebarType.Components);
} }
toggleSymbols={() => {
setOrToggleSidebar(SidebarType.Symbols);
} }
toggleTimeline={() => {
setOrToggleSidebar(SidebarType.History);
} }
toggleMessages={() => {
setOrToggleSidebar(SidebarType.Messages);
} }
toggleSettings={() => {
setOrToggleSidebar(SidebarType.Settings);
} }
/>
<Sidebar
className={`${[...isLeftSidebarOpenClasses.values()].join(' ')}`}
title={leftSidebarTitle}
>
{ leftChildren }
</Sidebar>
<Viewer
isLeftSidebarOpen={isLeftSidebarOpen}
isRightSidebarOpen={isRightSidebarOpen}
current={props.current}
selectedContainer={props.selectedContainer}
selectContainer={props.selectContainer}
/>
<Sidebar
className={`right-sidebar ${isRightSidebarOpenClasses}`}
title={rightSidebarTitle}
>
{ rightChildren }
</Sidebar>
</>
);
}