svg-layout-designer-react/src/Components/UI/UI.tsx
2023-02-14 16:44:50 +01:00

315 lines
11 KiB
TypeScript

import * as React from 'react';
import { ElementsSideBar } from '../ElementsList/ElementsSideBar';
import { History } from '../History/History';
import { Bar, BAR_WIDTH } from '../Bar/Bar';
import { Symbols } from '../Symbols/Symbols';
import { SymbolsSidebar } from '../SymbolsList/SymbolsSidebar';
import { type PropertyType } from '../../Enums/PropertyType';
import { Messages } from '../Messages/Messages';
import { Sidebar } from '../Sidebar/Sidebar';
import { Components } from '../Components/Components';
import { Viewer } from '../Viewer/Viewer';
import { Settings } from '../Settings/Settings';
import { type IMessage } from '../../Interfaces/IMessage';
import { DISABLE_API } from '../../utils/default';
import { UseWorker, UseAsync } from './UseWorker';
import { FindContainerById } from '../../utils/itertools';
import { type IEditorState } from '../../Interfaces/IEditorState';
import { GetCurrentHistoryState } from '../Editor/Editor';
import { Text } from '../Text/Text';
import { IReplaceContainer } from '../../Interfaces/IReplaceContainer';
import { Dispatch } from 'react';
export interface IUIProps {
editorState: IEditorState
replaceContainer: IReplaceContainer
selectContainer: (containerId: string) => void
deleteContainer: (containerId: string) => void
onPropertyChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
addOrReplaceContainer: (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
setReplaceContainer: Dispatch<React.SetStateAction<IReplaceContainer>>
}
export enum SidebarType {
None,
Components,
ComponentsExpanded,
Symbols,
SymbolsExpanded,
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({ editorState, replaceContainer, setReplaceContainer, ...methods }: IUIProps): JSX.Element {
const [selectedSidebar, setSelectedSidebar] = React.useState<SidebarType>(SidebarType.Components);
const [messages, setMessages] = React.useState<IMessage[]>([]);
const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep);
const configuration = editorState.configuration;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (window.Worker && !DISABLE_API) {
UseWorker(
current,
configuration.APIConfiguration?.apiGetFeedbackUrl,
setMessages
);
} else if (!DISABLE_API) {
UseAsync(
current,
configuration.APIConfiguration?.apiGetFeedbackUrl,
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(current.containers, current.mainContainer);
if (mainContainer === undefined) {
throw new Error('Tried to initialized UI but there is no main container!');
}
const selectedContainer = FindContainerById(current.containers, current.selectedContainerId);
const selectedSymbol = current.symbols.get(current.selectedSymbolId);
switch (selectedSidebar) {
case SidebarType.Components:
leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components
selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers}
categories={configuration.Categories}
buttonOnClick={methods.addOrReplaceContainer}
replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
selectedContainer={selectedContainer}
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={false}
onExpandChange={() => { setOrToggleSidebar(SidebarType.ComponentsExpanded); } }
/>;
break;
case SidebarType.ComponentsExpanded:
leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components
selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers}
categories={configuration.Categories}
buttonOnClick={methods.addOrReplaceContainer}
replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
selectedContainer={selectedContainer}
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Components); } }
/>;
break;
case SidebarType.Symbols:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
componentOptions={configuration.AvailableSymbols}
buttonOnClick={methods.addSymbol}
/>;
rightSidebarTitle = Text({ textId: '@SymbolsRight' });
rightChildren = <SymbolsSidebar
selectedSymbolId={current.selectedSymbolId}
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={false}
onExpandChange={() => { setOrToggleSidebar(SidebarType.SymbolsExpanded); } }
/>;
break;
case SidebarType.SymbolsExpanded:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
componentOptions={configuration.AvailableSymbols}
buttonOnClick={methods.addSymbol}
/>;
rightSidebarTitle = Text({ textId: '@SymbolsRight' });
rightChildren = <SymbolsSidebar
selectedSymbolId={current.selectedSymbolId}
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Symbols); }}
/>;
break;
case SidebarType.History:
leftSidebarTitle = Text({ textId: '@Timeline' });
leftChildren = <History
history={editorState.history}
historyCurrentStep={editorState.historyCurrentStep}
jumpTo={methods.loadState}
/>;
break;
case SidebarType.Messages:
leftSidebarTitle = Text({ textId: '@Messages' });
leftChildren = <Messages
historyState={current}
messages={messages}
clearMessage={() => { setMessages([]); }}
/>;
break;
case SidebarType.Settings:
leftSidebarTitle = Text({ textId: '@Settings' });
leftChildren = <Settings
saveEditorAsJSON={methods.saveEditorAsJSON}
saveEditorAsSVG={methods.saveEditorAsSVG}
/>;
break;
}
const isLeftSidebarOpen = selectedSidebar !== SidebarType.None;
const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols;
const isRightSidebarOpenExpanded = selectedSidebar === SidebarType.ComponentsExpanded || selectedSidebar === SidebarType.SymbolsExpanded;
const 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';
let marginSidebar = BAR_WIDTH;
const viewerMarginClasses = new Set<string>([
'ml-16'
]);
if (isLeftSidebarOpen) {
isLeftSidebarOpenClasses.delete('-bottom-full');
isLeftSidebarOpenClasses.delete('md:-left-64');
isLeftSidebarOpenClasses.delete('md:bottom-0');
marginSidebar += 256;
viewerMarginClasses.add(' md:ml-80');
}
if (isRightSidebarOpen || isRightSidebarOpenExpanded) {
isRightSidebarOpenClasses = 'right-0';
if (isRightSidebarOpenExpanded) {
viewerMarginClasses.add(' md:mr-[32rem]');
marginSidebar += 512;
} else {
viewerMarginClasses.add(' md:mr-64');
marginSidebar += 256;
}
} else {
isLeftSidebarOpenClasses.delete('left-sidebar');
isLeftSidebarOpenClasses.add('left-sidebar-single');
}
const clickRestrictionsClasses = replaceContainer.isReplacing ? 'pointer-events-none opacity-50' : '';
const isComponentsOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.ComponentsExpanded;
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols || selectedSidebar === SidebarType.SymbolsExpanded;
return (
<>
<Bar
className={clickRestrictionsClasses}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
isHistoryOpen={selectedSidebar === SidebarType.History}
isMessagesOpen={selectedSidebar === SidebarType.Messages}
isSettingsOpen={selectedSidebar === SidebarType.Settings}
toggleComponents={() => {
if (selectedSidebar === SidebarType.ComponentsExpanded) {
setOrToggleSidebar(SidebarType.ComponentsExpanded);
} else {
setOrToggleSidebar(SidebarType.Components);
}
} }
toggleSymbols={() => {
if (selectedSidebar === SidebarType.SymbolsExpanded) {
setOrToggleSidebar(SidebarType.SymbolsExpanded);
} else {
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
className={`${clickRestrictionsClasses} ${[...viewerMarginClasses.values()].join(' ')} w-full h-full`}
current={current}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
selectedContainer={selectedContainer}
selectContainer={methods.selectContainer}
selectedSymbol={selectedSymbol}
margin={marginSidebar}
/>
<Sidebar
className={`right-sidebar ${isRightSidebarOpenClasses} ${clickRestrictionsClasses}`}
title={rightSidebarTitle}
>
{ rightChildren }
</Sidebar>
</>
);
}