From 27593ac84e7ea6af7f323bca171b525a3a518ab2 Mon Sep 17 00:00:00 2001 From: Braulio Date: Sat, 7 Dec 2024 12:58:01 +0100 Subject: [PATCH 01/10] props in progress --- src/core/providers/canvas/canvas.model.ts | 1 + .../providers/canvas/use-selection.hook.ts | 58 ++++++++++---- src/pods/properties/components/show-prop.tsx | 36 +++++++++ src/pods/properties/properties.business.ts | 42 ++++++++++ src/pods/properties/properties.model.ts | 20 +++++ src/pods/properties/properties.pod.tsx | 78 +++++++++++++++---- 6 files changed, 206 insertions(+), 29 deletions(-) create mode 100644 src/pods/properties/components/show-prop.tsx create mode 100644 src/pods/properties/properties.business.ts create mode 100644 src/pods/properties/properties.model.ts diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 45d6274e..f2a0ac4f 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -51,6 +51,7 @@ export interface SelectionInfo { selectedShapesIds: string[]; selectedShapeType: ShapeType | null; getSelectedShapeData: (index?: number) => ShapeModel | undefined; + getAllSelectedShapesData: () => ShapeModel[]; setZIndexOnSelected: (action: ZIndexAction) => void; updateTextOnSelected: (text: string) => void; // TODO: Update, A. KeyOf B. Move To useSelectionInfo diff --git a/src/core/providers/canvas/use-selection.hook.ts b/src/core/providers/canvas/use-selection.hook.ts index e5ca7ac9..b9f187bc 100644 --- a/src/core/providers/canvas/use-selection.hook.ts +++ b/src/core/providers/canvas/use-selection.hook.ts @@ -178,22 +178,11 @@ export const useSelection = ( // TODO: Rather implement this using immmer - const updateOtherPropsOnSelected = ( + const updateOtherPropsOnSelectedSingleShape = ( + selectedShapeId: string, key: K, value: OtherProps[K] ) => { - if (!isPageIndexValid(document)) return; - - // TODO: Right now applying this only to single selection - // in the future we could apply to all selected shapes - // BUT, we have to show only common shapes (pain in the neck) - // Only when selection is one - if (selectedShapesIds.length !== 1) { - return; - } - - const selectedShapeId = selectedShapesIds[0]; - setDocument(prevDocument => produce(prevDocument, draft => { draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ @@ -207,6 +196,42 @@ export const useSelection = ( ); }; + const updateOtherPropsOnSelected = ( + key: K, + value: OtherProps[K], + multipleSelection: boolean = false + ) => { + if (!isPageIndexValid(document) || selectedShapesIds.length === 0) return; + + // TODO: Right now applying this only to single selection + // in the future we could apply to all selected shapes + // BUT, we have to show only common shapes (pain in the neck) + // Only when selection is one + if (selectedShapesIds.length === 1) { + const selectedShapeId = selectedShapesIds[0]; + updateOtherPropsOnSelectedSingleShape(selectedShapeId, key, value); + + return; + } + + if (multipleSelection) { + setDocument(prevDocument => + produce(prevDocument, draft => { + draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ + prevDocument.activePageIndex + ].shapes.map(shape => + selectedShapesIds.includes(shape.id) + ? { + ...shape, + otherProps: { ...shape.otherProps, [key]: value }, + } + : shape + ); + }) + ); + } + }; + // Added index, right now we got multiple selection // if not returning just 0 (first element) const getSelectedShapeData = (index: number = 0): ShapeModel | undefined => { @@ -225,6 +250,12 @@ export const useSelection = ( ); }; + const getAllSelectedShapesData = (): ShapeModel[] => { + return getActivePageShapes(document).filter(shape => + selectedShapesIds.includes(shape.id) + ); + }; + return { transformerRef, shapeRefs, @@ -235,6 +266,7 @@ export const useSelection = ( selectedShapesIds, selectedShapeType, getSelectedShapeData, + getAllSelectedShapesData, setZIndexOnSelected, updateTextOnSelected, updateOtherPropsOnSelected, diff --git a/src/pods/properties/components/show-prop.tsx b/src/pods/properties/components/show-prop.tsx new file mode 100644 index 00000000..abfba4e0 --- /dev/null +++ b/src/pods/properties/components/show-prop.tsx @@ -0,0 +1,36 @@ +import { useMemo } from 'react'; +import { CommonSelectedPropsAndValues } from '../properties.model'; +import { OtherProps } from '@/core/model'; + +interface Props { + singleSelection: boolean; + multipleSelectionPropsInCommon: CommonSelectedPropsAndValues; + propKey: keyof OtherProps; + propValue: unknown; + children: React.ReactNode; +} + +export const ShowProp: React.FC = props => { + const { + singleSelection, + multipleSelectionPropsInCommon, + propKey, + propValue, + children, + } = props; + + const showProp: boolean = useMemo( + () => + (singleSelection && propValue !== undefined) || + multipleSelectionPropsInCommon[propKey] !== undefined, + [multipleSelectionPropsInCommon, propKey, propValue] + ); + + console.log('showProp', showProp); + console.log( + 'multipleSelection propkey', + multipleSelectionPropsInCommon[propKey] + ); + + return <>{showProp ? children : null}; +}; diff --git a/src/pods/properties/properties.business.ts b/src/pods/properties/properties.business.ts new file mode 100644 index 00000000..2b5eb74c --- /dev/null +++ b/src/pods/properties/properties.business.ts @@ -0,0 +1,42 @@ +import { OtherProps, ShapeModel } from '@/core/model'; +import { + PropsValueTypes, + CommonSelectedPropsAndValues, + multiSelectEnabledProperties, +} from './properties.model'; + +// Helper function to check if a property is defined in all selected shapes +const isPropertyDefinedInAllShapes = ( + selectedShapes: ShapeModel[], + prop: keyof OtherProps +): boolean => { + return selectedShapes.every( + shape => shape.otherProps && shape.otherProps[prop] !== undefined + ); +}; + +// Helper function to get the common value of a property, or undefined if values differ +const getCommonValueForProperty = ( + selectedShapes: ShapeModel[], + prop: keyof OtherProps +): PropsValueTypes => { + const values = selectedShapes.map( + shape => shape.otherProps && shape.otherProps[prop] + ); + return values.every(value => value === values[0]) ? values[0] : undefined; +}; + +// Main function to extract common properties and their values +export const extractMultiplePropsInCommon = ( + selectedShapes: ShapeModel[] +): CommonSelectedPropsAndValues => { + const commonProps: CommonSelectedPropsAndValues = {}; + + multiSelectEnabledProperties.forEach(prop => { + if (isPropertyDefinedInAllShapes(selectedShapes, prop)) { + commonProps[prop] = getCommonValueForProperty(selectedShapes, prop); + } + }); + + return commonProps; +}; diff --git a/src/pods/properties/properties.model.ts b/src/pods/properties/properties.model.ts new file mode 100644 index 00000000..8bd7533c --- /dev/null +++ b/src/pods/properties/properties.model.ts @@ -0,0 +1,20 @@ +import { IconInfo, OtherProps } from '@/core/model'; + +export const multiSelectEnabledProperties: (keyof OtherProps)[] = [ + 'stroke', + 'backgroundColor', + 'textColor', + 'selectedBackgroundColor', +]; + +export type PropsValueTypes = + | string + | number + | boolean + | number[] + | IconInfo + | undefined; + +export interface CommonSelectedPropsAndValues { + [key: string]: PropsValueTypes; +} diff --git a/src/pods/properties/properties.pod.tsx b/src/pods/properties/properties.pod.tsx index 65402ae7..7cc793bf 100644 --- a/src/pods/properties/properties.pod.tsx +++ b/src/pods/properties/properties.pod.tsx @@ -14,26 +14,51 @@ import { FontVariant } from './components/font-variant/font-variant'; import { TextDecoration } from './components/text-decoration/text-decoration'; import { FontSize } from './components/font-size'; import { TextAlignment } from './components/text-alignment'; +import { useMemo } from 'react'; +import { extractMultiplePropsInCommon } from './properties.business'; +import { ShowProp } from './components/show-prop'; export const PropertiesPod = () => { const { selectionInfo } = useCanvasContext(); - const { getSelectedShapeData, updateOtherPropsOnSelected } = selectionInfo; + const { + getSelectedShapeData, + getAllSelectedShapesData, + updateOtherPropsOnSelected, + } = selectionInfo; // TODO: Right now we will enable properties when we have single selection // if we have multiple selection only zindex will be enabled // in the future we can just merge common props etc... but that's not straight forward + /* const selectedShapeRef = selectionInfo?.selectedShapesRefs.current && selectionInfo?.selectedShapesRefs.current.length === 1 ? selectionInfo.selectedShapesRefs.current[0] : null; + */ // Check if there are any shapes selected const hasSelectedShapes = selectionInfo?.selectedShapesRefs.current && selectionInfo.selectedShapesRefs.current.length > 0; - const selectedShapeData = getSelectedShapeData(); + const multipleSelectionShapeData = useMemo( + () => getAllSelectedShapesData(), + [selectionInfo.selectedShapesIds] + ); + + const multipleSelectionPropsInCommon = useMemo( + () => extractMultiplePropsInCommon(multipleSelectionShapeData), + [multipleSelectionShapeData] + ); + + console.log(multipleSelectionPropsInCommon); + + // TODO: This could be simplified, just use all selection index 0 + const selectedShapeData = useMemo( + () => getSelectedShapeData(), + [selectionInfo.selectedShapesIds] + ); return (
@@ -41,42 +66,63 @@ export const PropertiesPod = () => {

Properties

{hasSelectedShapes && } - {selectedShapeRef && ( + { <> - {selectedShapeData?.otherProps?.stroke && ( + updateOtherPropsOnSelected('stroke', color)} /> - )} - {selectedShapeData?.otherProps?.strokeStyle && ( + + updateOtherPropsOnSelected('strokeStyle', strokeStyle) } /> - )} - {selectedShapeData?.otherProps?.backgroundColor && ( + + + updateOtherPropsOnSelected('backgroundColor', color) } /> - )} - {selectedShapeData?.otherProps?.iconSize && ( + + updateOtherPropsOnSelected('iconSize', iconSize) } /> - )} + {selectedShapeData?.otherProps?.icon && ( { /> )} - )} + } {selectedShapeData?.otherProps?.activeElement !== undefined && ( Date: Sat, 7 Dec 2024 13:43:02 +0100 Subject: [PATCH 02/10] only colors and only selection --- src/pods/properties/properties.pod.tsx | 427 +++++++++++++++---------- 1 file changed, 250 insertions(+), 177 deletions(-) diff --git a/src/pods/properties/properties.pod.tsx b/src/pods/properties/properties.pod.tsx index 7cc793bf..8419b99e 100644 --- a/src/pods/properties/properties.pod.tsx +++ b/src/pods/properties/properties.pod.tsx @@ -17,6 +17,7 @@ import { TextAlignment } from './components/text-alignment'; import { useMemo } from 'react'; import { extractMultiplePropsInCommon } from './properties.business'; import { ShowProp } from './components/show-prop'; +import { iconCollection } from './components/icon-selector/modal/icons'; export const PropertiesPod = () => { const { selectionInfo } = useCanvasContext(); @@ -42,6 +43,9 @@ export const PropertiesPod = () => { selectionInfo?.selectedShapesRefs.current && selectionInfo.selectedShapesRefs.current.length > 0; + const isSingleSelection = + selectionInfo?.selectedShapesRefs?.current?.length === 1; + const multipleSelectionShapeData = useMemo( () => getAllSelectedShapesData(), [selectionInfo.selectedShapesIds] @@ -66,153 +70,207 @@ export const PropertiesPod = () => {

Properties

{hasSelectedShapes && } - { - <> - - updateOtherPropsOnSelected('stroke', color)} - /> - - - - updateOtherPropsOnSelected('strokeStyle', strokeStyle) - } - /> - + <> + + updateOtherPropsOnSelected('stroke', color)} + /> + + + + updateOtherPropsOnSelected('strokeStyle', strokeStyle) + } + /> + - - - updateOtherPropsOnSelected('backgroundColor', color) - } - /> - - - - updateOtherPropsOnSelected('iconSize', iconSize) - } - /> - - {selectedShapeData?.otherProps?.icon && ( - updateOtherPropsOnSelected('icon', icon)} - /> - )} - {selectedShapeData?.otherProps?.textColor && ( - updateOtherPropsOnSelected('textColor', color)} - /> - )} - {selectedShapeData?.otherProps?.checked != undefined && ( - - updateOtherPropsOnSelected('checked', checked) - } - /> - )} - {selectedShapeData?.otherProps?.imageSrc != undefined && ( - - updateOtherPropsOnSelected('imageSrc', imageSrc) - } - /> - )} - {selectedShapeData?.otherProps?.imageBlackAndWhite != undefined && ( - - updateOtherPropsOnSelected( - 'imageBlackAndWhite', - imageBlackAndWhite - ) - } - /> - )} - {selectedShapeData?.otherProps?.progress && ( - - updateOtherPropsOnSelected('progress', progress) - } - /> - )} - {selectedShapeData?.otherProps?.borderRadius && ( - - updateOtherPropsOnSelected('borderRadius', borderRadius) - } - /> - )} - {selectedShapeData?.otherProps?.textAlignment && ( - - updateOtherPropsOnSelected('textAlignment', textAlignment) - } - /> - )} - {selectedShapeData?.otherProps?.fontStyle && ( - - updateOtherPropsOnSelected('fontStyle', fontstyle) - } - /> - )} - {selectedShapeData?.otherProps?.fontVariant && ( - - updateOtherPropsOnSelected('fontVariant', fontvariant) - } - /> - )} + + + updateOtherPropsOnSelected('backgroundColor', color) + } + /> + + + + updateOtherPropsOnSelected('iconSize', iconSize) + } + /> + + + + updateOtherPropsOnSelected('icon', icon)} + /> + + + updateOtherPropsOnSelected('textColor', color)} + /> + + + updateOtherPropsOnSelected('checked', checked)} + /> + + + + updateOtherPropsOnSelected('imageSrc', imageSrc) + } + /> + + + + updateOtherPropsOnSelected( + 'imageBlackAndWhite', + imageBlackAndWhite + ) + } + /> + + + + updateOtherPropsOnSelected('progress', progress) + } + /> + + + + updateOtherPropsOnSelected('borderRadius', borderRadius) + } + /> + + + + updateOtherPropsOnSelected('textAlignment', textAlignment) + } + /> + + + + updateOtherPropsOnSelected('fontStyle', fontstyle) + } + /> + + + + updateOtherPropsOnSelected('fontVariant', fontvariant) + } + /> + + {selectedShapeData?.otherProps?.textDecoration && ( { } /> )} - {selectedShapeData?.otherProps?.fontSize && ( - - updateOtherPropsOnSelected('fontSize', fontSize) - } - /> - )} - - } - {selectedShapeData?.otherProps?.activeElement !== undefined && ( - - updateOtherPropsOnSelected('activeElement', activeElement) - } - /> - )} - {selectedShapeData?.otherProps?.selectedBackgroundColor != undefined && ( - - updateOtherPropsOnSelected('selectedBackgroundColor', color) - } - /> - )} + + + + updateOtherPropsOnSelected('fontSize', fontSize) + } + /> + + + + updateOtherPropsOnSelected('activeElement', activeElement) + } + /> + + + + updateOtherPropsOnSelected('selectedBackgroundColor', color) + } + /> + + ); }; From a5bab2fede9fccb7b3ef8a71d9e41ea43776cbb1 Mon Sep 17 00:00:00 2001 From: Braulio Date: Sat, 7 Dec 2024 23:29:00 +0100 Subject: [PATCH 03/10] fix --- src/core/providers/canvas/canvas.model.ts | 3 +- .../providers/canvas/use-selection.hook.ts | 8 +- .../color-picker/color-picker.component.tsx | 2 +- src/pods/properties/components/show-prop.tsx | 14 ++- src/pods/properties/properties.business.ts | 14 ++- src/pods/properties/properties.model.ts | 15 +++ src/pods/properties/properties.pod.tsx | 108 ++++++++++++++---- 7 files changed, 132 insertions(+), 32 deletions(-) diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index f2a0ac4f..6ec874d7 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -57,7 +57,8 @@ export interface SelectionInfo { // TODO: Update, A. KeyOf B. Move To useSelectionInfo updateOtherPropsOnSelected: ( key: K, - value: OtherProps[K] + value: OtherProps[K], + multipleSelection?: boolean ) => void; } diff --git a/src/core/providers/canvas/use-selection.hook.ts b/src/core/providers/canvas/use-selection.hook.ts index b9f187bc..cffc119f 100644 --- a/src/core/providers/canvas/use-selection.hook.ts +++ b/src/core/providers/canvas/use-selection.hook.ts @@ -239,15 +239,19 @@ export const useSelection = ( // check if it can be applied to multiple data // This is is used to lock temporarily the multiple selection properties // (right side panel) edit, it only will work when there is a single selection - if (index === undefined && selectedShapesIds.length !== 1) { + if (index === undefined || selectedShapesIds.length === 0) { return; } const selectedShapeId = selectedShapesIds[index]; - return getActivePageShapes(document).find( + const activeShape = getActivePageShapes(document).find( shape => shape.id === selectedShapeId ); + + console.log('Active Shape', activeShape); + + return activeShape; }; const getAllSelectedShapesData = (): ShapeModel[] => { diff --git a/src/pods/properties/components/color-picker/color-picker.component.tsx b/src/pods/properties/components/color-picker/color-picker.component.tsx index 92af5d54..806e5aa9 100644 --- a/src/pods/properties/components/color-picker/color-picker.component.tsx +++ b/src/pods/properties/components/color-picker/color-picker.component.tsx @@ -42,7 +42,7 @@ export const ColorPicker: React.FC = props => {

{label}