240 lines
6.2 KiB
TypeScript
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'
|
|
}
|
|
}
|
|
}
|