Moved UI Elements to separate components + moved replacer/reviver functions to saveload.ts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
Siklos 2022-08-05 21:16:40 +02:00
parent e120072cd9
commit 1fc616f91a
4 changed files with 206 additions and 155 deletions

View file

@ -2,9 +2,9 @@ import * as React from 'react';
import './App.scss'; import './App.scss';
import { MainMenu } from './Components/MainMenu/MainMenu'; import { MainMenu } from './Components/MainMenu/MainMenu';
import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel'; import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel';
import { findContainerById, MakeIterator } from './utils/itertools';
import Editor, { IEditorState } from './Editor'; import Editor, { IEditorState } from './Editor';
import { Configuration } from './Interfaces/Configuration'; import { Configuration } from './Interfaces/Configuration';
import { Revive } from './utils/saveload';
export interface IHistoryState { export interface IHistoryState {
MainContainer: IContainerModel | null MainContainer: IContainerModel | null
@ -182,38 +182,3 @@ export async function fetchConfiguration(): Promise<Configuration> {
xhr.send(); xhr.send();
}); });
} }
/**
* Revive the Editor state
* by setting the containers references to their parent
* @param editorState Editor state
*/
function Revive(editorState: IEditorState): void {
const history = editorState.history;
for (const state of history) {
if (state.MainContainer === null || state.MainContainer === undefined) {
continue;
}
const it = MakeIterator(state.MainContainer);
for (const container of it) {
const parentId = container.properties.parentId;
if (parentId === null) {
container.parent = null;
continue;
}
const parent = findContainerById(state.MainContainer, parentId);
if (parent === undefined) {
continue;
}
container.parent = parent;
}
const selected = findContainerById(state.MainContainer, state.SelectedContainerId);
if (selected === undefined) {
state.SelectedContainer = null;
continue;
}
state.SelectedContainer = selected;
}
}

139
src/Components/UI/UI.tsx Normal file
View file

@ -0,0 +1,139 @@
import * as React from 'react';
import { ElementsSidebar } from '../ElementsSidebar/ElementsSidebar';
import Sidebar from '../Sidebar/Sidebar';
import { History } from '../History/History';
import { AvailableContainer } from '../../Interfaces/AvailableContainer';
import { ContainerModel } from '../../Interfaces/ContainerModel';
import { IHistoryState } from '../../App';
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
import FloatingButton from '../FloatingButton/FloatingButton';
interface IUIProps {
current: IHistoryState
history: IHistoryState[]
historyCurrentStep: number
AvailableContainers: AvailableContainer[]
SelectContainer: (container: ContainerModel) => void
OnPropertyChange: (key: string, value: string) => void
AddContainer: (type: string) => void
SaveEditorAsJSON: () => void
SaveEditorAsSVG: () => void
LoadState: (move: number) => void
}
interface IUIState {
isSidebarOpen: boolean
isElementsSidebarOpen: boolean
isHistoryOpen: boolean
}
export class UI extends React.PureComponent<IUIProps, IUIState> {
constructor(props: IUIProps) {
super(props);
this.state = {
isSidebarOpen: true,
isElementsSidebarOpen: false,
isHistoryOpen: false
};
}
/**
* Toggle the components sidebar
*/
public ToggleSidebar(): void {
this.setState({
isSidebarOpen: !this.state.isSidebarOpen
});
}
/**
* Toggle the elements
*/
public ToggleElementsSidebar(): void {
this.setState({
isElementsSidebarOpen: !this.state.isElementsSidebarOpen
});
}
/**
* Toggle the elements
*/
public ToggleHistory(): void {
this.setState({
isHistoryOpen: !this.state.isHistoryOpen
});
}
public render(): JSX.Element {
let buttonRightOffsetClasses = 'right-12';
if (this.state.isElementsSidebarOpen || this.state.isHistoryOpen) {
buttonRightOffsetClasses = 'right-72';
}
if (this.state.isHistoryOpen && this.state.isElementsSidebarOpen) {
buttonRightOffsetClasses = 'right-[544px]';
}
return (
<>
<Sidebar
componentOptions={this.props.AvailableContainers}
isOpen={this.state.isSidebarOpen}
onClick={() => this.ToggleSidebar()}
buttonOnClick={(type: string) => this.props.AddContainer(type)}
/>
<button
className='fixed z-10 top-4 left-4 text-lg bg-blue-200 hover:bg-blue-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleSidebar()}
>
&#9776; Components
</button>
<ElementsSidebar
MainContainer={this.props.current.MainContainer}
SelectedContainer={this.props.current.SelectedContainer}
isOpen={this.state.isElementsSidebarOpen}
isHistoryOpen={this.state.isHistoryOpen}
onClick={() => this.ToggleElementsSidebar()}
onPropertyChange={this.props.OnPropertyChange}
selectContainer={this.props.SelectContainer}
/>
<button
className='fixed z-10 top-4 right-12 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleElementsSidebar()}
>
&#9776; Elements
</button>
<History
history={this.props.history}
historyCurrentStep={this.props.historyCurrentStep}
isOpen={this.state.isHistoryOpen}
onClick={() => this.ToggleHistory()}
jumpTo={this.props.LoadState}
/>
<button
className='fixed z-10 top-4 right-72 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleHistory()}>
&#9776; History
</button>
<FloatingButton className={`fixed flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
<button
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as JSON'
onClick={this.props.SaveEditorAsJSON}
>
<UploadIcon className="h-full w-full text-white align-middle items-center justify-center" />
</button>
<button
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as SVG'
onClick={this.props.SaveEditorAsSVG}
>
<PhotographIcon className="h-full w-full text-white align-middle items-center justify-center" />
</button>
</FloatingButton>
</>
);
}
}

View file

@ -1,15 +1,12 @@
import React from 'react'; import React from 'react';
import { UploadIcon, PhotographIcon } from '@heroicons/react/outline';
import './Editor.scss'; import './Editor.scss';
import Sidebar from './Components/Sidebar/Sidebar';
import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar';
import { Configuration } from './Interfaces/Configuration'; import { Configuration } from './Interfaces/Configuration';
import { SVG } from './Components/SVG/SVG'; import { SVG } from './Components/SVG/SVG';
import { History } from './Components/History/History';
import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel'; import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel';
import { findContainerById, MakeIterator } from './utils/itertools'; import { findContainerById, MakeIterator } from './utils/itertools';
import { IHistoryState } from './App'; import { IHistoryState } from './App';
import FloatingButton from './Components/FloatingButton/FloatingButton'; import { getCircularReplacer } from './utils/saveload';
import { UI } from './Components/UI/UI';
interface IEditorProps { interface IEditorProps {
configuration: Configuration configuration: Configuration
@ -18,9 +15,6 @@ interface IEditorProps {
} }
export interface IEditorState { export interface IEditorState {
isSidebarOpen: boolean
isElementsSidebarOpen: boolean
isHistoryOpen: boolean
history: IHistoryState[] history: IHistoryState[]
historyCurrentStep: number historyCurrentStep: number
// do not use it, use props.configuration // do not use it, use props.configuration
@ -34,9 +28,6 @@ class Editor extends React.Component<IEditorProps> {
constructor(props: IEditorProps) { constructor(props: IEditorProps) {
super(props); super(props);
this.state = { this.state = {
isSidebarOpen: true,
isElementsSidebarOpen: false,
isHistoryOpen: false,
configuration: Object.assign({}, props.configuration), configuration: Object.assign({}, props.configuration),
history: [...props.history], history: [...props.history],
historyCurrentStep: props.historyCurrentStep historyCurrentStep: props.historyCurrentStep
@ -46,33 +37,6 @@ class Editor extends React.Component<IEditorProps> {
public getCurrentHistory = (): IHistoryState[] => this.state.history.slice(0, this.state.historyCurrentStep + 1); public getCurrentHistory = (): IHistoryState[] => this.state.history.slice(0, this.state.historyCurrentStep + 1);
public getCurrentHistoryState = (): IHistoryState => this.state.history[this.state.historyCurrentStep]; public getCurrentHistoryState = (): IHistoryState => this.state.history[this.state.historyCurrentStep];
/**
* Toggle the components sidebar
*/
public ToggleSidebar(): void {
this.setState({
isSidebarOpen: !this.state.isSidebarOpen
});
}
/**
* Toggle the elements
*/
public ToggleElementsSidebar(): void {
this.setState({
isElementsSidebarOpen: !this.state.isElementsSidebarOpen
});
}
/**
* Toggle the elements
*/
public ToggleHistory(): void {
this.setState({
isHistoryOpen: !this.state.isHistoryOpen
});
}
/** /**
* Select a container * Select a container
* @param container Selected container * @param container Selected container
@ -251,7 +215,7 @@ class Editor extends React.Component<IEditorProps> {
}); });
} }
public jumpTo(move: number): void { public LoadState(move: number): void {
this.setState({ this.setState({
historyCurrentStep: move historyCurrentStep: move
}); });
@ -290,57 +254,20 @@ class Editor extends React.Component<IEditorProps> {
*/ */
render(): JSX.Element { render(): JSX.Element {
const current = this.getCurrentHistoryState(); const current = this.getCurrentHistoryState();
let buttonRightOffsetClasses = 'right-12';
if (this.state.isElementsSidebarOpen || this.state.isHistoryOpen) {
buttonRightOffsetClasses = 'right-72';
}
if (this.state.isHistoryOpen && this.state.isElementsSidebarOpen) {
buttonRightOffsetClasses = 'right-[544px]';
}
return ( return (
<div className="App font-sans h-full"> <div className="App font-sans h-full">
<Sidebar <UI
componentOptions={this.props.configuration.AvailableContainers} current={current}
isOpen={this.state.isSidebarOpen}
onClick={() => this.ToggleSidebar()}
buttonOnClick={(type: string) => this.AddContainer(type)}
/>
<button
className='fixed z-10 top-4 left-4 text-lg bg-blue-200 hover:bg-blue-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleSidebar()}
>
&#9776; Components
</button>
<ElementsSidebar
MainContainer={current.MainContainer}
SelectedContainer={current.SelectedContainer}
isOpen={this.state.isElementsSidebarOpen}
isHistoryOpen={this.state.isHistoryOpen}
onClick={() => this.ToggleElementsSidebar()}
onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)}
selectContainer={(container: ContainerModel) => this.SelectContainer(container)}
/>
<button
className='fixed z-10 top-4 right-12 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleElementsSidebar()}
>
&#9776; Elements
</button>
<History
history={this.state.history} history={this.state.history}
historyCurrentStep={this.state.historyCurrentStep} historyCurrentStep={this.state.historyCurrentStep}
isOpen={this.state.isHistoryOpen} AvailableContainers={this.state.configuration.AvailableContainers}
onClick={() => this.ToggleHistory()} SelectContainer={(container) => this.SelectContainer(container)}
jumpTo={(move) => { this.jumpTo(move); }} OnPropertyChange={(key, value) => this.OnPropertyChange(key, value)}
AddContainer={(type) => this.AddContainer(type)}
SaveEditorAsJSON={() => this.SaveEditorAsJSON()}
SaveEditorAsSVG={() => this.SaveEditorAsSVG()}
LoadState={(move) => this.LoadState(move)}
/> />
<button
className='fixed z-10 top-4 right-72 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleHistory()}>
&#9776; History
</button>
<SVG <SVG
width={Number(current.MainContainer?.properties.width)} width={Number(current.MainContainer?.properties.width)}
height={Number(current.MainContainer?.properties.height)} height={Number(current.MainContainer?.properties.height)}
@ -348,43 +275,9 @@ class Editor extends React.Component<IEditorProps> {
> >
{ current.MainContainer } { current.MainContainer }
</SVG> </SVG>
<FloatingButton className={`fixed flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
<button
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as JSON'
onClick={() => this.SaveEditorAsJSON()}
>
<UploadIcon className="h-full w-full text-white align-middle items-center justify-center" />
</button>
<button
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as SVG'
onClick={() => this.SaveEditorAsSVG()}
>
<PhotographIcon className="h-full w-full text-white align-middle items-center justify-center" />
</button>
</FloatingButton>
</div> </div>
); );
} }
} }
const getCircularReplacer = (): (key: any, value: object | null) => object | null | undefined => {
const seen = new WeakSet();
return (key: any, value: object | null) => {
if (key === 'parent') {
return;
}
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
export default Editor; export default Editor;

54
src/utils/saveload.ts Normal file
View file

@ -0,0 +1,54 @@
import { findContainerById, MakeIterator } from './itertools';
import { IEditorState } from '../Editor';
/**
* Revive the Editor state
* by setting the containers references to their parent
* @param editorState Editor state
*/
export function Revive(editorState: IEditorState): void {
const history = editorState.history;
for (const state of history) {
if (state.MainContainer === null || state.MainContainer === undefined) {
continue;
}
const it = MakeIterator(state.MainContainer);
for (const container of it) {
const parentId = container.properties.parentId;
if (parentId === null) {
container.parent = null;
continue;
}
const parent = findContainerById(state.MainContainer, parentId);
if (parent === undefined) {
continue;
}
container.parent = parent;
}
const selected = findContainerById(state.MainContainer, state.SelectedContainerId);
if (selected === undefined) {
state.SelectedContainer = null;
continue;
}
state.SelectedContainer = selected;
}
}
export const getCircularReplacer = (): (key: any, value: object | null) => object | null | undefined => {
const seen = new WeakSet();
return (key: any, value: object | null) => {
if (key === 'parent') {
return;
}
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};