Skip to content

Commit

Permalink
Merge pull request #605 from Lemoncode/dev
Browse files Browse the repository at this point in the history
multiple selection props editing
  • Loading branch information
brauliodiez authored Dec 8, 2024
2 parents 0b008ae + ecd421f commit 6a3850a
Show file tree
Hide file tree
Showing 7 changed files with 521 additions and 184 deletions.
4 changes: 3 additions & 1 deletion src/core/providers/canvas/canvas.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ 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
updateOtherPropsOnSelected: <K extends keyof OtherProps>(
key: K,
value: OtherProps[K]
value: OtherProps[K],
multipleSelection?: boolean
) => void;
}

Expand Down
78 changes: 57 additions & 21 deletions src/core/providers/canvas/use-selection.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,24 +176,11 @@ export const useSelection = (
);
};

// TODO: Rather implement this using immmer

const updateOtherPropsOnSelected = <K extends keyof OtherProps>(
const updateOtherPropsOnSelectedSingleShape = <K extends keyof OtherProps>(
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[
Expand All @@ -207,22 +194,70 @@ export const useSelection = (
);
};

const updateOtherPropsOnSelectedMutlipleShapes = <K extends keyof OtherProps>(
key: K,
value: OtherProps[K]
) => {
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
);
})
);
};

const updateOtherPropsOnSelected = <K extends keyof OtherProps>(
key: K,
value: OtherProps[K],
multipleSelection: boolean = false
) => {
if (!isPageIndexValid(document) || selectedShapesIds.length === 0) return;

// Single selection case
if (selectedShapesIds.length === 1) {
const selectedShapeId = selectedShapesIds[0];
updateOtherPropsOnSelectedSingleShape(selectedShapeId, key, value);

return;
}

// Multiple selection case
if (multipleSelection) {
updateOtherPropsOnSelectedMutlipleShapes(key, value);
}
};

// Added index, right now we got multiple selection
// if not returning just 0 (first element)
const getSelectedShapeData = (index: number = 0): ShapeModel | undefined => {
// TODO: we will only allow this when there is a single selection
// 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 there is one selected will return that item
// If there are multiple selected will return the first
// In case no selection will return undefined
if (index === undefined || selectedShapesIds.length === 0) {
return;
}

const selectedShapeId = selectedShapesIds[index];

return getActivePageShapes(document).find(
const activeShape = getActivePageShapes(document).find(
shape => shape.id === selectedShapeId
);

return activeShape;
};

const getAllSelectedShapesData = (): ShapeModel[] => {
return getActivePageShapes(document).filter(shape =>
selectedShapesIds.includes(shape.id)
);
};

return {
Expand All @@ -235,6 +270,7 @@ export const useSelection = (
selectedShapesIds,
selectedShapeType,
getSelectedShapeData,
getAllSelectedShapesData,
setZIndexOnSelected,
updateTextOnSelected,
updateOtherPropsOnSelected,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const ColorPicker: React.FC<Props> = props => {
<div className={classes.container}>
<p>{label}</p>
<button
data-color={hexToHsva(color).a === 0 ? 'noColor' : ''}
data-color={!color || hexToHsva(color).a === 0 ? 'noColor' : ''}
className={classes.button}
style={{ backgroundColor: color }}
onClick={togglePicker}
Expand Down
30 changes: 30 additions & 0 deletions src/pods/properties/components/show-prop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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> = props => {
const {
singleSelection,
multipleSelectionPropsInCommon,
propKey,
propValue,
children,
} = props;

const showProp: boolean = useMemo(
() =>
(singleSelection && propValue !== undefined) ||
multipleSelectionPropsInCommon[propKey] !== undefined,
[multipleSelectionPropsInCommon, propKey, propValue]
);

return <>{showProp ? children : null}</>;
};
52 changes: 52 additions & 0 deletions src/pods/properties/properties.business.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { OtherProps, ShapeModel } from '@/core/model';
import {
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
// TODO: Right now we are getting the first default value of the selectedShape
// this may not be accurate, maybe we could check if all values are not the same
// define a default prop for all the entries
/*
const getCommonValueForProperty = (
selectedShapes: ShapeModel[],
prop: keyof OtherProps
): PropsValueTypes => {
const values = selectedShapes.map(
shape => shape.otherProps && shape.otherProps[prop]
);
// TODO: Here is the trick, we should return a default value
// if the commonValue is not se or where it is consumed
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);
if (selectedShapes.length > 1 && selectedShapes[0].otherProps) {
commonProps[prop] = selectedShapes[0].otherProps[prop];
}
}
});

return commonProps;
};
33 changes: 33 additions & 0 deletions src/pods/properties/properties.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { IconInfo, OtherProps } from '@/core/model';

export const multiSelectEnabledProperties: (keyof OtherProps)[] = [
'stroke',
'backgroundColor',
'textColor',
'selectedBackgroundColor',
'strokeStyle',
'fontVariant',
'fontStyle',
'fontSize',
'textDecoration',
'checked',
'icon',
'iconSize',
'imageBlackAndWhite',
'progress',
'borderRadius',
'selectedBackgroundColor',
'textAlignment',
];

export type PropsValueTypes =
| string
| number
| boolean
| number[]
| IconInfo
| undefined;

export interface CommonSelectedPropsAndValues {
[key: string]: PropsValueTypes;
}
Loading

0 comments on commit 6a3850a

Please sign in to comment.