From 5c53bf4628fdc2853a8a3057c7a45443a23aaab7 Mon Sep 17 00:00:00 2001 From: manugallegob Date: Sat, 19 Oct 2024 14:13:52 +0200 Subject: [PATCH 01/27] subida inicial de archivos para timepickerinline --- .../mock-components/front-components/index.ts | 2 +- .../front-components/timepickerinput/index.ts | 1 + .../timepickerinput-shape.business.ts | 40 ++++++++++ .../timepickerinput-shape.tsx | 75 +++++++++++++------ .../canvas/model/inline-editable.model.ts | 3 + .../timepickerinput.renderer.tsx | 2 + .../active-element-selector.component.tsx | 2 +- 7 files changed, 100 insertions(+), 25 deletions(-) create mode 100644 src/common/components/mock-components/front-components/timepickerinput/index.ts create mode 100644 src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts rename src/common/components/mock-components/front-components/{ => timepickerinput}/timepickerinput-shape.tsx (52%) diff --git a/src/common/components/mock-components/front-components/index.ts b/src/common/components/mock-components/front-components/index.ts index ae303ff1..fe28a671 100644 --- a/src/common/components/mock-components/front-components/index.ts +++ b/src/common/components/mock-components/front-components/index.ts @@ -7,7 +7,7 @@ export * from './progressbar-shape'; export * from './listbox'; export * from './datepickerinput-shape'; export * from './button-shape'; -export * from './timepickerinput-shape'; +export * from './timepickerinput/timepickerinput-shape'; export * from './radiobutton-shape'; export * from './icon-shape'; export * from './verticalscrollbar-shape'; diff --git a/src/common/components/mock-components/front-components/timepickerinput/index.ts b/src/common/components/mock-components/front-components/timepickerinput/index.ts new file mode 100644 index 00000000..b2663481 --- /dev/null +++ b/src/common/components/mock-components/front-components/timepickerinput/index.ts @@ -0,0 +1 @@ +export * from './timepickerinput-shape.tsx'; diff --git a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts new file mode 100644 index 00000000..929a53a0 --- /dev/null +++ b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts @@ -0,0 +1,40 @@ +export const getCurrentTime = () => { + const currentDate = new Date(); + let hour = currentDate.getHours().toString(); + let minutes = currentDate.getMinutes().toString(); + hour = hour.length < 2 ? '0' + hour : hour; + minutes = minutes.length < 2 ? '0' + minutes : minutes; + return `${hour},${minutes}`; +}; + +export const splitCSVContent = (csvContent: string): string[] => { + const splitedCsvContent = csvContent + .trim() + .split(',') + .map(el => el.trim()) + .map(header => { + const cleanedHeaderText = header.replace(/[^0-9]/g, '').trim(); + return cleanedHeaderText; + }); + return splitedCsvContent; +}; + +export const setTime = (csvData: string[]) => { + if (csvData.length > 2) csvData = csvData.slice(0, 2); + + if (csvData.length === 1) csvData.push('00'); + + let [hora, minutos] = csvData; + + if (hora.length === 1) hora = '0' + hora; + + if (hora.length > 2 || (hora.length === 2 && hora > '23')) hora = '23'; + + if (hora === '') hora = '00'; + + if (minutos.length > 2 || minutos > '59' || minutos === '') minutos = '00'; + + if (minutos.length < 1) minutos = '0' + minutos; + + return [hora, minutos]; +}; diff --git a/src/common/components/mock-components/front-components/timepickerinput-shape.tsx b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx similarity index 52% rename from src/common/components/mock-components/front-components/timepickerinput-shape.tsx rename to src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx index a4b64adc..18d729f2 100644 --- a/src/common/components/mock-components/front-components/timepickerinput-shape.tsx +++ b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx @@ -1,11 +1,12 @@ import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; import { forwardRef } from 'react'; -import { ShapeProps } from '../shape.model'; +import { ShapeProps } from '../../shape.model'; import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions'; import { Group, Rect, Text } from 'react-konva'; -import { BASIC_SHAPE } from './shape.const'; -import { useShapeProps } from '../../shapes/use-shape-props.hook'; -import { useGroupShapeProps } from '../mock-components.utils'; +import { BASIC_SHAPE } from '../shape.const'; +import { useShapeProps } from '../../../shapes/use-shape-props.hook'; +import { useGroupShapeProps } from '../../mock-components.utils'; +import { splitCSVContent, setTime } from './timepickerinput-shape.business'; const timepickerInputShapeRestrictions: ShapeSizeRestrictions = { minWidth: 100, @@ -23,8 +24,18 @@ export const getTimepickerInputShapeSizeRestrictions = export const TimepickerInputShape = forwardRef( (props, ref) => { - const { x, y, width, height, id, onSelected, otherProps, ...shapeProps } = - props; + const { + x, + y, + width, + height, + id, + text, + onSelected, + otherProps, + ...shapeProps + } = props; + const restrictedSize = fitSizeToShapeSizeRestrictions( timepickerInputShapeRestrictions, width, @@ -32,6 +43,9 @@ export const TimepickerInputShape = forwardRef( ); const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; + const csvData2 = splitCSVContent(text); + const time = setTime(csvData2); + const separatorPadding = 3; // Extra padding for spacers const separator1X = restrictedWidth / 3; const separator2X = (2 * restrictedWidth) / 3; @@ -48,6 +62,8 @@ export const TimepickerInputShape = forwardRef( ref ); + const dynamicTabWidth = restrictedWidth / time.length; + return ( {/* input frame */} @@ -63,23 +79,36 @@ export const TimepickerInputShape = forwardRef( fill={fill} /> - {/* Separators : */} - - + {time.map((time, index) => ( + + + {index < time.length - 1 && ( + + )} + + ))} ); } diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts index a32cf96b..f3eeb91e 100644 --- a/src/pods/canvas/model/inline-editable.model.ts +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -30,6 +30,7 @@ const inlineEditableShapes = new Set([ 'buttonBar', 'tabsBar', 'tooltip', + 'timepickerinput', ]); // Check if a shape type allows inline editing @@ -64,6 +65,7 @@ const shapeTypesWithDefaultText = new Set([ 'appBar', 'buttonBar', 'tabsBar', + 'timepickerinput', ]); // Map of ShapeTypes to their default text values @@ -95,6 +97,7 @@ const defaultTextValueMap: Partial> = { appBar: 'AppBar', buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', + timepickerinput: 'hh, mm', }; export const generateDefaultTextValue = ( diff --git a/src/pods/canvas/shape-renderer/simple-component/timepickerinput.renderer.tsx b/src/pods/canvas/shape-renderer/simple-component/timepickerinput.renderer.tsx index 99bc8425..6476b6b6 100644 --- a/src/pods/canvas/shape-renderer/simple-component/timepickerinput.renderer.tsx +++ b/src/pods/canvas/shape-renderer/simple-component/timepickerinput.renderer.tsx @@ -25,6 +25,8 @@ export const renderTimepickerinput = ( onDragEnd={handleDragEnd(shape.id)} onTransform={handleTransform} onTransformEnd={handleTransform} + isEditable={shape.allowsInlineEdition} + text={shape.text} otherProps={shape.otherProps} /> ); 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 dd827294..9b0c6b71 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 @@ -27,7 +27,7 @@ export const ActiveElementSelector: React.FC = ({ // Checking whether the type is tabsBar and parsing the text const isElementTypeSupported = - type === 'tabsBar' || 'buttonBar' || 'horizontal-menu'; + type === 'tabsBar' || 'buttonBar' || 'horizontal-menu' || 'timepickerinput'; const elementNames = isElementTypeSupported && text ? extractElementNames(text) : []; From dde3c838b639352ec9d00b0d1d7ad46145f5f498 Mon Sep 17 00:00:00 2001 From: manugallegob Date: Sat, 19 Oct 2024 14:15:45 +0200 Subject: [PATCH 02/27] timepickerinput file components --- .../timepickerinput/timepickerinput-shape.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx index 18d729f2..64ae9349 100644 --- a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx +++ b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx @@ -47,8 +47,8 @@ export const TimepickerInputShape = forwardRef( const time = setTime(csvData2); const separatorPadding = 3; // Extra padding for spacers - const separator1X = restrictedWidth / 3; - const separator2X = (2 * restrictedWidth) / 3; + // const separator1X = restrictedWidth / 3; + // const separator2X = (2 * restrictedWidth) / 3; const { stroke, strokeStyle, fill, borderRadius } = useShapeProps( otherProps, From 108336465caff7f71eddd5d1b8cb84048c8b91f7 Mon Sep 17 00:00:00 2001 From: manugallegob Date: Sun, 20 Oct 2024 13:47:08 +0200 Subject: [PATCH 03/27] refactor timepicker svg and new component behavior --- public/widgets/timepicker.svg | 36 ++++-- .../datepickerinput-shape.tsx | 10 ++ .../timepickerinput-shape.business.ts | 49 +++----- .../timepickerinput/timepickerinput-shape.tsx | 110 +++++++++++------- .../canvas/model/inline-editable.model.ts | 2 +- 5 files changed, 122 insertions(+), 85 deletions(-) diff --git a/public/widgets/timepicker.svg b/public/widgets/timepicker.svg index d71771ce..fe80aecf 100644 --- a/public/widgets/timepicker.svg +++ b/public/widgets/timepicker.svg @@ -1,13 +1,25 @@ - - - - - - - : - - - : - + + + + + + Time + + + + + hh:mm + + + + + + + + + + + + + diff --git a/src/common/components/mock-components/front-components/datepickerinput-shape.tsx b/src/common/components/mock-components/front-components/datepickerinput-shape.tsx index 17d64b21..e3b1d90a 100644 --- a/src/common/components/mock-components/front-components/datepickerinput-shape.tsx +++ b/src/common/components/mock-components/front-components/datepickerinput-shape.tsx @@ -111,6 +111,16 @@ export const DatepickerInputShape = forwardRef( ellipsis={true} wrap="none" /> + + {/* Calendar Icon */} { - const currentDate = new Date(); - let hour = currentDate.getHours().toString(); - let minutes = currentDate.getMinutes().toString(); - hour = hour.length < 2 ? '0' + hour : hour; - minutes = minutes.length < 2 ? '0' + minutes : minutes; - return `${hour},${minutes}`; -}; - export const splitCSVContent = (csvContent: string): string[] => { const splitedCsvContent = csvContent .trim() - .split(',') - .map(el => el.trim()) - .map(header => { - const cleanedHeaderText = header.replace(/[^0-9]/g, '').trim(); - return cleanedHeaderText; - }); + .split(/[:|,]/) + .map(el => el.trim()); return splitedCsvContent; }; export const setTime = (csvData: string[]) => { - if (csvData.length > 2) csvData = csvData.slice(0, 2); - - if (csvData.length === 1) csvData.push('00'); - let [hora, minutos] = csvData; - - if (hora.length === 1) hora = '0' + hora; - - if (hora.length > 2 || (hora.length === 2 && hora > '23')) hora = '23'; - - if (hora === '') hora = '00'; - - if (minutos.length > 2 || minutos > '59' || minutos === '') minutos = '00'; - - if (minutos.length < 1) minutos = '0' + minutos; - - return [hora, minutos]; + if (csvData.length < 2) { + return true; + } + if (csvData[0] !== 'hh' || csvData[1] !== 'mm') { + if ( + csvData.length > 2 || + hora.length !== 2 || + hora === '' || + hora > '23' || + minutos.length !== 2 || + minutos === '' || + minutos > '59' + ) { + return true; + } + } }; diff --git a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx index 64ae9349..c40f9f3c 100644 --- a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx +++ b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx @@ -2,12 +2,14 @@ import { ShapeSizeRestrictions, ShapeType } from '@/core/model'; import { forwardRef } from 'react'; import { ShapeProps } from '../../shape.model'; import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions'; -import { Group, Rect, Text } from 'react-konva'; +import { Group, Rect, Text, Image } from 'react-konva'; import { BASIC_SHAPE } from '../shape.const'; import { useShapeProps } from '../../../shapes/use-shape-props.hook'; import { useGroupShapeProps } from '../../mock-components.utils'; import { splitCSVContent, setTime } from './timepickerinput-shape.business'; +import clockIconSrc from '/icons/clock.svg'; + const timepickerInputShapeRestrictions: ShapeSizeRestrictions = { minWidth: 100, minHeight: 50, @@ -43,13 +45,6 @@ export const TimepickerInputShape = forwardRef( ); const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const csvData2 = splitCSVContent(text); - const time = setTime(csvData2); - - const separatorPadding = 3; // Extra padding for spacers - // const separator1X = restrictedWidth / 3; - // const separator2X = (2 * restrictedWidth) / 3; - const { stroke, strokeStyle, fill, borderRadius } = useShapeProps( otherProps, BASIC_SHAPE @@ -62,11 +57,24 @@ export const TimepickerInputShape = forwardRef( ref ); - const dynamicTabWidth = restrictedWidth / time.length; + const csvData = splitCSVContent(text); + let isError = setTime(csvData); + + const iconWidth = 25; + const availableWidth = restrictedWidth - iconWidth - 20; + const fontSize = Math.min( + availableWidth * 0.2, + restrictedHeight * 0.35, + 20 + ); + const labelFontSize = Math.min(restrictedHeight * 0.3, 12); + + const clockIcon = new window.Image(); + clockIcon.src = clockIconSrc; return ( - {/* input frame */} + {/* External Rectangle */} ( height={restrictedHeight} cornerRadius={borderRadius} stroke={stroke} - strokeWidth={2} dash={strokeStyle} + strokeWidth={2} fill={fill} /> + {/* Background of Date Label */} + + {/* Label "Date" */} + + {/* Main Text */} + + {isError && ( + + )} - {time.map((time, index) => ( - - - {index < time.length - 1 && ( - - )} - - ))} + {/* Calendar Icon */} + ); } diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts index 6ae2de79..1508e5db 100644 --- a/src/pods/canvas/model/inline-editable.model.ts +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -99,7 +99,7 @@ const defaultTextValueMap: Partial> = { appBar: 'AppBar', buttonBar: 'Button 1, Button 2, Button 3', tabsBar: 'Tab 1, Tab 2, Tab 3', - timepickerinput: 'hh, mm', + timepickerinput: 'hh:mm', datepickerinput: new Date().toLocaleDateString(), }; From 01792466283ab0f4a561c52e1edce3a21f5b94bd Mon Sep 17 00:00:00 2001 From: Braulio Date: Sun, 20 Oct 2024 13:58:08 +0200 Subject: [PATCH 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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 2ffde89044c96331157b8632aa056538dd02c8f5 Mon Sep 17 00:00:00 2001 From: IonutGabi Date: Fri, 25 Oct 2024 11:28:43 +0200 Subject: [PATCH 09/27] Add new icons of the set --- public/icons/addlist.svg | 1 + public/icons/help.svg | 1 + public/icons/listchecks.svg | 1 + public/icons/listdashes.svg | 1 + public/icons/listheart.svg | 1 + public/icons/listnumbers.svg | 1 + public/icons/liststar.svg | 1 + public/icons/searchlist.svg | 1 + public/icons/shield.svg | 1 + public/icons/shieldcheck.svg | 1 + public/icons/shieldslash.svg | 1 + public/icons/star.svg | 1 + public/icons/textaligncenter.svg | 1 + public/icons/textalignjustify.svg | 1 + public/icons/textalignleft.svg | 1 + public/icons/textalignright.svg | 1 + public/icons/textindent.svg | 1 + public/icons/upload.svg | 1 + public/icons/warning.svg | 1 + public/icons/warningcircle.svg | 1 + .../components/icon-selector/modal/icons.ts | 120 ++++++++++++++++++ 21 files changed, 140 insertions(+) create mode 100644 public/icons/addlist.svg create mode 100644 public/icons/help.svg create mode 100644 public/icons/listchecks.svg create mode 100644 public/icons/listdashes.svg create mode 100644 public/icons/listheart.svg create mode 100644 public/icons/listnumbers.svg create mode 100644 public/icons/liststar.svg create mode 100644 public/icons/searchlist.svg create mode 100644 public/icons/shield.svg create mode 100644 public/icons/shieldcheck.svg create mode 100644 public/icons/shieldslash.svg create mode 100644 public/icons/star.svg create mode 100644 public/icons/textaligncenter.svg create mode 100644 public/icons/textalignjustify.svg create mode 100644 public/icons/textalignleft.svg create mode 100644 public/icons/textalignright.svg create mode 100644 public/icons/textindent.svg create mode 100644 public/icons/upload.svg create mode 100644 public/icons/warning.svg create mode 100644 public/icons/warningcircle.svg diff --git a/public/icons/addlist.svg b/public/icons/addlist.svg new file mode 100644 index 00000000..e8e1e1b4 --- /dev/null +++ b/public/icons/addlist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/help.svg b/public/icons/help.svg new file mode 100644 index 00000000..6097ec58 --- /dev/null +++ b/public/icons/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/listchecks.svg b/public/icons/listchecks.svg new file mode 100644 index 00000000..e447fa2b --- /dev/null +++ b/public/icons/listchecks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/listdashes.svg b/public/icons/listdashes.svg new file mode 100644 index 00000000..102e7a31 --- /dev/null +++ b/public/icons/listdashes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/listheart.svg b/public/icons/listheart.svg new file mode 100644 index 00000000..40ee0693 --- /dev/null +++ b/public/icons/listheart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/listnumbers.svg b/public/icons/listnumbers.svg new file mode 100644 index 00000000..1f72ebab --- /dev/null +++ b/public/icons/listnumbers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/liststar.svg b/public/icons/liststar.svg new file mode 100644 index 00000000..243c4eaf --- /dev/null +++ b/public/icons/liststar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/searchlist.svg b/public/icons/searchlist.svg new file mode 100644 index 00000000..d1575f46 --- /dev/null +++ b/public/icons/searchlist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/shield.svg b/public/icons/shield.svg new file mode 100644 index 00000000..e0623fd7 --- /dev/null +++ b/public/icons/shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/shieldcheck.svg b/public/icons/shieldcheck.svg new file mode 100644 index 00000000..ee9d4bfb --- /dev/null +++ b/public/icons/shieldcheck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/shieldslash.svg b/public/icons/shieldslash.svg new file mode 100644 index 00000000..267243bd --- /dev/null +++ b/public/icons/shieldslash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/star.svg b/public/icons/star.svg new file mode 100644 index 00000000..d2659c54 --- /dev/null +++ b/public/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/textaligncenter.svg b/public/icons/textaligncenter.svg new file mode 100644 index 00000000..ed31774d --- /dev/null +++ b/public/icons/textaligncenter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/textalignjustify.svg b/public/icons/textalignjustify.svg new file mode 100644 index 00000000..bdddc920 --- /dev/null +++ b/public/icons/textalignjustify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/textalignleft.svg b/public/icons/textalignleft.svg new file mode 100644 index 00000000..11f92aeb --- /dev/null +++ b/public/icons/textalignleft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/textalignright.svg b/public/icons/textalignright.svg new file mode 100644 index 00000000..365ec8cc --- /dev/null +++ b/public/icons/textalignright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/textindent.svg b/public/icons/textindent.svg new file mode 100644 index 00000000..6b6d648f --- /dev/null +++ b/public/icons/textindent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/upload.svg b/public/icons/upload.svg new file mode 100644 index 00000000..daf15cbe --- /dev/null +++ b/public/icons/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/warning.svg b/public/icons/warning.svg new file mode 100644 index 00000000..7924d15f --- /dev/null +++ b/public/icons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/warningcircle.svg b/public/icons/warningcircle.svg new file mode 100644 index 00000000..f645bd1c --- /dev/null +++ b/public/icons/warningcircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pods/properties/components/icon-selector/modal/icons.ts b/src/pods/properties/components/icon-selector/modal/icons.ts index efcce207..caaae1ee 100644 --- a/src/pods/properties/components/icon-selector/modal/icons.ts +++ b/src/pods/properties/components/icon-selector/modal/icons.ts @@ -1436,4 +1436,124 @@ export const iconCollection: IconInfo[] = [ searchTerms: ['list', 'categorize', 'unordered', 'classify'], categories: ['IT'], }, + { + name: 'List checks', + filename: 'listchecks.svg', + searchTerms: ['list', 'categorize', 'unordered', 'classify'], + categories: ['IT'], + }, + { + name: 'List dashes', + filename: 'listdashes.svg', + searchTerms: ['list', 'categorize', 'unordered', 'classify'], + categories: ['IT'], + }, + { + name: 'List heart', + filename: 'listheart.svg', + searchTerms: ['list', 'categorize', 'unordered', 'classify'], + categories: ['IT'], + }, + { + name: 'Search list', + filename: 'searchlist.svg', + searchTerms: ['search', 'list', 'find', 'lookup', 'query'], + categories: ['IT'], + }, + { + name: 'List numbers', + filename: 'listnumbers.svg', + searchTerms: ['list', 'categorize', 'ordered', 'classify', 'numbers'], + categories: ['IT'], + }, + { + name: 'Add list', + filename: 'addlist.svg', + searchTerms: ['add', 'list', 'categorize', 'unordered', 'classify'], + categories: ['IT'], + }, + { + name: 'list star', + filename: 'liststar.svg', + searchTerms: ['list', 'categorize', 'unordered', 'classify', 'favorite'], + categories: ['IT'], + }, + { + name: 'Help', + filename: 'help.svg', + searchTerms: ['help', 'question', 'support', 'assist'], + categories: ['IT'], + }, + { + name: 'Star', + filename: 'star.svg', + searchTerms: ['star', 'favorite', 'like', 'rate'], + categories: ['IT'], + }, + { + name: 'Text align center', + filename: 'textaligncenter.svg', + searchTerms: ['text', 'align', 'center', 'format', 'paragraph'], + categories: ['IT'], + }, + { + name: 'Text align justify', + filename: 'textalignjustify.svg', + searchTerms: ['text', 'align', 'justify', 'format', 'paragraph'], + categories: ['IT'], + }, + { + name: 'Text align left', + filename: 'textalignleft.svg', + searchTerms: ['text', 'align', 'left', 'format', 'paragraph'], + categories: ['IT'], + }, + { + name: 'Text align right', + filename: 'textalignright.svg', + searchTerms: ['text', 'align', 'right', 'format', 'paragraph'], + categories: ['IT'], + }, + { + name: 'Text indent', + filename: 'textindent.svg', + searchTerms: ['text', 'indent', 'format', 'paragraph'], + categories: ['IT'], + }, + { + name: 'Upload', + filename: 'upload.svg', + searchTerms: ['upload', 'transfer', 'load', 'charge', 'import'], + categories: ['IT'], + }, + { + name: 'Warning circle', + filename: 'warningcircle.svg', + searchTerms: ['warning', 'circle', 'alert', 'caution'], + categories: ['IT'], + }, + { + name: 'Warning', + filename: 'warning.svg', + searchTerms: ['warning', 'alert', 'caution', 'attention'], + categories: ['IT'], + }, + { + name: 'Shield check', + filename: 'shieldcheck.svg', + searchTerms: ['shield', 'checked', 'security', 'verified'], + categories: ['IT'], + }, + { + name: 'Shield', + filename: 'shield.svg', + searchTerms: ['shield', 'protection', 'security', 'defense'], + categories: ['IT'], + }, + { + name: 'Shield desactivated', + filename: 'shieldslash.svg', + searchTerms: ['shield', 'desactivated', 'security', 'off'], + categories: ['IT'], + }, ]; From 7c75bafdc51fb3be476c54fe52014c7993141172 Mon Sep 17 00:00:00 2001 From: IonutGabi Date: Fri, 25 Oct 2024 11:37:31 +0200 Subject: [PATCH 10/27] Fix width and height from the textalignleft svg file --- public/icons/textalignleft.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/icons/textalignleft.svg b/public/icons/textalignleft.svg index 11f92aeb..2b5c720e 100644 --- a/public/icons/textalignleft.svg +++ b/public/icons/textalignleft.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From b4dea09cb30ba7e9d61d81f9b2a315694335e8ec Mon Sep 17 00:00:00 2001 From: IonutGabi Date: Fri, 25 Oct 2024 13:28:44 +0200 Subject: [PATCH 11/27] Add 20 new icons to the set of icons --- public/icons/arrowsclockwise.svg | 1 + public/icons/callphoneincoming.svg | 1 + public/icons/filedoc.svg | 1 + public/icons/fileexcel.svg | 1 + public/icons/filejpg.svg | 1 + public/icons/filepdf.svg | 1 + public/icons/filepng.svg | 1 + public/icons/filepowerpoint.svg | 1 + public/icons/musicnote.svg | 1 + public/icons/normalshield.svg | 1 + public/icons/phone.svg | 1 + public/icons/phonecall.svg | 1 + public/icons/phonehang.svg | 1 + public/icons/phonelist.svg | 1 + public/icons/phonepause.svg | 1 + public/icons/phoneslash.svg | 1 + public/icons/scissors.svg | 1 + .../icons/{shield.svg => shieldcheckered.svg} | 0 public/icons/shieldwarning.svg | 1 + .../components/icon-selector/modal/icons.ts | 119 +++++++++++++++++- 20 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 public/icons/arrowsclockwise.svg create mode 100644 public/icons/callphoneincoming.svg create mode 100644 public/icons/filedoc.svg create mode 100644 public/icons/fileexcel.svg create mode 100644 public/icons/filejpg.svg create mode 100644 public/icons/filepdf.svg create mode 100644 public/icons/filepng.svg create mode 100644 public/icons/filepowerpoint.svg create mode 100644 public/icons/musicnote.svg create mode 100644 public/icons/normalshield.svg create mode 100644 public/icons/phone.svg create mode 100644 public/icons/phonecall.svg create mode 100644 public/icons/phonehang.svg create mode 100644 public/icons/phonelist.svg create mode 100644 public/icons/phonepause.svg create mode 100644 public/icons/phoneslash.svg create mode 100644 public/icons/scissors.svg rename public/icons/{shield.svg => shieldcheckered.svg} (100%) create mode 100644 public/icons/shieldwarning.svg diff --git a/public/icons/arrowsclockwise.svg b/public/icons/arrowsclockwise.svg new file mode 100644 index 00000000..2ac71b81 --- /dev/null +++ b/public/icons/arrowsclockwise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/callphoneincoming.svg b/public/icons/callphoneincoming.svg new file mode 100644 index 00000000..ec93c6e3 --- /dev/null +++ b/public/icons/callphoneincoming.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/filedoc.svg b/public/icons/filedoc.svg new file mode 100644 index 00000000..0e59f4fa --- /dev/null +++ b/public/icons/filedoc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/fileexcel.svg b/public/icons/fileexcel.svg new file mode 100644 index 00000000..daf4d734 --- /dev/null +++ b/public/icons/fileexcel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/filejpg.svg b/public/icons/filejpg.svg new file mode 100644 index 00000000..63c73cc0 --- /dev/null +++ b/public/icons/filejpg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/filepdf.svg b/public/icons/filepdf.svg new file mode 100644 index 00000000..db9d41a5 --- /dev/null +++ b/public/icons/filepdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/filepng.svg b/public/icons/filepng.svg new file mode 100644 index 00000000..2b12da29 --- /dev/null +++ b/public/icons/filepng.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/filepowerpoint.svg b/public/icons/filepowerpoint.svg new file mode 100644 index 00000000..9444ea8c --- /dev/null +++ b/public/icons/filepowerpoint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/musicnote.svg b/public/icons/musicnote.svg new file mode 100644 index 00000000..b77539be --- /dev/null +++ b/public/icons/musicnote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/normalshield.svg b/public/icons/normalshield.svg new file mode 100644 index 00000000..9293db66 --- /dev/null +++ b/public/icons/normalshield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/phone.svg b/public/icons/phone.svg new file mode 100644 index 00000000..6bf536a0 --- /dev/null +++ b/public/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/phonecall.svg b/public/icons/phonecall.svg new file mode 100644 index 00000000..fbecdd97 --- /dev/null +++ b/public/icons/phonecall.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/phonehang.svg b/public/icons/phonehang.svg new file mode 100644 index 00000000..09677754 --- /dev/null +++ b/public/icons/phonehang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/phonelist.svg b/public/icons/phonelist.svg new file mode 100644 index 00000000..57dd209d --- /dev/null +++ b/public/icons/phonelist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/phonepause.svg b/public/icons/phonepause.svg new file mode 100644 index 00000000..dbb6a8db --- /dev/null +++ b/public/icons/phonepause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/phoneslash.svg b/public/icons/phoneslash.svg new file mode 100644 index 00000000..e7086a36 --- /dev/null +++ b/public/icons/phoneslash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/scissors.svg b/public/icons/scissors.svg new file mode 100644 index 00000000..fc8fc248 --- /dev/null +++ b/public/icons/scissors.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/shield.svg b/public/icons/shieldcheckered.svg similarity index 100% rename from public/icons/shield.svg rename to public/icons/shieldcheckered.svg diff --git a/public/icons/shieldwarning.svg b/public/icons/shieldwarning.svg new file mode 100644 index 00000000..8c930220 --- /dev/null +++ b/public/icons/shieldwarning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pods/properties/components/icon-selector/modal/icons.ts b/src/pods/properties/components/icon-selector/modal/icons.ts index caaae1ee..d06ed852 100644 --- a/src/pods/properties/components/icon-selector/modal/icons.ts +++ b/src/pods/properties/components/icon-selector/modal/icons.ts @@ -1545,8 +1545,8 @@ export const iconCollection: IconInfo[] = [ categories: ['IT'], }, { - name: 'Shield', - filename: 'shield.svg', + name: 'Shield checkered', + filename: 'shieldcheckered.svg', searchTerms: ['shield', 'protection', 'security', 'defense'], categories: ['IT'], }, @@ -1556,4 +1556,119 @@ export const iconCollection: IconInfo[] = [ searchTerms: ['shield', 'desactivated', 'security', 'off'], categories: ['IT'], }, + { + name: 'Shield warning', + filename: 'shieldwarning.svg', + searchTerms: ['shield', 'warning', 'security', 'alert'], + categories: ['IT'], + }, + { + name: 'Shield', + filename: 'normalshield.svg', + searchTerms: ['shield', 'protection', 'security', 'defense'], + categories: ['IT'], + }, + { + name: 'Scissors', + filename: 'scissors.svg', + searchTerms: ['scissors', 'cut', 'tool', 'clip'], + categories: ['IT'], + }, + { + name: 'Phone', + filename: 'phone.svg', + searchTerms: ['phone', 'landline', 'device', 'telephone'], + categories: ['IT'], + }, + { + name: 'Phone call', + filename: 'phonecall.svg', + searchTerms: ['phone', 'call', 'landline', 'device', 'telephone'], + categories: ['IT'], + }, + { + name: 'Phone hang', + filename: 'phonehang.svg', + searchTerms: ['phone', 'hang', 'landline', 'device', 'telephone'], + categories: ['IT'], + }, + { + name: 'Phone disconnected', + filename: 'phoneslash.svg', + searchTerms: ['phone', 'disconnected', 'landline', 'device', 'telephone'], + categories: ['IT'], + }, + { + name: 'Phone pause', + filename: 'phonepause.svg', + searchTerms: ['phone', 'pause', 'landline', 'device', 'telephone'], + categories: ['IT'], + }, + { + name: 'Call phone incoming', + filename: 'callphoneincoming.svg', + searchTerms: [ + 'call', + 'phone', + 'incoming', + 'landline', + 'device', + 'telephone', + ], + categories: ['IT'], + }, + { + name: 'Phone list', + filename: 'phonelist.svg', + searchTerms: ['phone', 'list', 'landline', 'device', 'telephone'], + categories: ['IT'], + }, + { + name: 'Music note', + filename: 'musicnote.svg', + searchTerms: ['music', 'note', 'sound', 'audio', 'melody'], + categories: ['IT'], + }, + { + name: 'File document', + filename: 'filedoc.svg', + searchTerms: ['file', 'document', 'digital', 'sheet'], + categories: ['IT'], + }, + { + name: 'File pdf', + filename: 'filepdf.svg', + searchTerms: ['file', 'pdf', 'document', 'digital', 'sheet'], + categories: ['IT'], + }, + { + name: 'File png', + filename: 'filepng.svg', + searchTerms: ['file', 'png', 'document', 'digital', 'sheet'], + categories: ['IT'], + }, + { + name: 'File powerpoint', + filename: 'filepowerpoint.svg', + searchTerms: ['file', 'powerpoint', 'document', 'digital', 'sheet'], + categories: ['IT'], + }, + { + name: 'File jpg', + filename: 'filejpg.svg', + searchTerms: ['file', 'jpg', 'document', 'digital', 'sheet'], + categories: ['IT'], + }, + { + name: 'File excel', + filename: 'fileexcel.svg', + searchTerms: ['file', 'excel', 'document', 'digital', 'sheet'], + categories: ['IT'], + }, + { + name: 'Arrows clockwise', + filename: 'arrowsclockwise.svg', + searchTerms: ['arrows', 'clockwise', 'direction', 'rotate'], + categories: ['IT'], + }, ]; From cef287446fe840a109ddc5258af0cb1b04d4d344 Mon Sep 17 00:00:00 2001 From: oleojake Date: Fri, 25 Oct 2024 15:48:26 +0200 Subject: [PATCH 12/27] #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 > { From 3378698a3cf858f64e3f1a474ffe0d48bb5d6049 Mon Sep 17 00:00:00 2001 From: manugallegob Date: Sun, 27 Oct 2024 09:23:27 +0100 Subject: [PATCH 13/27] timepicker component modification: svg, const in english and style --- public/widgets/timepicker.svg | 33 +++++++++---------- .../timepickerinput-shape.business.ts | 24 +++++++++----- .../timepickerinput/timepickerinput-shape.tsx | 19 +++++++---- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/public/widgets/timepicker.svg b/public/widgets/timepicker.svg index fe80aecf..1a262986 100644 --- a/public/widgets/timepicker.svg +++ b/public/widgets/timepicker.svg @@ -1,25 +1,24 @@ - + - - + + + Time - Time - - - - hh:mm + + hh:mm - - - - - + + + + + + + - + + + - - - diff --git a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts index 3b0df9e0..a257c096 100644 --- a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts +++ b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.business.ts @@ -1,3 +1,9 @@ +const MAX_DIGITS = 2; +const MAX_HOURS = '23'; +const MAX_MINUTES = '59'; +const HOUR_MASK = 'hh'; +const MINUTES_MASK = 'mm'; + export const splitCSVContent = (csvContent: string): string[] => { const splitedCsvContent = csvContent .trim() @@ -7,19 +13,19 @@ export const splitCSVContent = (csvContent: string): string[] => { }; export const setTime = (csvData: string[]) => { - let [hora, minutos] = csvData; + let [hour, minutes] = csvData; if (csvData.length < 2) { return true; } - if (csvData[0] !== 'hh' || csvData[1] !== 'mm') { + if (csvData[0] !== HOUR_MASK || csvData[1] !== MINUTES_MASK) { if ( - csvData.length > 2 || - hora.length !== 2 || - hora === '' || - hora > '23' || - minutos.length !== 2 || - minutos === '' || - minutos > '59' + csvData.length > MAX_DIGITS || + hour.length !== MAX_DIGITS || + hour === '' || + hour > MAX_HOURS || + minutes.length !== MAX_DIGITS || + minutes === '' || + minutes > MAX_MINUTES ) { return true; } diff --git a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx index c40f9f3c..a0ec43c8 100644 --- a/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx +++ b/src/common/components/mock-components/front-components/timepickerinput/timepickerinput-shape.tsx @@ -83,24 +83,26 @@ export const TimepickerInputShape = forwardRef( cornerRadius={borderRadius} stroke={stroke} dash={strokeStyle} - strokeWidth={2} + strokeWidth={BASIC_SHAPE.DEFAULT_STROKE_WIDTH} fill={fill} /> - {/* Background of Date Label */} + {/* Background of Time Label */} - {/* Label "Date" */} + {/* Label "Time" */} ( {/* Main Text */} + {/* Error Text */} {isError && ( ( /> )} - {/* Calendar Icon */} + {/* Clock Icon */} Date: Mon, 28 Oct 2024 13:32:30 +0100 Subject: [PATCH 14/27] Add more icons to set of icons --- public/icons/arrowfatdown.svg | 1 + public/icons/arrowfatleft.svg | 1 + public/icons/arrowfatright.svg | 1 + public/icons/arrowfatup.svg | 1 + public/icons/arrowscounterclockwise.svg | 1 + public/icons/check.svg | 1 + public/icons/checkfat.svg | 1 + public/icons/company.svg | 1 + public/icons/copyright.svg | 1 + public/icons/doublecheck.svg | 1 + public/icons/factory.svg | 1 + public/icons/home.svg | 1 + public/icons/keyboard.svg | 1 + public/icons/plug.svg | 1 + public/icons/printer.svg | 1 + .../components/icon-selector/modal/icons.ts | 98 ++++++++++++++++++- 16 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 public/icons/arrowfatdown.svg create mode 100644 public/icons/arrowfatleft.svg create mode 100644 public/icons/arrowfatright.svg create mode 100644 public/icons/arrowfatup.svg create mode 100644 public/icons/arrowscounterclockwise.svg create mode 100644 public/icons/check.svg create mode 100644 public/icons/checkfat.svg create mode 100644 public/icons/company.svg create mode 100644 public/icons/copyright.svg create mode 100644 public/icons/doublecheck.svg create mode 100644 public/icons/factory.svg create mode 100644 public/icons/home.svg create mode 100644 public/icons/keyboard.svg create mode 100644 public/icons/plug.svg create mode 100644 public/icons/printer.svg diff --git a/public/icons/arrowfatdown.svg b/public/icons/arrowfatdown.svg new file mode 100644 index 00000000..3b68d491 --- /dev/null +++ b/public/icons/arrowfatdown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/arrowfatleft.svg b/public/icons/arrowfatleft.svg new file mode 100644 index 00000000..bc94d86f --- /dev/null +++ b/public/icons/arrowfatleft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/arrowfatright.svg b/public/icons/arrowfatright.svg new file mode 100644 index 00000000..0000fd9d --- /dev/null +++ b/public/icons/arrowfatright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/arrowfatup.svg b/public/icons/arrowfatup.svg new file mode 100644 index 00000000..c95f2b8d --- /dev/null +++ b/public/icons/arrowfatup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/arrowscounterclockwise.svg b/public/icons/arrowscounterclockwise.svg new file mode 100644 index 00000000..3756d8d0 --- /dev/null +++ b/public/icons/arrowscounterclockwise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/check.svg b/public/icons/check.svg new file mode 100644 index 00000000..f3015dcb --- /dev/null +++ b/public/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/checkfat.svg b/public/icons/checkfat.svg new file mode 100644 index 00000000..3a62fb8a --- /dev/null +++ b/public/icons/checkfat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/company.svg b/public/icons/company.svg new file mode 100644 index 00000000..a72ba20c --- /dev/null +++ b/public/icons/company.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/copyright.svg b/public/icons/copyright.svg new file mode 100644 index 00000000..b8838440 --- /dev/null +++ b/public/icons/copyright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/doublecheck.svg b/public/icons/doublecheck.svg new file mode 100644 index 00000000..0b8babe4 --- /dev/null +++ b/public/icons/doublecheck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/factory.svg b/public/icons/factory.svg new file mode 100644 index 00000000..c41a3fe1 --- /dev/null +++ b/public/icons/factory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/home.svg b/public/icons/home.svg new file mode 100644 index 00000000..188c3d2d --- /dev/null +++ b/public/icons/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/keyboard.svg b/public/icons/keyboard.svg new file mode 100644 index 00000000..a7a79892 --- /dev/null +++ b/public/icons/keyboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/plug.svg b/public/icons/plug.svg new file mode 100644 index 00000000..2e07385f --- /dev/null +++ b/public/icons/plug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/printer.svg b/public/icons/printer.svg new file mode 100644 index 00000000..c2df959d --- /dev/null +++ b/public/icons/printer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pods/properties/components/icon-selector/modal/icons.ts b/src/pods/properties/components/icon-selector/modal/icons.ts index d06ed852..3ddb954a 100644 --- a/src/pods/properties/components/icon-selector/modal/icons.ts +++ b/src/pods/properties/components/icon-selector/modal/icons.ts @@ -1175,7 +1175,7 @@ export const iconCollection: IconInfo[] = [ { name: 'Chat', filename: 'chat.svg', - searchTerms: ['chat', 'message', 'conversation', 'chatting'], + searchTerms: ['chat', 'message', 'conversation', 'chatting', 'comment'], categories: ['IT'], }, { @@ -1671,4 +1671,100 @@ export const iconCollection: IconInfo[] = [ searchTerms: ['arrows', 'clockwise', 'direction', 'rotate'], categories: ['IT'], }, + { + name: 'Arrows counter clockwise', + filename: 'arrowscounterclockwise.svg', + searchTerms: ['arrows', 'counter clockwise', 'direction', 'rotate'], + categories: ['IT'], + }, + { + name: 'Arrow fat down', + filename: 'arrowfatdown.svg', + searchTerms: ['arrow', 'fat', 'down', 'move'], + categories: ['IT'], + }, + { + name: 'Arrow fat left', + filename: 'arrowfatleft.svg', + searchTerms: ['arrow', 'fat', 'left', 'move'], + categories: ['IT'], + }, + { + name: 'Arrow fat right', + filename: 'arrowfatright.svg', + searchTerms: ['arrow', 'fat', 'right', 'move'], + categories: ['IT'], + }, + { + name: 'Arrow fat up', + filename: 'arrowfatup.svg', + searchTerms: ['arrow', 'fat', 'up', 'move'], + categories: ['IT'], + }, + { + name: 'Check fat', + filename: 'checkfat.svg', + searchTerms: ['check', 'confirmation', 'validate', 'success'], + categories: ['IT'], + }, + { + name: 'Check', + filename: 'check.svg', + searchTerms: ['check', 'confirmation', 'validate', 'success'], + categories: ['IT'], + }, + { + name: 'Double check', + filename: 'doublecheck.svg', + searchTerms: ['check', 'confirmation', 'validate', 'double'], + categories: ['IT'], + }, + { + name: 'Start', + filename: 'home.svg', + searchTerms: ['start', 'home', 'begin', 'launch'], + categories: ['IT'], + }, + { + name: 'Company', + filename: 'company.svg', + searchTerms: ['company', 'business', 'enterprise', 'corporation'], + categories: ['IT'], + }, + { + name: 'Factory', + filename: 'factory.svg', + searchTerms: ['factory', 'industry', 'manufacture', 'production'], + categories: ['IT'], + }, + { + name: 'Keyboard', + filename: 'keyboard.svg', + searchTerms: ['keyboard', 'device', 'computer', 'write'], + categories: ['IT'], + }, + { + name: 'Printer', + filename: 'printer.svg', + searchTerms: ['printer', 'device', 'computer', 'imprint'], + categories: ['IT'], + }, + { + name: 'Plug', + filename: 'plug.svg', + searchTerms: ['plug', 'device', 'connect', 'power'], + categories: ['IT'], + }, + { + name: 'Copyright', + filename: 'copyright.svg', + searchTerms: [ + 'copyright', + 'rights', + 'protected', + 'intellectual', + 'property', + ], + categories: ['IT'], + }, ]; From 2eb0a72f1b91f28ce35987ad694e520c33fd6f18 Mon Sep 17 00:00:00 2001 From: Jesus Sanz Date: Tue, 29 Oct 2024 18:33:54 +0100 Subject: [PATCH 15/27] Closes #491 Add flex-direction: column to thumb-pages module CSS --- src/pods/thumb-pages/thumb-pages.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pods/thumb-pages/thumb-pages.module.css b/src/pods/thumb-pages/thumb-pages.module.css index 47bc9871..ee6a955e 100644 --- a/src/pods/thumb-pages/thumb-pages.module.css +++ b/src/pods/thumb-pages/thumb-pages.module.css @@ -5,4 +5,5 @@ align-items: center; justify-content: center; flex-wrap: wrap; + flex-direction: column; } From 7c450d6f48c49e197c0d32d1c6f3a523ceb6b1c6 Mon Sep 17 00:00:00 2001 From: oleojake Date: Wed, 30 Oct 2024 12:33:33 +0100 Subject: [PATCH 16/27] #495 first approximation, context menu showing up on thumbnail --- src/common/components/icons/index.ts | 2 + .../icons/pencil-icon.component.tsx | 15 +++++ src/core/providers/canvas/canvas.model.ts | 4 ++ src/core/providers/canvas/canvas.provider.tsx | 4 ++ .../use-multiple-selection-shape.hook.tsx | 5 ++ .../context-menu.component.module.css | 33 +++++++++ .../context-menu/context-menu.component.tsx | 64 ++++++++++++++++++ .../thumb-pages/components/thumb-page.tsx | 67 ++++++++++++------- .../use-context-menu-thumb.hook.tsx | 42 ++++++++++++ 9 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 src/common/components/icons/pencil-icon.component.tsx create mode 100644 src/pods/thumb-pages/components/context-menu/context-menu.component.module.css create mode 100644 src/pods/thumb-pages/components/context-menu/context-menu.component.tsx create mode 100644 src/pods/thumb-pages/use-context-menu-thumb.hook.tsx diff --git a/src/common/components/icons/index.ts b/src/common/components/icons/index.ts index 2bf628b8..c5a2d422 100644 --- a/src/common/components/icons/index.ts +++ b/src/common/components/icons/index.ts @@ -8,3 +8,5 @@ export * from './x-icon.component'; export * from './quickmock-logo.component'; export * from './copy-icon.component'; export * from './paste-icon.component'; +export * from './delete-icon.component'; +export * from './pencil-icon.component'; diff --git a/src/common/components/icons/pencil-icon.component.tsx b/src/common/components/icons/pencil-icon.component.tsx new file mode 100644 index 00000000..232a6868 --- /dev/null +++ b/src/common/components/icons/pencil-icon.component.tsx @@ -0,0 +1,15 @@ +export const PencilIcon = () => { + return ( + + + + ); +}; diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 3bad17c5..a4827915 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -97,4 +97,8 @@ export interface CanvasContextModel { fullDocument: DocumentModel; addNewPage: () => void; setActivePage: (pageId: string) => void; + isThumbnailContextMenuVisible: boolean; + setIsThumbnailContextMenuVisible: React.Dispatch< + React.SetStateAction + >; } diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 4f3a51a0..65e53c57 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -23,6 +23,8 @@ export const CanvasProvider: React.FC = props => { const stageRef = React.useRef(null); const [isInlineEditing, setIsInlineEditing] = React.useState(false); const [fileName, setFileName] = React.useState(''); + const [isThumbnailContextMenuVisible, setIsThumbnailContextMenuVisible] = + React.useState(false); const { addSnapshot, @@ -246,6 +248,8 @@ export const CanvasProvider: React.FC = props => { fullDocument: document, addNewPage, setActivePage, + isThumbnailContextMenuVisible, + setIsThumbnailContextMenuVisible, }} > {children} diff --git a/src/pods/canvas/use-multiple-selection-shape.hook.tsx b/src/pods/canvas/use-multiple-selection-shape.hook.tsx index 676e1083..698c265c 100644 --- a/src/pods/canvas/use-multiple-selection-shape.hook.tsx +++ b/src/pods/canvas/use-multiple-selection-shape.hook.tsx @@ -13,6 +13,7 @@ import { calculateScaledCoordsFromCanvasDivCoordinatesNoScroll } from './canvas. import { Stage } from 'konva/lib/Stage'; import { isUserDoingMultipleSelectionUsingCtrlOrCmdKey } from '@/common/utils/shapes'; import { KonvaEventObject } from 'konva/lib/Node'; +import { useCanvasContext } from '@/core/providers'; // There's a bug here: if you make a multiple selectin and start dragging // inside the selection but on a blank area it won't drag the selection @@ -49,6 +50,8 @@ export const useMultipleSelectionShapeHook = ( visible: false, }); + const { setIsThumbnailContextMenuVisible } = useCanvasContext(); + const isDraggingSelection = (mouseCoords: Coord) => { if (!transformerRef.current) { return false; @@ -166,6 +169,8 @@ export const useMultipleSelectionShapeHook = ( height: 0, visible: true, }); + + setIsThumbnailContextMenuVisible(false); }; const handleMouseMove = (e: any) => { diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css new file mode 100644 index 00000000..2c92f409 --- /dev/null +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css @@ -0,0 +1,33 @@ +.context-menu { + position: absolute; + top: 50%; + left: 50%; + width: 80%; + height: auto; + transform: translate(-50%, -50%); + border: 1px solid var(--primary-500); + background-color: var(--primary-100); + opacity: 0.9; +} + +.container { + display: flex; + gap: 0.5em; + align-items: center; + padding: var(--space-xs) var(--space-md); + border-bottom: 1px solid var(--primary-300); + cursor: pointer; +} + +.container :first-child { + flex: 1; +} + +.container:hover { + background-color: var(--primary-200); +} + +.shortcut { + color: var(--primary-400); + font-weight: 500; +} diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx new file mode 100644 index 00000000..767b3bf7 --- /dev/null +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { useCanvasContext } from '@/core/providers'; +import classes from './context-menu.component.module.css'; +import { CopyIcon, DeleteIcon, PencilIcon } from '@/common/components/icons'; + +interface ThumbPageContextMenuProps { + contextMenuRef: React.RefObject; + setShowContextMenu: (show: boolean) => void; +} + +export const ThumbPageContextMenu: React.FunctionComponent< + ThumbPageContextMenuProps +> = props => { + const { contextMenuRef, setShowContextMenu } = props; + const { setIsThumbnailContextMenuVisible } = useCanvasContext(); + + enum ContextButtonType { + 'Duplicate', + 'Rename', + 'Delete', + } + + const handleClickOnContextButton = (buttonClicked: ContextButtonType) => { + switch (buttonClicked) { + case ContextButtonType.Duplicate: + console.log('Duplicate'); + break; + case ContextButtonType.Rename: + console.log('Rename'); + break; + case ContextButtonType.Delete: + console.log('Delete'); + break; + } + setShowContextMenu(false); + setIsThumbnailContextMenuVisible(false); + }; + + return ( +
+
handleClickOnContextButton(ContextButtonType.Duplicate)} + className={classes.container} + > +

Duplicate

+ +
+
handleClickOnContextButton(ContextButtonType.Rename)} + className={classes.container} + > +

Rename

+ +
+
handleClickOnContextButton(ContextButtonType.Delete)} + className={classes.container} + > +

Delete

+ +
+
+ ); +}; diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index f245fe39..0786d1ef 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -5,6 +5,8 @@ import { calculateCanvasBounds } from '@/pods/toolbar/components/export-button/e import { KonvaEventObject } from 'konva/lib/Node'; import { createRef, useRef } from 'react'; import { Layer, Stage } from 'react-konva'; +import { ThumbPageContextMenu } from './context-menu/context-menu.component'; +import { useContextMenu } from '../use-context-menu-thumb.hook'; interface Props { pageIndex: number; @@ -13,7 +15,6 @@ interface Props { export const ThumbPage: React.FunctionComponent = props => { const { pageIndex, onSetActivePage } = props; - const { fullDocument } = useCanvasContext(); const page = fullDocument.pages[pageIndex]; @@ -21,7 +22,6 @@ export const ThumbPage: React.FunctionComponent = props => { const fakeShapeRefs = useRef({}); const bounds = calculateCanvasBounds(shapes); - const canvasSize = { width: bounds.x + bounds.width, height: bounds.y + bounds.height, @@ -30,27 +30,48 @@ export const ThumbPage: React.FunctionComponent = props => { const scaleFactorY = 180 / canvasSize.height; const finalScale = Math.min(scaleFactorX, scaleFactorY); + const { + showContextMenu, + contextMenuRef, + setShowContextMenu, + handleShowContextMenu, + } = useContextMenu(); + return ( -
onSetActivePage(page.id)} - > - - - {shapes.map(shape => { - if (!fakeShapeRefs.current[shape.id]) { - fakeShapeRefs.current[shape.id] = createRef(); - } - return renderShapeComponent(shape, { - handleSelected: () => {}, - shapeRefs: fakeShapeRefs, - handleDragEnd: - (_: string) => (_: KonvaEventObject) => {}, - handleTransform: () => {}, - }); - })} - - -
+ <> +
onSetActivePage(page.id)} + onContextMenu={handleShowContextMenu} + > + + + {shapes.map(shape => { + if (!fakeShapeRefs.current[shape.id]) { + fakeShapeRefs.current[shape.id] = createRef(); + } + return renderShapeComponent(shape, { + handleSelected: () => {}, + shapeRefs: fakeShapeRefs, + handleDragEnd: + (_: string) => (_: KonvaEventObject) => {}, + handleTransform: () => {}, + }); + })} + + + {showContextMenu && ( + + )} +
+ ); }; diff --git a/src/pods/thumb-pages/use-context-menu-thumb.hook.tsx b/src/pods/thumb-pages/use-context-menu-thumb.hook.tsx new file mode 100644 index 00000000..5d5a65a6 --- /dev/null +++ b/src/pods/thumb-pages/use-context-menu-thumb.hook.tsx @@ -0,0 +1,42 @@ +import { useCanvasContext } from '@/core/providers'; +import { useEffect, useRef, useState } from 'react'; + +export const useContextMenu = () => { + const [showContextMenu, setShowContextMenu] = useState(false); + const contextMenuRef = useRef(null); + const { setIsThumbnailContextMenuVisible } = useCanvasContext(); + + const handleShowContextMenu = ( + event: React.MouseEvent + ) => { + event.preventDefault(); + if (!showContextMenu) { + setIsThumbnailContextMenuVisible(true); + setShowContextMenu(true); + } + }; + + useEffect(() => { + const closeContextMenu = (event: MouseEvent) => { + if ( + contextMenuRef.current && + !contextMenuRef.current.contains(event.target as Node) + ) { + setShowContextMenu(false); + setIsThumbnailContextMenuVisible(false); + } + }; + + window.addEventListener('mousedown', closeContextMenu); + return () => { + window.removeEventListener('mousedown', closeContextMenu); + }; + }, [showContextMenu, setIsThumbnailContextMenuVisible]); + + return { + showContextMenu, + contextMenuRef, + setShowContextMenu, + handleShowContextMenu, + }; +}; From 7944d9f95e711e9a8b4720bb94f7e30b302eec8d Mon Sep 17 00:00:00 2001 From: oleojake Date: Wed, 30 Oct 2024 13:15:06 +0100 Subject: [PATCH 17/27] #495 duplicate page working --- src/core/providers/canvas/canvas.model.ts | 1 + src/core/providers/canvas/canvas.provider.tsx | 21 +++++++++++++++++++ .../context-menu.component.module.css | 6 ++++++ .../context-menu/context-menu.component.tsx | 20 +++++++++++------- .../thumb-pages/components/thumb-page.tsx | 1 + 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index a4827915..dfb283ac 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -96,6 +96,7 @@ export interface CanvasContextModel { setFileName: (fileName: string) => void; fullDocument: DocumentModel; addNewPage: () => void; + duplicatePage: (pageIndex: number) => void; setActivePage: (pageId: string) => void; isThumbnailContextMenuVisible: boolean; setIsThumbnailContextMenuVisible: React.Dispatch< diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 65e53c57..82270520 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -55,6 +55,26 @@ export const CanvasProvider: React.FC = props => { ); }; + const duplicatePage = (pageIndex: number) => { + const newShapes: ShapeModel[] = document.pages[pageIndex].shapes.map( + shape => { + const newShape: ShapeModel = { ...shape }; + newShape.id = uuidv4(); + return newShape; + } + ); + + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages.push({ + id: uuidv4(), + name: `Page ${draft.pages.length + 1}`, + shapes: newShapes, + }); + }) + ); + }; + const setActivePage = (pageId: string) => { selectionInfo.clearSelection(); selectionInfo.shapeRefs.current = {}; @@ -247,6 +267,7 @@ export const CanvasProvider: React.FC = props => { setFileName, fullDocument: document, addNewPage, + duplicatePage, setActivePage, isThumbnailContextMenuVisible, setIsThumbnailContextMenuVisible, diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css index 2c92f409..41c04557 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css @@ -27,6 +27,12 @@ background-color: var(--primary-200); } +.disabled { + cursor: not-allowed; + opacity: 0.5; + background-color: var(--primary-200); +} + .shortcut { color: var(--primary-400); font-weight: 500; diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx index 767b3bf7..ee5ef5c8 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx @@ -1,18 +1,20 @@ import React from 'react'; import { useCanvasContext } from '@/core/providers'; import classes from './context-menu.component.module.css'; -import { CopyIcon, DeleteIcon, PencilIcon } from '@/common/components/icons'; +import { CopyIcon, DeleteIcon } from '@/common/components/icons'; interface ThumbPageContextMenuProps { contextMenuRef: React.RefObject; setShowContextMenu: (show: boolean) => void; + pageIndex: number; } export const ThumbPageContextMenu: React.FunctionComponent< ThumbPageContextMenuProps > = props => { - const { contextMenuRef, setShowContextMenu } = props; - const { setIsThumbnailContextMenuVisible } = useCanvasContext(); + const { contextMenuRef, setShowContextMenu, pageIndex } = props; + const { setIsThumbnailContextMenuVisible, duplicatePage, fullDocument } = + useCanvasContext(); enum ContextButtonType { 'Duplicate', @@ -23,7 +25,7 @@ export const ThumbPageContextMenu: React.FunctionComponent< const handleClickOnContextButton = (buttonClicked: ContextButtonType) => { switch (buttonClicked) { case ContextButtonType.Duplicate: - console.log('Duplicate'); + duplicatePage(pageIndex); break; case ContextButtonType.Rename: console.log('Rename'); @@ -45,16 +47,20 @@ export const ThumbPageContextMenu: React.FunctionComponent<

Duplicate

-
handleClickOnContextButton(ContextButtonType.Rename)} className={classes.container} >

Rename

-
+
*/}
handleClickOnContextButton(ContextButtonType.Delete)} - className={classes.container} + className={ + fullDocument.pages.length === 1 + ? `${classes.container} ${classes.disabled}` + : `${classes.container}` + } >

Delete

diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index 0786d1ef..a00549d9 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -69,6 +69,7 @@ export const ThumbPage: React.FunctionComponent = props => { )}
From b78aec4bf2a52f360b711af6e74f1498155011b2 Mon Sep 17 00:00:00 2001 From: IonutGabi Date: Wed, 30 Oct 2024 13:18:18 +0100 Subject: [PATCH 18/27] Add 20 new icons to set of icons --- public/icons/alternativemouse.svg | 1 + public/icons/autoflash.svg | 1 + public/icons/camera.svg | 1 + public/icons/caretdown.svg | 1 + public/icons/caretleft.svg | 1 + public/icons/caretright.svg | 1 + public/icons/caretup.svg | 1 + public/icons/controller.svg | 1 + public/icons/firstaid.svg | 1 + public/icons/flag.svg | 1 + public/icons/flashslash.svg | 1 + public/icons/hammer.svg | 1 + public/icons/joystick.svg | 1 + public/icons/key.svg | 1 + public/icons/lightning.svg | 1 + public/icons/lock.svg | 1 + public/icons/lockopen.svg | 1 + public/icons/power.svg | 1 + public/icons/spinner.svg | 1 + public/icons/subtitles.svg | 1 + .../components/icon-selector/modal/icons.ts | 120 ++++++++++++++++++ 21 files changed, 140 insertions(+) create mode 100644 public/icons/alternativemouse.svg create mode 100644 public/icons/autoflash.svg create mode 100644 public/icons/camera.svg create mode 100644 public/icons/caretdown.svg create mode 100644 public/icons/caretleft.svg create mode 100644 public/icons/caretright.svg create mode 100644 public/icons/caretup.svg create mode 100644 public/icons/controller.svg create mode 100644 public/icons/firstaid.svg create mode 100644 public/icons/flag.svg create mode 100644 public/icons/flashslash.svg create mode 100644 public/icons/hammer.svg create mode 100644 public/icons/joystick.svg create mode 100644 public/icons/key.svg create mode 100644 public/icons/lightning.svg create mode 100644 public/icons/lock.svg create mode 100644 public/icons/lockopen.svg create mode 100644 public/icons/power.svg create mode 100644 public/icons/spinner.svg create mode 100644 public/icons/subtitles.svg diff --git a/public/icons/alternativemouse.svg b/public/icons/alternativemouse.svg new file mode 100644 index 00000000..37a674b9 --- /dev/null +++ b/public/icons/alternativemouse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/autoflash.svg b/public/icons/autoflash.svg new file mode 100644 index 00000000..75495c8e --- /dev/null +++ b/public/icons/autoflash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/camera.svg b/public/icons/camera.svg new file mode 100644 index 00000000..d51fcf35 --- /dev/null +++ b/public/icons/camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/caretdown.svg b/public/icons/caretdown.svg new file mode 100644 index 00000000..6bcca3ef --- /dev/null +++ b/public/icons/caretdown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/caretleft.svg b/public/icons/caretleft.svg new file mode 100644 index 00000000..b4bf3c51 --- /dev/null +++ b/public/icons/caretleft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/caretright.svg b/public/icons/caretright.svg new file mode 100644 index 00000000..523c9793 --- /dev/null +++ b/public/icons/caretright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/caretup.svg b/public/icons/caretup.svg new file mode 100644 index 00000000..5f197bec --- /dev/null +++ b/public/icons/caretup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/controller.svg b/public/icons/controller.svg new file mode 100644 index 00000000..fbc75e00 --- /dev/null +++ b/public/icons/controller.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/firstaid.svg b/public/icons/firstaid.svg new file mode 100644 index 00000000..07581b08 --- /dev/null +++ b/public/icons/firstaid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/flag.svg b/public/icons/flag.svg new file mode 100644 index 00000000..36594a99 --- /dev/null +++ b/public/icons/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/flashslash.svg b/public/icons/flashslash.svg new file mode 100644 index 00000000..466efdd1 --- /dev/null +++ b/public/icons/flashslash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/hammer.svg b/public/icons/hammer.svg new file mode 100644 index 00000000..110aa5c6 --- /dev/null +++ b/public/icons/hammer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/joystick.svg b/public/icons/joystick.svg new file mode 100644 index 00000000..d5ecc1c1 --- /dev/null +++ b/public/icons/joystick.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/key.svg b/public/icons/key.svg new file mode 100644 index 00000000..b6ec42ae --- /dev/null +++ b/public/icons/key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/lightning.svg b/public/icons/lightning.svg new file mode 100644 index 00000000..88fd6ae3 --- /dev/null +++ b/public/icons/lightning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/lock.svg b/public/icons/lock.svg new file mode 100644 index 00000000..6eaba0b9 --- /dev/null +++ b/public/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/lockopen.svg b/public/icons/lockopen.svg new file mode 100644 index 00000000..bc29248f --- /dev/null +++ b/public/icons/lockopen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/power.svg b/public/icons/power.svg new file mode 100644 index 00000000..c52b50bb --- /dev/null +++ b/public/icons/power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/spinner.svg b/public/icons/spinner.svg new file mode 100644 index 00000000..94b6df09 --- /dev/null +++ b/public/icons/spinner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/subtitles.svg b/public/icons/subtitles.svg new file mode 100644 index 00000000..075c420d --- /dev/null +++ b/public/icons/subtitles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pods/properties/components/icon-selector/modal/icons.ts b/src/pods/properties/components/icon-selector/modal/icons.ts index 3ddb954a..8646e562 100644 --- a/src/pods/properties/components/icon-selector/modal/icons.ts +++ b/src/pods/properties/components/icon-selector/modal/icons.ts @@ -1767,4 +1767,124 @@ export const iconCollection: IconInfo[] = [ ], categories: ['IT'], }, + { + name: 'Caret down', + filename: 'caretdown.svg', + searchTerms: ['caret', 'down', 'arrow', 'move'], + categories: ['IT'], + }, + { + name: 'Caret left', + filename: 'caretleft.svg', + searchTerms: ['caret', 'left', 'arrow', 'move'], + categories: ['IT'], + }, + { + name: 'Caret up', + filename: 'caretup.svg', + searchTerms: ['caret', 'up', 'arrow', 'move'], + categories: ['IT'], + }, + { + name: 'Caret right', + filename: 'caretright.svg', + searchTerms: ['caret', 'right', 'arrow', 'move'], + categories: ['IT'], + }, + { + name: 'Camera', + filename: 'camera.svg', + searchTerms: ['camera', 'photo', 'shot', 'capture', 'snapshot'], + categories: ['IT'], + }, + { + name: 'Flag', + filename: 'flag.svg', + searchTerms: ['flag', 'signal', 'banderole', 'banner'], + categories: ['IT'], + }, + { + name: 'First aid', + filename: 'firstaid.svg', + searchTerms: ['hospital', 'medical', 'emergency', 'health'], + categories: ['IT'], + }, + { + name: 'Hammer', + filename: 'hammer.svg', + searchTerms: ['hammer', 'tool', 'build', 'repair'], + categories: ['IT'], + }, + { + name: 'Joystick', + filename: 'joystick.svg', + searchTerms: ['joystick', 'game', 'play', 'controller'], + categories: ['IT'], + }, + { + name: 'Controller', + filename: 'controller.svg', + searchTerms: ['controller', 'game', 'play', 'gamepad'], + categories: ['IT'], + }, + { + name: 'Key', + filename: 'key.svg', + searchTerms: ['key', 'secure', 'password', 'access'], + categories: ['IT'], + }, + { + name: 'Lock', + filename: 'lock.svg', + searchTerms: ['lock', 'secure', 'password', 'access'], + categories: ['IT'], + }, + { + name: 'Unlock', + filename: 'lockopen.svg', + searchTerms: ['unlock', 'open', 'access', 'password'], + categories: ['IT'], + }, + { + name: 'Flash', + filename: 'lightning.svg', + searchTerms: ['flash', 'lightning', 'energy', 'power'], + categories: ['IT'], + }, + { + name: 'Auto flash', + filename: 'autoflash.svg', + searchTerms: ['auto', 'flash', 'lightning', 'energy', 'power'], + categories: ['IT'], + }, + { + name: 'Flash slash', + filename: 'flashslash.svg', + searchTerms: ['flash', 'slash', 'lightning', 'energy', 'power'], + categories: ['IT'], + }, + { + name: 'Another Mouse', + filename: 'alternativemouse.svg', + searchTerms: ['mouse', 'device', 'computer', 'click'], + categories: ['IT'], + }, + { + name: 'Power', + filename: 'power.svg', + searchTerms: ['power', 'on', 'off', 'energy'], + categories: ['IT'], + }, + { + name: 'Spinner', + filename: 'spinner.svg', + searchTerms: ['spinner', 'loading', 'wait', 'progress'], + categories: ['IT'], + }, + { + name: 'Subtitles', + filename: 'subtitles.svg', + searchTerms: ['subtitles', 'caption', 'language', 'translate'], + categories: ['IT'], + }, ]; From 4e8981be58ccfcdc357dd5b0650ec6623a3ddcb8 Mon Sep 17 00:00:00 2001 From: oleojake Date: Wed, 30 Oct 2024 21:23:50 +0100 Subject: [PATCH 19/27] #495 limit shape context menu to main canvas --- src/pods/context-menu/use-context-menu.hook.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pods/context-menu/use-context-menu.hook.tsx b/src/pods/context-menu/use-context-menu.hook.tsx index d2c53fbb..3b8e1177 100644 --- a/src/pods/context-menu/use-context-menu.hook.tsx +++ b/src/pods/context-menu/use-context-menu.hook.tsx @@ -26,7 +26,11 @@ export const ContextMenu: React.FC = ({ dropRef }) => { const handleRightClick = (event: MouseEvent) => { event.preventDefault(); - if (selectionInfo.getSelectedShapeData()) { + if ( + selectionInfo.getSelectedShapeData() && + stageRef.current && + stageRef.current.container().contains(event.target as Node) + ) { setShowContextMenu(true); setContextMenuPosition({ x: event.clientX, y: event.clientY }); } From 473e4dec6dd08e4a14c218e41e74065af62b4eaa Mon Sep 17 00:00:00 2001 From: oleojake Date: Wed, 30 Oct 2024 22:00:53 +0100 Subject: [PATCH 20/27] #495 delete thumb pages working --- src/core/providers/canvas/canvas.model.ts | 1 + src/core/providers/canvas/canvas.provider.tsx | 33 +++++++++++++++---- .../context-menu/context-menu.component.tsx | 31 +++++++++-------- .../thumb-pages/components/thumb-page.tsx | 1 - 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index dfb283ac..7143f211 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -98,6 +98,7 @@ export interface CanvasContextModel { addNewPage: () => void; duplicatePage: (pageIndex: number) => void; setActivePage: (pageId: string) => void; + deletePage: (pageIndex: number) => void; isThumbnailContextMenuVisible: boolean; setIsThumbnailContextMenuVisible: React.Dispatch< React.SetStateAction diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 82270520..b9910739 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -66,24 +66,44 @@ export const CanvasProvider: React.FC = props => { setDocument(lastDocument => produce(lastDocument, draft => { - draft.pages.push({ + const newPage = { id: uuidv4(), name: `Page ${draft.pages.length + 1}`, shapes: newShapes, - }); + }; + draft.pages.push(newPage); + setActivePage(newPage.id); }) ); }; + const deletePage = (pageIndex: number) => { + const newActivePageId = + pageIndex < document.pages.length - 1 + ? document.pages[pageIndex + 1].id // If it's not the last page, select the next one + : document.pages[pageIndex - 1].id; // Otherwise, select the previous one + + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages = draft.pages.filter( + currentPage => document.pages[pageIndex].id !== currentPage.id + ); + }) + ); + + setActivePage(newActivePageId); + }; + 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 pageIndex = draft.pages.findIndex(page => page.id === pageId); + if (pageIndex !== -1) { + draft.activePageIndex = pageIndex; + } }) ); }; @@ -269,6 +289,7 @@ export const CanvasProvider: React.FC = props => { addNewPage, duplicatePage, setActivePage, + deletePage, isThumbnailContextMenuVisible, setIsThumbnailContextMenuVisible, }} diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx index ee5ef5c8..b70d8052 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx @@ -13,8 +13,12 @@ export const ThumbPageContextMenu: React.FunctionComponent< ThumbPageContextMenuProps > = props => { const { contextMenuRef, setShowContextMenu, pageIndex } = props; - const { setIsThumbnailContextMenuVisible, duplicatePage, fullDocument } = - useCanvasContext(); + const { + setIsThumbnailContextMenuVisible, + fullDocument, + duplicatePage, + deletePage, + } = useCanvasContext(); enum ContextButtonType { 'Duplicate', @@ -22,7 +26,11 @@ export const ThumbPageContextMenu: React.FunctionComponent< 'Delete', } - const handleClickOnContextButton = (buttonClicked: ContextButtonType) => { + const handleClickOnContextButton = ( + event: React.MouseEvent, + buttonClicked: ContextButtonType + ) => { + event.stopPropagation(); switch (buttonClicked) { case ContextButtonType.Duplicate: duplicatePage(pageIndex); @@ -31,7 +39,7 @@ export const ThumbPageContextMenu: React.FunctionComponent< console.log('Rename'); break; case ContextButtonType.Delete: - console.log('Delete'); + deletePage(pageIndex); break; } setShowContextMenu(false); @@ -41,21 +49,18 @@ export const ThumbPageContextMenu: React.FunctionComponent< return (
handleClickOnContextButton(ContextButtonType.Duplicate)} + onClick={event => + handleClickOnContextButton(event, ContextButtonType.Duplicate) + } className={classes.container} >

Duplicate

- {/*
handleClickOnContextButton(ContextButtonType.Rename)} - className={classes.container} - > -

Rename

- -
*/}
handleClickOnContextButton(ContextButtonType.Delete)} + onClick={event => + handleClickOnContextButton(event, ContextButtonType.Delete) + } className={ fullDocument.pages.length === 1 ? `${classes.container} ${classes.disabled}` diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index a00549d9..fae5a419 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -16,7 +16,6 @@ interface Props { export const ThumbPage: React.FunctionComponent = props => { const { pageIndex, onSetActivePage } = props; const { fullDocument } = useCanvasContext(); - const page = fullDocument.pages[pageIndex]; const shapes = page.shapes; const fakeShapeRefs = useRef({}); From ebb5e62cef7ee606d69b7d1855cbbfd4e0a6c120 Mon Sep 17 00:00:00 2001 From: oleojake Date: Wed, 30 Oct 2024 22:10:22 +0100 Subject: [PATCH 21/27] #495 fix font size --- .../components/context-menu/context-menu.component.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css index 41c04557..3493bb7b 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css @@ -14,6 +14,7 @@ display: flex; gap: 0.5em; align-items: center; + font-size: var(--fs-xs); padding: var(--space-xs) var(--space-md); border-bottom: 1px solid var(--primary-300); cursor: pointer; From 17c32ec06364140dcd843d47347b8b4c4891e394 Mon Sep 17 00:00:00 2001 From: Fran Lopez Date: Thu, 31 Oct 2024 18:38:23 +0100 Subject: [PATCH 22/27] add option to stroke style none --- .../components/stroke-style/stroke.style.component.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pods/properties/components/stroke-style/stroke.style.component.tsx b/src/pods/properties/components/stroke-style/stroke.style.component.tsx index e1af3ad6..4529bc4c 100644 --- a/src/pods/properties/components/stroke-style/stroke.style.component.tsx +++ b/src/pods/properties/components/stroke-style/stroke.style.component.tsx @@ -31,6 +31,7 @@ export const StrokeStyle: React.FC = props => { +
); From 575e073dfa4b715ec3c35d8d2b5cfda853c9c995 Mon Sep 17 00:00:00 2001 From: oleojake Date: Sat, 2 Nov 2024 20:29:05 +0100 Subject: [PATCH 23/27] #496 allow page title edit --- src/core/providers/canvas/canvas.model.ts | 1 + src/core/providers/canvas/canvas.provider.tsx | 11 +++- .../context-menu.component.module.css | 2 +- .../context-menu/context-menu.component.tsx | 24 +++++++-- .../components/context-menu/index.ts | 1 + src/pods/thumb-pages/components/index.ts | 1 + .../page-title-inline-edit.component.tsx | 54 +++++++++++++++++++ .../thumb-pages/components/thumb-page.tsx | 6 ++- src/pods/thumb-pages/thumb-pages.pod.tsx | 22 ++++++-- 9 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 src/pods/thumb-pages/components/context-menu/index.ts create mode 100644 src/pods/thumb-pages/components/page-title-inline-edit.component.tsx diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 7143f211..0337290d 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -99,6 +99,7 @@ export interface CanvasContextModel { duplicatePage: (pageIndex: number) => void; setActivePage: (pageId: string) => void; deletePage: (pageIndex: number) => void; + editPageTitle: (pageIndex: number, newName: string) => void; isThumbnailContextMenuVisible: boolean; setIsThumbnailContextMenuVisible: React.Dispatch< React.SetStateAction diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index b9910739..8e27e500 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -68,7 +68,7 @@ export const CanvasProvider: React.FC = props => { produce(lastDocument, draft => { const newPage = { id: uuidv4(), - name: `Page ${draft.pages.length + 1}`, + name: `${document.pages[pageIndex].name} - copy`, shapes: newShapes, }; draft.pages.push(newPage); @@ -108,6 +108,14 @@ export const CanvasProvider: React.FC = props => { ); }; + const editPageTitle = (pageIndex: number, newName: string) => { + setDocument(lastDocument => + produce(lastDocument, draft => { + draft.pages[pageIndex].name = newName; + }) + ); + }; + const pasteShapes = (shapes: ShapeModel[]) => { const newShapes: ShapeModel[] = shapes.map(shape => { shape.id = uuidv4(); @@ -290,6 +298,7 @@ export const CanvasProvider: React.FC = props => { duplicatePage, setActivePage, deletePage, + editPageTitle, isThumbnailContextMenuVisible, setIsThumbnailContextMenuVisible, }} diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css index 3493bb7b..82de8324 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.module.css @@ -7,7 +7,7 @@ transform: translate(-50%, -50%); border: 1px solid var(--primary-500); background-color: var(--primary-100); - opacity: 0.9; + opacity: 0.98; } .container { diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx index b70d8052..8b5a474e 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx @@ -1,18 +1,24 @@ import React from 'react'; import { useCanvasContext } from '@/core/providers'; import classes from './context-menu.component.module.css'; -import { CopyIcon, DeleteIcon } from '@/common/components/icons'; +import { CopyIcon, DeleteIcon, PencilIcon } from '@/common/components/icons'; interface ThumbPageContextMenuProps { contextMenuRef: React.RefObject; setShowContextMenu: (show: boolean) => void; pageIndex: number; + setPageTitleBeingEdited: (index: number) => void; } export const ThumbPageContextMenu: React.FunctionComponent< ThumbPageContextMenuProps > = props => { - const { contextMenuRef, setShowContextMenu, pageIndex } = props; + const { + contextMenuRef, + setShowContextMenu, + pageIndex, + setPageTitleBeingEdited, + } = props; const { setIsThumbnailContextMenuVisible, fullDocument, @@ -37,9 +43,12 @@ export const ThumbPageContextMenu: React.FunctionComponent< break; case ContextButtonType.Rename: console.log('Rename'); + setPageTitleBeingEdited(pageIndex); break; case ContextButtonType.Delete: - deletePage(pageIndex); + if (fullDocument.pages.length !== 1) { + deletePage(pageIndex); + } break; } setShowContextMenu(false); @@ -57,6 +66,15 @@ export const ThumbPageContextMenu: React.FunctionComponent<

Duplicate

+
+ handleClickOnContextButton(event, ContextButtonType.Rename) + } + className={classes.container} + > +

Rename

+ +
handleClickOnContextButton(event, ContextButtonType.Delete) diff --git a/src/pods/thumb-pages/components/context-menu/index.ts b/src/pods/thumb-pages/components/context-menu/index.ts new file mode 100644 index 00000000..61e476da --- /dev/null +++ b/src/pods/thumb-pages/components/context-menu/index.ts @@ -0,0 +1 @@ +export * from './context-menu.component'; diff --git a/src/pods/thumb-pages/components/index.ts b/src/pods/thumb-pages/components/index.ts index 07ae2fd2..f096ec4a 100644 --- a/src/pods/thumb-pages/components/index.ts +++ b/src/pods/thumb-pages/components/index.ts @@ -1 +1,2 @@ export * from './thumb-page'; +export * from './page-title-inline-edit.component'; diff --git a/src/pods/thumb-pages/components/page-title-inline-edit.component.tsx b/src/pods/thumb-pages/components/page-title-inline-edit.component.tsx new file mode 100644 index 00000000..da42d0fd --- /dev/null +++ b/src/pods/thumb-pages/components/page-title-inline-edit.component.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState } from 'react'; +import { useCanvasContext } from '@/core/providers'; + +interface PageTitleInlineEditProps { + pageIndex: number; + setPageTitleBeingEdited: (index: number | null) => void; +} + +export const PageTitleInlineEdit: React.FC = ({ + pageIndex, + setPageTitleBeingEdited, +}) => { + const { fullDocument, editPageTitle, setIsInlineEditing } = + useCanvasContext(); + const [inputValue, setInputValue] = useState( + fullDocument.pages[pageIndex].name + ); + const inputRef = React.useRef(null); + + const updatePageTitle = () => { + editPageTitle(pageIndex, inputValue); + setPageTitleBeingEdited(null); + setIsInlineEditing(false); + }; + + const handleAction = ( + event: React.FormEvent | React.FocusEvent + ) => { + if (event.type === 'submit') { + event.preventDefault(); + } + updatePageTitle(); + }; + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + setIsInlineEditing(true); + } + }, []); + + return ( +
+ setInputValue(e.target.value)} + onBlur={handleAction} + /> +
+ ); +}; diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index fae5a419..490aec39 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -5,16 +5,17 @@ import { calculateCanvasBounds } from '@/pods/toolbar/components/export-button/e import { KonvaEventObject } from 'konva/lib/Node'; import { createRef, useRef } from 'react'; import { Layer, Stage } from 'react-konva'; -import { ThumbPageContextMenu } from './context-menu/context-menu.component'; +import { ThumbPageContextMenu } from './context-menu'; import { useContextMenu } from '../use-context-menu-thumb.hook'; interface Props { pageIndex: number; onSetActivePage: (pageId: string) => void; + setPageTitleBeingEdited: (index: number) => void; } export const ThumbPage: React.FunctionComponent = props => { - const { pageIndex, onSetActivePage } = props; + const { pageIndex, onSetActivePage, setPageTitleBeingEdited } = props; const { fullDocument } = useCanvasContext(); const page = fullDocument.pages[pageIndex]; const shapes = page.shapes; @@ -69,6 +70,7 @@ export const ThumbPage: React.FunctionComponent = props => { contextMenuRef={contextMenuRef} setShowContextMenu={setShowContextMenu} pageIndex={pageIndex} + setPageTitleBeingEdited={setPageTitleBeingEdited} /> )}
diff --git a/src/pods/thumb-pages/thumb-pages.pod.tsx b/src/pods/thumb-pages/thumb-pages.pod.tsx index d71e8f5d..edbdded9 100644 --- a/src/pods/thumb-pages/thumb-pages.pod.tsx +++ b/src/pods/thumb-pages/thumb-pages.pod.tsx @@ -1,10 +1,13 @@ import React from 'react'; import classes from './thumb-pages.module.css'; import { useCanvasContext } from '@/core/providers'; -import { ThumbPage } from './components'; +import { PageTitleInlineEdit, ThumbPage } from './components'; export const ThumbPagesPod: React.FC = () => { const { fullDocument, addNewPage, setActivePage } = useCanvasContext(); + const [pageTitleBeingEdited, setPageTitleBeingEdited] = React.useState< + number | null + >(null); const handleAddNewPage = () => { addNewPage(); @@ -18,8 +21,21 @@ export const ThumbPagesPod: React.FC = () => {
{fullDocument.pages.map((page, index) => ( - -
{page.name}
+ + {pageTitleBeingEdited === index ? ( + + ) : ( +
setPageTitleBeingEdited(index)}> + {page.name} +
+ )}
))} From 9a364fa2614928044ee5978c0db7867da38b1e0a Mon Sep 17 00:00:00 2001 From: Jesus Sanz Date: Sun, 3 Nov 2024 10:55:39 +0100 Subject: [PATCH 24/27] #503 - BugFix - Updated Version on Save File to 0.2. - Created new function to handle V0.1 Saves into one page. --- .../local-disk/shapes-to-document.mapper.ts | 27 ++++++++++++++++++- src/core/local-disk/use-local-disk.hook.ts | 16 ++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/core/local-disk/shapes-to-document.mapper.ts b/src/core/local-disk/shapes-to-document.mapper.ts index 4fdd81f4..e32bbe69 100644 --- a/src/core/local-disk/shapes-to-document.mapper.ts +++ b/src/core/local-disk/shapes-to-document.mapper.ts @@ -1,3 +1,4 @@ +import { ShapeModel } from '../model'; import { DocumentModel } from '../providers/canvas/canvas.model'; import { QuickMockFileContract } from './local-disk.model'; @@ -6,7 +7,7 @@ export const mapFromShapesArrayToQuickMockFileDocument = ( ): QuickMockFileContract => { // TODO: Serialize the activePageIndex? return { - version: '0.1', + version: '0.2', pages: fullDocument.pages, }; }; @@ -19,3 +20,27 @@ export const mapFromQuickMockFileDocumentToApplicationDocument = ( pages: fileDocument.pages, }; }; + +// Example function to handle version 0.1 parsing +export const mapFromQuickMockFileDocumentToApplicationDocumentV0_1 = ( + fileDocument: QuickMockFileContract +): DocumentModel => { + // Combine all shapes into a single page + const combinedShapes: ShapeModel[] = fileDocument.pages.reduce( + (acc: ShapeModel[], page) => { + return acc.concat(page.shapes); + }, + [] + ); + + return { + activePageIndex: 0, + pages: [ + { + id: '1', + name: 'default', + shapes: combinedShapes, + }, + ], + }; +}; diff --git a/src/core/local-disk/use-local-disk.hook.ts b/src/core/local-disk/use-local-disk.hook.ts index 66cc2bb0..1d5bd4ea 100644 --- a/src/core/local-disk/use-local-disk.hook.ts +++ b/src/core/local-disk/use-local-disk.hook.ts @@ -3,6 +3,7 @@ import { useCanvasContext } from '../providers'; import { mapFromShapesArrayToQuickMockFileDocument, mapFromQuickMockFileDocumentToApplicationDocument, + mapFromQuickMockFileDocumentToApplicationDocumentV0_1, } from './shapes-to-document.mapper'; import { fileInput, OnFileSelectedCallback } from '@/common/file-input'; import { QuickMockFileContract } from './local-disk.model'; @@ -57,9 +58,18 @@ export const useLocalDisk = () => { reader.onload = () => { const content = reader.result as string; const parseData: QuickMockFileContract = JSON.parse(content); - const appDocument = - mapFromQuickMockFileDocumentToApplicationDocument(parseData); - loadDocument(appDocument); + + if (parseData.version === '0.1') { + // Handle version 0.1 parsing + const appDocument = + mapFromQuickMockFileDocumentToApplicationDocumentV0_1(parseData); + loadDocument(appDocument); + } else { + // Handle other versions + const appDocument = + mapFromQuickMockFileDocumentToApplicationDocument(parseData); + loadDocument(appDocument); + } }; reader.readAsText(file); }; From ee02af56371bb0d3d706f9e9e299bd056a82d666 Mon Sep 17 00:00:00 2001 From: Jesus Sanz Date: Sun, 3 Nov 2024 11:08:22 +0100 Subject: [PATCH 25/27] Updated shapes-to-document tests to handle v0.2 and test v0.1 imports. --- .../shapes-to.document.mapper.spec.ts | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) 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 8106f583..f5b713af 100644 --- a/src/core/local-disk/shapes-to.document.mapper.spec.ts +++ b/src/core/local-disk/shapes-to.document.mapper.spec.ts @@ -1,6 +1,7 @@ import { mapFromShapesArrayToQuickMockFileDocument, mapFromQuickMockFileDocumentToApplicationDocument, + mapFromQuickMockFileDocumentToApplicationDocumentV0_1, } from './shapes-to-document.mapper'; import { ShapeModel } from '../model'; import { QuickMockFileContract } from './local-disk.model'; @@ -35,7 +36,7 @@ describe('shapes to document mapper', () => { }; const expectedResult: QuickMockFileContract = { - version: '0.1', + version: '0.2', pages: [ { id: '1', @@ -88,7 +89,7 @@ describe('shapes to document mapper', () => { }; const expectedResult: QuickMockFileContract = { - version: '0.1', + version: '0.2', pages: [ { id: '1', @@ -110,7 +111,7 @@ describe('shapes to document mapper', () => { it('Should return a document model with a empty shapes array when we feed a empty pages array', () => { //arrange const fileDocument: QuickMockFileContract = { - version: '0.1', + version: '0.2', pages: [ { id: '1', @@ -140,7 +141,7 @@ describe('shapes to document mapper', () => { it('Should return a document model with a empty shapes array when we feed a file document with a one page but with empty shapes', () => { //arrange const fileDocument: QuickMockFileContract = { - version: '0.1', + version: '0.2', pages: [ { id: '1', @@ -287,5 +288,67 @@ describe('shapes to document mapper', () => { //assert expect(result).toEqual(expectedResult); }); + + it('Should return a document model with shapes in one page when we feed a file document from version 0.1', () => { + //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: shapespageA, + }, + { + id: '2', + name: 'default', + shapes: shapesPageB, + }, + ], + }; + + const expectedResult: DocumentModel = { + activePageIndex: 0, + pages: [ + { + id: '1', + name: 'default', + shapes: shapespageA.concat(shapesPageB), + }, + ], + }; + + //act + const result = + mapFromQuickMockFileDocumentToApplicationDocumentV0_1(fileDocument); + //assert + expect(result).toEqual(expectedResult); + }); }); }); From 3ee5a32a6f099615ea033468949bec76e51baf4e Mon Sep 17 00:00:00 2001 From: Fran Lopez Date: Mon, 4 Nov 2024 13:00:23 +0100 Subject: [PATCH 26/27] add style thumbnail rectangle and highlight selected --- .../components/thumb-page.module.css | 11 +++++++ .../thumb-pages/components/thumb-page.tsx | 8 ++--- src/pods/thumb-pages/thumb-pages.module.css | 4 +++ src/pods/thumb-pages/thumb-pages.pod.tsx | 32 ++++++++++++------- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/pods/thumb-pages/components/thumb-page.module.css b/src/pods/thumb-pages/components/thumb-page.module.css index e69de29b..efec2108 100644 --- a/src/pods/thumb-pages/components/thumb-page.module.css +++ b/src/pods/thumb-pages/components/thumb-page.module.css @@ -0,0 +1,11 @@ +.container { + width: '250px'; + height: '180px'; + border: 3px solid; + border-color: black; + border-radius: 5px; + position: 'relative'; + background-color: white; + margin-top: 10px; + padding: 5px; +} diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index 490aec39..1325620f 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -7,6 +7,7 @@ import { createRef, useRef } from 'react'; import { Layer, Stage } from 'react-konva'; import { ThumbPageContextMenu } from './context-menu'; import { useContextMenu } from '../use-context-menu-thumb.hook'; +import classes from './thumb-page.module.css'; interface Props { pageIndex: number; @@ -40,12 +41,7 @@ export const ThumbPage: React.FunctionComponent = props => { return ( <>
onSetActivePage(page.id)} onContextMenu={handleShowContextMenu} > diff --git a/src/pods/thumb-pages/thumb-pages.module.css b/src/pods/thumb-pages/thumb-pages.module.css index ee6a955e..846a12c2 100644 --- a/src/pods/thumb-pages/thumb-pages.module.css +++ b/src/pods/thumb-pages/thumb-pages.module.css @@ -7,3 +7,7 @@ flex-wrap: wrap; flex-direction: column; } + +.activeThumb { + background-color: rgb(41, 41, 235); +} diff --git a/src/pods/thumb-pages/thumb-pages.pod.tsx b/src/pods/thumb-pages/thumb-pages.pod.tsx index edbdded9..b61134be 100644 --- a/src/pods/thumb-pages/thumb-pages.pod.tsx +++ b/src/pods/thumb-pages/thumb-pages.pod.tsx @@ -8,6 +8,7 @@ export const ThumbPagesPod: React.FC = () => { const [pageTitleBeingEdited, setPageTitleBeingEdited] = React.useState< number | null >(null); + const [activePageId, setActivePageId] = React.useState(null); const handleAddNewPage = () => { addNewPage(); @@ -15,27 +16,34 @@ export const ThumbPagesPod: React.FC = () => { const handleSetActivePage = (pageId: string) => { setActivePage(pageId); + setActivePageId(pageId); }; return (
{fullDocument.pages.map((page, index) => ( - - {pageTitleBeingEdited === index ? ( - + - ) : ( -
setPageTitleBeingEdited(index)}> - {page.name} -
- )} + {pageTitleBeingEdited === index ? ( + + ) : ( +
setPageTitleBeingEdited(index)}> + {page.name} +
+ )} +
))} From b929c83f24218c0a0397c3a19b0b9cf67d2b0f36 Mon Sep 17 00:00:00 2001 From: Fran Lopez Date: Mon, 4 Nov 2024 17:12:22 +0100 Subject: [PATCH 27/27] update text color to white for active page and change width Stage --- src/pods/thumb-pages/components/thumb-page.module.css | 2 +- src/pods/thumb-pages/components/thumb-page.tsx | 4 ++-- src/pods/thumb-pages/thumb-pages.module.css | 4 ++++ src/pods/thumb-pages/thumb-pages.pod.tsx | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pods/thumb-pages/components/thumb-page.module.css b/src/pods/thumb-pages/components/thumb-page.module.css index efec2108..bfa1c2be 100644 --- a/src/pods/thumb-pages/components/thumb-page.module.css +++ b/src/pods/thumb-pages/components/thumb-page.module.css @@ -1,5 +1,5 @@ .container { - width: '250px'; + width: '200px'; height: '180px'; border: 3px solid; border-color: black; diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index 1325620f..3c031d0c 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -27,7 +27,7 @@ export const ThumbPage: React.FunctionComponent = props => { width: bounds.x + bounds.width, height: bounds.y + bounds.height, }; - const scaleFactorX = 250 / canvasSize.width; + const scaleFactorX = 200 / canvasSize.width; const scaleFactorY = 180 / canvasSize.height; const finalScale = Math.min(scaleFactorX, scaleFactorY); @@ -45,7 +45,7 @@ export const ThumbPage: React.FunctionComponent = props => { onClick={() => onSetActivePage(page.id)} onContextMenu={handleShowContextMenu} > - + {shapes.map(shape => { if (!fakeShapeRefs.current[shape.id]) { diff --git a/src/pods/thumb-pages/thumb-pages.module.css b/src/pods/thumb-pages/thumb-pages.module.css index 846a12c2..361dbc37 100644 --- a/src/pods/thumb-pages/thumb-pages.module.css +++ b/src/pods/thumb-pages/thumb-pages.module.css @@ -11,3 +11,7 @@ .activeThumb { background-color: rgb(41, 41, 235); } + +.activeText { + color: white; +} diff --git a/src/pods/thumb-pages/thumb-pages.pod.tsx b/src/pods/thumb-pages/thumb-pages.pod.tsx index b61134be..69261574 100644 --- a/src/pods/thumb-pages/thumb-pages.pod.tsx +++ b/src/pods/thumb-pages/thumb-pages.pod.tsx @@ -39,7 +39,10 @@ export const ThumbPagesPod: React.FC = () => { setPageTitleBeingEdited={setPageTitleBeingEdited} /> ) : ( -
setPageTitleBeingEdited(index)}> +
setPageTitleBeingEdited(index)} + className={page.id === activePageId ? classes.activeText : ''} + > {page.name}
)}