201 lines
6.3 KiB
TypeScript
201 lines
6.3 KiB
TypeScript
import React, { Dispatch, SetStateAction, useEffect, useRef } from 'react';
|
|
import './Editor.scss';
|
|
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
|
import { SVG } from '../SVG/SVG';
|
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
|
import { UI } from '../UI/UI';
|
|
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, AddContainer, OnPropertyChange } from './Actions/ContainerOperations';
|
|
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
|
import { onKeyDown } from './Actions/Shortcuts';
|
|
import EditorEvents from '../../Events/EditorEvents';
|
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
|
import { MAX_HISTORY } from '../../utils/default';
|
|
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
|
|
import { findContainerById } from '../../utils/itertools';
|
|
|
|
interface IEditorProps {
|
|
configuration: IConfiguration
|
|
history: IHistoryState[]
|
|
historyCurrentStep: number
|
|
}
|
|
|
|
export function UpdateCounters(counters: Record<string, number>, type: string): void {
|
|
if (counters[type] === null ||
|
|
counters[type] === undefined) {
|
|
counters[type] = 0;
|
|
} else {
|
|
counters[type]++;
|
|
}
|
|
}
|
|
|
|
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] =>
|
|
history.slice(
|
|
Math.max(0, history.length - MAX_HISTORY), // change this to 0 for unlimited (not recommanded because of overflow)
|
|
historyCurrentStep + 1
|
|
);
|
|
|
|
export const getCurrentHistoryState = (history: IHistoryState[], historyCurrentStep: number): IHistoryState => history[historyCurrentStep];
|
|
|
|
function useShortcuts(
|
|
history: IHistoryState[],
|
|
historyCurrentStep: number,
|
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
): void {
|
|
useEffect(() => {
|
|
const onKeyUp = (event: KeyboardEvent): void => onKeyDown(
|
|
event,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistoryCurrentStep
|
|
);
|
|
|
|
window.addEventListener('keyup', onKeyUp);
|
|
return () => {
|
|
window.removeEventListener('keyup', onKeyUp);
|
|
};
|
|
});
|
|
}
|
|
|
|
function useWindowEvents(
|
|
history: IHistoryState[],
|
|
historyCurrentStep: number,
|
|
configuration: IConfiguration,
|
|
editorRef: React.RefObject<HTMLDivElement>
|
|
): void {
|
|
useEffect(() => {
|
|
const events = EditorEvents;
|
|
const editorState: IEditorState = {
|
|
history,
|
|
historyCurrentStep,
|
|
configuration
|
|
};
|
|
|
|
const funcs = new Map<string, () => void>();
|
|
for (const event of events) {
|
|
const func = (): void => event.func(editorState);
|
|
editorRef.current?.addEventListener(event.name, func);
|
|
funcs.set(event.name, func);
|
|
}
|
|
return () => {
|
|
for (const event of events) {
|
|
const func = funcs.get(event.name);
|
|
if (func === undefined) {
|
|
continue;
|
|
}
|
|
editorRef.current?.removeEventListener(event.name, func);
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
|
const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
|
|
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
|
const editorRef = useRef<HTMLDivElement>(null);
|
|
|
|
useShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
|
useWindowEvents(history, historyCurrentStep, props.configuration, editorRef);
|
|
|
|
const configuration = props.configuration;
|
|
const current = getCurrentHistoryState(history, historyCurrentStep);
|
|
const selected = findContainerById(current.MainContainer, current.SelectedContainerId);
|
|
return (
|
|
<div ref={editorRef} className="Editor font-sans h-full">
|
|
<UI
|
|
SelectedContainer={selected}
|
|
current={current}
|
|
history={history}
|
|
historyCurrentStep={historyCurrentStep}
|
|
AvailableContainers={configuration.AvailableContainers}
|
|
AvailableSymbols={configuration.AvailableSymbols}
|
|
SelectContainer={(container) => SelectContainer(
|
|
container,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
DeleteContainer={(containerId: string) => DeleteContainer(
|
|
containerId,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
OnPropertyChange={(key, value, isStyle) => OnPropertyChange(
|
|
key, value, isStyle,
|
|
selected,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer(
|
|
type,
|
|
selected,
|
|
configuration,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
AddContainer={(index, type, parentId) => AddContainer(
|
|
index,
|
|
type,
|
|
parentId,
|
|
configuration,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
AddSymbol={(type) => AddSymbol(
|
|
type,
|
|
configuration,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
OnSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
|
|
key, value,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
SelectSymbol={(symbolId) => SelectSymbol(
|
|
symbolId,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
DeleteSymbol={(symbolId) => DeleteSymbol(
|
|
symbolId,
|
|
history,
|
|
historyCurrentStep,
|
|
setHistory,
|
|
setHistoryCurrentStep
|
|
)}
|
|
SaveEditorAsJSON={() => SaveEditorAsJSON(
|
|
history,
|
|
historyCurrentStep,
|
|
configuration
|
|
)}
|
|
SaveEditorAsSVG={() => SaveEditorAsSVG()}
|
|
LoadState={(move) => setHistoryCurrentStep(move)}
|
|
/>
|
|
<SVG
|
|
width={current.MainContainer?.properties.width}
|
|
height={current.MainContainer?.properties.height}
|
|
selected={selected}
|
|
symbols={current.Symbols}
|
|
>
|
|
{ current.MainContainer }
|
|
</SVG>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Editor;
|