From 0fd354fc1f0f92206ff9a5718a6da10f63f933e0 Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 10:37:39 +0200 Subject: [PATCH 01/14] Fix text not centered --- src/Editor.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Editor.scss b/src/Editor.scss index 32b3726..cc037c8 100644 --- a/src/Editor.scss +++ b/src/Editor.scss @@ -14,6 +14,8 @@ text { stroke-linecap: butt; stroke-linejoin: miter; stroke-opacity: 1; + transform: translateX(-50%); + transform-box: fill-box; } @keyframes fadein { From d1e04769bb6b2bc5785212ee12f254193ce874c5 Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 10:45:16 +0200 Subject: [PATCH 02/14] http.js reduce API size --- test-server/http.js | 195 ++++---------------------------------------- 1 file changed, 16 insertions(+), 179 deletions(-) diff --git a/test-server/http.js b/test-server/http.js index 043229c..7042bee 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -43,23 +43,8 @@ const GetSVGLayoutConfiguration = () => { return { AvailableContainers: [ { - BodyColor: null, - BorderColor: '#ff0000', - BorderWidth: 48, - ContainerActions: null, - ContainerDimensionning: null, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 3000, - MinHeight: 0, - MinWidth: 500, Type: 'Chassis', - TypeChildContainerDefault: 'Trou', Width: 500, - XPositionReference: 0, Style: { fillOpacity: 0, borderWidth: 2, @@ -67,158 +52,25 @@ const GetSVGLayoutConfiguration = () => { } }, { - BodyColor: null, - BorderColor: '#FFFFFF', - BorderWidth: 0, - ContainerActions: null, - ContainerDimensionning: null, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, Type: 'Trou', - TypeChildContainerDefault: 'Remplissage', - Width: 0, - XPositionReference: 0 + Width: 300, + Style: { + fillOpacity: 0, + borderWidth: 2, + stroke: 'green' + } }, { - BodyColor: '#99C8FF', - BorderColor: '#00FF00', - BorderWidth: 0, - ContainerActions: [ - { - Action: 'SplitRemplissage', - AddingBehavior: 0, - CustomLogo: { - Base64Image: null, - Name: null, - Svg: null, - Url: '' - }, - Description: 'Diviser le remplissage en insérant un montant', - Id: null, - Label: 'Diviser le remplissage' - } - ], - ContainerDimensionning: { - DimensionningStyle: 1, - ShowDimensionning: false, - ShowLabel: false - }, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, - Type: 'Remplissage', - TypeChildContainerDefault: null, - Width: 0, - XPositionReference: 0 - }, - { - BodyColor: '#FFA947', - BorderColor: '#FFA947', - BorderWidth: 0, - ContainerActions: null, - ContainerDimensionning: null, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, Type: 'Montant', - TypeChildContainerDefault: null, - Width: 50, - XPositionReference: 1 - }, - { - BodyColor: '#FFA3D1', - BorderColor: '#FF6DE6', - BorderWidth: 0, - ContainerActions: null, - ContainerDimensionning: { - DimensionningStyle: 0, - ShowDimensionning: false, - ShowLabel: false - }, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, - Type: 'Ouverture', - TypeChildContainerDefault: null, - Width: 0, - XPositionReference: 0 - }, - { - BodyColor: '#000000', - BorderColor: null, - BorderWidth: 0, - ContainerActions: null, - ContainerDimensionning: { - DimensionningStyle: 0, - ShowDimensionning: false, - ShowLabel: false - }, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, - Type: 'Dilatation', - TypeChildContainerDefault: null, - Width: 8, - XPositionReference: 0 - }, - { - BodyColor: '#dee2e4', - BorderColor: '#54616c', - BorderWidth: 0, - ContainerActions: [ - { - Action: 'FillHoleWithChassis', - AddingBehavior: 1, - CustomLogo: { - Base64Image: null, - Name: null, - Svg: null, - Url: '' - }, - Description: 'Remplir le trou avec des châssis', - Id: null, - Label: 'Calepiner' - } - ], - ContainerDimensionning: null, - DefaultChildrenContainers: null, - Height: 0, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, - Type: '', - TypeChildContainerDefault: null, - Width: 0, - XPositionReference: 0 + Width: 100, + Style: { + fillOpacity: 0, + borderWidth: 2, + stroke: 'blue', + transform: 'translateX(-50%)', + transformOrigin: 'center', + transformBox: 'fill-box' + } } ], AvailableSymbols: [ @@ -248,23 +100,8 @@ const GetSVGLayoutConfiguration = () => { } ], MainContainer: { - BodyColor: null, - BorderColor: '#FFFFFF', - BorderWidth: 0, - ContainerActions: null, - ContainerDimensionning: null, - DefaultChildrenContainers: null, Height: 200, - IsPositionFixed: false, - IsWidthFixed: false, - MaxHeight: 0, - MaxWidth: 0, - MinHeight: 0, - MinWidth: 0, - Type: 'Trou', - TypeChildContainerDefault: null, - Width: 1000, - XPositionReference: 0 + Width: 1000 } }; }; From 35bb95f206c51ff017d0173ccfb7499793c418d8 Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 11:56:45 +0200 Subject: [PATCH 03/14] Fix node-http.js --- test-server/node-http.js | 44 +++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/test-server/node-http.js b/test-server/node-http.js index b78fe50..e6c0d74 100644 --- a/test-server/node-http.js +++ b/test-server/node-http.js @@ -2,27 +2,39 @@ import http from 'http'; const host = 'localhost'; const port = 5000; -const requestListener = function(request, response) { +const requestListener = async(request, response) => { + response.setHeader('Access-Control-Allow-Origin', '*'); + response.setHeader('Access-Control-Allow-Headers', '*'); + response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS'); if (request.method === 'POST') { - response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Content-Type', 'application/json'); const url = request.url; let json; - if (url === '/GetSVGLayoutConfiguration') { - json = GetSVGLayoutConfiguration(); - } else if (url === '/FillHoleWithChassis') { - json = FillHoleWithChassis(request); - } else if (url === '/SplitRemplissage') { - json = SplitRemplissage(request); + if (url === '/ApplicationState') { + const buffers = []; + for await (const chunk of request) { + buffers.push(chunk); + } + const data = Buffer.concat(buffers).toString(); + const bodyParsed = JSON.parse(data); + console.log(bodyParsed); + switch (bodyParsed.Action) { + case 'FillHoleWithChassis': + json = FillHoleWithChassis(bodyParsed); + break; + case 'SplitRemplissage': + json = SplitRemplissage(bodyParsed); + break; + default: + break; + } } else { - // TODO: Return 404 rather than this + // TODO: Return 404 rather than this json = GetSVGLayoutConfiguration(); } response.writeHead(200); return response.end(JSON.stringify(json)); } else if (request.method === 'OPTIONS') { - response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS'); - response.setHeader('Access-Control-Allow-Headers', '*'); response.writeHead(200); return response.end(); } @@ -269,18 +281,18 @@ const GetSVGLayoutConfiguration = () => { const FillHoleWithChassis = (request) => { const maxWidthChassis = 3000; - const nbChassis = Math.ceil(request.ContainerActions.Width / maxWidthChassis); + const nbChassis = Math.ceil(request.ContainerAction.Width / maxWidthChassis); const lstModels = []; for (let i = 0; i <= nbChassis; i++) { if (i === 1 && request.ContainerAction.ExistOnBefore) { - lstModels.Add({ Type: 'Dilatation' }); + lstModels.push({ Type: 'Dilatation' }); } - lstModels.Add({ Type: 'Chassis' }); + lstModels.push({ Type: 'Chassis' }); if (i < nbChassis) { - lstModels.Add({ Type: 'Dilatation' }); + lstModels.push({ Type: 'Dilatation' }); } if (i === nbChassis && request.ContainerAction.ExistOnAfter) { - lstModels.Add({ Type: 'Dilatation' }); + lstModels.push({ Type: 'Dilatation' }); } } return { From cf1a3a7eec95ab297edbb37501e1ab4f2e5cb7ac Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 11:57:03 +0200 Subject: [PATCH 04/14] Default action of AddContainer will now be append --- src/Editor.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Editor.tsx b/src/Editor.tsx index 8b4edb6..5e24be6 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -212,13 +212,19 @@ class Editor extends React.Component { throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); } + let x = 0; + const lastChild: IContainerModel | undefined = parent.children.at(-1); + if (lastChild !== undefined) { + x = lastChild.properties.x + Number(lastChild.properties.width); + } + // Create the container const newContainer = new ContainerModel( parent, { id: `${type}-${count}`, parentId: parent.properties.id, - x: 0, + x, y: 0, width: properties?.Width, height: parent.properties.height, From 8fdf75019fc34ee9451511e0c660789b66fa85fe Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 12:06:15 +0200 Subject: [PATCH 05/14] Fix bun test-server --- test-server/http.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/test-server/http.js b/test-server/http.js index 7042bee..c4f3238 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -3,22 +3,31 @@ import { serve } from 'bun'; serve({ port: 5000, - fetch(request) { + async fetch(request) { console.log(`${request.method}: ${request.url}`); if (request.method === 'POST') { const url = new URL(request.url); let json; if (url.pathname === '/GetSVGLayoutConfiguration') { - json = JSON.stringify(GetSVGLayoutConfiguration()); - } else if (url.pathname === '/FillHoleWithChassis') { - json = JSON.stringify(FillHoleWithChassis(request)); - } else if (url.pathname === '/SplitRemplissage') { - json = JSON.stringify(SplitRemplissage(request)); + json = GetSVGLayoutConfiguration(); + } else if (url.pathname === '/ApplicationState') { + const bodyParsed = await request.json(); + console.log(bodyParsed); + switch (bodyParsed.Action) { + case 'FillHoleWithChassis': + json = FillHoleWithChassis(bodyParsed); + break; + case 'SplitRemplissage': + json = SplitRemplissage(bodyParsed); + break; + default: + break; + } } else { // TODO: Return 404 rather than this - json = JSON.stringify(GetSVGLayoutConfiguration()); + json = GetSVGLayoutConfiguration(); } - return new Response(json, { + return new Response(JSON.stringify(json), { status: 200, headers: { 'Content-Type': 'application/json', @@ -108,18 +117,18 @@ const GetSVGLayoutConfiguration = () => { const FillHoleWithChassis = (request) => { const maxWidthChassis = 3000; - const nbChassis = Math.ceil(request.ContainerActions.Width / maxWidthChassis); + const nbChassis = Math.ceil(request.ContainerAction.Width / maxWidthChassis); const lstModels = []; for (let i = 0; i <= nbChassis; i++) { if (i === 1 && request.ContainerAction.ExistOnBefore) { - lstModels.Add({ Type: 'Dilatation' }); + lstModels.push({ Type: 'Dilatation' }); } - lstModels.Add({ Type: 'Chassis' }); + lstModels.push({ Type: 'Chassis' }); if (i < nbChassis) { - lstModels.Add({ Type: 'Dilatation' }); + lstModels.push({ Type: 'Dilatation' }); } if (i === nbChassis && request.ContainerAction.ExistOnAfter) { - lstModels.Add({ Type: 'Dilatation' }); + lstModels.push({ Type: 'Dilatation' }); } } return { From 563840080c7f9905ef8400f8f76978078c3a0665 Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 12:28:23 +0200 Subject: [PATCH 06/14] Fix bug where when an input is emptied, its type change to 'number' --- src/Components/Properties/Properties.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index e75e86b..61d0bff 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -42,7 +42,7 @@ export class Properties extends React.Component { const id = `property-${key}`; - const type = isNaN(Number(value)) ? 'text' : 'number'; + const type = 'text'; const isDisabled = key === 'id' || key === 'parentId'; // hardcoded groupInput.push(
From 415318a81cd6febd1e5b565ac05e65efe14e9f48 Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 13:03:00 +0200 Subject: [PATCH 07/14] Fix Error message when string empty in input --- src/Components/SVG/Elements/Container.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index ab8d92c..bf56127 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -17,6 +17,7 @@ export class Container extends React.Component { 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 transform = `translate(${Number(this.props.model.properties.x)}, ${Number(this.props.model.properties.y)})`; // g style const defaultStyle = { @@ -46,7 +47,7 @@ export class Container extends React.Component { return ( Date: Fri, 5 Aug 2022 13:48:53 +0200 Subject: [PATCH 08/14] Add state.json to src/tests/resources for testing --- src/tests/resources/state.json | 182 +++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/tests/resources/state.json diff --git a/src/tests/resources/state.json b/src/tests/resources/state.json new file mode 100644 index 0000000..2152cd1 --- /dev/null +++ b/src/tests/resources/state.json @@ -0,0 +1,182 @@ +{ + "isSidebarOpen": true, + "isElementsSidebarOpen": false, + "isHistoryOpen": false, + "configuration": { + "AvailableContainers": [ + { + "Type": "Chassis", + "Width": 500, + "Style": { + "fillOpacity": 0, + "borderWidth": 2, + "stroke": "red" + } + }, + { + "Type": "Trou", + "Width": 300, + "Style": { + "fillOpacity": 0, + "borderWidth": 2, + "stroke": "green" + } + }, + { + "Type": "Montant", + "Width": 100, + "Style": { + "fillOpacity": 0, + "borderWidth": 2, + "stroke": "blue", + "transform": "translateX(-50%)", + "transformOrigin": "center", + "transformBox": "fill-box" + } + } + ], + "AvailableSymbols": [ + { + "Height": 0, + "Image": { + "Base64Image": null, + "Name": null, + "Svg": null, + "Url": "https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg" + }, + "Name": "Poteau structure", + "Width": 0, + "XPositionReference": 1 + }, + { + "Height": 0, + "Image": { + "Base64Image": null, + "Name": null, + "Svg": null, + "Url": "https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png" + }, + "Name": "Joint de structure", + "Width": 0, + "XPositionReference": 0 + } + ], + "MainContainer": { + "Height": 200, + "Width": 1000 + } + }, + "history": [ + { + "MainContainer": { + "children": [], + "properties": { + "id": "main", + "parentId": "null", + "x": 0, + "y": 0, + "width": 1000, + "height": 200, + "fillOpacity": 0, + "stroke": "black" + }, + "userData": {} + }, + "TypeCounters": {} + }, + { + "MainContainer": { + "children": [ + { + "children": [], + "properties": { + "id": "Chassis-0", + "parentId": "main", + "x": 0, + "y": 0, + "width": 500, + "height": 200, + "fillOpacity": 0, + "borderWidth": 2, + "stroke": "red" + }, + "userData": { + "type": "Chassis" + } + } + ], + "properties": { + "id": "main", + "parentId": "null", + "x": 0, + "y": 0, + "width": 1000, + "height": 200, + "fillOpacity": 0, + "stroke": "black" + }, + "userData": {} + }, + "TypeCounters": { + "Chassis": 0 + }, + "SelectedContainerId": "main" + }, + { + "MainContainer": { + "children": [ + { + "children": [], + "properties": { + "id": "Chassis-0", + "parentId": "main", + "x": 0, + "y": 0, + "width": 500, + "height": 200, + "fillOpacity": 0, + "borderWidth": 2, + "stroke": "red" + }, + "userData": { + "type": "Chassis" + } + }, + { + "children": [], + "properties": { + "id": "Chassis-1", + "parentId": "main", + "x": 500, + "y": 0, + "width": 500, + "height": 200, + "fillOpacity": 0, + "borderWidth": 2, + "stroke": "red" + }, + "userData": { + "type": "Chassis" + } + } + ], + "properties": { + "id": "main", + "parentId": "null", + "x": 0, + "y": 0, + "width": 1000, + "height": 200, + "fillOpacity": 0, + "stroke": "black" + }, + "userData": {} + }, + "TypeCounters": { + "Chassis": 1 + }, + "SelectedContainerId": "main" + } + ], + "historyCurrentStep": 2 +} \ No newline at end of file From 893876ddaa24cc8c1a3bc1a6ac55651566afc4fd Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 13:49:22 +0200 Subject: [PATCH 09/14] Allow LoadEditor through external resource --- src/App.tsx | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index e6ea857..58e73c5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,6 +40,22 @@ export class App extends React.Component { }; } + componentDidMount() { + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const state = urlParams.get('state'); + + if (state === null) { + return; + } + + fetch(state) + .then((response) => response.json()) + .then((data: IEditorState) => { + this.LoadState(data); + }); + } + public NewEditor() { // Fetch the configuration from the API fetchConfiguration().then((configuration: Configuration) => { @@ -86,18 +102,22 @@ export class App extends React.Component { const result = reader.result as string; const editorState: IEditorState = JSON.parse(result); - Revive(editorState); - - this.setState({ - configuration: editorState.configuration, - history: editorState.history, - historyCurrentStep: editorState.historyCurrentStep, - isLoaded: true - } as IAppState); + this.LoadState(editorState); }); reader.readAsText(file); } + private LoadState(editorState: IEditorState) { + Revive(editorState); + + this.setState({ + configuration: editorState.configuration, + history: editorState.history, + historyCurrentStep: editorState.historyCurrentStep, + isLoaded: true + } as IAppState); + } + public render() { if (this.state.isLoaded) { return ( From d5f89523dbd9b812d74bc613fbf94a47aed5d74d Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 14:11:30 +0200 Subject: [PATCH 10/14] Update Readme --- README.md | 64 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 5ecff80..7fa3b5c 100644 --- a/README.md +++ b/README.md @@ -15,34 +15,6 @@ Run `npm ci` Run `npm run dev` -## Testing the API - -This program fetch the data structure from others application, allowing it to assemble them later. - -### With NodeJS - -```bash -node run ./test-server/node-http.js -``` - -The web server will be running at `http://localhost:5000` - -Configure the file `.env.development` with the url - - -### With bun - -Install `bun` - -Inside `test-server` folder, run : - -```bash -bun run http.js -``` - -The web server will be running at `http://localhost:5000` - -Configure the file `.env.development` with the url # Deploy @@ -57,3 +29,39 @@ Run `npm run build` Run `npm ci` Run `npm test` + + +# API + +You can preload a state by setting the `state` URL parameter +with a url address to a `state.json` file. + +Example: `http://localhost:4000/?state=http://localhost:5000/state.json` + +# Testing the external API + +This program fetch the data structure from others applications, allowing it to assemble them later. + +## With NodeJS + +```bash +node run ./test-server/node-http.js +``` + +The web server will be running at `http://localhost:5000` + +Configure the file `.env.development` with the url + +## With bun.sh + +Install `bun` + +Inside `test-server` folder, run : + +```bash +bun run http.js +``` + +The web server will be running at `http://localhost:5000` + +Configure the file `.env.development` with the url \ No newline at end of file From bc6d44db6a75b7ac00bc37723d738ce02bbe4075 Mon Sep 17 00:00:00 2001 From: Siklos Date: Fri, 5 Aug 2022 16:00:35 +0200 Subject: [PATCH 11/14] Fix stuck svg pan zoom + improve minimap --- src/Components/SVG/SVG.tsx | 37 ++++++++++--------------------------- src/Editor.tsx | 8 ++++++-- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index 9583571..c5404a2 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -5,12 +5,13 @@ import { ContainerModel } from './Elements/ContainerModel'; import { Selector } from './Elements/Selector'; interface ISVGProps { + width: number, + height: number, children: ContainerModel | ContainerModel[] | null, selected: ContainerModel | null } interface ISVGState { - viewBox: number[], value: Value, tool: Tool } @@ -22,12 +23,6 @@ export class SVG extends React.Component { constructor(props: ISVGProps) { super(props); this.state = { - viewBox: [ - 0, - 0, - window.innerWidth, - window.innerHeight - ], value: { viewerWidth: window.innerWidth, viewerHeight: window.innerHeight @@ -37,30 +32,12 @@ export class SVG extends React.Component { this.svg = React.createRef(); } - resizeViewBox() { - this.setState({ - viewBox: [ - 0, - 0, - window.innerWidth, - window.innerHeight - ] - }); - } - - componentDidMount() { - window.addEventListener('resize', this.resizeViewBox.bind(this)); - } - - componentWillUnmount() { - window.removeEventListener('resize', this.resizeViewBox.bind(this)); - } - render() { const xmlns = ''; const properties = { - viewBox: this.state.viewBox.join(' '), + width: this.props.width, + height: this.props.height, xmlns }; @@ -79,6 +56,12 @@ export class SVG extends React.Component { defaultTool='pan' value={this.state.value} onChangeValue={value => this.setState({ value })} tool={this.state.tool} onChangeTool={tool => this.setState({ tool })} + miniatureProps={{ + position: 'left', + background: '#616264', + width: window.innerWidth - 12, + height: 120 + }} > { children } diff --git a/src/Editor.tsx b/src/Editor.tsx index 5e24be6..3834f42 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -327,11 +327,15 @@ class Editor extends React.Component { ☰ History - + { current.MainContainer }
; diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 94a4924..a352bcd 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getDepth, IContainerModel } from './ContainerModel'; +import { getDepth, IContainerModel } from '../../../Interfaces/ContainerModel'; import { Dimension } from './Dimension'; export interface IContainerProps { @@ -8,7 +8,7 @@ export interface IContainerProps { const GAP = 50; -export class Container extends React.Component { +export class Container extends React.PureComponent { /** * Render the container * @returns Render the container diff --git a/src/Components/SVG/Elements/Dimension.tsx b/src/Components/SVG/Elements/Dimension.tsx index 276345f..96ab2c9 100644 --- a/src/Components/SVG/Elements/Dimension.tsx +++ b/src/Components/SVG/Elements/Dimension.tsx @@ -9,7 +9,7 @@ interface IDimensionProps { strokeWidth: number; } -export class Dimension extends React.Component { +export class Dimension extends React.PureComponent { public render() { const style = { stroke: 'black' diff --git a/src/Components/SVG/Elements/DimensionLayer.tsx b/src/Components/SVG/Elements/DimensionLayer.tsx index 3c73f3a..3a3dee8 100644 --- a/src/Components/SVG/Elements/DimensionLayer.tsx +++ b/src/Components/SVG/Elements/DimensionLayer.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ContainerModel, getDepth, MakeIterator } from './ContainerModel'; +import { ContainerModel, getDepth, MakeIterator } from '../../../Interfaces/ContainerModel'; import { Dimension } from './Dimension'; interface IDimensionLayerProps { diff --git a/src/Components/SVG/Elements/Selector.tsx b/src/Components/SVG/Elements/Selector.tsx index 6fc1b9b..de6d9a3 100644 --- a/src/Components/SVG/Elements/Selector.tsx +++ b/src/Components/SVG/Elements/Selector.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IContainerModel, getAbsolutePosition } from './ContainerModel'; +import { IContainerModel, getAbsolutePosition } from '../../../Interfaces/ContainerModel'; interface ISelectorProps { selected: IContainerModel | null diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index 4d8c301..c11e39b 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -1,7 +1,7 @@ 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 { ContainerModel } from '../../Interfaces/ContainerModel'; import { Selector } from './Elements/Selector'; interface ISVGProps { @@ -16,7 +16,7 @@ interface ISVGState { tool: Tool } -export class SVG extends React.Component { +export class SVG extends React.PureComponent { public state: ISVGState; public svg: React.RefObject; diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx index af08ce9..dd34812 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Sidebar/Sidebar.tsx @@ -8,7 +8,7 @@ interface ISidebarProps { buttonOnClick: (type: string) => void; } -export default class Sidebar extends React.Component { +export default class Sidebar extends React.PureComponent { public render() { const listElements = this.props.componentOptions.map(componentOption => +
); }; export default FloatingButton; diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index c11e39b..bf8704b 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -18,7 +18,7 @@ interface ISVGState { export class SVG extends React.PureComponent { public state: ISVGState; - public svg: React.RefObject; + public static ID = 'svg'; constructor(props: ISVGProps) { super(props); @@ -29,7 +29,6 @@ export class SVG extends React.PureComponent { } as Value, tool: TOOL_PAN }; - this.svg = React.createRef(); } render() { @@ -49,25 +48,28 @@ export class SVG extends React.PureComponent { } return ( - this.setState({ value })} - tool={this.state.tool} onChangeTool={tool => this.setState({ tool })} - miniatureProps={{ - position: 'left', - background: '#616264', - width: window.innerWidth - 12, - height: 120 - }} - > - - { children } - - - +
+ this.setState({ value })} + tool={this.state.tool} onChangeTool={tool => this.setState({ tool })} + miniatureProps={{ + position: 'left', + background: '#616264', + width: window.innerWidth - 12, + height: 120 + }} + > + + { children } + + + +
+ ); }; } diff --git a/src/Editor.tsx b/src/Editor.tsx index 482b91a..89c38aa 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { UploadIcon } from '@heroicons/react/outline'; +import { UploadIcon, PhotographIcon } from '@heroicons/react/outline'; import './Editor.scss'; import Sidebar from './Components/Sidebar/Sidebar'; import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar'; @@ -9,6 +9,7 @@ import { History } from './Components/History/History'; import { ContainerModel, findContainerById, IContainerModel, MakeIterator } from './Interfaces/ContainerModel'; import Properties from './Interfaces/Properties'; import { IHistoryState } from './App'; +import FloatingButton from './Components/FloatingButton/FloatingButton'; interface IEditorProps { configuration: Configuration, @@ -257,7 +258,7 @@ class Editor extends React.Component { } as IEditorState); } - public SaveEditor() { + public SaveEditorAsJSON() { const exportName = 'state'; const spaces = import.meta.env.DEV ? 4 : 0; const data = JSON.stringify(this.state, getCircularReplacer(), spaces); @@ -270,6 +271,20 @@ class Editor extends React.Component { downloadAnchorNode.remove(); } + public SaveEditorAsSVG() { + const svgWrapper = document.getElementById(SVG.ID) as HTMLElement; + const svg = svgWrapper.querySelector('svg') as SVGSVGElement; + const preface = '\r\n'; + const svgBlob = new Blob([preface, svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' }); + const svgUrl = URL.createObjectURL(svgBlob); + const downloadLink = document.createElement('a'); + downloadLink.href = svgUrl; + downloadLink.download = 'newesttree.svg'; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + } + /** * Render the application * @returns {JSX.Element} Rendered JSX element @@ -334,13 +349,23 @@ class Editor extends React.Component { > { current.MainContainer } - + + + + + ); } diff --git a/src/index.scss b/src/index.scss index c7e0d50..ad410c8 100644 --- a/src/index.scss +++ b/src/index.scss @@ -10,9 +10,12 @@ @apply pl-6 pr-6 pt-2 pb-2 w-full } .close-button { - @apply transition-all w-full h-auto p-4 flex + @apply transition-all w-full h-auto p-4 flex } .mainmenu-btn { @apply transition-all bg-blue-100 hover:bg-blue-200 text-blue-700 text-lg font-semibold p-8 rounded-lg } + .floating-btn { + @apply h-full w-full text-white align-middle items-center justify-center + } } \ No newline at end of file