From 340cc86aa9b0d3dc43373ee143b6044d557b19cd Mon Sep 17 00:00:00 2001 From: Siklos Date: Thu, 4 Aug 2022 14:53:49 +0200 Subject: [PATCH] Extract Editor from App --- src/App.scss | 22 +--- src/App.tsx | 342 +++++++----------------------------------------- src/Editor.scss | 22 ++++ src/Editor.tsx | 299 ++++++++++++++++++++++++++++++++++++++++++ src/main.tsx | 2 +- 5 files changed, 373 insertions(+), 314 deletions(-) create mode 100644 src/Editor.scss create mode 100644 src/Editor.tsx diff --git a/src/App.scss b/src/App.scss index a32c70f..2cb61ee 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,24 +1,6 @@ html, body, -#root, -svg { - height: 100%; +#root { width: 100%; -} - -text { - font-size: 18px; - font-weight: 800; - fill: none; - fill-opacity: 0; - stroke: #000000; - stroke-width: 1px; - stroke-linecap: butt; - stroke-linejoin: miter; - stroke-opacity: 1; -} - -@keyframes fadein { - from { opacity: 0; } - to { opacity: 1; } + height: 100%; } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 3db4ae4..5d2c9ae 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,8 @@ -import React from 'react'; -import './App.scss'; -import Sidebar from './Components/Sidebar/Sidebar'; -import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar'; +import * as React from 'react'; +import { ContainerModel, IContainerModel } from './Components/SVG/Elements/ContainerModel'; +import Editor from './Editor'; import { AvailableContainer } from './Interfaces/AvailableContainer'; import { Configuration } from './Interfaces/Configuration'; -import { SVG } from './Components/SVG/SVG'; -import { History } from './Components/History/History'; -import { ContainerModel, IContainerModel, MakeIterator } from './Components/SVG/Elements/ContainerModel'; -import Properties from './Interfaces/Properties'; - -interface IAppProps { -} export interface IHistoryState { MainContainer: IContainerModel | null, @@ -18,24 +10,35 @@ export interface IHistoryState { TypeCounters: Record } -interface IAppState { - isSidebarOpen: boolean, - isSVGSidebarOpen: boolean, - isHistoryOpen: boolean, - configuration: Configuration, - history: Array, - historyCurrentStep: 0 +interface IAppProps { } -class App extends React.Component { +interface IAppState { + configuration: Configuration, + history: IHistoryState[], + historyCurrentStep: number, + isLoaded: boolean +} + +export class App extends React.Component { public state: IAppState; - constructor(props: IAppProps) { + public constructor(props: IAppProps) { super(props); + + const MainContainer = new ContainerModel( + null, + { + id: 'main', + x: 0, + y: 0, + width: 1000, + height: 1000, + fillOpacity: 0, + stroke: 'black' + } + ); this.state = { - isSidebarOpen: true, - isSVGSidebarOpen: false, - isHistoryOpen: false, configuration: { AvailableContainers: [], AvailableSymbols: [], @@ -43,18 +46,16 @@ class App extends React.Component { }, history: [ { - MainContainer: null, - SelectedContainer: null, + MainContainer, + SelectedContainer: MainContainer, TypeCounters: {} } ], - historyCurrentStep: 0 - } as IAppState; + historyCurrentStep: 0, + isLoaded: false + }; } - public getCurrentHistory = (): IHistoryState[] => this.state.history.slice(0, this.state.historyCurrentStep + 1); - public getCurrentHistoryState = (): IHistoryState => this.state.history[this.state.historyCurrentStep]; - componentDidMount() { // Fetch the configuration from the API fetchConfiguration().then((configuration: Configuration) => { @@ -72,279 +73,36 @@ class App extends React.Component { } ); - const history = this.getCurrentHistory(); - const current = history[history.length - 1]; - // Save the configuration and the new MainContainer // and default the selected container to it - this.setState(prevState => ({ - ...prevState, + this.setState({ configuration, - history: history.concat( + history: [ { MainContainer, SelectedContainer: MainContainer, - TypeCounters: current.TypeCounters + TypeCounters: {} } - ] - ), - historyCurrentStep: history.length - } as IAppState)); + ], + historyCurrentStep: 0, + isLoaded: true + } as IAppState); }); } - /** - * Toggle the components sidebar - */ - public ToggleSidebar() { - this.setState({ - isSidebarOpen: !this.state.isSidebarOpen - } as IAppState); - } - - /** - * Toggle the elements - */ - public ToggleElementsSidebar() { - this.setState({ - isSVGSidebarOpen: !this.state.isSVGSidebarOpen - } as IAppState); - } - - /** - * Toggle the elements - */ - public ToggleHistory() { - this.setState({ - isHistoryOpen: !this.state.isHistoryOpen - } as IAppState); - } - - /** - * Select a container - * @param container Selected container - */ - public SelectContainer(container: ContainerModel) { - const history = this.getCurrentHistory(); - const current = history[history.length - 1]; - this.setState({ - history: history.concat([{ - MainContainer: current.MainContainer, - TypeCounters: current.TypeCounters, - SelectedContainer: container - }]), - historyCurrentStep: history.length - } as IAppState); - } - - /** - * Handled the property change event in the properties form - * @param key Property name - * @param value New value of the property - * @returns void - */ - public OnPropertyChange(key: string, value: string | number): void { - const history = this.getCurrentHistory(); - const current = history[history.length - 1]; - - if (current.SelectedContainer === null || - current.SelectedContainer === undefined) { - throw new Error('[OnPropertyChange] Property was changed before selecting a Container'); + public render() { + if (this.state.isLoaded) { + return ( +
+ +
+ ); } - - if (current.MainContainer === null || - current.MainContainer === undefined) { - throw new Error('[OnPropertyChange] Property was changed before the main container was added'); - } - - if (parent === null) { - const clone: IContainerModel = structuredClone(current.SelectedContainer); - (clone.properties as any)[key] = value; - this.setState({ - history: history.concat([{ - SelectedContainer: clone, - MainContainer: clone, - TypeCounters: current.TypeCounters - }]), - historyCurrentStep: history.length - } as IAppState); - return; - } - - const clone: IContainerModel = structuredClone(current.MainContainer); - const it = MakeIterator(clone); - let container: ContainerModel | null = null; - for (const child of it) { - if (child.properties.id === current.SelectedContainer.properties.id) { - container = child as ContainerModel; - break; - } - } - - if (container === null) { - throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); - } - - (container.properties as any)[key] = value; - - this.setState( - { - history: history.concat([{ - SelectedContainer: container, - MainContainer: clone, - TypeCounters: current.TypeCounters - }]), - historyCurrentStep: history.length - } as IAppState); - } - - /** - * Add a new container to a selected container - * @param type The type of container - * @returns void - */ - public AddContainer(type: string): void { - const history = this.getCurrentHistory(); - const current = history[history.length - 1]; - - if (current.SelectedContainer === null || - current.SelectedContainer === undefined) { - return; - } - - if (current.MainContainer === null || - current.MainContainer === undefined) { - return; - } - - // Get the preset properties from the API - const properties = this.state.configuration.AvailableContainers.find(option => option.Type === type); - - if (properties === undefined) { - throw new Error(`[AddContainer] Object type not found. Found: ${type}`); - } - - // Set the counter of the object type in order to assign an unique id - const newCounters = Object.assign({}, current.TypeCounters); - if (newCounters[type] === null || - newCounters[type] === undefined) { - newCounters[type] = 0; - } else { - newCounters[type]++; - } - const count = newCounters[type]; - - // Create maincontainer model - const structure: IContainerModel = structuredClone(current.MainContainer); - const clone = Object.assign(new ContainerModel(null, {} as Properties), structure); - - // Find the parent - const it = MakeIterator(clone); - let parent: ContainerModel | null = null; - for (const child of it) { - if (child.properties.id === current.SelectedContainer.properties.id) { - parent = child as ContainerModel; - break; - } - } - - if (parent === null) { - throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); - } - - // Create the container - const newContainer = new ContainerModel( - parent, - { - id: `${type}-${count}`, - x: 0, - y: 0, - width: properties?.Width, - height: parent.properties.height, - ...properties.Style - } as Properties, - [], - { - type - } - ); - - // And push it the the parent children - parent.children.push(newContainer); - - // Update the state - this.setState({ - history: history.concat([{ - MainContainer: clone, - TypeCounters: newCounters, - SelectedContainer: parent - }]), - historyCurrentStep: history.length - } as IAppState); - } - - public jumpTo(move: number): void { - this.setState({ - historyCurrentStep: move - } as IAppState); - } - - /** - * Render the application - * @returns {JSX.Element} Rendered JSX element - */ - render() { - const current = this.getCurrentHistoryState(); - return ( -
- this.ToggleSidebar()} - buttonOnClick={(type: string) => this.AddContainer(type)} - /> - - - this.ToggleElementsSidebar()} - onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)} - selectContainer={(container: ContainerModel) => this.SelectContainer(container)} - /> - - - this.ToggleHistory()} - jumpTo={(move) => { this.jumpTo(move); }} - /> - - - - { current.MainContainer } - -
- ); } } @@ -377,5 +135,3 @@ export async function fetchConfiguration(): Promise { xhr.send(); }); } - -export default App; diff --git a/src/Editor.scss b/src/Editor.scss new file mode 100644 index 0000000..32b3726 --- /dev/null +++ b/src/Editor.scss @@ -0,0 +1,22 @@ + +svg { + height: 100%; + width: 100%; +} + +text { + font-size: 18px; + font-weight: 800; + fill: none; + fill-opacity: 0; + stroke: #000000; + stroke-width: 1px; + stroke-linecap: butt; + stroke-linejoin: miter; + stroke-opacity: 1; +} + +@keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } +} \ No newline at end of file diff --git a/src/Editor.tsx b/src/Editor.tsx new file mode 100644 index 0000000..81db229 --- /dev/null +++ b/src/Editor.tsx @@ -0,0 +1,299 @@ +import React from 'react'; +import './Editor.scss'; +import Sidebar from './Components/Sidebar/Sidebar'; +import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar'; +import { AvailableContainer } from './Interfaces/AvailableContainer'; +import { Configuration } from './Interfaces/Configuration'; +import { SVG } from './Components/SVG/SVG'; +import { History } from './Components/History/History'; +import { ContainerModel, IContainerModel, MakeIterator } from './Components/SVG/Elements/ContainerModel'; +import Properties from './Interfaces/Properties'; +import { IHistoryState } from './App'; + +interface IEditorProps { + configuration: Configuration, + history: Array, + historyCurrentStep: number +} + +interface IEditorState { + isSidebarOpen: boolean, + isSVGSidebarOpen: boolean, + isHistoryOpen: boolean, + history: Array, + historyCurrentStep: number, +} + +class Editor extends React.Component { + public state: IEditorState; + + constructor(props: IEditorProps) { + super(props); + this.state = { + isSidebarOpen: true, + isSVGSidebarOpen: false, + isHistoryOpen: false, + configuration: props.configuration, + history: props.history, + historyCurrentStep: props.historyCurrentStep + } as IEditorState; + } + + 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() { + this.setState({ + isSidebarOpen: !this.state.isSidebarOpen + } as IEditorState); + } + + /** + * Toggle the elements + */ + public ToggleElementsSidebar() { + this.setState({ + isSVGSidebarOpen: !this.state.isSVGSidebarOpen + } as IEditorState); + } + + /** + * Toggle the elements + */ + public ToggleHistory() { + this.setState({ + isHistoryOpen: !this.state.isHistoryOpen + } as IEditorState); + } + + /** + * Select a container + * @param container Selected container + */ + public SelectContainer(container: ContainerModel) { + const history = this.getCurrentHistory(); + const current = history[history.length - 1]; + this.setState({ + history: history.concat([{ + MainContainer: current.MainContainer, + TypeCounters: current.TypeCounters, + SelectedContainer: container + }]), + historyCurrentStep: history.length + } as IEditorState); + } + + /** + * Handled the property change event in the properties form + * @param key Property name + * @param value New value of the property + * @returns void + */ + public OnPropertyChange(key: string, value: string | number): void { + const history = this.getCurrentHistory(); + const current = history[history.length - 1]; + + if (current.SelectedContainer === null || + current.SelectedContainer === undefined) { + throw new Error('[OnPropertyChange] Property was changed before selecting a Container'); + } + + if (current.MainContainer === null || + current.MainContainer === undefined) { + throw new Error('[OnPropertyChange] Property was changed before the main container was added'); + } + + if (parent === null) { + const clone: IContainerModel = structuredClone(current.SelectedContainer); + (clone.properties as any)[key] = value; + this.setState({ + history: history.concat([{ + SelectedContainer: clone, + MainContainer: clone, + TypeCounters: current.TypeCounters + }]), + historyCurrentStep: history.length + } as IEditorState); + return; + } + + const clone: IContainerModel = structuredClone(current.MainContainer); + const it = MakeIterator(clone); + let container: ContainerModel | null = null; + for (const child of it) { + if (child.properties.id === current.SelectedContainer.properties.id) { + container = child as ContainerModel; + break; + } + } + + if (container === null) { + throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); + } + + (container.properties as any)[key] = value; + + this.setState( + { + history: history.concat([{ + SelectedContainer: container, + MainContainer: clone, + TypeCounters: current.TypeCounters + }]), + historyCurrentStep: history.length + } as IEditorState); + } + + /** + * Add a new container to a selected container + * @param type The type of container + * @returns void + */ + public AddContainer(type: string): void { + const history = this.getCurrentHistory(); + const current = history[history.length - 1]; + + if (current.SelectedContainer === null || + current.SelectedContainer === undefined) { + return; + } + + if (current.MainContainer === null || + current.MainContainer === undefined) { + return; + } + + // Get the preset properties from the API + const properties = this.props.configuration.AvailableContainers.find(option => option.Type === type); + + if (properties === undefined) { + throw new Error(`[AddContainer] Object type not found. Found: ${type}`); + } + + // Set the counter of the object type in order to assign an unique id + const newCounters = Object.assign({}, current.TypeCounters); + if (newCounters[type] === null || + newCounters[type] === undefined) { + newCounters[type] = 0; + } else { + newCounters[type]++; + } + const count = newCounters[type]; + + // Create maincontainer model + const structure: IContainerModel = structuredClone(current.MainContainer); + const clone = Object.assign(new ContainerModel(null, {} as Properties), structure); + + // Find the parent + const it = MakeIterator(clone); + let parent: ContainerModel | null = null; + for (const child of it) { + if (child.properties.id === current.SelectedContainer.properties.id) { + parent = child as ContainerModel; + break; + } + } + + if (parent === null) { + throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); + } + + // Create the container + const newContainer = new ContainerModel( + parent, + { + id: `${type}-${count}`, + x: 0, + y: 0, + width: properties?.Width, + height: parent.properties.height, + ...properties.Style + } as Properties, + [], + { + type + } + ); + + // And push it the the parent children + parent.children.push(newContainer); + + // Update the state + this.setState({ + history: history.concat([{ + MainContainer: clone, + TypeCounters: newCounters, + SelectedContainer: parent + }]), + historyCurrentStep: history.length + } as IEditorState); + } + + public jumpTo(move: number): void { + this.setState({ + historyCurrentStep: move + } as IEditorState); + } + + /** + * Render the application + * @returns {JSX.Element} Rendered JSX element + */ + render() { + const current = this.getCurrentHistoryState(); + return ( +
+ this.ToggleSidebar()} + buttonOnClick={(type: string) => this.AddContainer(type)} + /> + + + this.ToggleElementsSidebar()} + onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)} + selectContainer={(container: ContainerModel) => this.SelectContainer(container)} + /> + + + this.ToggleHistory()} + jumpTo={(move) => { this.jumpTo(move); }} + /> + + + + { current.MainContainer } + +
+ ); + } +} + +export default Editor; diff --git a/src/main.tsx b/src/main.tsx index 95b3daf..6893f34 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './App'; +import { App } from './App'; import './index.scss'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(