Implement webworker for save operation + Limit the history size #29

Merged
Siklos merged 3 commits from dev.optimize into dev 2022-08-15 11:52:18 -04:00
6 changed files with 64 additions and 18 deletions

View file

@ -10,6 +10,7 @@ import { onKeyDown } from './Shortcuts';
import { OnPropertyChange, OnPropertiesSubmit } from './PropertiesOperations'; import { OnPropertyChange, OnPropertiesSubmit } from './PropertiesOperations';
import EditorEvents from '../../Events/EditorEvents'; import EditorEvents from '../../Events/EditorEvents';
import { IEditorState } from '../../Interfaces/IEditorState'; import { IEditorState } from '../../Interfaces/IEditorState';
import { MAX_HISTORY } from '../../utils/default';
interface IEditorProps { interface IEditorProps {
configuration: IConfiguration configuration: IConfiguration
@ -17,7 +18,12 @@ interface IEditorProps {
historyCurrentStep: number historyCurrentStep: number
} }
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] => history.slice(0, historyCurrentStep + 1); 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]; export const getCurrentHistoryState = (history: IHistoryState[], historyCurrentStep: number): IHistoryState => history[historyCurrentStep];
const Editor: React.FunctionComponent<IEditorProps> = (props) => { const Editor: React.FunctionComponent<IEditorProps> = (props) => {

View file

@ -3,27 +3,38 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
import { getCircularReplacer } from '../../utils/saveload'; import { getCircularReplacer } from '../../utils/saveload';
import { ID } from '../SVG/SVG'; import { ID } from '../SVG/SVG';
import { IEditorState } from '../../Interfaces/IEditorState'; import { IEditorState } from '../../Interfaces/IEditorState';
import Worker from '../../workers/worker?worker';
export function SaveEditorAsJSON( export function SaveEditorAsJSON(
history: IHistoryState[], history: IHistoryState[],
historyCurrentStep: number, historyCurrentStep: number,
configuration: IConfiguration configuration: IConfiguration
): void { ): void {
const exportName = 'state'; const exportName = 'state.json';
const spaces = import.meta.env.DEV ? 4 : 0; const spaces = import.meta.env.DEV ? 4 : 0;
const editorState: IEditorState = { const editorState: IEditorState = {
history, history,
historyCurrentStep, historyCurrentStep,
configuration configuration
}; };
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (window.Worker) {
// use webworker for the stringify to avoid freezing
const myWorker = new Worker();
myWorker.postMessage({ editorState, spaces });
myWorker.onmessage = (event) => {
const data = event.data;
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
createDownloadNode(exportName, dataStr);
myWorker.terminate();
};
return;
}
const data = JSON.stringify(editorState, getCircularReplacer(), spaces); const data = JSON.stringify(editorState, getCircularReplacer(), spaces);
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`; const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
const downloadAnchorNode = document.createElement('a'); createDownloadNode(exportName, dataStr);
downloadAnchorNode.setAttribute('href', dataStr);
downloadAnchorNode.setAttribute('download', `${exportName}.json`);
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
} }
export function SaveEditorAsSVG(): void { export function SaveEditorAsSVG(): void {
@ -32,10 +43,14 @@ export function SaveEditorAsSVG(): void {
const preface = '<?xml version="1.0" standalone="no"?>\r\n'; const preface = '<?xml version="1.0" standalone="no"?>\r\n';
const svgBlob = new Blob([preface, svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' }); const svgBlob = new Blob([preface, svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' });
const svgUrl = URL.createObjectURL(svgBlob); const svgUrl = URL.createObjectURL(svgBlob);
const downloadLink = document.createElement('a'); createDownloadNode('state.svg', svgUrl);
downloadLink.href = svgUrl; }
downloadLink.download = 'newesttree.svg';
document.body.appendChild(downloadLink); function createDownloadNode(filename: string, datastring: string) {
downloadLink.click(); const downloadAnchorNode = document.createElement('a');
document.body.removeChild(downloadLink); downloadAnchorNode.href = datastring;
downloadAnchorNode.download = filename;
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
} }

View file

@ -14,9 +14,7 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => { const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
const reversedIndex = (props.history.length - 1) - index; const reversedIndex = (props.history.length - 1) - index;
const step = props.history[reversedIndex]; const step = props.history[reversedIndex];
const desc = reversedIndex > 0 const desc = step.LastAction;
? `${reversedIndex}: ${step.LastAction}`
: 'Go to the beginning';
const selectedClass = reversedIndex === props.historyCurrentStep const selectedClass = reversedIndex === props.historyCurrentStep
? 'bg-blue-500 hover:bg-blue-600' ? 'bg-blue-500 hover:bg-blue-600'

View file

@ -39,3 +39,5 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
}; };
export const NOTCHES_LENGTH = 4; export const NOTCHES_LENGTH = 4;
export const MAX_HISTORY = 200;

25
src/workers/worker.js Normal file
View file

@ -0,0 +1,25 @@
onmessage = (e) => {
const data = JSON.stringify(e.data.editorState, getCircularReplacer(), e.data.spaces);
postMessage(data);
};
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (key === 'parent') {
return;
}
if (key === 'SelectedContainer') {
return;
}
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};

View file

@ -16,7 +16,7 @@
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src"], "include": ["src", "src/workers"],
"exclude": ["test-server"], "exclude": ["test-server"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }