svg-layout-designer-react/src/App.tsx

240 lines
6.2 KiB
TypeScript

import * as React from 'react';
import './App.scss';
import { MainMenu } from './Components/MainMenu/MainMenu';
import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel';
import Editor, { IEditorState } from './Editor';
import { Configuration } from './Interfaces/Configuration';
import { Revive } from './utils/saveload';
export interface IHistoryState {
MainContainer: IContainerModel | null
SelectedContainer: IContainerModel | null
SelectedContainerId: string
TypeCounters: Record<string, number>
}
// App will never have props
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IAppProps {
}
interface IAppState {
configuration: Configuration
history: IHistoryState[]
historyCurrentStep: number
isLoaded: boolean
}
export class App extends React.Component<IAppProps> {
public state: IAppState;
constructor(props: IAppProps) {
super(props);
this.state = {
configuration: {
AvailableContainers: [],
AvailableSymbols: [],
MainContainer: {
Type: 'EmptyContainer',
Width: 3000,
Height: 200,
Style: {}
}
},
history: [],
historyCurrentStep: 0,
isLoaded: false
};
}
componentDidMount(): void {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const state = urlParams.get('state');
if (state === null) {
return;
}
fetch(state)
.then(
async(response) => await response.json(),
(error) => { throw new Error(error); }
)
.then((data: IEditorState) => {
this.LoadState(data);
}, (error) => { throw new Error(error); });
}
public NewEditor(): void {
// 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,
{
id: 'main',
parentId: 'null',
x: 0,
y: 0,
width: configuration.MainContainer.Width,
height: configuration.MainContainer.Height,
fillOpacity: 0,
stroke: 'black'
}
);
// Save the configuration and the new MainContainer
// and default the selected container to it
this.setState({
configuration,
history:
[
{
MainContainer,
SelectedContainer: MainContainer,
TypeCounters: {}
}
],
historyCurrentStep: 0,
isLoaded: true
});
}, (error) => {
// TODO: Implement an alert component
console.warn('[NewEditor] Could not fetch resource from API. Returning default.', error);
const MainContainer = new ContainerModel(
null,
{
id: 'main',
parentId: 'null',
x: 0,
y: 0,
width: DEFAULT_CONFIG.MainContainer.Width,
height: DEFAULT_CONFIG.MainContainer.Height,
fillOpacity: DEFAULT_CONFIG.MainContainer.Style.fillOpacity,
stroke: DEFAULT_CONFIG.MainContainer.Style.stroke,
}
);
// Save the configuration and the new MainContainer
// and default the selected container to it
this.setState({
configuration: DEFAULT_CONFIG,
history:
[
{
MainContainer,
SelectedContainer: MainContainer,
TypeCounters: {}
}
],
historyCurrentStep: 0,
isLoaded: true
});
});
}
public LoadEditor(files: FileList | null): void {
if (files === null) {
return;
}
const file = files[0];
const reader = new FileReader();
reader.addEventListener('load', () => {
const result = reader.result as string;
const editorState: IEditorState = JSON.parse(result);
this.LoadState(editorState);
});
reader.readAsText(file);
}
private LoadState(editorState: IEditorState): void {
Revive(editorState);
this.setState({
configuration: editorState.configuration,
history: editorState.history,
historyCurrentStep: editorState.historyCurrentStep,
isLoaded: true
});
}
public render(): JSX.Element {
if (this.state.isLoaded) {
return (
<div>
<Editor
configuration={this.state.configuration}
history={this.state.history}
historyCurrentStep={this.state.historyCurrentStep}
/>
</div>
);
} else {
return (
<div className='bg-blue-100 h-full w-full'>
<MainMenu
newEditor={() => this.NewEditor()}
loadEditor={(files: FileList | null) => this.LoadEditor(files)}
/>
</div>
);
}
}
}
/**
* Fetch the configuration from the API
* @returns {Configation} The model of the configuration for the application
*/
export async function fetchConfiguration(): Promise<Configuration> {
const url = `${import.meta.env.VITE_API_URL}`;
// The test library cannot use the Fetch API
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (window.fetch) {
return await fetch(url, {
method: 'POST'
})
.then(async(response) =>
await response.json()
) as Configuration;
}
return await new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onreadystatechange = function() { // Call a function when the state changes.
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
resolve(JSON.parse(this.responseText));
}
};
xhr.send();
});
}
const DEFAULT_CONFIG: Configuration = {
AvailableContainers: [
{
Type: 'Container',
Width: 75,
Height: 100,
Style: {
fillOpacity: 0,
stroke: 'green'
}
}
],
AvailableSymbols: [],
MainContainer: {
Type: 'Container',
Width: 2000,
Height: 100,
Style: {
fillOpacity: 0,
stroke: 'black'
}
}
}