From af73fa60832d878f2eec0281a8ebcb8ac91e7820 Mon Sep 17 00:00:00 2001 From: Siklos Date: Thu, 11 Aug 2022 12:39:42 +0200 Subject: [PATCH] Implement basic form for properties to avoid history pollution --- src/Components/Editor/ContainerOperations.ts | 74 ++++++++++++++++- src/Components/Editor/Editor.tsx | 10 ++- .../ElementsSidebar/ElementsSidebar.tsx | 7 +- src/Components/MainMenu/MainMenu.tsx | 9 +-- src/Components/Properties/Properties.tsx | 81 +++++++++++++++---- src/Components/UI/UI.tsx | 2 + src/index.scss | 10 +++ src/utils/default.ts | 2 +- 8 files changed, 167 insertions(+), 28 deletions(-) diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index ea4db42..8ad34ec 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction } from 'react'; +import React, { Dispatch, SetStateAction } from 'react'; import { HistoryState } from '../../Interfaces/HistoryState'; import { Configuration } from '../../Interfaces/Configuration'; import { ContainerModel, IContainerModel } from '../../Interfaces/ContainerModel'; @@ -60,7 +60,7 @@ export function DeleteContainer( } if (container === null || container === undefined) { - throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); + throw new Error('[DeleteContainer] Container model was not found among children of the main container!'); } if (container.parent != null) { @@ -266,6 +266,76 @@ export function OnPropertyChange( setHistoryCurrentStep(history.length); } +/** + * Handled the property change event in the properties form + * @param key Property name + * @param value New value of the property + * @returns void + */ +export function OnPropertiesSubmit( + event: React.SyntheticEvent, + refs: Array>, + fullHistory: HistoryState[], + historyCurrentStep: number, + setHistory: Dispatch>, + setHistoryCurrentStep: Dispatch> +): void { + event.preventDefault(); + const history = getCurrentHistory(fullHistory, historyCurrentStep); + 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 (parent === null) { + const selectedContainerClone: IContainerModel = structuredClone(current.SelectedContainer); + for (const ref of refs) { + const input = ref.current; + if (input instanceof HTMLInputElement) { + (selectedContainerClone.properties as any)[input.id] = input.value; + } + } + setHistory(history.concat([{ + LastAction: 'Change property of main', + MainContainer: selectedContainerClone, + SelectedContainer: selectedContainerClone, + SelectedContainerId: selectedContainerClone.properties.id, + TypeCounters: Object.assign({}, current.TypeCounters) + }])); + setHistoryCurrentStep(history.length); + return; + } + + const mainContainerClone: IContainerModel = structuredClone(current.MainContainer); + const container: ContainerModel | undefined = findContainerById(mainContainerClone, current.SelectedContainer.properties.id); + + if (container === null || container === undefined) { + throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); + } + + for (const ref of refs) { + const input = ref.current; + if (input instanceof HTMLInputElement) { + (container.properties as any)[input.id] = input.value; + } + } + + if (container.properties.isRigidBody) { + RecalculatePhysics(container); + } + + setHistory(history.concat([{ + LastAction: `Change property of container ${container.properties.id}`, + MainContainer: mainContainerClone, + SelectedContainer: container, + SelectedContainerId: container.properties.id, + TypeCounters: Object.assign({}, current.TypeCounters) + }])); + setHistoryCurrentStep(history.length); +} + // TODO put this in a different file export function RecalculatePhysics(container: IContainerModel): IContainerModel { diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index ad27c22..b008607 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -4,7 +4,7 @@ import { Configuration } from '../../Interfaces/Configuration'; import { SVG } from '../SVG/SVG'; import { HistoryState } from '../../Interfaces/HistoryState'; import { UI } from '../UI/UI'; -import { SelectContainer, DeleteContainer, OnPropertyChange, AddContainerToSelectedContainer, AddContainer } from './ContainerOperations'; +import { SelectContainer, DeleteContainer, OnPropertyChange, AddContainerToSelectedContainer, AddContainer, OnPropertiesSubmit } from './ContainerOperations'; import { SaveEditorAsJSON, SaveEditorAsSVG } from './Save'; import { onKeyDown } from './Shortcuts'; @@ -72,6 +72,14 @@ const Editor: React.FunctionComponent = (props) => { setHistory, setHistoryCurrentStep )} + OnPropertiesSubmit={(event, refs) => OnPropertiesSubmit( + event, + refs, + history, + historyCurrentStep, + setHistory, + setHistoryCurrentStep + )} AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer( type, configuration, diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index 873161d..96be3fe 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -14,6 +14,7 @@ interface IElementsSidebarProps { isHistoryOpen: boolean SelectedContainer: IContainerModel | null OnPropertyChange: (key: string, value: string | number | boolean) => void + OnPropertiesSubmit: (event: React.FormEvent, refs: Array>) => void SelectContainer: (container: IContainerModel) => void DeleteContainer: (containerid: string) => void AddContainer: (index: number, type: string, parent: string) => void @@ -145,7 +146,11 @@ export const ElementsSidebar: React.FC = (props: IElement props.DeleteContainer(onClickContainerId); }} /> - + ); }; diff --git a/src/Components/MainMenu/MainMenu.tsx b/src/Components/MainMenu/MainMenu.tsx index 1d84f9a..9c8b27f 100644 --- a/src/Components/MainMenu/MainMenu.tsx +++ b/src/Components/MainMenu/MainMenu.tsx @@ -38,13 +38,8 @@ export const MainMenu: React.FC = (props) => { diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index c1ac966..9c90705 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -1,10 +1,11 @@ -import * as React from 'react'; +import React, { useState } from 'react'; import ContainerProperties from '../../Interfaces/Properties'; import { INPUT_TYPES } from './PropertiesInputTypes'; interface IPropertiesProps { properties?: ContainerProperties onChange: (key: string, value: string | number | boolean) => void + onSubmit: (event: React.FormEvent, refs: Array>) => void } export const Properties: React.FC = (props: IPropertiesProps) => { @@ -12,14 +13,35 @@ export const Properties: React.FC = (props: IPropertiesProps) return
; } + const [isDynamicInput, setIsDynamicInput] = useState(true); + const groupInput: React.ReactNode[] = []; + const refs: Array> = []; Object .entries(props.properties) - .forEach((pair) => handleProperties(pair, groupInput, props.onChange)); + .forEach((pair) => handleProperties(pair, groupInput, refs, isDynamicInput, props.onChange)); + + const form = isDynamicInput + ?
+ { groupInput } +
+ :
props.onSubmit(event, refs)} + > + + { groupInput } +
+ ; return ( -
- { groupInput } +
+ { setIsDynamicInput(!isDynamicInput); }} + checked={isDynamicInput} + /> + { form }
); }; @@ -27,6 +49,8 @@ export const Properties: React.FC = (props: IPropertiesProps) const handleProperties = ( [key, value]: [string, string | number], groupInput: React.ReactNode[], + refs: Array>, + isDynamicInput: boolean, onChange: (key: string, value: string | number | boolean) => void ): void => { const id = `property-${key}`; @@ -42,9 +66,40 @@ const handleProperties = ( type = INPUT_TYPES[key]; } - const isDisabled = ['id', 'parentId'].includes(key); - /// + const ref: React.RefObject = React.useRef(null); + refs.push(ref); + const isDisabled = ['id', 'parentId'].includes(key); + if (isDynamicInput) { + groupInput.push( +
+ + { + if (type === 'checkbox') { + onChange(key, event.target.checked); + return; + } + onChange(key, event.target.value); + }} + disabled={isDisabled} + /> +
+ ); + return; + } + + /// groupInput.push(
@@ -55,16 +110,10 @@ const handleProperties = ( disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none ' type={type} - id={id} - value={value} - checked={checked} - onChange={(event) => { - if (type === 'checkbox') { - onChange(key, event.target.checked); - return; - } - onChange(key, event.target.value); - }} + id={key} + ref={ref} + defaultValue={value} + defaultChecked={checked} disabled={isDisabled} />
diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 6d6adbe..5c7012e 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -17,6 +17,7 @@ interface IUIProps { SelectContainer: (container: ContainerModel) => void DeleteContainer: (containerId: string) => void OnPropertyChange: (key: string, value: string | number | boolean) => void + OnPropertiesSubmit: (event: React.FormEvent, refs: Array>) => void AddContainerToSelectedContainer: (type: string) => void AddContainer: (index: number, type: string, parentId: string) => void SaveEditorAsJSON: () => void @@ -59,6 +60,7 @@ export const UI: React.FunctionComponent = (props: IUIProps) => { isOpen={isElementsSidebarOpen} isHistoryOpen={isHistoryOpen} OnPropertyChange={props.OnPropertyChange} + OnPropertiesSubmit={props.OnPropertiesSubmit} SelectContainer={props.SelectContainer} DeleteContainer={props.DeleteContainer} AddContainer={props.AddContainer} diff --git a/src/index.scss b/src/index.scss index 1bc3361..f653990 100644 --- a/src/index.scss +++ b/src/index.scss @@ -23,6 +23,16 @@ @apply transition-all bg-blue-100 hover:bg-blue-200 text-blue-700 text-lg font-semibold p-8 rounded-lg } + .normal-btn { + @apply text-sm + py-2 px-4 + rounded-full border-0 + font-semibold + transition-all + bg-blue-100 text-blue-700 + hover:bg-blue-200 + } + .floating-btn { @apply h-full w-full text-white align-middle items-center justify-center } diff --git a/src/utils/default.ts b/src/utils/default.ts index 552b8a0..a117ef0 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -30,9 +30,9 @@ export const DEFAULT_MAINCONTAINER_PROPS: Properties = { parentId: 'null', x: 0, y: 0, - isRigidBody: false, width: DEFAULT_CONFIG.MainContainer.Width, height: DEFAULT_CONFIG.MainContainer.Height, + isRigidBody: false, fillOpacity: 0, stroke: 'black' };