Moved UI Elements to separate components + moved replacer/reviver functions to saveload.ts
This commit is contained in:
parent
e120072cd9
commit
1fc616f91a
4 changed files with 206 additions and 155 deletions
37
src/App.tsx
37
src/App.tsx
|
@ -2,9 +2,9 @@ import * as React from 'react';
|
|||
import './App.scss';
|
||||
import { MainMenu } from './Components/MainMenu/MainMenu';
|
||||
import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel';
|
||||
import { findContainerById, MakeIterator } from './utils/itertools';
|
||||
import Editor, { IEditorState } from './Editor';
|
||||
import { Configuration } from './Interfaces/Configuration';
|
||||
import { Revive } from './utils/saveload';
|
||||
|
||||
export interface IHistoryState {
|
||||
MainContainer: IContainerModel | null
|
||||
|
@ -182,38 +182,3 @@ export async function fetchConfiguration(): Promise<Configuration> {
|
|||
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
139
src/Components/UI/UI.tsx
Normal 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()}
|
||||
>
|
||||
☰ 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()}
|
||||
>
|
||||
☰ 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()}>
|
||||
☰ 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
131
src/Editor.tsx
131
src/Editor.tsx
|
@ -1,15 +1,12 @@
|
|||
import React from 'react';
|
||||
import { UploadIcon, PhotographIcon } from '@heroicons/react/outline';
|
||||
import './Editor.scss';
|
||||
import Sidebar from './Components/Sidebar/Sidebar';
|
||||
import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar';
|
||||
import { Configuration } from './Interfaces/Configuration';
|
||||
import { SVG } from './Components/SVG/SVG';
|
||||
import { History } from './Components/History/History';
|
||||
import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel';
|
||||
import { findContainerById, MakeIterator } from './utils/itertools';
|
||||
import { IHistoryState } from './App';
|
||||
import FloatingButton from './Components/FloatingButton/FloatingButton';
|
||||
import { getCircularReplacer } from './utils/saveload';
|
||||
import { UI } from './Components/UI/UI';
|
||||
|
||||
interface IEditorProps {
|
||||
configuration: Configuration
|
||||
|
@ -18,9 +15,6 @@ interface IEditorProps {
|
|||
}
|
||||
|
||||
export interface IEditorState {
|
||||
isSidebarOpen: boolean
|
||||
isElementsSidebarOpen: boolean
|
||||
isHistoryOpen: boolean
|
||||
history: IHistoryState[]
|
||||
historyCurrentStep: number
|
||||
// do not use it, use props.configuration
|
||||
|
@ -34,9 +28,6 @@ class Editor extends React.Component<IEditorProps> {
|
|||
constructor(props: IEditorProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isSidebarOpen: true,
|
||||
isElementsSidebarOpen: false,
|
||||
isHistoryOpen: false,
|
||||
configuration: Object.assign({}, props.configuration),
|
||||
history: [...props.history],
|
||||
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 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
|
||||
* @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({
|
||||
historyCurrentStep: move
|
||||
});
|
||||
|
@ -290,57 +254,20 @@ class Editor extends React.Component<IEditorProps> {
|
|||
*/
|
||||
render(): JSX.Element {
|
||||
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 (
|
||||
<div className="App font-sans h-full">
|
||||
<Sidebar
|
||||
componentOptions={this.props.configuration.AvailableContainers}
|
||||
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()}
|
||||
>
|
||||
☰ 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()}
|
||||
>
|
||||
☰ Elements
|
||||
</button>
|
||||
|
||||
<History
|
||||
<UI
|
||||
current={current}
|
||||
history={this.state.history}
|
||||
historyCurrentStep={this.state.historyCurrentStep}
|
||||
isOpen={this.state.isHistoryOpen}
|
||||
onClick={() => this.ToggleHistory()}
|
||||
jumpTo={(move) => { this.jumpTo(move); }}
|
||||
AvailableContainers={this.state.configuration.AvailableContainers}
|
||||
SelectContainer={(container) => this.SelectContainer(container)}
|
||||
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()}>
|
||||
☰ History
|
||||
</button>
|
||||
|
||||
<SVG
|
||||
width={Number(current.MainContainer?.properties.width)}
|
||||
height={Number(current.MainContainer?.properties.height)}
|
||||
|
@ -348,43 +275,9 @@ class Editor extends React.Component<IEditorProps> {
|
|||
>
|
||||
{ current.MainContainer }
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
54
src/utils/saveload.ts
Normal file
54
src/utils/saveload.ts
Normal 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;
|
||||
};
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue