From 01792466283ab0f4a561c52e1edce3a21f5b94bd Mon Sep 17 00:00:00 2001 From: Braulio Date: Sun, 20 Oct 2024 13:58:08 +0200 Subject: [PATCH 1/6] test passing unit --- .../local-disk/shapes-to-document.mapper.ts | 27 +-- .../shapes-to.document.mapper.spec.ts | 212 +++++++++--------- src/core/local-disk/use-local-disk.hook.ts | 6 +- src/core/providers/canvas/canvas.business.ts | 16 ++ src/core/providers/canvas/canvas.model.ts | 33 ++- src/core/providers/canvas/canvas.provider.tsx | 108 +++++---- .../providers/canvas/use-selection.hook.ts | 77 +++++-- .../components/new-button/new-button.tsx | 2 +- 8 files changed, 282 insertions(+), 199 deletions(-) diff --git a/src/core/local-disk/shapes-to-document.mapper.ts b/src/core/local-disk/shapes-to-document.mapper.ts index 2d01dbbe..4fdd81f4 100644 --- a/src/core/local-disk/shapes-to-document.mapper.ts +++ b/src/core/local-disk/shapes-to-document.mapper.ts @@ -1,36 +1,21 @@ -import { ShapeModel } from '../model'; import { DocumentModel } from '../providers/canvas/canvas.model'; -import { QuickMockFileContract, Page } from './local-disk.model'; +import { QuickMockFileContract } from './local-disk.model'; export const mapFromShapesArrayToQuickMockFileDocument = ( - shapes: ShapeModel[] + fullDocument: DocumentModel ): QuickMockFileContract => { - const pages: Page[] = shapes.reduce((acc, shape) => { - /* - * TODO: Add the correct id, name and version values. - */ - const newPage: Page = { - id: '1', - name: 'default', - shapes: [{ ...shape }], - }; - - return [...acc, newPage]; - }, [] as Page[]); - + // TODO: Serialize the activePageIndex? return { version: '0.1', - pages, + pages: fullDocument.pages, }; }; export const mapFromQuickMockFileDocumentToApplicationDocument = ( fileDocument: QuickMockFileContract ): DocumentModel => { - const shapes: ShapeModel[] = fileDocument.pages.reduce((acc, page) => { - return [...acc, ...page.shapes]; - }, [] as ShapeModel[]); return { - shapes, + activePageIndex: 0, + pages: fileDocument.pages, }; }; diff --git a/src/core/local-disk/shapes-to.document.mapper.spec.ts b/src/core/local-disk/shapes-to.document.mapper.spec.ts index f5c943cb..8106f583 100644 --- a/src/core/local-disk/shapes-to.document.mapper.spec.ts +++ b/src/core/local-disk/shapes-to.document.mapper.spec.ts @@ -8,20 +8,6 @@ import { DocumentModel } from '../providers/canvas/canvas.model'; describe('shapes to document mapper', () => { describe('mapFromShapesArrayToQuickMockFileDocument', () => { - it('Should return a ShapeModel with empty pages', () => { - // Arrange - const shapes: ShapeModel[] = []; - const expectedResult: QuickMockFileContract = { - version: '0.1', - pages: [], - }; - // Act - const result = mapFromShapesArrayToQuickMockFileDocument(shapes); - - // Assert - expect(result).toEqual(expectedResult); - }); - it('Should return a ShapeModel with one pages and shapes', () => { // Arrange const shapes: ShapeModel[] = [ @@ -36,29 +22,30 @@ describe('shapes to document mapper', () => { typeOfTransformer: ['rotate'], }, ]; + + const document: DocumentModel = { + activePageIndex: 0, + pages: [ + { + id: '1', + name: 'default', + shapes: shapes, + }, + ], + }; + const expectedResult: QuickMockFileContract = { version: '0.1', pages: [ { id: '1', name: 'default', - shapes: [ - { - id: '1', - x: 0, - y: 0, - width: 100, - height: 100, - type: 'rectangle', - allowsInlineEdition: false, - typeOfTransformer: ['rotate'], - }, - ], + shapes: shapes, }, ], }; // Act - const result = mapFromShapesArrayToQuickMockFileDocument(shapes); + const result = mapFromShapesArrayToQuickMockFileDocument(document); // Assert expect(result).toEqual(expectedResult); @@ -88,45 +75,31 @@ describe('shapes to document mapper', () => { typeOfTransformer: ['rotate'], }, ]; - const expectedResult: QuickMockFileContract = { - version: '0.1', + + const document: DocumentModel = { + activePageIndex: 0, pages: [ { id: '1', name: 'default', - shapes: [ - { - id: '1', - x: 0, - y: 0, - width: 100, - height: 100, - type: 'rectangle', - allowsInlineEdition: false, - typeOfTransformer: ['rotate'], - }, - ], + shapes: shapes, }, + ], + }; + + const expectedResult: QuickMockFileContract = { + version: '0.1', + pages: [ { id: '1', name: 'default', - shapes: [ - { - id: '2', - x: 0, - y: 0, - width: 100, - height: 100, - type: 'circle', - allowsInlineEdition: true, - typeOfTransformer: ['rotate'], - }, - ], + shapes: shapes, }, ], }; + // Act - const result = mapFromShapesArrayToQuickMockFileDocument(shapes); + const result = mapFromShapesArrayToQuickMockFileDocument(document); // Assert expect(result).toEqual(expectedResult); @@ -138,10 +111,24 @@ describe('shapes to document mapper', () => { //arrange const fileDocument: QuickMockFileContract = { version: '0.1', - pages: [], + pages: [ + { + id: '1', + name: 'default', + shapes: [], + }, + ], }; + const expectedResult: DocumentModel = { - shapes: [], + activePageIndex: 0, + pages: [ + { + id: '1', + name: 'default', + shapes: [], + }, + ], }; //act const result = @@ -162,9 +149,18 @@ describe('shapes to document mapper', () => { }, ], }; + const expectedResult: DocumentModel = { - shapes: [], + activePageIndex: 0, + pages: [ + { + id: '1', + name: 'default', + shapes: [], + }, + ], }; + //act const result = mapFromQuickMockFileDocumentToApplicationDocument(fileDocument); @@ -195,20 +191,29 @@ describe('shapes to document mapper', () => { }, ], }; + const expectedResult: DocumentModel = { - shapes: [ + activePageIndex: 0, + pages: [ { id: '1', - type: 'rectangle', - x: 0, - y: 0, - width: 100, - height: 100, - allowsInlineEdition: false, - typeOfTransformer: ['rotate'], + name: 'default', + shapes: [ + { + id: '1', + type: 'rectangle', + x: 0, + y: 0, + width: 100, + height: 100, + allowsInlineEdition: false, + typeOfTransformer: ['rotate'], + }, + ], }, ], }; + //act const result = mapFromQuickMockFileDocumentToApplicationDocument(fileDocument); @@ -218,67 +223,64 @@ describe('shapes to document mapper', () => { it('Should return a document model with shapes when we feed a file document with two pages and shapes', () => { //arrange + const shapespageA: ShapeModel[] = [ + { + id: '1', + type: 'rectangle', + x: 0, + y: 0, + width: 100, + height: 100, + allowsInlineEdition: false, + typeOfTransformer: ['rotate'], + }, + ]; + + const shapesPageB: ShapeModel[] = [ + { + id: '3', + type: 'browser', + x: 0, + y: 0, + width: 100, + height: 100, + allowsInlineEdition: true, + typeOfTransformer: [' rotate'], + }, + ]; + const fileDocument: QuickMockFileContract = { version: '0.1', pages: [ { id: '1', name: 'default', - shapes: [ - { - id: '1', - type: 'rectangle', - x: 0, - y: 0, - width: 100, - height: 100, - allowsInlineEdition: false, - typeOfTransformer: ['rotate'], - }, - ], + shapes: shapespageA, }, { id: '2', name: 'default', - shapes: [ - { - id: '3', - type: 'browser', - x: 0, - y: 0, - width: 100, - height: 100, - allowsInlineEdition: true, - typeOfTransformer: [' rotate'], - }, - ], + shapes: shapesPageB, }, ], }; + const expectedResult: DocumentModel = { - shapes: [ + activePageIndex: 0, + pages: [ { id: '1', - type: 'rectangle', - x: 0, - y: 0, - width: 100, - height: 100, - allowsInlineEdition: false, - typeOfTransformer: ['rotate'], + name: 'default', + shapes: shapespageA, }, { - id: '3', - type: 'browser', - x: 0, - y: 0, - width: 100, - height: 100, - allowsInlineEdition: true, - typeOfTransformer: [' rotate'], + id: '2', + name: 'default', + shapes: shapesPageB, }, ], }; + //act const result = mapFromQuickMockFileDocumentToApplicationDocument(fileDocument); diff --git a/src/core/local-disk/use-local-disk.hook.ts b/src/core/local-disk/use-local-disk.hook.ts index bdb71bc7..66cc2bb0 100644 --- a/src/core/local-disk/use-local-disk.hook.ts +++ b/src/core/local-disk/use-local-disk.hook.ts @@ -12,10 +12,12 @@ const DEFAULT_FILE_EXTENSION = 'qm'; const DEFAULT_EXTENSION_DESCRIPTION = 'quick mock'; export const useLocalDisk = () => { - const { shapes, loadDocument, fileName, setFileName } = useCanvasContext(); + const { fullDocument, loadDocument, fileName, setFileName } = + useCanvasContext(); const serializeShapes = (): string => { - const quickMockDocument = mapFromShapesArrayToQuickMockFileDocument(shapes); + const quickMockDocument = + mapFromShapesArrayToQuickMockFileDocument(fullDocument); return JSON.stringify(quickMockDocument); }; diff --git a/src/core/providers/canvas/canvas.business.ts b/src/core/providers/canvas/canvas.business.ts index 2458e7db..be5a6b9e 100644 --- a/src/core/providers/canvas/canvas.business.ts +++ b/src/core/providers/canvas/canvas.business.ts @@ -1,4 +1,5 @@ import { ShapeModel } from '@/core/model'; +import { DocumentModel } from './canvas.model'; export const removeShapesFromList = ( shapeIds: string[], @@ -10,3 +11,18 @@ export const removeShapesFromList = ( return shapeCollection.filter(shape => !shapeIds.includes(shape.id)); }; + +export const isPageIndexValid = (document: DocumentModel) => { + return ( + document.activePageIndex !== -1 && + document.activePageIndex < document.pages.length + ); +}; + +export const getActivePageShapes = (document: DocumentModel) => { + if (!isPageIndexValid(document)) { + return []; + } + + return document.pages[document.activePageIndex].shapes; +}; diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 3807f144..e601b72b 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -11,6 +11,28 @@ import { Node, NodeConfig } from 'konva/lib/Node'; export type ZIndexAction = 'top' | 'bottom' | 'up' | 'down'; +export interface Page { + id: string; + name: string; + shapes: ShapeModel[]; +} + +export interface DocumentModel { + pages: Page[]; + activePageIndex: number; +} + +export const createDefaultDocumentModel = (): DocumentModel => ({ + activePageIndex: 0, + pages: [ + { + id: '1', + name: 'Page 1', + shapes: [], + }, + ], +}); + export interface SelectionInfo { transformerRef: React.RefObject; shapeRefs: React.MutableRefObject; @@ -40,7 +62,7 @@ export interface SelectionInfo { export interface CanvasContextModel { shapes: ShapeModel[]; scale: number; - clearCanvas: () => void; + createNewFullDocument: () => void; setScale: React.Dispatch>; addNewShape: ( type: ShapeType, @@ -71,12 +93,5 @@ export interface CanvasContextModel { setIsInlineEditing: React.Dispatch>; fileName: string; setFileName: (fileName: string) => void; + fullDocument: DocumentModel; } - -export interface DocumentModel { - shapes: ShapeModel[]; -} - -export const createDefaultDocumentModel = (): DocumentModel => ({ - shapes: [], -}); diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 6248d1f3..ea34ccff 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -8,8 +8,9 @@ import { useStateWithInterceptor } from './canvas.hook'; import { createDefaultDocumentModel, DocumentModel } from './canvas.model'; import { v4 as uuidv4 } from 'uuid'; import Konva from 'konva'; -import { removeShapesFromList } from './canvas.business'; +import { isPageIndexValid, removeShapesFromList } from './canvas.business'; import { useClipboard } from './use-clipboard.hook'; +import { produce } from 'immer'; interface Props { children: React.ReactNode; @@ -46,10 +47,13 @@ export const CanvasProvider: React.FC = props => { return shape; }); - setDocument(prevDocument => ({ - ...prevDocument, - shapes: [...prevDocument.shapes, ...newShapes], - })); + if (isPageIndexValid(document)) { + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages[lastDocument.activePageIndex].shapes.push(...newShapes); + }) + ); + } // Just select the new pasted shapes // need to wait for the shapes to be rendered (previous set document is async) @@ -71,20 +75,28 @@ export const CanvasProvider: React.FC = props => { }; const { copyShapeToClipboard, pasteShapeFromClipboard, canCopy, canPaste } = - useClipboard(pasteShapes, document.shapes, selectionInfo); + useClipboard( + pasteShapes, + document.pages[document.activePageIndex].shapes, + selectionInfo + ); - const clearCanvas = () => { - setDocument({ shapes: [] }); + const createNewFullDocument = () => { + setDocument(createDefaultDocumentModel()); }; const deleteSelectedShapes = () => { - setDocument(prevDocument => ({ - ...prevDocument, - shapes: removeShapesFromList( - selectionInfo.selectedShapesIds, - prevDocument.shapes - ), - })); + if (isPageIndexValid(document)) { + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages[lastDocument.activePageIndex].shapes = + removeShapesFromList( + selectionInfo.selectedShapesIds, + draft.pages[lastDocument.activePageIndex].shapes + ); + }) + ); + } }; // TODO: instenad of x,y use Coord and reduce the number of arguments @@ -94,14 +106,17 @@ export const CanvasProvider: React.FC = props => { y: number, otherProps?: OtherProps ) => { + if (!isPageIndexValid(document)) { + return ''; + } + const newShape = createShape({ x, y }, type, otherProps); - setDocument(({ shapes }) => { - const newShapes = [...shapes, newShape]; - return { - shapes: newShapes, - }; - }); + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages[document.activePageIndex].shapes.push(newShape); + }) + ); return newShape.id; }; @@ -112,27 +127,43 @@ export const CanvasProvider: React.FC = props => { size: Size, skipHistory: boolean = false ) => { + if (!isPageIndexValid(document)) { + return; + } + if (skipHistory) { - setShapesSkipHistory(({ shapes }) => ({ - shapes: shapes.map(shape => - shape.id === id ? { ...shape, ...position, ...size } : shape - ), - })); + setShapesSkipHistory(fullDocument => { + return produce(fullDocument, draft => { + draft.pages[document.activePageIndex].shapes = draft.pages[ + document.activePageIndex + ].shapes.map(shape => + shape.id === id ? { ...shape, ...position, ...size } : shape + ); + }); + }); } else { - setDocument(({ shapes }) => ({ - shapes: shapes.map(shape => - shape.id === id ? { ...shape, ...position, ...size } : shape - ), - })); + setDocument(fullDocument => { + return produce(fullDocument, draft => { + draft.pages[document.activePageIndex].shapes = draft.pages[ + document.activePageIndex + ].shapes.map(shape => + shape.id === id ? { ...shape, ...position, ...size } : shape + ); + }); + }); } }; const updateShapePosition = (id: string, { x, y }: Coord) => { - setDocument(({ shapes }) => ({ - shapes: shapes.map(shape => - shape.id === id ? { ...shape, x, y } : shape - ), - })); + if (isPageIndexValid(document)) { + setDocument(fullDocument => { + return produce(fullDocument, draft => { + draft.pages[document.activePageIndex].shapes = draft.pages[ + document.activePageIndex + ].shapes.map(shape => (shape.id === id ? { ...shape, x, y } : shape)); + }); + }); + } }; const doUndo = () => { @@ -164,10 +195,10 @@ export const CanvasProvider: React.FC = props => { return ( = props => { setIsInlineEditing, fileName, setFileName, + fullDocument: document, }} > {children} diff --git a/src/core/providers/canvas/use-selection.hook.ts b/src/core/providers/canvas/use-selection.hook.ts index e8c491c3..db63f743 100644 --- a/src/core/providers/canvas/use-selection.hook.ts +++ b/src/core/providers/canvas/use-selection.hook.ts @@ -3,6 +3,8 @@ import Konva from 'konva'; import { OtherProps, ShapeModel, ShapeRefs, ShapeType } from '@/core/model'; import { DocumentModel, SelectionInfo, ZIndexAction } from './canvas.model'; import { performZIndexAction } from './zindex.util'; +import { getActivePageShapes, isPageIndexValid } from './canvas.business'; +import { produce } from 'immer'; export const useSelection = ( document: DocumentModel, @@ -28,7 +30,11 @@ export const useSelection = ( // Remove unused shapes and reset selectedShapeId if it no longer exists useEffect(() => { - const shapes = document.shapes; + if (!isPageIndexValid(document)) { + return; + } + + const shapes = getActivePageShapes(document); const currentIds = shapes.map(shape => shape.id); // 1. First cleanup Refs, let's get the list of shape and if there are any @@ -50,7 +56,7 @@ export const useSelection = ( setSelectedShapeType(null); } } - }, [document.shapes, selectedShapesIds]); + }, [document.pages, selectedShapesIds]); const isDeselectSingleItem = (arrayIds: string[]) => { return ( @@ -81,11 +87,17 @@ export const useSelection = ( type: ShapeType, isUserDoingMultipleSelection: boolean ) => { + // TODO: remove this, it's a temporary hack + // element should be in the list already + //if (selectedShapesRefs.current?.length === 0) { + // return; + // } + // I want to know if the ids is string or array const arrayIds = typeof ids === 'string' ? [ids] : ids; if (!isUserDoingMultipleSelection) { - // No multiple selectio, just replace selection with current selected item(s) + // No multiple selection, just replace selection with current selected item(s) selectedShapesRefs.current = arrayIds.map( id => shapeRefs.current[id].current ); @@ -127,27 +139,37 @@ export const useSelection = ( }; const setZIndexOnSelected = (action: ZIndexAction) => { - setDocument(prevDocument => ({ - shapes: performZIndexAction( - selectedShapesIds, - action, - prevDocument.shapes - ), - })); + if (!isPageIndexValid(document)) return; + + setDocument(prevDocument => + produce(prevDocument, draft => { + draft.pages[prevDocument.activePageIndex].shapes = performZIndexAction( + selectedShapesIds, + action, + getActivePageShapes(prevDocument) + ); + }) + ); }; const updateTextOnSelected = (text: string) => { + if (!isPageIndexValid(document)) return; + // Only when selection is one if (selectedShapesIds.length !== 1) { return; } const selectedShapeId = selectedShapesIds[0]; - setDocument(prevDocument => ({ - shapes: prevDocument.shapes.map(shape => - shape.id === selectedShapeId ? { ...shape, text } : shape - ), - })); + setDocument(prevDocument => + produce(prevDocument, draft => { + draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ + prevDocument.activePageIndex + ].shapes.map(shape => + shape.id === selectedShapeId ? { ...shape, text } : shape + ); + }) + ); }; // TODO: Rather implement this using immmer @@ -156,6 +178,8 @@ export const useSelection = ( 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) @@ -165,13 +189,18 @@ export const useSelection = ( } const selectedShapeId = selectedShapesIds[0]; - setDocument(prevDocument => ({ - shapes: prevDocument.shapes.map(shape => - shape.id === selectedShapeId - ? { ...shape, otherProps: { ...shape.otherProps, [key]: value } } - : shape - ), - })); + + setDocument(prevDocument => + produce(prevDocument, draft => { + draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ + prevDocument.activePageIndex + ].shapes.map(shape => + shape.id === selectedShapeId + ? { ...shape, otherProps: { ...shape.otherProps, [key]: value } } + : shape + ); + }) + ); }; // Added index, right now we got multiple selection @@ -187,7 +216,9 @@ export const useSelection = ( const selectedShapeId = selectedShapesIds[index]; - return document.shapes.find(shape => shape.id === selectedShapeId); + return getActivePageShapes(document).find( + shape => shape.id === selectedShapeId + ); }; return { diff --git a/src/pods/toolbar/components/new-button/new-button.tsx b/src/pods/toolbar/components/new-button/new-button.tsx index fa694298..54501807 100644 --- a/src/pods/toolbar/components/new-button/new-button.tsx +++ b/src/pods/toolbar/components/new-button/new-button.tsx @@ -4,7 +4,7 @@ import { useCanvasContext } from '@/core/providers'; import { ToolbarButton } from '../toolbar-button'; export const NewButton = () => { - const { clearCanvas } = useCanvasContext(); + const { createNewFullDocument: clearCanvas } = useCanvasContext(); const handleClick = () => { clearCanvas(); From abe6c71bef6cb33b1d695806768bce5657dcd564 Mon Sep 17 00:00:00 2001 From: Braulio Date: Sun, 20 Oct 2024 19:15:28 +0200 Subject: [PATCH 2/6] progress thumbnail --- .../front-components/input-shape.tsx | 30 ++++++++++ src/pods/thumb-pages/components/index.ts | 1 + .../components/thumb-page.module.css | 0 .../thumb-pages/components/thumb-page.tsx | 56 +++++++++++++++++++ src/pods/thumb-pages/index.ts | 1 + src/pods/thumb-pages/thumb-pages.module.css | 8 +++ src/pods/thumb-pages/thumb-pages.pod.tsx | 19 +++++++ src/scenes/main.scene.tsx | 5 ++ 8 files changed, 120 insertions(+) create mode 100644 src/pods/thumb-pages/components/index.ts create mode 100644 src/pods/thumb-pages/components/thumb-page.module.css create mode 100644 src/pods/thumb-pages/components/thumb-page.tsx create mode 100644 src/pods/thumb-pages/index.ts create mode 100644 src/pods/thumb-pages/thumb-pages.module.css create mode 100644 src/pods/thumb-pages/thumb-pages.pod.tsx diff --git a/src/common/components/mock-components/front-components/input-shape.tsx b/src/common/components/mock-components/front-components/input-shape.tsx index 2c415044..959c66e8 100644 --- a/src/common/components/mock-components/front-components/input-shape.tsx +++ b/src/common/components/mock-components/front-components/input-shape.tsx @@ -82,3 +82,33 @@ export const InputShape = forwardRef((props, ref) => { ); }); + +/* + + + + + +*/ diff --git a/src/pods/thumb-pages/components/index.ts b/src/pods/thumb-pages/components/index.ts new file mode 100644 index 00000000..07ae2fd2 --- /dev/null +++ b/src/pods/thumb-pages/components/index.ts @@ -0,0 +1 @@ +export * from './thumb-page'; diff --git a/src/pods/thumb-pages/components/thumb-page.module.css b/src/pods/thumb-pages/components/thumb-page.module.css new file mode 100644 index 00000000..e69de29b diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx new file mode 100644 index 00000000..9d5852f4 --- /dev/null +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -0,0 +1,56 @@ +import { ShapeRefs } from '@/core/model'; +import { useCanvasContext } from '@/core/providers'; +import { renderShapeComponent } from '@/pods/canvas/shape-renderer'; +import { calculateCanvasBounds } from '@/pods/toolbar/components/export-button/export-button.utils'; +import { KonvaEventObject } from 'konva/lib/Node'; +import { createRef, useRef } from 'react'; +import { Layer, Stage } from 'react-konva'; + +interface Props { + pageIndex: number; +} + +export const ThumbPage: React.FunctionComponent = props => { + const { pageIndex } = props; + + const { fullDocument } = useCanvasContext(); + + const page = fullDocument.pages[pageIndex]; + const shapes = page.shapes; + const fakeShapeRefs = useRef({}); + + const bounds = calculateCanvasBounds(shapes); + + const canvasSize = { + width: bounds.x + bounds.width, + height: bounds.y + bounds.height, + }; + const scaleFactorX = 250 / canvasSize.width; + const scaleFactorY = 180 / canvasSize.height; + const finalScale = Math.min(scaleFactorX, scaleFactorY); + + console.log('canvasSize', canvasSize); + console.log('scaleFactorX', scaleFactorX); + console.log('scaleFactorY', scaleFactorY); + + return ( +
+ + + {shapes.map(shape => { + if (!fakeShapeRefs.current[shape.id]) { + fakeShapeRefs.current[shape.id] = createRef(); + } + return renderShapeComponent(shape, { + handleSelected: () => {}, + shapeRefs: fakeShapeRefs, + handleDragEnd: + (_: string) => (_: KonvaEventObject) => {}, + handleTransform: () => {}, + }); + })} + + +
+ ); +}; diff --git a/src/pods/thumb-pages/index.ts b/src/pods/thumb-pages/index.ts new file mode 100644 index 00000000..864664f5 --- /dev/null +++ b/src/pods/thumb-pages/index.ts @@ -0,0 +1 @@ +export * from './thumb-pages.pod'; diff --git a/src/pods/thumb-pages/thumb-pages.module.css b/src/pods/thumb-pages/thumb-pages.module.css new file mode 100644 index 00000000..47bc9871 --- /dev/null +++ b/src/pods/thumb-pages/thumb-pages.module.css @@ -0,0 +1,8 @@ +.container { + display: flex; + padding: var(--space-md); + gap: var(--space-md); + align-items: center; + justify-content: center; + flex-wrap: wrap; +} diff --git a/src/pods/thumb-pages/thumb-pages.pod.tsx b/src/pods/thumb-pages/thumb-pages.pod.tsx new file mode 100644 index 00000000..5914a6ab --- /dev/null +++ b/src/pods/thumb-pages/thumb-pages.pod.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import classes from './thumb-pages.module.css'; +import { useCanvasContext } from '@/core/providers'; +import { ThumbPage } from './components'; + +export const ThumbPagesPod: React.FC = () => { + const { fullDocument } = useCanvasContext(); + + return ( +
+ {fullDocument.pages.map((page, index) => ( + + +
{page.name}
+
+ ))} +
+ ); +}; diff --git a/src/scenes/main.scene.tsx b/src/scenes/main.scene.tsx index 086b26f9..93d74453 100644 --- a/src/scenes/main.scene.tsx +++ b/src/scenes/main.scene.tsx @@ -12,12 +12,17 @@ import { } from '@/pods'; import { PropertiesPod } from '@/pods/properties'; import { FooterPod } from '@/pods/footer/footer.pod'; +import { ThumbPagesPod } from '@/pods/thumb-pages'; export const MainScene = () => { return (
+
+ Pages + +
Devices From 236a24fa930ba899208f84fed5fcfd1f2161d948 Mon Sep 17 00:00:00 2001 From: Alber EE <122263897+Alber-Writer@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:13:30 +0200 Subject: [PATCH 3/6] add toolbar undo-redo e2e tests --- e2e/helpers/konva-testing.helpers.ts | 17 +- e2e/helpers/position.helpers.ts | 13 +- e2e/helpers/ui-buttons.helpers.ts | 6 + .../toolbar_undo-redo.spec.ts | 166 ++++++++++++++++++ 4 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 e2e/helpers/ui-buttons.helpers.ts create mode 100644 e2e/ui-functionality/toolbar_undo-redo.spec.ts diff --git a/e2e/helpers/konva-testing.helpers.ts b/e2e/helpers/konva-testing.helpers.ts index 519df69e..dfe19902 100644 --- a/e2e/helpers/konva-testing.helpers.ts +++ b/e2e/helpers/konva-testing.helpers.ts @@ -3,6 +3,7 @@ import { Layer } from 'konva/lib/Layer'; import { Shape } from 'konva/lib/Shape'; import { Group } from 'konva/lib/Group'; import { E2E_CanvasItemKeyAttrs } from './types/e2e-types'; +import { getCanvasBoundingBox } from './position.helpers'; const getLayer = async (page: Page): Promise => await page.evaluate(() => { @@ -77,8 +78,7 @@ export const clickOnCanvasItem = async ( item: E2E_CanvasItemKeyAttrs ) => { const { x, y } = item; - const canvasWindowPos = await page.locator('canvas').boundingBox(); - if (!canvasWindowPos) throw new Error('Canvas is not loaded on ui'); + const canvasWindowPos = await getCanvasBoundingBox(page); await page.mouse.move( canvasWindowPos?.x + x + 20, canvasWindowPos?.y + y + 20 @@ -90,6 +90,19 @@ export const clickOnCanvasItem = async ( return item; }; +export const dbClickOnCanvasItem = async ( + page: Page, + item: E2E_CanvasItemKeyAttrs +) => { + const { x, y } = item; + const canvasWindowPos = await getCanvasBoundingBox(page); + await page.mouse.dblclick( + canvasWindowPos?.x + x + 20, + canvasWindowPos?.y + y + 20 + ); + return item; +}; + export const ctrlClickOverCanvasItems = async ( page: Page, itemList: E2E_CanvasItemKeyAttrs[] diff --git a/e2e/helpers/position.helpers.ts b/e2e/helpers/position.helpers.ts index c0e85df5..6f4ab598 100644 --- a/e2e/helpers/position.helpers.ts +++ b/e2e/helpers/position.helpers.ts @@ -9,6 +9,7 @@ interface Position { export const getLocatorPosition = async ( locator: Locator ): Promise => { + await locator.scrollIntoViewIfNeeded(); const box = (await locator.boundingBox()) || { x: 0, y: 0, @@ -18,6 +19,15 @@ export const getLocatorPosition = async ( return { x: box.x + box.width / 2, y: box.y + box.height / 2 }; }; +export const getCanvasBoundingBox = async (page: Page) => { + const canvasWindowPos = await page.locator('canvas').boundingBox(); + if (canvasWindowPos) { + return canvasWindowPos; + } else { + throw new Error('Canvas is not loaded on ui'); + } +}; + export const dragAndDrop = async ( page: Page, aPosition: Position, @@ -33,8 +43,7 @@ export const addComponentsToCanvas = async ( page: Page, components: string[] ) => { - const canvasPosition = await page.locator('canvas').boundingBox(); - if (!canvasPosition) throw new Error('No canvas found'); + const canvasPosition = await getCanvasBoundingBox(page); for await (const [index, c] of components.entries()) { const component = page.getByAltText(c, { exact: true }); diff --git a/e2e/helpers/ui-buttons.helpers.ts b/e2e/helpers/ui-buttons.helpers.ts new file mode 100644 index 00000000..ed7d2f3a --- /dev/null +++ b/e2e/helpers/ui-buttons.helpers.ts @@ -0,0 +1,6 @@ +import { Page } from '@playwright/test'; + +export const clickUndoUiButton = async (page: Page) => + await page.getByRole('button', { name: 'Undo' }).click(); +export const clickRedoUiButton = async (page: Page) => + await page.getByRole('button', { name: 'Redo' }).click(); diff --git a/e2e/ui-functionality/toolbar_undo-redo.spec.ts b/e2e/ui-functionality/toolbar_undo-redo.spec.ts new file mode 100644 index 00000000..c9ad1c68 --- /dev/null +++ b/e2e/ui-functionality/toolbar_undo-redo.spec.ts @@ -0,0 +1,166 @@ +import { test, expect } from '@playwright/test'; +import { + addComponentsToCanvas, + getWithinCanvasItemList, + getByShapeType, + dbClickOnCanvasItem, + getCanvasBoundingBox, + getShapePosition, +} from '../helpers'; +import { E2E_CanvasItemKeyAttrs } from '../helpers/types/e2e-types'; +import { Group } from 'konva/lib/Group'; +import { + clickRedoUiButton, + clickUndoUiButton, +} from '../helpers/ui-buttons.helpers'; + +test.describe('ToolBar buttons Undo/Redo functionality tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto(''); + }); + + test('Should remove and restore a just dragged into canvas-item, respectively', async ({ + page, + }) => { + //Arrange + const addInputIntoCanvas = ['Input']; + await addComponentsToCanvas(page, addInputIntoCanvas); + + //Undo and check within canvas items + await clickUndoUiButton(page); + const insideCanvasItemList = await getWithinCanvasItemList(page); + + expect(insideCanvasItemList.length).toEqual(0); + + //Redo and check existing item within canvas + await clickRedoUiButton(page); + const updatedInsideCanvasItemList = await getWithinCanvasItemList(page); + + expect(updatedInsideCanvasItemList.length).toEqual(1); + }); + + test('Should remove and restore the last item you just dragged into the canvas', async ({ + page, + }) => { + //Arrange + const addComponentsIntoCanvas = ['Input', 'Combobox']; + await addComponentsToCanvas(page, addComponentsIntoCanvas); + + //Undo and assert there is only one Item within canvas + await clickUndoUiButton(page); + const insideCanvasItemList = await getWithinCanvasItemList(page); + + expect(insideCanvasItemList.length).toEqual(1); + + const firsCanvastItem = await getByShapeType(page, 'input'); + expect(firsCanvastItem).toBeDefined(); + + //Redo and assert both items are contained within the canvas + await clickRedoUiButton(page); + const updatedInsideCanvasItemList = await getWithinCanvasItemList(page); + const secondCanvasItem = await getByShapeType(page, 'combobox'); + + expect(updatedInsideCanvasItemList.length).toEqual(2); + expect(firsCanvastItem).toBeDefined(); + expect(secondCanvasItem).toBeDefined(); + }); + + test('Should reverse and restore an edited text of an Input Component', async ({ + page, + }) => { + //Arrange data and drag an input + const addComponentsIntoCanvas = ['Input']; + const defaultInputPlaceholder = 'Placeholder'; + const updatedText = 'Hello'; + + await addComponentsToCanvas(page, addComponentsIntoCanvas); + const [inputOnCanvas] = (await getWithinCanvasItemList( + page + )) as E2E_CanvasItemKeyAttrs[]; + + //Start Input component inline editing + await dbClickOnCanvasItem(page, inputOnCanvas); + const editableInput = page.locator('input[data-is-inline-edition-on=true]'); + const defaultInputValue = await editableInput.inputValue(); + + await editableInput.fill(updatedText); + const updatedInputValue = await editableInput.inputValue(); + + //Undo edit and assert text is reversed + await clickUndoUiButton(page); + expect(defaultInputValue).toEqual(defaultInputPlaceholder); + + //Redo edit and assert that input contains the restored updated text + await clickRedoUiButton(page); + expect(updatedInputValue).toEqual(updatedText); + }); + + test('Should restore the item position to its previous placement', async ({ + page, + }) => { + //Arrange data and drag an input into canvas + const componentToAddintoCanvas = ['Input']; + await addComponentsToCanvas(page, componentToAddintoCanvas); + + const { x: canvasXStart, y: canvasYStart } = + await getCanvasBoundingBox(page); + + const inputElement = (await getByShapeType(page, 'input')) as Group; + + const inputInitialPosition = await getShapePosition(inputElement); + const inputModifiedPosition = { + x: inputInitialPosition.x + canvasXStart + 200, + y: inputInitialPosition.y + canvasYStart, + }; + + //Displace item within the canvas + await page.mouse.down(); + await page.mouse.move(inputModifiedPosition.x, inputModifiedPosition.y); + await page.mouse.up(); + + //Undo and assert that the item is placed in its initial position + await clickUndoUiButton(page); + const finalInputPosition = await getShapePosition(inputElement); + + expect(finalInputPosition).toEqual(inputInitialPosition); + }); + + test('Should undo and redo, backward and forward severals steps consistently', async ({ + page, + }) => { + //Arrange data and drag an items into canvas + const componentsToAddIntoCanvas = ['Input', 'Combobox']; + await addComponentsToCanvas(page, componentsToAddIntoCanvas); + + await page.getByText('Rich Components').click(); + const richComponentsToAddintoCanvas = ['Accordion']; + await addComponentsToCanvas(page, richComponentsToAddintoCanvas); + + //Assert there are 3 items within the canvas + const itemsQtyWithinCanvas_step1 = (await getWithinCanvasItemList(page)) + .length; + + expect(itemsQtyWithinCanvas_step1).toEqual(3); + + //x3 undo + await clickUndoUiButton(page); + await clickUndoUiButton(page); + await clickUndoUiButton(page); + + //Assert there are no items within the canvas + const itemsQtyWithinCanvas_step2 = (await getWithinCanvasItemList(page)) + .length; + + expect(itemsQtyWithinCanvas_step2).toEqual(0); + + //x3 redo + await clickRedoUiButton(page); + await clickRedoUiButton(page); + await clickRedoUiButton(page); + + //Assert there are again 3 items within the canvas + const itemsQtyWithinCanvas_step3 = (await getWithinCanvasItemList(page)) + .length; + expect(itemsQtyWithinCanvas_step3).toEqual(3); + }); +}); From 9d623e9f470ab86d1cc6b982e9f7f87e8a6cda90 Mon Sep 17 00:00:00 2001 From: Fran Lopez Date: Wed, 23 Oct 2024 19:25:17 +0200 Subject: [PATCH 4/6] add inline editing to the window container title --- .../front-containers/browserwindow-shape.tsx | 8 ++++---- src/pods/canvas/model/inline-editable.model.ts | 3 +++ .../simple-container/browserwindow.renderer.tsx | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/components/mock-components/front-containers/browserwindow-shape.tsx b/src/common/components/mock-components/front-containers/browserwindow-shape.tsx index 524d8937..a40eca77 100644 --- a/src/common/components/mock-components/front-containers/browserwindow-shape.tsx +++ b/src/common/components/mock-components/front-containers/browserwindow-shape.tsx @@ -21,7 +21,7 @@ export const getBrowserWindowShapeSizeRestrictions = const shapeType: ShapeType = 'browser'; export const BrowserWindowShape = forwardRef((props, ref) => { - const { x, y, width, height, id, onSelected, ...shapeProps } = props; + const { x, y, width, height, id, onSelected, text, ...shapeProps } = props; const restrictedSize = fitSizeToShapeSizeRestrictions( browserWindowShapeSizeRestrictions, width, @@ -114,9 +114,9 @@ export const BrowserWindowShape = forwardRef((props, ref) => { ([ 'tabsBar', 'tooltip', 'datepickerinput', + 'browser', ]); // Check if a shape type allows inline editing @@ -66,6 +67,7 @@ const shapeTypesWithDefaultText = new Set([ 'buttonBar', 'tabsBar', 'datepickerinput', + 'browser', ]); // Map of ShapeTypes to their default text values @@ -98,6 +100,7 @@ const defaultTextValueMap: Partial> = { buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', datepickerinput: new Date().toLocaleDateString(), + browser: 'https://example.com', }; export const generateDefaultTextValue = ( diff --git a/src/pods/canvas/shape-renderer/simple-container/browserwindow.renderer.tsx b/src/pods/canvas/shape-renderer/simple-container/browserwindow.renderer.tsx index ccfd8809..aadcf152 100644 --- a/src/pods/canvas/shape-renderer/simple-container/browserwindow.renderer.tsx +++ b/src/pods/canvas/shape-renderer/simple-container/browserwindow.renderer.tsx @@ -24,6 +24,8 @@ export const renderBrowserWindow = ( onDragEnd={handleDragEnd(shape.id)} onTransform={handleTransform} onTransformEnd={handleTransform} + isEditable={shape.allowsInlineEdition} + text={shape.text} /> ); }; From ece5b0fa82d8d6b0e24b53c63019b4270b26fd13 Mon Sep 17 00:00:00 2001 From: Braulio Date: Thu, 24 Oct 2024 11:42:51 +0200 Subject: [PATCH 5/6] fixes --- src/core/providers/canvas/canvas.model.ts | 3 ++ src/core/providers/canvas/canvas.provider.tsx | 29 ++++++++++++++++++- .../providers/canvas/use-selection.hook.ts | 23 +++++++++------ .../thumb-pages/components/thumb-page.tsx | 12 ++++---- src/pods/thumb-pages/thumb-pages.pod.tsx | 13 +++++++-- 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index e601b72b..3bad17c5 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -46,6 +46,7 @@ export interface SelectionInfo { | Konva.KonvaEventObject | Konva.KonvaEventObject ) => void; + clearSelection: () => void; selectedShapesRefs: React.MutableRefObject[] | null>; selectedShapesIds: string[]; selectedShapeType: ShapeType | null; @@ -94,4 +95,6 @@ export interface CanvasContextModel { fileName: string; setFileName: (fileName: string) => void; fullDocument: DocumentModel; + addNewPage: () => void; + setActivePage: (pageId: string) => void; } diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index ea34ccff..4f3a51a0 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -41,6 +41,31 @@ export const CanvasProvider: React.FC = props => { const selectionInfo = useSelection(document, setDocument); + const addNewPage = () => { + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages.push({ + id: uuidv4(), + name: `Page ${draft.pages.length + 1}`, + shapes: [], + }); + }) + ); + }; + + const setActivePage = (pageId: string) => { + selectionInfo.clearSelection(); + selectionInfo.shapeRefs.current = {}; + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.activePageIndex = draft.pages.findIndex( + page => page.id === pageId + ); + console.log(draft.activePageIndex); + }) + ); + }; + const pasteShapes = (shapes: ShapeModel[]) => { const newShapes: ShapeModel[] = shapes.map(shape => { shape.id = uuidv4(); @@ -114,7 +139,7 @@ export const CanvasProvider: React.FC = props => { setDocument(lastDocument => produce(lastDocument, draft => { - draft.pages[document.activePageIndex].shapes.push(newShape); + draft.pages[lastDocument.activePageIndex].shapes.push(newShape); }) ); @@ -219,6 +244,8 @@ export const CanvasProvider: React.FC = props => { fileName, setFileName, fullDocument: document, + addNewPage, + setActivePage, }} > {children} diff --git a/src/core/providers/canvas/use-selection.hook.ts b/src/core/providers/canvas/use-selection.hook.ts index db63f743..e5ca7ac9 100644 --- a/src/core/providers/canvas/use-selection.hook.ts +++ b/src/core/providers/canvas/use-selection.hook.ts @@ -87,11 +87,11 @@ export const useSelection = ( type: ShapeType, isUserDoingMultipleSelection: boolean ) => { - // TODO: remove this, it's a temporary hack - // element should be in the list already - //if (selectedShapesRefs.current?.length === 0) { - // return; - // } + // When chaging active pages, the refs are not yet updated + // check if this is something temporary or final solution + if (Object.keys(shapeRefs.current).length === 0) { + return; + } // I want to know if the ids is string or array const arrayIds = typeof ids === 'string' ? [ids] : ids; @@ -125,16 +125,20 @@ export const useSelection = ( setSelectedShapeType(type); }; + const clearSelection = () => { + transformerRef.current?.nodes([]); + selectedShapesRefs.current = []; + setSelectedShapesIds([]); + setSelectedShapeType(null); + }; + const handleClearSelection = ( mouseEvent?: | Konva.KonvaEventObject | Konva.KonvaEventObject ) => { if (!mouseEvent || mouseEvent.target === mouseEvent.target.getStage()) { - transformerRef.current?.nodes([]); - selectedShapesRefs.current = []; - setSelectedShapesIds([]); - setSelectedShapeType(null); + clearSelection(); } }; @@ -226,6 +230,7 @@ export const useSelection = ( shapeRefs, handleSelected, handleClearSelection, + clearSelection, selectedShapesRefs, selectedShapesIds, selectedShapeType, diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index 9d5852f4..f245fe39 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -8,10 +8,11 @@ import { Layer, Stage } from 'react-konva'; interface Props { pageIndex: number; + onSetActivePage: (pageId: string) => void; } export const ThumbPage: React.FunctionComponent = props => { - const { pageIndex } = props; + const { pageIndex, onSetActivePage } = props; const { fullDocument } = useCanvasContext(); @@ -29,12 +30,11 @@ export const ThumbPage: React.FunctionComponent = props => { const scaleFactorY = 180 / canvasSize.height; const finalScale = Math.min(scaleFactorX, scaleFactorY); - console.log('canvasSize', canvasSize); - console.log('scaleFactorX', scaleFactorX); - console.log('scaleFactorY', scaleFactorY); - return ( -
+
onSetActivePage(page.id)} + > {shapes.map(shape => { diff --git a/src/pods/thumb-pages/thumb-pages.pod.tsx b/src/pods/thumb-pages/thumb-pages.pod.tsx index 5914a6ab..d71e8f5d 100644 --- a/src/pods/thumb-pages/thumb-pages.pod.tsx +++ b/src/pods/thumb-pages/thumb-pages.pod.tsx @@ -4,16 +4,25 @@ import { useCanvasContext } from '@/core/providers'; import { ThumbPage } from './components'; export const ThumbPagesPod: React.FC = () => { - const { fullDocument } = useCanvasContext(); + const { fullDocument, addNewPage, setActivePage } = useCanvasContext(); + + const handleAddNewPage = () => { + addNewPage(); + }; + + const handleSetActivePage = (pageId: string) => { + setActivePage(pageId); + }; return (
{fullDocument.pages.map((page, index) => ( - +
{page.name}
))} +
); }; From cef287446fe840a109ddc5258af0cb1b04d4d344 Mon Sep 17 00:00:00 2001 From: oleojake Date: Fri, 25 Oct 2024 15:48:26 +0200 Subject: [PATCH 6/6] #121 added id for konva stage --- e2e/helpers/konva-testing.helpers.ts | 3 ++- e2e/helpers/position.helpers.ts | 3 ++- e2e/selection/shape-selection.spec.ts | 3 ++- src/pods/canvas/canvas.pod.tsx | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/e2e/helpers/konva-testing.helpers.ts b/e2e/helpers/konva-testing.helpers.ts index 519df69e..fc2ce6c4 100644 --- a/e2e/helpers/konva-testing.helpers.ts +++ b/e2e/helpers/konva-testing.helpers.ts @@ -77,7 +77,8 @@ export const clickOnCanvasItem = async ( item: E2E_CanvasItemKeyAttrs ) => { const { x, y } = item; - const canvasWindowPos = await page.locator('canvas').boundingBox(); + const stageCanvas = await page.locator('#konva-stage canvas').first(); + const canvasWindowPos = await stageCanvas.boundingBox(); if (!canvasWindowPos) throw new Error('Canvas is not loaded on ui'); await page.mouse.move( canvasWindowPos?.x + x + 20, diff --git a/e2e/helpers/position.helpers.ts b/e2e/helpers/position.helpers.ts index f2bc5a86..fb824f40 100644 --- a/e2e/helpers/position.helpers.ts +++ b/e2e/helpers/position.helpers.ts @@ -34,7 +34,8 @@ export const addComponentsToCanvas = async ( page: Page, components: string[] ) => { - const canvasPosition = await page.locator('canvas').boundingBox(); + const stageCanvas = await page.locator('#konva-stage canvas').first(); + const canvasPosition = await stageCanvas.boundingBox(); if (!canvasPosition) throw new Error('No canvas found'); for await (const [index, c] of components.entries()) { diff --git a/e2e/selection/shape-selection.spec.ts b/e2e/selection/shape-selection.spec.ts index ad9a7b93..cc352ccc 100644 --- a/e2e/selection/shape-selection.spec.ts +++ b/e2e/selection/shape-selection.spec.ts @@ -46,7 +46,8 @@ test('drop shape in canvas, click on canvas, drop diselected', async ({ const inputShape = (await getByShapeType(page, 'input')) as Group; expect(inputShape).toBeDefined(); - await page.click('canvas'); + //Click Away + await page.mouse.click(800, 130); const transformer = await getTransformer(page); expect(transformer).toBeDefined(); diff --git a/src/pods/canvas/canvas.pod.tsx b/src/pods/canvas/canvas.pod.tsx index 9363dd00..930989ff 100644 --- a/src/pods/canvas/canvas.pod.tsx +++ b/src/pods/canvas/canvas.pod.tsx @@ -132,6 +132,7 @@ export const CanvasPod = () => { onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} + id="konva-stage" // data-id did not work for some reason > {