diff --git a/src/common/components/icons/delete-icon.component.tsx b/src/common/components/icons/delete-icon.component.tsx new file mode 100644 index 00000000..f991af72 --- /dev/null +++ b/src/common/components/icons/delete-icon.component.tsx @@ -0,0 +1,15 @@ +export const DeleteIcon = () => { + return ( + + + + ); +}; diff --git a/src/core/providers/canvas/canvas.business.spec.ts b/src/core/providers/canvas/canvas.business.spec.ts new file mode 100644 index 00000000..f252897e --- /dev/null +++ b/src/core/providers/canvas/canvas.business.spec.ts @@ -0,0 +1,168 @@ +import { describe, it, expect } from 'vitest'; +import { removeShapeFromList } from './canvas.business'; +import { ShapeModel } from '@/core/model'; + +describe('canvas.business', () => { + describe('removeShapeFromList', () => { + it('should return an empty array if shapeCollection is empty', () => { + // Arrange + const shapeId = ''; + const shapeCollection: ShapeModel[] = []; + + // Act + const result = removeShapeFromList(shapeId, shapeCollection); + + // Assert + expect(result).toEqual([]); + }); + + it('should return the same array if shapeCollection has elements and shapeId is empty string', () => { + // Arrange + const shapeId = ''; + const shapeCollection: ShapeModel[] = [ + { + id: '1', + x: 0, + y: 0, + width: 10, + height: 10, + type: 'combobox', + allowsInlineEdition: false, + }, + { + id: '2', + x: 1, + y: 1, + width: 20, + height: 20, + type: 'input', + allowsInlineEdition: true, + }, + ]; + + // Act + const result = removeShapeFromList(shapeId, shapeCollection); + + // Assert + expect(result).toHaveLength(2); + expect(result).toEqual(shapeCollection); + }); + + it('should remove the shape with the specified shapeId if it exists in a collection with multiple elements', () => { + // Arrange + const shapeId = '2'; + const shapeCollection: ShapeModel[] = [ + { + id: '1', + x: 0, + y: 0, + width: 10, + height: 10, + type: 'combobox', + allowsInlineEdition: false, + }, + { + id: '2', + x: 1, + y: 1, + width: 20, + height: 20, + type: 'input', + allowsInlineEdition: true, + }, + { + id: '3', + x: 2, + y: 2, + width: 30, + height: 30, + type: 'button', + allowsInlineEdition: false, + }, + { + id: '4', + x: 3, + y: 3, + width: 40, + height: 40, + type: 'checkbox', + allowsInlineEdition: true, + }, + ]; + + // Act + const result = removeShapeFromList(shapeId, shapeCollection); + + // Assert + expect(result).toHaveLength(3); + expect(result).toEqual([ + { + id: '1', + x: 0, + y: 0, + width: 10, + height: 10, + type: 'combobox', + allowsInlineEdition: false, + }, + { + id: '3', + x: 2, + y: 2, + width: 30, + height: 30, + type: 'button', + allowsInlineEdition: false, + }, + { + id: '4', + x: 3, + y: 3, + width: 40, + height: 40, + type: 'checkbox', + allowsInlineEdition: true, + }, + ]); + }); + + it('should return an empty array if the shape with the specified shapeId exists in a collection with a single element', () => { + // Arrange + const shapeId = '1'; + const shapeCollection: ShapeModel[] = [ + { + id: '1', + x: 0, + y: 0, + width: 10, + height: 10, + type: 'combobox', + allowsInlineEdition: false, + }, + ]; + + // Act + const result = removeShapeFromList(shapeId, shapeCollection); + + // Assert + expect(result).toEqual([]); + }); + }); + + it('shapeID contains a value and exists on the shape list (1 element)', () => { + const shapeId = '1'; + const shapeCollection: ShapeModel[] = [ + { + id: '1', + x: 0, + y: 0, + width: 10, + height: 10, + type: 'combobox', + allowsInlineEdition: false, + }, + ]; + const result = removeShapeFromList(shapeId, shapeCollection); + expect(result).toEqual([]); + }); +}); diff --git a/src/core/providers/canvas/canvas.business.ts b/src/core/providers/canvas/canvas.business.ts new file mode 100644 index 00000000..30ef7a00 --- /dev/null +++ b/src/core/providers/canvas/canvas.business.ts @@ -0,0 +1,11 @@ +import { ShapeModel } from '@/core/model'; + +export const removeShapeFromList = ( + shapeId: string, + shapeCollection: ShapeModel[] +): ShapeModel[] => { + if (shapeId === '') { + return shapeCollection; + } + return shapeCollection.filter(shape => shape.id !== shapeId); +}; diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 9da5fd9c..1c7af256 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -30,4 +30,5 @@ export interface CanvasContextModel { updateShapeSizeAndPosition: (id: string, position: Coord, size: Size) => void; updateShapePosition: (id: string, position: Coord) => void; selectionInfo: SelectionInfo; + deleteSelectedShape: () => void; } diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 1f10a97a..3981e7d9 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -4,6 +4,7 @@ import { CanvasContext } from './canvas.context'; import { useSelection } from './use-selection.hook'; import { createShape } from '@/pods/canvas/canvas.model'; import { v4 as uuidv4 } from 'uuid'; +import { removeShapeFromList } from './canvas.business'; interface Props { children: React.ReactNode; @@ -16,6 +17,12 @@ export const CanvasProvider: React.FC = props => { const selectionInfo = useSelection(shapes, setShapes); + const deleteSelectedShape = () => { + setShapes(prevShapes => + removeShapeFromList(selectionInfo.selectedShapeId, prevShapes) + ); + }; + const clearCanvas = () => { setShapes([]); }; @@ -62,6 +69,7 @@ export const CanvasProvider: React.FC = props => { pasteShape, updateShapeSizeAndPosition, updateShapePosition, + deleteSelectedShape, }} > {children} diff --git a/src/pods/toolbar/components/delete-button/delete-button.tsx b/src/pods/toolbar/components/delete-button/delete-button.tsx new file mode 100644 index 00000000..c4ce131a --- /dev/null +++ b/src/pods/toolbar/components/delete-button/delete-button.tsx @@ -0,0 +1,18 @@ +import { DeleteIcon } from '@/common/components/icons/delete-icon.component'; +import { ToolbarButton } from '@/pods/toolbar/components/toolbar-button/toolbar-button'; +import classes from '@/pods/toolbar/toolbar.pod.module.css'; +import { useCanvasContext } from '@/core/providers'; +export const DeleteButton = () => { + const { deleteSelectedShape, selectionInfo } = useCanvasContext(); + + return ( + + + Delete + + ); +}; diff --git a/src/pods/toolbar/components/delete-button/index.ts b/src/pods/toolbar/components/delete-button/index.ts new file mode 100644 index 00000000..b7ab2a9a --- /dev/null +++ b/src/pods/toolbar/components/delete-button/index.ts @@ -0,0 +1 @@ +export * from './delete-button'; diff --git a/src/pods/toolbar/toolbar.pod.tsx b/src/pods/toolbar/toolbar.pod.tsx index e15558ae..ec08b634 100644 --- a/src/pods/toolbar/toolbar.pod.tsx +++ b/src/pods/toolbar/toolbar.pod.tsx @@ -1,3 +1,4 @@ +import { DeleteButton } from './components/delete-button'; import { ZoomInButton, ZoomOutButton, @@ -22,6 +23,7 @@ export const ToolbarPod: React.FC = () => { + );