From 3b8367799c2295d5cb647021a65e713ebcde9fce Mon Sep 17 00:00:00 2001 From: Fran Lopez Date: Tue, 17 Sep 2024 13:11:59 +0200 Subject: [PATCH 1/3] add horizontal menu active selected item and refactor --- .../horizontal-menu/horizontal-menu.tsx | 53 +++++++------------ src/pods/canvas/canvas.model.ts | 10 +++- .../active-element-selector.component.tsx | 3 +- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/common/components/front-rich-components/horizontal-menu/horizontal-menu.tsx b/src/common/components/front-rich-components/horizontal-menu/horizontal-menu.tsx index bbdd7741..32722125 100644 --- a/src/common/components/front-rich-components/horizontal-menu/horizontal-menu.tsx +++ b/src/common/components/front-rich-components/horizontal-menu/horizontal-menu.tsx @@ -1,15 +1,18 @@ import { Group, Rect, Text } from 'react-konva'; import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; -import { forwardRef, useEffect, useState } from 'react'; +import { forwardRef } from 'react'; import { ShapeProps } from '../../front-components/shape.model'; import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions'; import { BASIC_SHAPE } from '../../front-components/shape.const'; import { useShapeComponentSelection } from '../../shapes/use-shape-selection.hook'; -import { mapHorizontalMenuTextToItems } from './hozontal-menu.business'; import { useShapeProps } from '../../shapes/use-shape-props.hook'; +import { + extractCSVHeaders, + splitCSVContentIntoRows, +} from '@/common/utils/active-element-selector.utils'; const horizontalMenuShapeSizeRestrictions: ShapeSizeRestrictions = { - minWidth: 75, + minWidth: 200, minHeight: 25, maxWidth: -1, maxHeight: 100, @@ -35,36 +38,17 @@ export const HorizontalMenu = forwardRef((props, ref) => { ...shapeProps } = props; - const [selectedItem, setSelectedItem] = useState(null); - const [items, setItems] = useState([ - '[*]Home, About, Services, Contact', - ]); - const handleClick = (itemIndex: number) => { - setSelectedItem(itemIndex); - onSelected(id, 'horizontal-menu', true); - }; + const csvData = splitCSVContentIntoRows(text); + const headers = extractCSVHeaders(csvData[0]); + const itemLabels = headers.map(header => header.text); - useEffect(() => { - if (typeof text === 'string') { - const { items, selectedItemIndex } = mapHorizontalMenuTextToItems(text); - setItems(items); - setSelectedItem(selectedItemIndex); - } else { - setItems([]); - } - }, [text]); + const numberOfItems = itemLabels.length; + const itemSpacing = 10; - const numberOfItems = items.length; - const minItemWidth = 100; - const itemSpacing = 20; - const totalWidth = Math.max( - minItemWidth * numberOfItems + itemSpacing * (numberOfItems + 1), - width - ); const { width: restrictedWidth, height: restrictedHeight } = fitSizeToShapeSizeRestrictions( horizontalMenuShapeSizeRestrictions, - totalWidth, + width, height ); const totalMargins = restrictedWidth - itemSpacing * (numberOfItems + 1); @@ -76,8 +60,11 @@ export const HorizontalMenu = forwardRef((props, ref) => { otherProps, BASIC_SHAPE ); + const itemVerticalPadding = 4; + const activeSelected = otherProps?.activeElement ?? 0; + return ( ((props, ref) => { cornerRadius={borderRadius} /> - {items.map((e: string, index: number) => ( - handleClick(index)}> + {itemLabels.map((header, index) => ( + { case 'listbox': return '[*]Item\nItem1\nItem2\nItem3\nItem4\nItem5\nItem6'; case 'horizontal-menu': - return '[*]Home, About, Services, Contact'; + return 'Home, About, Services, Contact'; case 'vertical-menu': return 'Option 1\nOption 2\n----\nOption 3\nOption 4'; case 'heading1': @@ -396,6 +396,14 @@ export const generateDefaultOtherProps = ( case 'listbox': case 'vertical-menu': case 'horizontal-menu': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + borderRadius: `${BASIC_SHAPE.DEFAULT_CORNER_RADIUS}`, + activeElement: 0, + }; case 'datepickerinput': case 'timepickerinput': return { diff --git a/src/pods/properties/components/active-element-selector/active-element-selector.component.tsx b/src/pods/properties/components/active-element-selector/active-element-selector.component.tsx index 6975a80f..dd827294 100644 --- a/src/pods/properties/components/active-element-selector/active-element-selector.component.tsx +++ b/src/pods/properties/components/active-element-selector/active-element-selector.component.tsx @@ -26,7 +26,8 @@ export const ActiveElementSelector: React.FC = ({ }; // Checking whether the type is tabsBar and parsing the text - const isElementTypeSupported = type === 'tabsBar' || 'buttonBar'; + const isElementTypeSupported = + type === 'tabsBar' || 'buttonBar' || 'horizontal-menu'; const elementNames = isElementTypeSupported && text ? extractElementNames(text) : []; From c91560af4f15a4d51d38399acda94bbca035123e Mon Sep 17 00:00:00 2001 From: Fran Lopez Date: Tue, 17 Sep 2024 18:32:55 +0200 Subject: [PATCH 2/3] delete business horizontal menu, now use generic utils active element selector utils --- .../horizontal-menu/hozontal-menu.business.ts | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/common/components/front-rich-components/horizontal-menu/hozontal-menu.business.ts diff --git a/src/common/components/front-rich-components/horizontal-menu/hozontal-menu.business.ts b/src/common/components/front-rich-components/horizontal-menu/hozontal-menu.business.ts deleted file mode 100644 index 3cdff6a7..00000000 --- a/src/common/components/front-rich-components/horizontal-menu/hozontal-menu.business.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface HorizontalMenuItemsInfo { - items: string[]; - selectedItemIndex: number; -} - -export const mapHorizontalMenuTextToItems = ( - text: string -): HorizontalMenuItemsInfo => { - let items: string[] = text.split(',').map(item => item.trim()); - - const selectedItemIndex = items.findIndex(item => item.startsWith('[*]')); - - items = items.map(item => item.replace(/^\[\*\]/, '')); - items = items.filter(item => item !== ''); - - return { - items: items, - selectedItemIndex: selectedItemIndex === -1 ? 0 : selectedItemIndex, - }; -}; From 80a3befa9efe5eff03cfcd9021f6044525d9fad1 Mon Sep 17 00:00:00 2001 From: oleojake Date: Thu, 19 Sep 2024 11:58:59 +0200 Subject: [PATCH 3/3] #385 promoted isInlineEditing to provider --- .../hooks/use-submit-cancel-hook.ts | 5 +++++ .../components/inline-edit/inline-edit.tsx | 4 ++++ src/core/providers/canvas/canvas.model.ts | 2 ++ src/core/providers/canvas/canvas.provider.tsx | 3 +++ src/pods/canvas/canvas.pod.tsx | 2 -- src/pods/canvas/use-keyboard-displacement.tsx | 20 +++---------------- src/pods/toolbar/shortcut/shortcut.const.ts | 4 ++-- src/pods/toolbar/shortcut/shortcut.hook.tsx | 5 +++++ 8 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/common/components/inline-edit/hooks/use-submit-cancel-hook.ts b/src/common/components/inline-edit/hooks/use-submit-cancel-hook.ts index ac7d98ec..c3d4fc2f 100644 --- a/src/common/components/inline-edit/hooks/use-submit-cancel-hook.ts +++ b/src/common/components/inline-edit/hooks/use-submit-cancel-hook.ts @@ -1,4 +1,5 @@ import { EditType } from '@/core/model'; +import { useCanvasContext } from '@/core/providers'; import { useEffect, useRef, useState } from 'react'; interface Configuration { @@ -18,6 +19,7 @@ export const useSubmitCancelHook = ( const divRef = useRef(null); const [isEditing, setIsEditing] = useState(false); + const { setIsInlineEditing } = useCanvasContext(); const getActiveInputRef = (): | HTMLInputElement @@ -45,6 +47,7 @@ export const useSubmitCancelHook = ( !getActiveInputRef()?.contains(event.target as Node) ) { setIsEditing(false); + setIsInlineEditing(false); if (editType === 'input' || editType === 'textarea') { const inputRef = getActiveInputRef() as any; onTextSubmit(inputRef?.value || ''); @@ -55,11 +58,13 @@ export const useSubmitCancelHook = ( const handleKeyDown = (event: KeyboardEvent) => { if (isEditing && event.key === 'Escape') { setIsEditing(false); + setIsInlineEditing(false); setEditText(text); } if (editType === 'input' && isEditable && event.key === 'Enter') { setIsEditing(false); + setIsInlineEditing(false); if (editType === 'input' || editType === 'textarea') { const inputRef = getActiveInputRef() as any; onTextSubmit(inputRef?.value || ''); diff --git a/src/common/components/inline-edit/inline-edit.tsx b/src/common/components/inline-edit/inline-edit.tsx index 945483ec..23ec25d1 100644 --- a/src/common/components/inline-edit/inline-edit.tsx +++ b/src/common/components/inline-edit/inline-edit.tsx @@ -3,6 +3,7 @@ import { Group } from 'react-konva'; import { Coord, EditType, Size } from '@/core/model'; import { HtmlEditWidget } from './components'; import { useSubmitCancelHook, usePositionHook } from './hooks'; +import { useCanvasContext } from '@/core/providers'; interface Props { coords: Coord; @@ -30,6 +31,7 @@ export const EditableComponent: React.FC = props => { } = props; const [editText, setEditText] = useState(text); const isInputInitiallyFocused = useRef(false); + const { setIsInlineEditing } = useCanvasContext(); const { inputRef, textAreaRef, divRef, isEditing, setIsEditing } = useSubmitCancelHook( @@ -45,6 +47,7 @@ export const EditableComponent: React.FC = props => { const handleDoubleClick = () => { if (isEditable) { setIsEditing(true); + setIsInlineEditing(true); } }; @@ -90,6 +93,7 @@ export const EditableComponent: React.FC = props => { const handleImageSrcSubmit = (src: string) => { onImageSrcSubmit(src); setIsEditing(false); + setIsInlineEditing(false); }; return ( diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 5310607c..844ab71d 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -67,6 +67,8 @@ export interface CanvasContextModel { copyShapeToClipboard: () => void; pasteShapeFromClipboard: () => void; loadDocument: (document: DocumentModel) => void; + isInlineEditing: boolean; + setIsInlineEditing: React.Dispatch>; } export interface DocumentModel { diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 688512c5..5e553335 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -20,6 +20,7 @@ export const CanvasProvider: React.FC = props => { const [scale, setScale] = React.useState(1); const stageRef = React.useRef(null); + const [isInlineEditing, setIsInlineEditing] = React.useState(false); const { addSnapshot, @@ -181,6 +182,8 @@ export const CanvasProvider: React.FC = props => { stageRef, deleteSelectedShapes, loadDocument, + isInlineEditing, + setIsInlineEditing, }} > {children} diff --git a/src/pods/canvas/canvas.pod.tsx b/src/pods/canvas/canvas.pod.tsx index 11fb0008..96b2379d 100644 --- a/src/pods/canvas/canvas.pod.tsx +++ b/src/pods/canvas/canvas.pod.tsx @@ -101,8 +101,6 @@ export const CanvasPod = () => { updateShapePosition(id, { x, y }); }; - // TODO: Temporary disabled, conflicts with inline edition - // and likely keboard shortcuts useKeyboardDisplacement(); { diff --git a/src/pods/canvas/use-keyboard-displacement.tsx b/src/pods/canvas/use-keyboard-displacement.tsx index 70bdcbd9..6767f9dd 100644 --- a/src/pods/canvas/use-keyboard-displacement.tsx +++ b/src/pods/canvas/use-keyboard-displacement.tsx @@ -3,7 +3,8 @@ import { useCanvasContext } from '@/core/providers'; import { Coord } from '@/core/model'; export const useKeyboardDisplacement = () => { - const { selectionInfo, updateShapePosition } = useCanvasContext(); + const { selectionInfo, updateShapePosition, isInlineEditing } = + useCanvasContext(); // TODO: move this to business/utils const updateShapeCollectionPosition = ( @@ -25,24 +26,9 @@ export const useKeyboardDisplacement = () => { }); }; - const isKeyboardKey = (key: string) => { - return ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key); - }; - useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - // Keydown keys can conflict when we are performing inlne edtiion - // input and textarea will use the cursosr and stage keyboard should not - // the bubble order is Stage >> Input, so we cannot stop propagation - // BUT - // We have added a data attribute to the input and textarea to check - // if the inline edition is on - // here we check if the event target has the data attribute - // then we return and let the input and textare control it - const isInlineEditing = - (event.target as any)?.attributes['data-is-inline-edition-on'] !== - undefined; - if (isInlineEditing || !isKeyboardKey(event.key)) { + if (isInlineEditing) { return; } diff --git a/src/pods/toolbar/shortcut/shortcut.const.ts b/src/pods/toolbar/shortcut/shortcut.const.ts index 0483bcca..0d901ad7 100644 --- a/src/pods/toolbar/shortcut/shortcut.const.ts +++ b/src/pods/toolbar/shortcut/shortcut.const.ts @@ -8,8 +8,8 @@ export const SHORTCUTS: Shortcut = { delete: { description: 'Delete', id: 'delete-button-shortcut', - targetKey: ['Ctrl+backspace', 'Meta+backspace'], - targetKeyLabel: 'Ctrl + Backspace', + targetKey: ['backspace', 'delete'], + targetKeyLabel: 'Backspace', }, copy: { description: 'Copy', diff --git a/src/pods/toolbar/shortcut/shortcut.hook.tsx b/src/pods/toolbar/shortcut/shortcut.hook.tsx index 9069b142..aa0698fa 100644 --- a/src/pods/toolbar/shortcut/shortcut.hook.tsx +++ b/src/pods/toolbar/shortcut/shortcut.hook.tsx @@ -1,4 +1,5 @@ import { isMacOS } from '@/common/helpers/platform.helpers'; +import { useCanvasContext } from '@/core/providers'; import { useEffect } from 'react'; export interface ShortcutHookProps { @@ -7,7 +8,11 @@ export interface ShortcutHookProps { } export const useShortcut = ({ targetKey, callback }: ShortcutHookProps) => { + const { isInlineEditing } = useCanvasContext(); const handleKeyPress = (event: KeyboardEvent) => { + if (isInlineEditing) { + return; + } // TODO: later on this needs discussio about shortcut keys // Right now enable CTRL+C, CTRL+V for windows, linux and mac //const isAltKeyPressed = event.getModifierState('Alt');