diff --git a/src/App.tsx b/src/App.tsx index ed36af1..ac29d48 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,20 +1,19 @@ -import React from 'react'; +import React, { Children } from 'react'; import './App.scss'; import Sidebar from './Components/Sidebar/Sidebar'; import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar'; import { AvailableContainer } from './Interfaces/AvailableContainer'; import { Configuration } from './Interfaces/Configuration'; +import { Container } from './Components/SVG/Elements/Container'; 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, - SelectedContainer: IContainerModel | null, + MainContainer: Container | null, + SelectedContainer: Container | null, TypeCounters: Record } @@ -57,16 +56,19 @@ class App extends React.Component { // Fetch the configuration from the API fetchConfiguration().then((configuration: Configuration) => { // Set the main container from the given properties of the API - const MainContainer = new ContainerModel( - null, + const MainContainer = new Container( { - id: 'main', - x: 0, - y: 0, - width: configuration.MainContainer.Width, - height: configuration.MainContainer.Height, - fillOpacity: 0, - stroke: 'black' + parent: null, + properties: { + id: 'main', + x: 0, + y: 0, + width: configuration.MainContainer.Width, + height: configuration.MainContainer.Height, + fillOpacity: 0, + stroke: 'black' + }, + children: [] } ); @@ -114,7 +116,7 @@ class App extends React.Component { * Select a container * @param container Selected container */ - public SelectContainer(container: ContainerModel) { + public SelectContainer(container: Container) { const history = this.getCurrentHistory(); const current = history[history.length - 1]; this.setState({ @@ -139,21 +141,30 @@ class App extends React.Component { if (current.SelectedContainer === null || current.SelectedContainer === undefined) { - throw new Error('[OnPropertyChange] Property was changed before selecting a Container'); + throw new Error('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'); + throw new Error('Property was changed before the main container was added'); } + const pair = {} as Record; + pair[key] = value; + const properties = Object.assign(current.SelectedContainer.props.properties, pair); + const props = { + ...current.SelectedContainer.props, + properties + }; + + const newSelectedContainer = new Container(props); + + const parent = current.SelectedContainer.props.parent; if (parent === null) { - const clone: IContainerModel = structuredClone(current.SelectedContainer); - (clone.properties as any)[key] = value; this.setState({ history: history.concat([{ - SelectedContainer: clone, - MainContainer: clone, + SelectedContainer: newSelectedContainer, + MainContainer: newSelectedContainer, TypeCounters: current.TypeCounters }]), historyCurrentStep: history.length @@ -161,27 +172,15 @@ class App extends React.Component { 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; + const index = parent.props.children.indexOf(current.SelectedContainer); + parent.props.children[index] = newSelectedContainer; + const newMainContainer = new Container(Object.assign({}, current.MainContainer.props)); this.setState( { history: history.concat([{ - SelectedContainer: container, - MainContainer: clone, + SelectedContainer: newSelectedContainer, + MainContainer: newMainContainer, TypeCounters: current.TypeCounters }]), historyCurrentStep: history.length @@ -222,52 +221,36 @@ class App extends React.Component { } 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( + const parent = current.SelectedContainer; + const count = newCounters[type]; + const container = new Container({ parent, - { + properties: { id: `${type}-${count}`, x: 0, y: 0, width: properties?.Width, - height: parent.properties.height, + height: parent.props.properties.height, ...properties.Style - } as Properties, - [], - { + }, + children: [], + userData: { type } - ); + }); // And push it the the parent children - parent.children.push(newContainer); + parent.props.children.push(container); // Update the state + const newMainContainer = new Container(Object.assign({}, current.MainContainer.props)); this.setState({ history: history.concat([{ - MainContainer: clone, + MainContainer: newMainContainer, TypeCounters: newCounters, - SelectedContainer: parent + SelectedContainer: current.SelectedContainer }]), historyCurrentStep: history.length } as IAppState); @@ -300,7 +283,7 @@ class App extends React.Component { isOpen={this.state.isSVGSidebarOpen} onClick={() => this.ToggleElementsSidebar()} onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)} - selectContainer={(container: ContainerModel) => this.SelectContainer(container)} + selectContainer={(container: Container) => this.SelectContainer(container)} /> diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index 3caea25..afec90a 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -1,26 +1,26 @@ import * as React from 'react'; import { motion } from 'framer-motion'; import { Properties } from '../Properties/Properties'; -import { IContainerModel, getDepth, MakeIterator } from '../SVG/Elements/ContainerModel'; +import { Container } from '../SVG/Elements/Container'; interface IElementsSidebarProps { - MainContainer: IContainerModel | null, + MainContainer: Container | null, isOpen: boolean, - SelectedContainer: IContainerModel | null, + SelectedContainer: Container | null, onClick: () => void, onPropertyChange: (key: string, value: string) => void, - selectContainer: (container: IContainerModel) => void + selectContainer: (container: Container) => void } export class ElementsSidebar extends React.Component { - public iterateChilds(handleContainer: (container: IContainerModel) => void): React.ReactNode { + public iterateChilds(handleContainer: (container: Container) => void): React.ReactNode { if (!this.props.MainContainer) { return null; } - const it = MakeIterator(this.props.MainContainer); + const it = this.props.MainContainer.MakeIterator(); for (const container of it) { - handleContainer(container as IContainerModel); + handleContainer(container); } } @@ -28,29 +28,29 @@ export class ElementsSidebar extends React.Component { const isOpenClasses = this.props.isOpen ? 'right-0' : '-right-64'; const containerRows: React.ReactNode[] = []; - this.iterateChilds((container: IContainerModel) => { - const depth: number = getDepth(container); - const key = container.properties.id.toString(); + this.iterateChilds((container: Container) => { + const depth: number = container.getDepth(); + const key = container.props.properties.id.toString(); const text = '|\t'.repeat(depth) + key; const selectedClass: string = this.props.SelectedContainer !== null && - this.props.SelectedContainer.properties.id === container.properties.id + this.props.SelectedContainer.props.properties.id === container.props.properties.id ? 'bg-blue-500 hover:bg-blue-600' : 'bg-slate-400 hover:bg-slate-600'; containerRows.push( this.props.selectContainer(container)}> + } + key={key} + onClick={() => this.props.selectContainer(container)}> { text } ); @@ -67,7 +67,7 @@ export class ElementsSidebar extends React.Component {
{ containerRows }
- + ); } diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index ab8d92c..1094588 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -1,22 +1,92 @@ import * as React from 'react'; -import { getDepth, IContainerModel } from './ContainerModel'; +import Properties from '../../../Interfaces/Properties'; import { Dimension } from './Dimension'; -export interface IContainerProps { - model: IContainerModel +interface IContainerProps { + // eslint-disable-next-line no-use-before-define + parent: Container | null, + // eslint-disable-next-line no-use-before-define + children: Container[], + properties: Properties, + userData?: Record } const GAP = 50; export class Container extends React.Component { + /** + * Returns A copy of the properties of the Container + * @returns A copy of the properties of the Container + */ + public GetProperties(): Properties { + const properties : Properties = { + ...this.props.properties + }; + return properties; + } + + /** + * Returns a Generator iterating of over the children depth-first + */ + public * MakeIterator(): Generator { + const queue: Container[] = [this]; + const visited = new Set(queue); + while (queue.length > 0) { + const container = queue.pop() as Container; + + yield container; + + // if this reverse() gets costly, replace it by a simple for + container.props.children.reverse().forEach((child) => { + if (visited.has(child)) { + return; + } + visited.add(child); + queue.push(child); + }); + } + } + + /** + * Returns the depth of the container + * @returns The depth of the container + */ + public getDepth() { + let depth = 0; + + let current: Container | null = this.props.parent; + while (current != null) { + depth++; + current = current.props.parent; + } + + return depth; + } + + /** + * Returns the absolute position by iterating to the parent + * @returns The absolute position of the container + */ + public getAbsolutePosition(): [number, number] { + let x = Number(this.props.properties.x); + let y = Number(this.props.properties.y); + let current = this.props.parent; + while (current != null) { + x += Number(current.props.properties.x); + y += Number(current.props.properties.y); + current = current.props.parent; + } + return [x, y]; + } + /** * Render the container * @returns Render the container */ public render(): React.ReactNode { - const containersElements = this.props.model.children.map(child => new Container({ model: child } as IContainerProps).render()); - const xText = Number(this.props.model.properties.width) / 2; - const yText = Number(this.props.model.properties.height) / 2; + const containersElements = this.props.children.map(child => child.render()); + const xText = Number(this.props.properties.width) / 2; + const yText = Number(this.props.properties.height) / 2; // g style const defaultStyle = { @@ -28,7 +98,7 @@ export class Container extends React.Component { // Rect style const style = Object.assign( JSON.parse(JSON.stringify(defaultStyle)), - this.props.model.properties + this.props.properties ); style.x = 0; style.y = 0; @@ -36,18 +106,18 @@ export class Container extends React.Component { delete style.width; // Dimension props - const id = `dim-${this.props.model.properties.id}`; + const id = `dim-${this.props.properties.id}`; const xStart: number = 0; - const xEnd = Number(this.props.model.properties.width); - const y = -(GAP * (getDepth(this.props.model) + 1)); + const xEnd = Number(this.props.properties.width); + const y = -(GAP * (this.getDepth() + 1)); const strokeWidth = 1; - const text = (this.props.model.properties.width ?? 0).toString(); + const text = (this.props.properties.width ?? 0).toString(); return ( { text={text} /> @@ -67,7 +137,7 @@ export class Container extends React.Component { x={xText} y={yText} > - {this.props.model.properties.id} + {this.props.properties.id} { containersElements } diff --git a/src/Components/SVG/Elements/ContainerModel.ts b/src/Components/SVG/Elements/ContainerModel.ts deleted file mode 100644 index 4b35082..0000000 --- a/src/Components/SVG/Elements/ContainerModel.ts +++ /dev/null @@ -1,80 +0,0 @@ -import Properties from '../../../Interfaces/Properties'; - -export interface IContainerModel { - children: IContainerModel[], - parent: IContainerModel | null, - properties: Properties, - userData: Record -} - -export class ContainerModel implements IContainerModel { - public children: IContainerModel[]; - public parent: IContainerModel | null; - public properties: Properties; - public userData: Record; - - constructor( - parent: IContainerModel | null, - properties: Properties, - children: IContainerModel[] = [], - userData = {}) { - this.parent = parent; - this.properties = properties; - this.children = children; - this.userData = userData; - } -}; - -/** - * Returns a Generator iterating of over the children depth-first - */ -export function * MakeIterator(root: IContainerModel): Generator { - const queue: IContainerModel[] = [root]; - const visited = new Set(queue); - while (queue.length > 0) { - const container = queue.pop() as IContainerModel; - - yield container; - - // if this reverse() gets costly, replace it by a simple for - container.children.forEach((child) => { - if (visited.has(child)) { - return; - } - visited.add(child); - queue.push(child); - }); - } -} - -/** - * Returns the depth of the container - * @returns The depth of the container - */ -export function getDepth(parent: IContainerModel) { - let depth = 0; - - let current: IContainerModel | null = parent; - while (current != null) { - depth++; - current = current.parent; - } - - return depth; -} - -/** - * Returns the absolute position by iterating to the parent - * @returns The absolute position of the container - */ -export function getAbsolutePosition(container: IContainerModel): [number, number] { - let x = Number(container.properties.x); - let y = Number(container.properties.y); - let current = container.parent; - while (current != null) { - x += Number(current.properties.x); - y += Number(current.properties.y); - current = current.parent; - } - return [x, y]; -} diff --git a/src/Components/SVG/Elements/DimensionLayer.tsx b/src/Components/SVG/Elements/DimensionLayer.tsx index 610177b..601fedd 100644 --- a/src/Components/SVG/Elements/DimensionLayer.tsx +++ b/src/Components/SVG/Elements/DimensionLayer.tsx @@ -1,25 +1,25 @@ import * as React from 'react'; -import { ContainerModel, getDepth, MakeIterator } from './ContainerModel'; +import { Container } from './Container'; import { Dimension } from './Dimension'; interface IDimensionLayerProps { isHidden: boolean, - roots: ContainerModel | ContainerModel[] | null, + roots: Container | Container[] | null, } const GAP: number = 50; -const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => { - const it = MakeIterator(root); +const getDimensionsNodes = (root: Container): React.ReactNode[] => { + const it = root.MakeIterator(); const dimensions: React.ReactNode[] = []; for (const container of it) { // WARN: this might be dangerous later when using other units/rules - const width = Number(container.properties.width); + const width = Number(container.props.properties.width); - const id = `dim-${container.properties.id}`; - const xStart: number = container.properties.x; + const id = `dim-${container.props.properties.id}`; + const xStart: number = container.props.properties.x; const xEnd = xStart + width; - const y = -(GAP * (getDepth(container) + 1)); + const y = -(GAP * (container.getDepth() + 1)); const strokeWidth = 1; const text = width.toString(); const dimension = new Dimension({ diff --git a/src/Components/SVG/Elements/Selector.tsx b/src/Components/SVG/Elements/Selector.tsx index 6fc1b9b..6268d2f 100644 --- a/src/Components/SVG/Elements/Selector.tsx +++ b/src/Components/SVG/Elements/Selector.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; -import { IContainerModel, getAbsolutePosition } from './ContainerModel'; +import { Container } from './Container'; interface ISelectorProps { - selected: IContainerModel | null + selected: Container | null } export const Selector: React.FC = (props) => { @@ -13,10 +13,10 @@ export const Selector: React.FC = (props) => { ); } - const [x, y] = getAbsolutePosition(props.selected); + const [x, y] = props.selected.getAbsolutePosition(); const [width, height] = [ - props.selected.properties.width, - props.selected.properties.height + props.selected.props.properties.width, + props.selected.props.properties.height ]; const style = { stroke: '#3B82F6', // tw blue-500 diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index 9583571..64a3246 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; import { ReactSVGPanZoom, Tool, Value, TOOL_PAN } from 'react-svg-pan-zoom'; import { Container } from './Elements/Container'; -import { ContainerModel } from './Elements/ContainerModel'; import { Selector } from './Elements/Selector'; interface ISVGProps { - children: ContainerModel | ContainerModel[] | null, - selected: ContainerModel | null + children: Container | Container[] | null, + selected: Container | null } interface ISVGState { @@ -66,9 +65,9 @@ export class SVG extends React.Component { let children: React.ReactNode | React.ReactNode[] = []; if (Array.isArray(this.props.children)) { - children = this.props.children.map(child => new Container({ model: child }).render()); + children = this.props.children.map(child => child.render()); } else if (this.props.children !== null) { - children = new Container({ model: this.props.children }).render(); + children = this.props.children.render(); } return (