Extract Editor from App
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
72dfb4f9bb
commit
340cc86aa9
5 changed files with 373 additions and 314 deletions
22
src/App.scss
22
src/App.scss
|
@ -1,24 +1,6 @@
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root,
|
#root {
|
||||||
svg {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
height: 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; }
|
|
||||||
}
|
}
|
332
src/App.tsx
332
src/App.tsx
|
@ -1,16 +1,8 @@
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
import './App.scss';
|
import { ContainerModel, IContainerModel } from './Components/SVG/Elements/ContainerModel';
|
||||||
import Sidebar from './Components/Sidebar/Sidebar';
|
import Editor from './Editor';
|
||||||
import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar';
|
|
||||||
import { AvailableContainer } from './Interfaces/AvailableContainer';
|
import { AvailableContainer } from './Interfaces/AvailableContainer';
|
||||||
import { Configuration } from './Interfaces/Configuration';
|
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 {
|
export interface IHistoryState {
|
||||||
MainContainer: IContainerModel | null,
|
MainContainer: IContainerModel | null,
|
||||||
|
@ -18,24 +10,35 @@ export interface IHistoryState {
|
||||||
TypeCounters: Record<string, number>
|
TypeCounters: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAppState {
|
interface IAppProps {
|
||||||
isSidebarOpen: boolean,
|
|
||||||
isSVGSidebarOpen: boolean,
|
|
||||||
isHistoryOpen: boolean,
|
|
||||||
configuration: Configuration,
|
|
||||||
history: Array<IHistoryState>,
|
|
||||||
historyCurrentStep: 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends React.Component<IAppProps> {
|
interface IAppState {
|
||||||
|
configuration: Configuration,
|
||||||
|
history: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
isLoaded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class App extends React.Component<IAppProps> {
|
||||||
public state: IAppState;
|
public state: IAppState;
|
||||||
|
|
||||||
constructor(props: IAppProps) {
|
public constructor(props: IAppProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
const MainContainer = new ContainerModel(
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
id: 'main',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
fillOpacity: 0,
|
||||||
|
stroke: 'black'
|
||||||
|
}
|
||||||
|
);
|
||||||
this.state = {
|
this.state = {
|
||||||
isSidebarOpen: true,
|
|
||||||
isSVGSidebarOpen: false,
|
|
||||||
isHistoryOpen: false,
|
|
||||||
configuration: {
|
configuration: {
|
||||||
AvailableContainers: [],
|
AvailableContainers: [],
|
||||||
AvailableSymbols: [],
|
AvailableSymbols: [],
|
||||||
|
@ -43,18 +46,16 @@ class App extends React.Component<IAppProps> {
|
||||||
},
|
},
|
||||||
history: [
|
history: [
|
||||||
{
|
{
|
||||||
MainContainer: null,
|
MainContainer,
|
||||||
SelectedContainer: null,
|
SelectedContainer: MainContainer,
|
||||||
TypeCounters: {}
|
TypeCounters: {}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
historyCurrentStep: 0
|
historyCurrentStep: 0,
|
||||||
} as IAppState;
|
isLoaded: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrentHistory = (): IHistoryState[] => this.state.history.slice(0, this.state.historyCurrentStep + 1);
|
|
||||||
public getCurrentHistoryState = (): IHistoryState => this.state.history[this.state.historyCurrentStep];
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// Fetch the configuration from the API
|
// Fetch the configuration from the API
|
||||||
fetchConfiguration().then((configuration: Configuration) => {
|
fetchConfiguration().then((configuration: Configuration) => {
|
||||||
|
@ -72,280 +73,37 @@ class App extends React.Component<IAppProps> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const history = this.getCurrentHistory();
|
|
||||||
const current = history[history.length - 1];
|
|
||||||
|
|
||||||
// Save the configuration and the new MainContainer
|
// Save the configuration and the new MainContainer
|
||||||
// and default the selected container to it
|
// and default the selected container to it
|
||||||
this.setState(prevState => ({
|
this.setState({
|
||||||
...prevState,
|
|
||||||
configuration,
|
configuration,
|
||||||
history: history.concat(
|
history:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
MainContainer,
|
MainContainer,
|
||||||
SelectedContainer: MainContainer,
|
SelectedContainer: MainContainer,
|
||||||
TypeCounters: current.TypeCounters
|
TypeCounters: {}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
),
|
historyCurrentStep: 0,
|
||||||
historyCurrentStep: history.length
|
isLoaded: true
|
||||||
} as IAppState));
|
} as IAppState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public render() {
|
||||||
* Toggle the components sidebar
|
if (this.state.isLoaded) {
|
||||||
*/
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<div className="App font-sans h-full">
|
<div>
|
||||||
<Sidebar
|
<Editor
|
||||||
componentOptions={this.state.configuration.AvailableContainers}
|
configuration={this.state.configuration}
|
||||||
isOpen={this.state.isSidebarOpen}
|
|
||||||
onClick={() => this.ToggleSidebar()}
|
|
||||||
buttonOnClick={(type: string) => this.AddContainer(type)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className='fixed z-10 top-4 left-4 text-lg bg-blue-200 hover:bg-blue-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
|
|
||||||
onClick={() => this.ToggleSidebar()}
|
|
||||||
>
|
|
||||||
☰ Components
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ElementsSidebar
|
|
||||||
MainContainer={current.MainContainer}
|
|
||||||
SelectedContainer={current.SelectedContainer}
|
|
||||||
isOpen={this.state.isSVGSidebarOpen}
|
|
||||||
isHistoryOpen={this.state.isHistoryOpen}
|
|
||||||
onClick={() => this.ToggleElementsSidebar()}
|
|
||||||
onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)}
|
|
||||||
selectContainer={(container: ContainerModel) => this.SelectContainer(container)}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className='fixed z-10 top-4 right-12 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
|
|
||||||
onClick={() => this.ToggleElementsSidebar()}
|
|
||||||
>
|
|
||||||
☰ Elements
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<History
|
|
||||||
history={this.state.history}
|
history={this.state.history}
|
||||||
historyCurrentStep={this.state.historyCurrentStep}
|
historyCurrentStep={this.state.historyCurrentStep}
|
||||||
isOpen={this.state.isHistoryOpen}
|
|
||||||
onClick={() => this.ToggleHistory()}
|
|
||||||
jumpTo={(move) => { this.jumpTo(move); }}
|
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
className='fixed z-10 top-4 right-72 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
|
|
||||||
onClick={() => this.ToggleHistory()}>
|
|
||||||
☰ History
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<SVG selected={current.SelectedContainer}>
|
|
||||||
{ current.MainContainer }
|
|
||||||
</SVG>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -377,5 +135,3 @@ export async function fetchConfiguration(): Promise<Configuration> {
|
||||||
xhr.send();
|
xhr.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
22
src/Editor.scss
Normal file
22
src/Editor.scss
Normal file
|
@ -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; }
|
||||||
|
}
|
299
src/Editor.tsx
Normal file
299
src/Editor.tsx
Normal file
|
@ -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<IHistoryState>,
|
||||||
|
historyCurrentStep: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IEditorState {
|
||||||
|
isSidebarOpen: boolean,
|
||||||
|
isSVGSidebarOpen: boolean,
|
||||||
|
isHistoryOpen: boolean,
|
||||||
|
history: Array<IHistoryState>,
|
||||||
|
historyCurrentStep: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Editor extends React.Component<IEditorProps> {
|
||||||
|
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 (
|
||||||
|
<div className="App font-sans h-full">
|
||||||
|
<Sidebar
|
||||||
|
componentOptions={this.props.configuration.AvailableContainers}
|
||||||
|
isOpen={this.state.isSidebarOpen}
|
||||||
|
onClick={() => this.ToggleSidebar()}
|
||||||
|
buttonOnClick={(type: string) => this.AddContainer(type)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='fixed z-10 top-4 left-4 text-lg bg-blue-200 hover:bg-blue-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
|
||||||
|
onClick={() => this.ToggleSidebar()}
|
||||||
|
>
|
||||||
|
☰ Components
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ElementsSidebar
|
||||||
|
MainContainer={current.MainContainer}
|
||||||
|
SelectedContainer={current.SelectedContainer}
|
||||||
|
isOpen={this.state.isSVGSidebarOpen}
|
||||||
|
isHistoryOpen={this.state.isHistoryOpen}
|
||||||
|
onClick={() => this.ToggleElementsSidebar()}
|
||||||
|
onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)}
|
||||||
|
selectContainer={(container: ContainerModel) => this.SelectContainer(container)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='fixed z-10 top-4 right-12 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
|
||||||
|
onClick={() => this.ToggleElementsSidebar()}
|
||||||
|
>
|
||||||
|
☰ Elements
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<History
|
||||||
|
history={this.state.history}
|
||||||
|
historyCurrentStep={this.state.historyCurrentStep}
|
||||||
|
isOpen={this.state.isHistoryOpen}
|
||||||
|
onClick={() => this.ToggleHistory()}
|
||||||
|
jumpTo={(move) => { this.jumpTo(move); }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='fixed z-10 top-4 right-72 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
|
||||||
|
onClick={() => this.ToggleHistory()}>
|
||||||
|
☰ History
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<SVG selected={current.SelectedContainer}>
|
||||||
|
{ current.MainContainer }
|
||||||
|
</SVG>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Editor;
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App';
|
import { App } from './App';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue