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
233 lines
7.5 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|