Optimize history and fix nodes pollution + fix css + removes motion.framer (#28)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: https://git.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react/pulls/28
This commit is contained in:
Siklos 2022-08-12 16:31:37 -04:00
parent 704dab7307
commit d6eb9ea364
11 changed files with 196 additions and 575 deletions

View file

@ -15,7 +15,7 @@ export const BAR_WIDTH = 64; // 4rem
export const Bar: React.FC<IBarProps> = (props) => {
return (
<div className='fixed z-20 flex flex-col top-0 left-0 h-screen w-16 bg-slate-100'>
<div className='fixed z-20 flex flex-col top-0 left-0 h-full w-16 bg-slate-100'>
<BarIcon
isActive={props.isSidebarOpen}
title='Components'

View file

@ -27,14 +27,15 @@ export function SelectContainer(
throw new Error('[SelectContainer] Cannot find container among children of main container!');
}
setHistory(history.concat([{
LastAction: `Select container ${selectedContainer.properties.id}`,
history.push({
LastAction: `Select ${selectedContainer.properties.id}`,
MainContainer: mainContainerClone,
SelectedContainer: selectedContainer,
SelectedContainerId: selectedContainer.properties.id,
TypeCounters: Object.assign({}, current.TypeCounters)
}]));
setHistoryCurrentStep(history.length);
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
}
/**
@ -87,14 +88,15 @@ export function DeleteContainer(
container.parent;
const SelectedContainerId = SelectedContainer.properties.id;
setHistory(history.concat([{
LastAction: `Delete container ${containerId}`,
history.push({
LastAction: `Delete ${containerId}`,
MainContainer: mainContainerClone,
SelectedContainer,
SelectedContainerId,
TypeCounters: Object.assign({}, current.TypeCounters)
}]));
setHistoryCurrentStep(history.length);
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
}
/**
@ -235,12 +237,13 @@ export function AddContainer(
}
// Update the state
setHistory(history.concat([{
history.push({
LastAction: 'Add container',
MainContainer: clone,
SelectedContainer: parentClone,
SelectedContainerId: parentClone.properties.id,
TypeCounters: newCounters
}]));
setHistoryCurrentStep(history.length);
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
}

View file

@ -42,12 +42,23 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
configuration: props.configuration
};
const funcs = new Map<string, () => void>();
for (const event of events) {
editorRef.current?.addEventListener(event.name, () => event.func(editorState));
const func = (): void => event.func(editorState);
editorRef.current?.addEventListener(event.name, func);
funcs.set(event.name, func);
}
return () => {
window.removeEventListener('keyup', onKeyUp);
for (const event of events) {
const func = funcs.get(event.name);
if (func === undefined) {
continue;
}
editorRef.current?.removeEventListener(event.name, func);
}
};
});

View file

@ -51,14 +51,15 @@ export function OnPropertyChange(
RecalculatePhysics(container);
}
setHistory(history.concat([{
LastAction: `Change property of container ${container.properties.id}`,
history.push({
LastAction: `Change ${key} of ${container.properties.id}`,
MainContainer: mainContainerClone,
SelectedContainer: container,
SelectedContainerId: container.properties.id,
TypeCounters: Object.assign({}, current.TypeCounters)
}]));
setHistoryCurrentStep(history.length);
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
}
/**
@ -107,12 +108,13 @@ export function OnPropertiesSubmit(
RecalculatePhysics(container);
}
setHistory(history.concat([{
LastAction: `Change property of container ${container.properties.id}`,
history.push({
LastAction: `Change properties of ${container.properties.id}`,
MainContainer: mainContainerClone,
SelectedContainer: container,
SelectedContainerId: container.properties.id,
TypeCounters: Object.assign({}, current.TypeCounters)
}]));
setHistoryCurrentStep(history.length);
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
}

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import { motion } from 'framer-motion';
import { FixedSizeList as List } from 'react-window';
import { Properties } from '../Properties/Properties';
import ContainerProperties from '../../Interfaces/IProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel';
@ -21,45 +21,6 @@ interface IElementsSidebarProps {
AddContainer: (index: number, type: string, parent: string) => void
}
function createRows(
container: IContainerModel,
props: IElementsSidebarProps,
containerRows: React.ReactNode[]
): void {
const depth: number = getDepth(container);
const key = container.properties.id.toString();
const text = '|\t'.repeat(depth) + key;
const selectedClass: string = props.SelectedContainer !== undefined &&
props.SelectedContainer !== null &&
props.SelectedContainer.properties.id === container.properties.id
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
containerRows.push(
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 1.2 }}
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: 0.150
}}
className={
`w-full border-blue-500 elements-sidebar-row whitespace-pre
text-left text-sm font-medium transition-all ${selectedClass}`
}
id={key}
key={key}
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
onDragLeave={(event) => handleDragLeave(event)}
onClick={() => props.SelectContainer(container)}
>
{ text }
</motion.button>
);
};
export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElementsSidebarProps): JSX.Element => {
// States
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
@ -117,24 +78,55 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
: 'right-0';
}
const containerRows: React.ReactNode[] = [];
const it = MakeIterator(props.MainContainer);
for (const container of it) {
createRows(
container,
props,
containerRows
);
}
const containers = [...it];
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
const container = containers[index];
const depth: number = getDepth(container);
const key = container.properties.id.toString();
const text = '|\t'.repeat(depth) + key;
const selectedClass: string = props.SelectedContainer !== undefined &&
props.SelectedContainer !== null &&
props.SelectedContainer.properties.id === container.properties.id
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
return (
<button
className={
`w-full border-blue-500 elements-sidebar-row whitespace-pre
text-left text-sm font-medium transition-all ${selectedClass}`
}
id={key}
key={key}
style={style}
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
onDragLeave={(event) => handleDragLeave(event)}
onClick={() => props.SelectContainer(container)}
>
{ text }
</button>
);
};
const ROW_HEIGHT = 35;
const NUMBERS_OF_ROWS = 10;
return (
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<div className='bg-slate-100 font-bold sidebar-title'>
Elements
</div>
<div ref={elementRef} className='overflow-y-auto overflow-x-hidden text-gray-800 flex-grow'>
{ containerRows }
<div ref={elementRef} className='h-96 text-gray-800'>
<List
className='List'
itemCount={containers.length}
itemSize={35}
height={384}
width={256}
>
{ Row }
</List>
</div>
<Menu
className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'

View file

@ -1,5 +1,6 @@
import * as React from 'react';
import { IHistoryState } from "../../Interfaces/IHistoryState";
import { FixedSizeList as List } from 'react-window';
import { IHistoryState } from '../../Interfaces/IHistoryState';
interface IHistoryProps {
history: IHistoryState[]
@ -10,47 +11,47 @@ interface IHistoryProps {
export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
const isOpenClasses = props.isOpen ? 'right-0' : '-right-64';
const states = props.history.map((step, move) => {
const desc = move > 0
? `Go to modification n°${move}`
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
const reversedIndex = (props.history.length - 1) - index;
const step = props.history[reversedIndex];
const desc = reversedIndex > 0
? `${reversedIndex}: ${step.LastAction}`
: 'Go to the beginning';
const isCurrent = move === props.historyCurrentStep;
const selectedClass = isCurrent
const selectedClass = reversedIndex === props.historyCurrentStep
? 'bg-blue-500 hover:bg-blue-600'
: 'bg-slate-500 hover:bg-slate-700';
const isCurrentText = isCurrent
? ' (current)'
: '';
return (
<button
key={move}
onClick={() => props.jumpTo(move)}
key={reversedIndex}
style={style}
onClick={() => props.jumpTo(reversedIndex)}
title={step.LastAction}
className={
`w-full elements-sidebar-row whitespace-pre
`w-full elements-sidebar-row whitespace-pre overflow-hidden
text-left text-sm font-medium transition-all ${selectedClass}`
}
>
{desc}{isCurrentText}
{desc}
</button>
);
});
// recent first
states.reverse();
};
return (
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<div className='bg-slate-600 font-bold sidebar-title'>
Timeline
</div>
<div className='overflow-y-auto overflow-x-hidden text-slate-300 flex-grow divide-y divide-solid divide-slate-600'>
{ states }
</div>
<List
className='List overflow-x-hidden'
itemCount={props.history.length}
itemSize={35}
height={window.innerHeight}
width={256}
>
{ Row }
</List>
</div>
);
};

View file

@ -30,7 +30,7 @@ function resizeViewBox(
export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
const [viewer, setViewer] = React.useState<Viewer>({
viewerWidth: window.innerWidth,
viewerWidth: window.innerWidth - BAR_WIDTH,
viewerHeight: window.innerHeight
});

View file

@ -30,7 +30,7 @@ export const Sidebar: React.FC<ISidebarProps> = (props: ISidebarProps) => {
const isOpenClasses = props.isOpen ? 'left-16' : '-left-64';
return (
<div className={`fixed z-10 bg-slate-200
text-gray-700 transition-all h-screen w-64
text-gray-700 transition-all h-full w-64
overflow-y-auto ${isOpenClasses}`}>
<div className='bg-slate-100 sidebar-title'>
Components