From 62abd3ff037cd857189601f88fa44c275bcd62e8 Mon Sep 17 00:00:00 2001 From: Carl Fuchs Date: Fri, 3 Feb 2023 16:58:54 +0100 Subject: [PATCH 1/8] WIP --- src/Components/Components/Components.tsx | 5 ++ .../Editor/Actions/ContainerOperations.ts | 68 ++++++++++++++++++- .../Editor/Actions/ContextMenuActions.ts | 12 +++- src/Components/UI/UI.tsx | 4 ++ 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/Components/Components/Components.tsx b/src/Components/Components/Components.tsx index 53a3dd9..314ecff 100644 --- a/src/Components/Components/Components.tsx +++ b/src/Components/Components/Components.tsx @@ -11,6 +11,7 @@ interface IComponentsProps { selectedContainer: IContainerModel | undefined componentOptions: IAvailableContainer[] categories: ICategory[] + replaceableCategoryName: string | undefined buttonOnClick: (type: string) => void } @@ -62,6 +63,10 @@ export function Components(props: IComponentsProps): JSX.Element { disabled = config.Blacklist?.find(type => type === componentOption.Type) !== undefined ?? false; } + if (componentOption.Category !== props.replaceableCategoryName) { + disabled = true; + } + if (disabled && hideDisabled) { return; } diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index 131df82..dc5bfb5 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -8,6 +8,7 @@ import Swal from 'sweetalert2'; import { PropertyType } from '../../../Enums/PropertyType'; import { TransformX, TransformY } from '../../../utils/svg'; import { Orientation } from '../../../Enums/Orientation'; +import { AddContainers, AddContainerToSelectedContainer } from './AddContainer'; /** * Select a container @@ -109,6 +110,68 @@ export function DeleteContainer( return history; } +/** + * Replace a container + * @param containerId containerId of the container to delete + * @param fullHistory History of the editor + * @param historyCurrentStep Current step + * @returns New history + */ +// export function ReplaceByContainer( +// containerId: string, +// newContainerId: string, +// fullHistory: IHistoryState[], +// historyCurrentStep: number +// ): IHistoryState[] { +// const history = GetCurrentHistory(fullHistory, historyCurrentStep); +// const current = history[history.length - 1]; +// +// +// const containers = structuredClone(current.containers); +// const container = FindContainerById(containers, containerId); +// if (container === undefined) { +// throw new Error(`[ReplaceContainer] Tried to delete a container that is not present in the main container: ${containerId}`); +// } +// /// +// const parent = FindContainerById(containers, container.properties.parentId); +// if (parent === undefined || parent === null) { +// throw new Error('[ReplaceContainer] Cannot replace a container that does not exists'); +// } +// +// const index = parent.children.indexOf(container.properties.id); +// +// const newHistoryAfterDelete = DeleteContainer( +// container.properties.id, +// history, +// historyCurrentStep +// ); +// +// const newContainer = FindContainerById(containers, container.properties.parentId); +// +// AddContainerToSelectedContainer( +// ne, +// selected, +// configuration, +// history, +// historyCurrentStep +// ); +// +// +// /// / +// +// +// history.push({ +// lastAction: `Replace ${containerId} By InsertnewId`, +// mainContainer: current.mainContainer, +// containers, +// newContainerId, +// typeCounters: Object.assign({}, current.typeCounters), +// symbols: newSymbols, +// selectedSymbolId: current.selectedSymbolId +// }); +// return history; +// } + /** * Returns the next container that will be selected * after the selectedContainer is removed. @@ -371,9 +434,8 @@ function AssignProperty(container: IContainerModel, key: string, value: string | /** * Link a symbol to a container * @param containerId Container id - * @param oldSymbolId Old Symbol id - * @param newSymbolId New Symbol id - * @param symbols Current list of symbols + * @param oldSymbol + * @param newSymbol * @returns */ export function LinkSymbol( diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index 69a3121..2781d1e 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -56,9 +56,18 @@ export function InitActions( menuActions.set( 'elements-sidebar-row', [{ + text: Text({ textId: '@ReplaceByContainer' }), + title: Text({ textId: '@ReplaceByContainerTitle' }), + shortcut: 'R', + action: (target: HTMLElement) => { + const id = target.id; + console.log('replace'); + } + }, { text: Text({ textId: '@DeleteContainer' }), title: Text({ textId: '@DeleteContainerTitle' }), shortcut: 'Suppr', + action: (target: HTMLElement) => { const id = target.id; const newHistory = DeleteContainer( @@ -68,7 +77,8 @@ export function InitActions( ); setNewHistory(newHistory); } - }] + } + ] ); menuActions.set( diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index d80e869..5e7a6a4 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -97,11 +97,15 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element { switch (selectedSidebar) { case SidebarType.Components: leftSidebarTitle = Text({ textId: '@Components' }); + + const AllowedReplaceCategory = undefined; + leftChildren = ; rightSidebarTitle = Text({ textId: '@Elements' }); rightChildren = Date: Mon, 6 Feb 2023 16:45:34 +0100 Subject: [PATCH 2/8] Implemtation in progress, UIX working , replacing in ContainerOperations.ts working but not properly need fix --- src/Components/Bar/Bar.tsx | 3 +- src/Components/Components/Components.tsx | 20 ++- .../Editor/Actions/ContainerOperations.ts | 118 ++++++++---------- .../Editor/Actions/ContextMenuActions.ts | 14 ++- src/Components/Editor/Editor.tsx | 42 +++++-- src/Components/Menu/Menu.tsx | 5 +- src/Components/UI/UI.tsx | 24 ++-- src/Components/Viewer/Viewer.tsx | 6 +- src/Interfaces/IReplaceContainer.ts | 5 + 9 files changed, 139 insertions(+), 98 deletions(-) create mode 100644 src/Interfaces/IReplaceContainer.ts diff --git a/src/Components/Bar/Bar.tsx b/src/Components/Bar/Bar.tsx index 8f28635..a693fc2 100644 --- a/src/Components/Bar/Bar.tsx +++ b/src/Components/Bar/Bar.tsx @@ -17,6 +17,7 @@ import { BarIcon } from './BarIcon'; import { Text } from '../Text/Text'; interface IBarProps { + className: string isComponentsOpen: boolean isSymbolsOpen: boolean isHistoryOpen: boolean @@ -33,7 +34,7 @@ export const BAR_WIDTH = 64; // 4rem export function Bar(props: IBarProps): JSX.Element { return ( -
+
> buttonOnClick: (type: string) => void } @@ -63,7 +66,7 @@ export function Components(props: IComponentsProps): JSX.Element { disabled = config.Blacklist?.find(type => type === componentOption.Type) !== undefined ?? false; } - if (componentOption.Category !== props.replaceableCategoryName) { + if (props.replaceContainer.isReplacing && componentOption.Category !== props.replaceContainer.category) { disabled = true; } @@ -101,6 +104,15 @@ export function Components(props: IComponentsProps): JSX.Element { return (
+ {props.replaceContainer.isReplacing && + }
); -}; +} diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index dc5bfb5..57a0d72 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -8,7 +8,8 @@ import Swal from 'sweetalert2'; import { PropertyType } from '../../../Enums/PropertyType'; import { TransformX, TransformY } from '../../../utils/svg'; import { Orientation } from '../../../Enums/Orientation'; -import { AddContainers, AddContainerToSelectedContainer } from './AddContainer'; +import { AddContainerToSelectedContainer } from './AddContainer'; +import { IConfiguration } from '../../../Interfaces/IConfiguration'; /** * Select a container @@ -113,64 +114,44 @@ export function DeleteContainer( /** * Replace a container * @param containerId containerId of the container to delete + * @param newContainerId + * @param configuration * @param fullHistory History of the editor * @param historyCurrentStep Current step * @returns New history */ -// export function ReplaceByContainer( -// containerId: string, -// newContainerId: string, -// fullHistory: IHistoryState[], -// historyCurrentStep: number -// ): IHistoryState[] { -// const history = GetCurrentHistory(fullHistory, historyCurrentStep); -// const current = history[history.length - 1]; -// -// -// const containers = structuredClone(current.containers); -// const container = FindContainerById(containers, containerId); -// if (container === undefined) { -// throw new Error(`[ReplaceContainer] Tried to delete a container that is not present in the main container: ${containerId}`); -// } -// /// -// const parent = FindContainerById(containers, container.properties.parentId); -// if (parent === undefined || parent === null) { -// throw new Error('[ReplaceContainer] Cannot replace a container that does not exists'); -// } -// -// const index = parent.children.indexOf(container.properties.id); -// -// const newHistoryAfterDelete = DeleteContainer( -// container.properties.id, -// history, -// historyCurrentStep -// ); -// -// const newContainer = FindContainerById(containers, container.properties.parentId); -// -// AddContainerToSelectedContainer( -// ne, -// selected, -// configuration, -// history, -// historyCurrentStep -// ); -// -// -// /// / -// -// -// history.push({ -// lastAction: `Replace ${containerId} By InsertnewId`, -// mainContainer: current.mainContainer, -// containers, -// newContainerId, -// typeCounters: Object.assign({}, current.typeCounters), -// symbols: newSymbols, -// selectedSymbolId: current.selectedSymbolId -// }); -// return history; -// } +export function ReplaceByContainer( + containerId: string, + newContainerId: string, + configuration: IConfiguration, + fullHistory: IHistoryState[], + historyCurrentStep: number +): IHistoryState[] { + const history = GetCurrentHistory(fullHistory, historyCurrentStep); + const current = history[history.length - 1]; + + const historyDelete = DeleteContainer(containerId, fullHistory, historyCurrentStep); + const currentDelete = historyDelete[historyDelete.length - 1]; + const selectedContainer = FindContainerById(currentDelete.containers, currentDelete.selectedContainerId); + if (selectedContainer != null) { + const historyAdd = AddContainerToSelectedContainer(newContainerId, selectedContainer, configuration, fullHistory, historyCurrentStep); + + const currentAdd = historyAdd[historyAdd.length - 1]; + + fullHistory.push({ + lastAction: `Replace ${containerId} by ${newContainerId}`, + mainContainer: currentAdd.mainContainer, + containers: currentAdd.containers, + selectedContainerId: currentAdd.selectedContainerId, + typeCounters: Object.assign({}, currentAdd.typeCounters), + symbols: current.symbols, + selectedSymbolId: current.selectedSymbolId + }); + + return fullHistory; + } + return history; +} /** * Returns the next container that will be selected @@ -178,7 +159,7 @@ export function DeleteContainer( * If the selected container is removed, select the sibling after, * If there is no sibling, select the parent, * - * @param mainContainerClone Main container + * @param containers * @param selectedContainerId Current selected container * @param parent Parent of the selected/deleted container * @param index Index of the selected/deleted container @@ -190,11 +171,10 @@ function GetSelectedContainerOnDelete( parent: IContainerModel, index: number ): string { - const newSelectedContainerId = FindContainerById(containers, selectedContainerId)?.properties.id ?? - parent.children.at(index) ?? - parent.children.at(index - 1) ?? - parent.properties.id; - return newSelectedContainerId; + return FindContainerById(containers, selectedContainerId)?.properties.id ?? + parent.children.at(index) ?? + parent.children.at(index - 1) ?? + parent.properties.id; } /** @@ -220,11 +200,15 @@ function UnlinkContainerFromSymbols( } /** - * Handled the property change event in the properties form - * @param key Property name - * @param value New value of the property - * @returns void - */ + * Handled the property change event in the properties form + * @param key Property name + * @param value New value of the property + * @param type + * @param selected + * @param fullHistory + * @param historyCurrentStep + * @returns void + */ export function OnPropertyChange( key: string, value: string | number | boolean | number[], @@ -264,6 +248,7 @@ export function OnPropertyChange( /** * Sort the parent children by x + * @param containers * @param parent The clone used for the sort * @returns void */ @@ -328,6 +313,7 @@ export function SortChildren( /** * Set the container with properties and behaviors (mutate) + * @param containers * @param container Container to update * @param key Key of the property to update * @param value Value of the property to update diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index 2781d1e..5b2a546 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -16,6 +16,7 @@ import { AddContainers } from './AddContainer'; import { DeleteContainer } from './ContainerOperations'; import { DeleteSymbol } from './SymbolOperations'; import { Text } from '../../Text/Text'; +import { IReplaceContainer } from '../../../Interfaces/IReplaceContainer'; export function InitActions( menuActions: Map, @@ -23,7 +24,8 @@ export function InitActions( history: IHistoryState[], historyCurrentStep: number, setNewHistory: (newHistory: IHistoryState[]) => void, - setHistoryCurrentStep: Dispatch> + setHistoryCurrentStep: Dispatch>, + setIsReplacingContainer: Dispatch> ): void { menuActions.set( '', @@ -60,8 +62,14 @@ export function InitActions( title: Text({ textId: '@ReplaceByContainerTitle' }), shortcut: 'R', action: (target: HTMLElement) => { - const id = target.id; - console.log('replace'); + const targetContainer = FindContainerById(history[historyCurrentStep].containers, target.id); + const targetAvailableContainer = configuration.AvailableContainers.find((availableContainer) => availableContainer.Type === targetContainer?.properties.type); + + if (targetAvailableContainer === undefined) { + return; + } + + setIsReplacingContainer({ isReplacing: true, id: target.id, category: targetAvailableContainer.Category }); } }, { text: Text({ textId: '@DeleteContainer' }), diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 372ab06..6644bed 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -3,7 +3,7 @@ import './Editor.scss'; import { IConfiguration } from '../../Interfaces/IConfiguration'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { UI } from '../UI/UI'; -import { SelectContainer, DeleteContainer, OnPropertyChange } from './Actions/ContainerOperations'; +import { SelectContainer, DeleteContainer, OnPropertyChange, ReplaceByContainer } from './Actions/ContainerOperations'; import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save'; import { OnKey } from './Actions/Shortcuts'; import { UseCustomEvents, UseEditorListener } from '../../Events/EditorEvents'; @@ -13,6 +13,7 @@ import { FindContainerById } from '../../utils/itertools'; import { Menu } from '../Menu/Menu'; import { InitActions } from './Actions/ContextMenuActions'; import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer'; +import { IReplaceContainer } from '../../Interfaces/IReplaceContainer'; interface IEditorProps { root: Element | Document @@ -66,6 +67,8 @@ export function Editor(props: IEditorProps): JSX.Element { // States const [history, setHistory] = React.useState(structuredClone(props.history)); const [historyCurrentStep, setHistoryCurrentStep] = React.useState(props.historyCurrentStep); + const [replaceContainer, setReplaceContainer] = React.useState({ isReplacing: false, id: undefined, category: undefined }); + const editorRef = useRef(null); const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep); @@ -104,7 +107,8 @@ export function Editor(props: IEditorProps): JSX.Element { history, historyCurrentStep, setNewHistory, - setHistoryCurrentStep + setHistoryCurrentStep, + setReplaceContainer ); // Render @@ -113,7 +117,7 @@ export function Editor(props: IEditorProps): JSX.Element { const selected = FindContainerById(current.containers, current.selectedContainerId); return ( -
+
{ + addOrReplaceContainer={(type) => { if (selected === null || selected === undefined) { return; } - - setNewHistory(AddContainerToSelectedContainer( - type, - selected, - configuration, - history, - historyCurrentStep - )); + if (replaceContainer.isReplacing && replaceContainer.id !== undefined) { + const newHistory = ReplaceByContainer( + replaceContainer.id, + type, + configuration, + history, + historyCurrentStep + ); + setReplaceContainer({ isReplacing: false, id: undefined, category: undefined }); + setNewHistory(newHistory); + } else { + setNewHistory(AddContainerToSelectedContainer( + type, + selected, + configuration, + history, + historyCurrentStep + )); + } }} addContainerAt={(index, type, parent) => setNewHistory( AddContainer( @@ -194,9 +209,10 @@ export function Editor(props: IEditorProps): JSX.Element { )} saveEditorAsSVG={() => SaveEditorAsSVG()} loadState={(move) => setHistoryCurrentStep(move)} - /> + replaceContainer ={replaceContainer} setReplaceContainer={setReplaceContainer}/> editorRef.current} + configuration={configuration} actions={menuActions} className="z-30 transition-opacity rounded bg-slate-200 drop-shadow-xl" /> diff --git a/src/Components/Menu/Menu.tsx b/src/Components/Menu/Menu.tsx index a0fbc03..c8da101 100644 --- a/src/Components/Menu/Menu.tsx +++ b/src/Components/Menu/Menu.tsx @@ -2,11 +2,13 @@ import useSize from '@react-hook/size'; import * as React from 'react'; import { IPoint } from '../../Interfaces/IPoint'; import { MenuItem } from './MenuItem'; +import { IConfiguration } from '../../Interfaces/IConfiguration'; interface IMenuProps { getListener: () => HTMLElement | null actions: Map className?: string + configuration: IConfiguration } export interface IMenuAction { @@ -21,6 +23,7 @@ export interface IMenuAction { /** function to be called on button click */ action: (target: HTMLElement) => void + } function UseMouseEvents( @@ -139,7 +142,7 @@ function AddClassSpecificActions( onClick={() => action.action(target)} />); }); children.push(
); - }; + } return count; } diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 5e7a6a4..cab0e17 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -17,13 +17,15 @@ import { FindContainerById } from '../../utils/itertools'; import { IEditorState } from '../../Interfaces/IEditorState'; import { GetCurrentHistoryState } from '../Editor/Editor'; import { Text } from '../Text/Text'; +import { IReplaceContainer } from '../../Interfaces/IReplaceContainer'; +import { Dispatch } from 'react'; export interface IUIProps { editorState: IEditorState selectContainer: (containerId: string) => void deleteContainer: (containerId: string) => void onPropertyChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void - addContainer: (type: string) => void + addOrReplaceContainer: (type: string) => void addContainerAt: (index: number, type: string, parent: string) => void addSymbol: (type: string) => void onSymbolPropertyChange: (key: string, value: string | number | boolean) => void @@ -32,6 +34,9 @@ export interface IUIProps { saveEditorAsJSON: () => void saveEditorAsSVG: () => void loadState: (move: number) => void + replaceContainer: IReplaceContainer + setReplaceContainer: Dispatch> + } export enum SidebarType { @@ -57,8 +62,9 @@ function UseSetOrToggleSidebar( }; } -export function UI({ editorState, ...methods }: IUIProps): JSX.Element { +export function UI({ editorState, replaceContainer, setReplaceContainer, ...methods }: IUIProps): JSX.Element { const [selectedSidebar, setSelectedSidebar] = React.useState(SidebarType.Components); + const [messages, setMessages] = React.useState([]); const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep); const configuration = editorState.configuration; @@ -98,15 +104,13 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element { case SidebarType.Components: leftSidebarTitle = Text({ textId: '@Components' }); - const AllowedReplaceCategory = undefined; - leftChildren = ; + buttonOnClick={methods.addOrReplaceContainer} + replaceContainer={replaceContainer} + setReplaceContainer={setReplaceContainer}/>; rightSidebarTitle = Text({ textId: '@Elements' }); rightChildren = { rightChildren } diff --git a/src/Components/Viewer/Viewer.tsx b/src/Components/Viewer/Viewer.tsx index ba0560b..474ac57 100644 --- a/src/Components/Viewer/Viewer.tsx +++ b/src/Components/Viewer/Viewer.tsx @@ -12,6 +12,7 @@ import { SVG } from '../SVG/SVG'; import { RenderSymbol } from '../Canvas/Symbol'; interface IViewerProps { + className: string isLeftSidebarOpen: boolean isRightSidebarOpen: boolean current: IHistoryState @@ -73,6 +74,7 @@ function UseSVGAutoResizerOnSidebar( } export function Viewer({ + className, isLeftSidebarOpen, isRightSidebarOpen, current, selectedContainer, @@ -160,7 +162,7 @@ export function Viewer({ return ( @@ -169,7 +171,7 @@ export function Viewer({ return ( Date: Tue, 7 Feb 2023 16:07:51 +0100 Subject: [PATCH 3/8] [WIP] ReplaceBy in ContainerOperations.ts --- .../Editor/Actions/ContainerOperations.ts | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index 57a0d72..43b14b0 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -8,7 +8,7 @@ import Swal from 'sweetalert2'; import { PropertyType } from '../../../Enums/PropertyType'; import { TransformX, TransformY } from '../../../utils/svg'; import { Orientation } from '../../../Enums/Orientation'; -import { AddContainerToSelectedContainer } from './AddContainer'; +import { AddContainers } from './AddContainer'; import { IConfiguration } from '../../../Interfaces/IConfiguration'; /** @@ -130,27 +130,35 @@ export function ReplaceByContainer( const history = GetCurrentHistory(fullHistory, historyCurrentStep); const current = history[history.length - 1]; - const historyDelete = DeleteContainer(containerId, fullHistory, historyCurrentStep); - const currentDelete = historyDelete[historyDelete.length - 1]; - const selectedContainer = FindContainerById(currentDelete.containers, currentDelete.selectedContainerId); - if (selectedContainer != null) { - const historyAdd = AddContainerToSelectedContainer(newContainerId, selectedContainer, configuration, fullHistory, historyCurrentStep); - - const currentAdd = historyAdd[historyAdd.length - 1]; - - fullHistory.push({ - lastAction: `Replace ${containerId} by ${newContainerId}`, - mainContainer: currentAdd.mainContainer, - containers: currentAdd.containers, - selectedContainerId: currentAdd.selectedContainerId, - typeCounters: Object.assign({}, currentAdd.typeCounters), - symbols: current.symbols, - selectedSymbolId: current.selectedSymbolId - }); - - return fullHistory; + const containerToReplace = FindContainerById(current.containers, containerId); + if (containerToReplace === undefined) { + return history; } - return history; + const containerParent = FindContainerById(current.containers,containerToReplace.properties.parentId); + if (containerParent=== undefined) { + return history; + } + const historyAdd = AddContainers(containerParent.children.indexOf(containerId), [{ Type: newContainerId }], containerParent.properties.id, configuration, fullHistory, historyCurrentStep); + // Copy des possibles enfants + if (historyAdd.newContainers[0] === undefined) { + return history; + } + historyAdd.newContainers[0].children = containerToReplace.children; + + const historyDelete = DeleteContainer(containerId, historyAdd.history, historyCurrentStep + 1); + const currentDelete = historyDelete[historyDelete.length - 1]; + + fullHistory.push({ + lastAction: `Replace ${containerId} by ${newContainerId}`, + mainContainer: currentDelete.mainContainer, + containers: currentDelete.containers, + selectedContainerId: currentDelete.selectedContainerId, + typeCounters: Object.assign({}, currentDelete.typeCounters), + symbols: current.symbols, + selectedSymbolId: current.selectedSymbolId + }); + + return fullHistory; } /** From 01059aa2f737de7eb5d2e5537684cb2ded449956 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 14 Feb 2023 15:53:55 +0100 Subject: [PATCH 4/8] Reorder some parameters in Editor and UI --- src/Components/Editor/Editor.tsx | 4 +++- src/Components/UI/UI.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 6644bed..70f2350 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -124,6 +124,7 @@ export function Editor(props: IEditorProps): JSX.Element { history, historyCurrentStep }} + replaceContainer={replaceContainer} selectContainer={(container) => setNewHistory( SelectContainer( container, @@ -209,7 +210,8 @@ export function Editor(props: IEditorProps): JSX.Element { )} saveEditorAsSVG={() => SaveEditorAsSVG()} loadState={(move) => setHistoryCurrentStep(move)} - replaceContainer ={replaceContainer} setReplaceContainer={setReplaceContainer}/> + setReplaceContainer={setReplaceContainer} + /> editorRef.current} configuration={configuration} diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index cab0e17..20c17b5 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -22,6 +22,7 @@ import { Dispatch } from 'react'; export interface IUIProps { editorState: IEditorState + replaceContainer: IReplaceContainer selectContainer: (containerId: string) => void deleteContainer: (containerId: string) => void onPropertyChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void @@ -34,7 +35,6 @@ export interface IUIProps { saveEditorAsJSON: () => void saveEditorAsSVG: () => void loadState: (move: number) => void - replaceContainer: IReplaceContainer setReplaceContainer: Dispatch> } From c4bf4586acb1e89a875859f0e870a1225117790d Mon Sep 17 00:00:00 2001 From: Eric Nguyen Date: Tue, 14 Feb 2023 15:00:18 +0000 Subject: [PATCH 5/8] Remove unused configuration from Menu --- src/Components/Editor/Editor.tsx | 1 - src/Components/Menu/Menu.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 70f2350..14fd26b 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -214,7 +214,6 @@ export function Editor(props: IEditorProps): JSX.Element { /> editorRef.current} - configuration={configuration} actions={menuActions} className="z-30 transition-opacity rounded bg-slate-200 drop-shadow-xl" /> diff --git a/src/Components/Menu/Menu.tsx b/src/Components/Menu/Menu.tsx index c8da101..57c3445 100644 --- a/src/Components/Menu/Menu.tsx +++ b/src/Components/Menu/Menu.tsx @@ -2,13 +2,11 @@ import useSize from '@react-hook/size'; import * as React from 'react'; import { IPoint } from '../../Interfaces/IPoint'; import { MenuItem } from './MenuItem'; -import { IConfiguration } from '../../Interfaces/IConfiguration'; interface IMenuProps { getListener: () => HTMLElement | null actions: Map className?: string - configuration: IConfiguration } export interface IMenuAction { From b4c9c3440345847d4e50b1574c348b44126a6aeb Mon Sep 17 00:00:00 2001 From: Eric Nguyen Date: Tue, 14 Feb 2023 15:01:39 +0000 Subject: [PATCH 6/8] Apply suggestions from code review --- src/Components/Editor/Actions/ContextMenuActions.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index 5b2a546..9fb86f4 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -85,8 +85,7 @@ export function InitActions( ); setNewHistory(newHistory); } - } - ] + }] ); menuActions.set( From 2d1e5c94d7e50083f7f6a11f28834edd7a096ea7 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 14 Feb 2023 16:10:00 +0100 Subject: [PATCH 7/8] Add Escape to shortcut to deselect replaceContainer --- src/Components/Editor/Actions/Shortcuts.ts | 5 +- src/Components/Editor/Editor.tsx | 162 ++++++++++++--------- 2 files changed, 98 insertions(+), 69 deletions(-) diff --git a/src/Components/Editor/Actions/Shortcuts.ts b/src/Components/Editor/Actions/Shortcuts.ts index a81660c..082770e 100644 --- a/src/Components/Editor/Actions/Shortcuts.ts +++ b/src/Components/Editor/Actions/Shortcuts.ts @@ -7,7 +7,8 @@ export function OnKey( history: IHistoryState[], historyCurrentStep: number, setHistoryCurrentStep: Dispatch>, - deleteAction: () => void + deleteAction: () => void, + resetState: () => void ): void { if (!ENABLE_SHORTCUTS) { return; @@ -27,5 +28,7 @@ export function OnKey( setHistoryCurrentStep(historyCurrentStep + 1); } else if (event.key === 'Delete') { deleteAction(); + } else if (event.key === 'Escape') { + resetState(); } } diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 14fd26b..095f644 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -1,7 +1,7 @@ -import React, { Dispatch, SetStateAction, useEffect, useRef } from 'react'; +import React, { type Dispatch, type SetStateAction, useEffect, useRef } from 'react'; import './Editor.scss'; -import { IConfiguration } from '../../Interfaces/IConfiguration'; -import { IHistoryState } from '../../Interfaces/IHistoryState'; +import { type IConfiguration } from '../../Interfaces/IConfiguration'; +import { type IHistoryState } from '../../Interfaces/IHistoryState'; import { UI } from '../UI/UI'; import { SelectContainer, DeleteContainer, OnPropertyChange, ReplaceByContainer } from './Actions/ContainerOperations'; import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save'; @@ -13,7 +13,7 @@ import { FindContainerById } from '../../utils/itertools'; import { Menu } from '../Menu/Menu'; import { InitActions } from './Actions/ContextMenuActions'; import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer'; -import { IReplaceContainer } from '../../Interfaces/IReplaceContainer'; +import { type IReplaceContainer } from '../../Interfaces/IReplaceContainer'; interface IEditorProps { root: Element | Document @@ -26,16 +26,18 @@ function UseShortcuts( history: IHistoryState[], historyCurrentStep: number, setHistoryCurrentStep: Dispatch>, - deleteAction: () => void + deleteAction: () => void, + resetState: () => void ): void { useEffect(() => { function OnKeyUp(event: KeyboardEvent): void { - return OnKey( + OnKey( event, history, historyCurrentStep, setHistoryCurrentStep, - deleteAction + deleteAction, + resetState ); } @@ -63,6 +65,7 @@ function UseNewHistoryState( }; } + export function Editor(props: IEditorProps): JSX.Element { // States const [history, setHistory] = React.useState(structuredClone(props.history)); @@ -72,6 +75,10 @@ export function Editor(props: IEditorProps): JSX.Element { const editorRef = useRef(null); const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep); + function ResetState(): void { + setReplaceContainer({ isReplacing: false, id: undefined, category: undefined }); + } + // Events UseShortcuts( history, @@ -82,7 +89,8 @@ export function Editor(props: IEditorProps): JSX.Element { setNewHistory( DeleteContainer(current.selectedContainerId, history, historyCurrentStep) ); - } + }, + ResetState ); UseCustomEvents( props.root, @@ -125,25 +133,31 @@ export function Editor(props: IEditorProps): JSX.Element { historyCurrentStep }} replaceContainer={replaceContainer} - selectContainer={(container) => setNewHistory( - SelectContainer( - container, - history, - historyCurrentStep - ))} - deleteContainer={(containerId: string) => setNewHistory( - DeleteContainer( - containerId, - history, - historyCurrentStep - ))} - onPropertyChange={(key, value, type) => setNewHistory( - OnPropertyChange( - key, value, type, - selected, - history, - historyCurrentStep - ))} + selectContainer={(container) => { + setNewHistory( + SelectContainer( + container, + history, + historyCurrentStep + )); + }} + deleteContainer={(containerId: string) => { + setNewHistory( + DeleteContainer( + containerId, + history, + historyCurrentStep + )); + }} + onPropertyChange={(key, value, type) => { + setNewHistory( + OnPropertyChange( + key, value, type, + selected, + history, + historyCurrentStep + )); + }} addOrReplaceContainer={(type) => { if (selected === null || selected === undefined) { return; @@ -168,48 +182,60 @@ export function Editor(props: IEditorProps): JSX.Element { )); } }} - addContainerAt={(index, type, parent) => setNewHistory( - AddContainer( - index, - type, - parent, - configuration, + addContainerAt={(index, type, parent) => { + setNewHistory( + AddContainer( + index, + type, + parent, + configuration, + history, + historyCurrentStep + ) + ); + }} + addSymbol={(type) => { + setNewHistory( + AddSymbol( + type, + configuration, + history, + historyCurrentStep + )); + }} + onSymbolPropertyChange={(key, value) => { + setNewHistory( + OnSymbolPropertyChange( + key, value, + history, + historyCurrentStep + )); + }} + selectSymbol={(symbolId) => { + setNewHistory( + SelectSymbol( + symbolId, + history, + historyCurrentStep + )); + }} + deleteSymbol={(symbolId) => { + setNewHistory( + DeleteSymbol( + symbolId, + history, + historyCurrentStep + )); + }} + saveEditorAsJSON={() => { + SaveEditorAsJSON( history, - historyCurrentStep - ) - )} - addSymbol={(type) => setNewHistory( - AddSymbol( - type, - configuration, - history, - historyCurrentStep - ))} - onSymbolPropertyChange={(key, value) => setNewHistory( - OnSymbolPropertyChange( - key, value, - history, - historyCurrentStep - ))} - selectSymbol={(symbolId) => setNewHistory( - SelectSymbol( - symbolId, - history, - historyCurrentStep - ))} - deleteSymbol={(symbolId) => setNewHistory( - DeleteSymbol( - symbolId, - history, - historyCurrentStep - ))} - saveEditorAsJSON={() => SaveEditorAsJSON( - history, - historyCurrentStep, - configuration - )} - saveEditorAsSVG={() => SaveEditorAsSVG()} - loadState={(move) => setHistoryCurrentStep(move)} + historyCurrentStep, + configuration + ); + }} + saveEditorAsSVG={() => { SaveEditorAsSVG(); }} + loadState={(move) => { setHistoryCurrentStep(move); }} setReplaceContainer={setReplaceContainer} /> Date: Fri, 17 Feb 2023 09:58:51 +0100 Subject: [PATCH 8/8] Remove children replacement --- .../Editor/Actions/ContainerOperations.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index 3dd1e74..684b443 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -158,16 +158,18 @@ export function ReplaceByContainer( if (containerToReplace === undefined) { return history; } - const containerParent = FindContainerById(current.containers,containerToReplace.properties.parentId); - if (containerParent=== undefined) { + + const containerParent = FindContainerById(current.containers, containerToReplace.properties.parentId); + if (containerParent === undefined) { return history; } - const historyAdd = AddContainers(containerParent.children.indexOf(containerId), [{ Type: newContainerId }], containerParent.properties.id, configuration, fullHistory, historyCurrentStep); - // Copy des possibles enfants - if (historyAdd.newContainers[0] === undefined) { - return history; - } - historyAdd.newContainers[0].children = containerToReplace.children; + + const historyAdd = AddContainers( + containerParent.children.indexOf(containerId), + [{ Type: newContainerId }], + containerParent.properties.id, + configuration, fullHistory, historyCurrentStep + ); const historyDelete = DeleteContainer(containerId, historyAdd.history, historyCurrentStep + 1); const currentDelete = historyDelete[historyDelete.length - 1];