diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 5e553335..dcfa6c4c 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Coord, OtherProps, ShapeModel, ShapeType, Size } from '@/core/model'; import { CanvasContext } from './canvas.context'; import { useSelection } from './use-selection.hook'; -import { createShape } from '@/pods/canvas/canvas.model'; +import { createShape } from '@/pods/canvas/model'; import { useHistoryManager } from '@/common/undo-redo'; import { useStateWithInterceptor } from './canvas.hook'; import { createDefaultDocumentModel, DocumentModel } from './canvas.model'; diff --git a/src/pods/canvas/canvas.model.ts b/src/pods/canvas/canvas.model.ts deleted file mode 100644 index 84c97869..00000000 --- a/src/pods/canvas/canvas.model.ts +++ /dev/null @@ -1,604 +0,0 @@ -import { - Coord, - ShapeType, - Size, - ShapeModel, - EditType, - OtherProps, - ShapeSizeRestrictions, -} from '@/core/model'; -import { v4 as uuidv4 } from 'uuid'; - -import { - getComboBoxShapeSizeRestrictions, - getInputShapeSizeRestrictions, - getListboxShapeSizeRestrictions, - getTextAreaSizeRestrictions, - getToggleSwitchShapeSizeRestrictions, - getProgressBarShapeSizeRestrictions, - getDatepickerInputShapeSizeRestrictions, - getButtonShapeSizeRestrictions, - getTimepickerInputShapeSizeRestrictions, - getRadioButtonShapeSizeRestrictions, - getCheckboxShapeSizeRestrictions, - getIconShapeSizeRestrictions, - getHorizontalScrollBarShapeSizeRestrictions, - getVerticalScrollBarShapeSizeRestrictions, - getTooltipShapeSizeRestrictions, - getLabelSizeRestrictions, - getSliderShapeSizeRestrictions, -} from '@/common/components/mock-components/front-components'; -import { - getBrowserWindowShapeSizeRestrictions, - getMobilePhoneShapeSizeRestrictions, - getModalDialogShapeSizeRestrictions, - getTabletShapeSizeRestrictions, -} from '@/common/components/mock-components/front-containers'; -import { - getTriangleShapeSizeRestrictions, - getCircleShapeSizeRestrictions, - getDiamondShapeSizeRestrictions, - getPostItShapeSizeRestrictions, - getRectangleShapeSizeRestrictions, - getlineShapeRestrictions, - getStarShapeSizeRestrictions, - getLargeArrowShapeSizeRestrictions, - getImageShapeSizeRestrictions, -} from '@/common/components/mock-components/front-basic-shapes'; -import { - getAccordionShapeSizeRestrictions, - getBreadcrumbShapeSizeRestrictions, - getPieChartShapeSizeRestrictions, - getBarChartShapeSizeRestrictions, - getVideoPlayerShapeSizeRestrictions, - getHorizontalMenuShapeSizeRestrictions, - getMapChartShapeSizeRestrictions, - getLineChartShapeSizeRestrictions, - getVerticalMenuShapeSizeRestrictions, - getCalendarShapeSizeRestrictions, - getTableSizeRestrictions, - getModalShapeSizeRestrictions, - getAppBarShapeSizeRestrictions, - getButtonBarShapeSizeRestrictions, - getTabsBarShapeSizeRestrictions, -} from '@/common/components/mock-components/front-rich-components'; -import { - getHeading1SizeRestrictions, - getHeading2SizeRestrictions, - getHeading3SizeRestrictions, - getNormaltextSizeRestrictions, - getParagraphSizeRestrictions, - getSmalltextSizeRestrictions, -} from '@/common/components/mock-components/front-text-components'; -import { - BASIC_SHAPE, - INPUT_SHAPE, -} from '@/common/components/mock-components/front-components/shape.const'; - -export const getSizeRestrictionFromShape = ( - shapeType: ShapeType -): ShapeSizeRestrictions => { - switch (shapeType) { - case 'label': - return getLabelSizeRestrictions(); - case 'combobox': - return getComboBoxShapeSizeRestrictions(); - case 'input': - return getInputShapeSizeRestrictions(); - case 'toggleswitch': - return getToggleSwitchShapeSizeRestrictions(); - case 'textarea': - return getTextAreaSizeRestrictions(); - case 'datepickerinput': - return getDatepickerInputShapeSizeRestrictions(); - case 'button': - return getButtonShapeSizeRestrictions(); - case 'progressbar': - return getProgressBarShapeSizeRestrictions(); - case 'listbox': - return getListboxShapeSizeRestrictions(); - case 'browser': - return getBrowserWindowShapeSizeRestrictions(); - case 'mobilePhone': - return getMobilePhoneShapeSizeRestrictions(); - case 'tablet': - return getTabletShapeSizeRestrictions(); - case 'modalDialog': - return getModalDialogShapeSizeRestrictions(); - case 'timepickerinput': - return getTimepickerInputShapeSizeRestrictions(); - case 'rectangle': - return getRectangleShapeSizeRestrictions(); - case 'videoPlayer': - return getVideoPlayerShapeSizeRestrictions(); - case 'diamond': - return getDiamondShapeSizeRestrictions(); - case 'line': - return getlineShapeRestrictions(); - case 'accordion': - return getAccordionShapeSizeRestrictions(); - case 'triangle': - return getTriangleShapeSizeRestrictions(); - case 'postit': - return getPostItShapeSizeRestrictions(); - case 'pie': - return getPieChartShapeSizeRestrictions(); - case 'horizontal-menu': - return getHorizontalMenuShapeSizeRestrictions(); - case 'vertical-menu': - return getVerticalMenuShapeSizeRestrictions(); - case 'breadcrumb': - return getBreadcrumbShapeSizeRestrictions(); - case 'map': - return getMapChartShapeSizeRestrictions(); - case 'circle': - return getCircleShapeSizeRestrictions(); - case 'star': - return getStarShapeSizeRestrictions(); - case 'linechart': - return getLineChartShapeSizeRestrictions(); - case 'heading1': - return getHeading1SizeRestrictions(); - case 'heading2': - return getHeading2SizeRestrictions(); - case 'heading3': - return getHeading3SizeRestrictions(); - case 'normaltext': - return getNormaltextSizeRestrictions(); - case 'smalltext': - return getSmalltextSizeRestrictions(); - case 'paragraph': - return getParagraphSizeRestrictions(); - case 'largeArrow': - return getLargeArrowShapeSizeRestrictions(); - case 'radiobutton': - return getRadioButtonShapeSizeRestrictions(); - case 'checkbox': - return getCheckboxShapeSizeRestrictions(); - case 'icon': - return getIconShapeSizeRestrictions(); - case 'bar': - return getBarChartShapeSizeRestrictions(); - case 'image': - return getImageShapeSizeRestrictions(); - case 'table': - return getTableSizeRestrictions(); - case 'horizontalScrollBar': - return getHorizontalScrollBarShapeSizeRestrictions(); - case 'calendar': - return getCalendarShapeSizeRestrictions(); - case 'verticalScrollBar': - return getVerticalScrollBarShapeSizeRestrictions(); - case 'modal': - return getModalShapeSizeRestrictions(); - case 'tabsBar': - return getTabsBarShapeSizeRestrictions(); - case 'appBar': - return getAppBarShapeSizeRestrictions(); - case 'buttonBar': - return getButtonBarShapeSizeRestrictions(); - case 'tooltip': - return getTooltipShapeSizeRestrictions(); - case 'slider': - return getSliderShapeSizeRestrictions(); - default: - console.warn( - `** Shape ${shapeType} has not defined default size, check getDefaultSizeFromShape helper function` - ); - return { - minWidth: 200, - minHeight: 50, - maxWidth: -1, - maxHeight: -1, - defaultWidth: 200, - defaultHeight: 50, - }; - } -}; - -export const getMinSizeFromShape = (shapeType: ShapeType): Size => { - const { minWidth: width, minHeight: height } = - getSizeRestrictionFromShape(shapeType); - - return { width, height }; -}; - -export const getDefaultSizeFromShape = (shapeType: ShapeType): Size => { - const { defaultWidth: width, defaultHeight: height } = - getSizeRestrictionFromShape(shapeType); - - return { width, height }; -}; - -const doesShapeAllowInlineEdition = (shapeType: ShapeType): boolean => { - switch (shapeType) { - case 'input': - case 'label': - case 'combobox': - case 'button': - case 'textarea': - case 'accordion': - case 'checkbox': - case 'radiobutton': - case 'postit': - case 'horizontal-menu': - case 'vertical-menu': - case 'breadcrumb': - case 'heading1': - case 'heading2': - case 'heading3': - case 'normaltext': - case 'smalltext': - case 'paragraph': - case 'listbox': - case 'image': - case 'table': - case 'modal': - case 'appBar': - case 'buttonBar': - case 'tabsBar': - case 'tooltip': - return true; - default: - return false; - } -}; - -const generateTypeOfTransformer = (shapeType: ShapeType): string[] => { - switch (shapeType) { - case 'label': - case 'input': - case 'button': - case 'combobox': - case 'line': - case 'listbox': - case 'checkbox': - case 'toggleswitch': - case 'progressbar': - case 'datepickerinput': - case 'timepickerinput': - case 'radiobutton': - case 'horizontal-menu': - case 'breadcrumb': - case 'heading1': - case 'heading2': - case 'heading3': - case 'normaltext': - case 'smalltext': - case 'horizontalScrollBar': - case 'appBar': - case 'buttonBar': - case 'slider': - return ['middle-left', 'middle-right']; - case 'verticalScrollBar': - return ['top-center', 'bottom-center']; - case 'icon': - case 'multiple': - return []; - case 'image': - return ['top-left', 'top-right', 'bottom-left', 'bottom-right']; - default: - return [ - 'top-left', - 'top-center', - 'top-right', - 'middle-left', - 'middle-right', - 'bottom-left', - 'bottom-center', - 'bottom-right', - ]; - } -}; - -const generateDefaultTextValue = (shapeType: ShapeType): string | undefined => { - switch (shapeType) { - case 'input': - return 'Placeholder'; - case 'label': - return 'Label'; - case 'combobox': - return 'Select an option'; - case 'button': - return 'Click Me!'; - case 'radiobutton': - return 'Select me!'; - case 'textarea': - return 'Your text here...'; - case 'accordion': - return '[*]Section A\nSection B'; - case 'breadcrumb': - return 'Home, Category, Products'; - case 'checkbox': - return 'Check me!'; - case 'postit': - return ''; - case 'listbox': - return '[*]Item\nItem1\nItem2\nItem3\nItem4\nItem5\nItem6'; - case 'horizontal-menu': - return 'Home, About, Services, Contact'; - case 'vertical-menu': - return 'Option 1\nOption 2\n----\nOption 3\nOption 4'; - case 'heading1': - return 'Heading 1'; - case 'heading2': - return 'Heading 2'; - case 'heading3': - return 'Heading 3'; - case 'tooltip': - return 'Sample Text'; - case 'normaltext': - return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; - case 'smalltext': - return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; - case 'paragraph': - return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nSed do eiusmod tempor incididunt ut labore et dolore magna \naliqua.Ut enim ad minim veniam, quis nostrud exercitation \nullamco laboris nisi ut aliquip ex ea commodo consequat \nDuis aute irure dolor in reprehenderit in voluptate velit\nesse cillum dolore eu fugiat nulla pariatur. \nExcepteur sint occaecat cupidatat non proident, sunt in \nculpa qui officia deserunt mollit anim id est laborum.'; - case 'table': - return 'Name ^, Age ^v, Country v\nJohn Doe, 30, USA\nJane Smith, 25, UK\nLuis Gomez, 35, Argentina\n{*L,20R,30C}'; - case 'modal': - return 'Alert\nWarning: The action you are about to perform may affect existing data. Are you sure you want to proceed? Once confirmed, this action cannot be undone.\nConfirm,Cancel'; - case 'appBar': - return 'AppBar'; - case 'buttonBar': - return 'Button 1, Button 2, Button 3'; - case 'tabsBar': - return 'Tab 1, Tab 2, Tab 3'; - default: - return undefined; - } -}; - -const getShapeEditInlineType = (shapeType: ShapeType): EditType | undefined => { - const result = undefined; - - switch (shapeType) { - case 'textarea': - case 'accordion': - case 'postit': - case 'paragraph': - case 'listbox': - case 'vertical-menu': - case 'table': - case 'modal': - case 'appBar': - case 'tabsBar': - case 'tooltip': - return 'textarea'; - break; - case 'image': - return 'imageupload'; - break; - } - return result; -}; - -export const generateDefaultOtherProps = ( - shapeType: ShapeType -): OtherProps | undefined => { - switch (shapeType) { - case 'input': - return { - stroke: INPUT_SHAPE.DEFAULT_STROKE_COLOR, - backgroundColor: INPUT_SHAPE.DEFAULT_FILL_BACKGROUND, - textColor: INPUT_SHAPE.DEFAULT_FILL_TEXT, - strokeStyle: [], - borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, - }; - case 'tooltip': - return { - stroke: '#bbbbbb', - backgroundColor: '#bbbbbb', - textColor: '#ffffff', - strokeStyle: [], - }; - case 'button': - case 'textarea': - case 'vertical-menu': - case 'horizontal-menu': - return { - stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, - backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, - textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, - strokeStyle: [], - borderRadius: `${BASIC_SHAPE.DEFAULT_CORNER_RADIUS}`, - activeElement: 0, - }; - case 'datepickerinput': - case 'timepickerinput': - return { - stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, - backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, - textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, - strokeStyle: [], - borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, - }; - case 'combobox': - return { - stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, - backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, - textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, - strokeStyle: [], - borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, - }; - case 'modal': - case 'buttonBar': - return { - stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, - backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, - textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, - strokeStyle: [], - activeElement: 0, - }; - case 'largeArrow': - return { - stroke: '#000000', - backgroundColor: '#d3d3d3', - strokeStyle: [], - }; - case 'postit': - return { - stroke: '#000000', - backgroundColor: '#FFFF99', - textColor: '#000000', - strokeStyle: [], - borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, - }; - case 'listbox': - return { - stroke: '#000000', - backgroundColor: '#ffffff', - textColor: '#000000', - selectedBackgroundColor: '#add8e6', - }; - - case 'circle': - case 'star': - case 'diamond': - case 'triangle': - return { - stroke: '#000000', - backgroundColor: '#ffffff', - strokeStyle: [], - }; - case 'rectangle': - return { - stroke: '#000000', - backgroundColor: '#ffffff', - strokeStyle: [], - borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, - }; - case 'line': - return { - stroke: '#000000', - strokeStyle: [], - }; - case 'breadcrumb': - case 'heading1': - case 'heading2': - case 'heading3': - case 'normaltext': - case 'smalltext': - case 'paragraph': - case 'label': - return { - textColor: '#000000', - }; - case 'toggleswitch': - return { - checked: true, - }; - case 'checkbox': - case 'radiobutton': - return { - checked: true, - textColor: '#000000', - }; - - case 'appBar': - return { - stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, - backgroundColor: '#A9A9A9', - textColor: '#ffffff', - strokeStyle: [], - }; - case 'icon': - return { - icon: { - name: 'open', - filename: 'open.svg', - searchTerms: ['open', 'folder', 'load'], - categories: ['IT'], - }, - iconSize: 'M', - }; - case 'image': - return { - imageSrc: '', - imageBlackAndWhite: false, - }; - case 'progressbar': - return { - progress: '50', - }; - case 'slider': - return { - progress: '50', - backgroundColor: '#A9A9A9', - }; - case 'tabsBar': - return { - activeElement: 0, - }; - default: - return undefined; - } -}; - -// TODO: create interfaces to hold Coordination and Size -// coordinate: { x: number, y: number } -// size: { width: number, height: number } -export const createShape = ( - coord: Coord, - shapeType: ShapeType, - otherProps?: OtherProps -): ShapeModel => { - const { x, y } = coord; - const { width, height } = getDefaultSizeFromShape(shapeType); - - const defaultProps = generateDefaultOtherProps(shapeType); - - return { - id: uuidv4(), - x, - y, - width, - height, - type: shapeType, - allowsInlineEdition: doesShapeAllowInlineEdition(shapeType), - typeOfTransformer: generateTypeOfTransformer(shapeType), - text: generateDefaultTextValue(shapeType), - editType: getShapeEditInlineType(shapeType), - otherProps: otherProps ? { ...defaultProps, ...otherProps } : defaultProps, - }; -}; - -// Snap model -export const SNAP_THRESHOLD = 5; - -export type SnapLines = { - vertical: number[]; - horizontal: number[]; -}; - -export type SnapType = 'center' | 'start' | 'end'; - -export interface SnapEdge { - guide: number; - offset: number; - snapType: SnapType; -} - -export type SnapEdges = { - vertical: SnapEdge[]; - horizontal: SnapEdge[]; -}; - -export type SnapLineSubset = { - snapLine: number; - diff: number; - snap: SnapType; - offset: number; -}; - -export type ClosestSnapLines = { - vertical: SnapLineSubset | null; - horizontal: SnapLineSubset | null; -}; - -export interface SelectionRect { - x: number; - y: number; - width: number; - height: number; - visible: boolean; -} diff --git a/src/pods/canvas/model/index.ts b/src/pods/canvas/model/index.ts new file mode 100644 index 00000000..6f09b250 --- /dev/null +++ b/src/pods/canvas/model/index.ts @@ -0,0 +1,3 @@ +export * from './transformer.model'; +export * from './shape.model'; +export * from './shape-size.utils'; diff --git a/src/pods/canvas/model/inline-editable.model.ts b/src/pods/canvas/model/inline-editable.model.ts new file mode 100644 index 00000000..a32cf96b --- /dev/null +++ b/src/pods/canvas/model/inline-editable.model.ts @@ -0,0 +1,133 @@ +// src/common/shape-utils/inlineEditableShapes.ts + +import { EditType, ShapeType } from '@/core/model'; + +// Define which shapes allow inline editing +const inlineEditableShapes = new Set([ + 'input', + 'label', + 'combobox', + 'button', + 'textarea', + 'accordion', + 'checkbox', + 'radiobutton', + 'postit', + 'horizontal-menu', + 'vertical-menu', + 'breadcrumb', + 'heading1', + 'heading2', + 'heading3', + 'normaltext', + 'smalltext', + 'paragraph', + 'listbox', + 'image', + 'table', + 'modal', + 'appBar', + 'buttonBar', + 'tabsBar', + 'tooltip', +]); + +// Check if a shape type allows inline editing +export const doesShapeAllowInlineEdition = (shapeType: ShapeType): boolean => { + return inlineEditableShapes.has(shapeType); +}; + +// Set of ShapeTypes that have default text values +const shapeTypesWithDefaultText = new Set([ + 'input', + 'label', + 'combobox', + 'button', + 'radiobutton', + 'textarea', + 'accordion', + 'breadcrumb', + 'checkbox', + 'postit', + 'listbox', + 'horizontal-menu', + 'vertical-menu', + 'heading1', + 'heading2', + 'heading3', + 'tooltip', + 'normaltext', + 'smalltext', + 'paragraph', + 'table', + 'modal', + 'appBar', + 'buttonBar', + 'tabsBar', +]); + +// Map of ShapeTypes to their default text values +const defaultTextValueMap: Partial> = { + input: 'Placeholder', + label: 'Label', + combobox: 'Select an option', + button: 'Click Me!', + radiobutton: 'Select me!', + textarea: 'Your text here...', + accordion: '[*]Section A\nSection B', + breadcrumb: 'Home, Category, Products', + checkbox: 'Check me!', + postit: '', + listbox: '[*]Item\nItem1\nItem2\nItem3\nItem4\nItem5\nItem6', + 'horizontal-menu': 'Home, About, Services, Contact', + 'vertical-menu': 'Option 1\nOption 2\n----\nOption 3\nOption 4', + heading1: 'Heading 1', + heading2: 'Heading 2', + heading3: 'Heading 3', + tooltip: 'Sample Text', + normaltext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + smalltext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + paragraph: `Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nSed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`, + table: + 'Name ^, Age ^v, Country v\nJohn Doe, 30, USA\nJane Smith, 25, UK\nLuis Gomez, 35, Argentina\n{*L,20R,30C}', + modal: + 'Alert\nWarning: The action you are about to perform may affect existing data. Are you sure you want to proceed? Once confirmed, this action cannot be undone.\nConfirm,Cancel', + appBar: 'AppBar', + buttonBar: 'Button 1, Button 2, Button 3', + tabsBar: 'Tab 1, Tab 2, Tab 3', +}; + +export const generateDefaultTextValue = ( + shapeType: ShapeType +): string | undefined => { + if (shapeTypesWithDefaultText.has(shapeType)) { + return defaultTextValueMap[shapeType]; + } + return undefined; +}; + +export const getShapeEditInlineType = ( + shapeType: ShapeType +): EditType | undefined => { + const result = undefined; + + switch (shapeType) { + case 'textarea': + case 'accordion': + case 'postit': + case 'paragraph': + case 'listbox': + case 'vertical-menu': + case 'table': + case 'modal': + case 'appBar': + case 'tabsBar': + case 'tooltip': + return 'textarea'; + break; + case 'image': + return 'imageupload'; + break; + } + return result; +}; diff --git a/src/pods/canvas/model/shape-other-props.utils.ts b/src/pods/canvas/model/shape-other-props.utils.ts new file mode 100644 index 00000000..3b0b21ee --- /dev/null +++ b/src/pods/canvas/model/shape-other-props.utils.ts @@ -0,0 +1,167 @@ +import { + INPUT_SHAPE, + BASIC_SHAPE, +} from '@/common/components/mock-components/front-components/shape.const'; +import { ShapeType, OtherProps } from '@/core/model'; + +export const generateDefaultOtherProps = ( + shapeType: ShapeType +): OtherProps | undefined => { + switch (shapeType) { + case 'input': + return { + stroke: INPUT_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: INPUT_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: INPUT_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, + }; + case 'tooltip': + return { + stroke: '#bbbbbb', + backgroundColor: '#bbbbbb', + textColor: '#ffffff', + strokeStyle: [], + }; + case 'button': + case 'textarea': + case 'vertical-menu': + case 'horizontal-menu': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + borderRadius: `${BASIC_SHAPE.DEFAULT_CORNER_RADIUS}`, + activeElement: 0, + }; + case 'datepickerinput': + case 'timepickerinput': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, + }; + case 'combobox': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, + }; + case 'modal': + case 'buttonBar': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: BASIC_SHAPE.DEFAULT_FILL_BACKGROUND, + textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, + strokeStyle: [], + activeElement: 0, + }; + case 'largeArrow': + return { + stroke: '#000000', + backgroundColor: '#d3d3d3', + strokeStyle: [], + }; + case 'postit': + return { + stroke: '#000000', + backgroundColor: '#FFFF99', + textColor: '#000000', + strokeStyle: [], + borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, + }; + case 'listbox': + return { + stroke: '#000000', + backgroundColor: '#ffffff', + textColor: '#000000', + selectedBackgroundColor: '#add8e6', + }; + + case 'circle': + case 'star': + case 'diamond': + case 'triangle': + return { + stroke: '#000000', + backgroundColor: '#ffffff', + strokeStyle: [], + }; + case 'rectangle': + return { + stroke: '#000000', + backgroundColor: '#ffffff', + strokeStyle: [], + borderRadius: `${INPUT_SHAPE.DEFAULT_CORNER_RADIUS}`, + }; + case 'line': + return { + stroke: '#000000', + strokeStyle: [], + }; + case 'breadcrumb': + case 'heading1': + case 'heading2': + case 'heading3': + case 'normaltext': + case 'smalltext': + case 'paragraph': + case 'label': + return { + textColor: '#000000', + }; + case 'toggleswitch': + return { + checked: true, + }; + case 'checkbox': + case 'radiobutton': + return { + checked: true, + textColor: '#000000', + }; + + case 'appBar': + return { + stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, + backgroundColor: '#A9A9A9', + textColor: '#ffffff', + strokeStyle: [], + }; + case 'icon': + return { + icon: { + name: 'open', + filename: 'open.svg', + searchTerms: ['open', 'folder', 'load'], + categories: ['IT'], + }, + iconSize: 'M', + }; + case 'image': + return { + imageSrc: '', + imageBlackAndWhite: false, + }; + case 'progressbar': + return { + progress: '50', + }; + case 'slider': + return { + progress: '50', + backgroundColor: '#A9A9A9', + }; + case 'tabsBar': + return { + activeElement: 0, + }; + default: + return undefined; + } +}; diff --git a/src/pods/canvas/model/shape-size.mapper.ts b/src/pods/canvas/model/shape-size.mapper.ts new file mode 100644 index 00000000..be3af373 --- /dev/null +++ b/src/pods/canvas/model/shape-size.mapper.ts @@ -0,0 +1,135 @@ +// src/common/shape-utils/shapeSizeMap.ts +import { ShapeType, ShapeSizeRestrictions } from '@/core/model'; +import { + getButtonShapeSizeRestrictions, + getCheckboxShapeSizeRestrictions, + getComboBoxShapeSizeRestrictions, + getDatepickerInputShapeSizeRestrictions, + getHorizontalScrollBarShapeSizeRestrictions, + getIconShapeSizeRestrictions, + getInputShapeSizeRestrictions, + getLabelSizeRestrictions, + getListboxShapeSizeRestrictions, + getProgressBarShapeSizeRestrictions, + getRadioButtonShapeSizeRestrictions, + // import all other necessary functions + getSliderShapeSizeRestrictions, + getTextAreaSizeRestrictions, + getTimepickerInputShapeSizeRestrictions, + getToggleSwitchShapeSizeRestrictions, + getTooltipShapeSizeRestrictions, + getVerticalScrollBarShapeSizeRestrictions, +} from '@/common/components/mock-components/front-components'; +import { + getBrowserWindowShapeSizeRestrictions, + getMobilePhoneShapeSizeRestrictions, + getModalDialogShapeSizeRestrictions, + getTabletShapeSizeRestrictions, + // other imports +} from '@/common/components/mock-components/front-containers'; +import { + getTriangleShapeSizeRestrictions, + getCircleShapeSizeRestrictions, + getDiamondShapeSizeRestrictions, + getImageShapeSizeRestrictions, + getLargeArrowShapeSizeRestrictions, + getlineShapeRestrictions, + getPostItShapeSizeRestrictions, + getRectangleShapeSizeRestrictions, + getStarShapeSizeRestrictions, + // other imports +} from '@/common/components/mock-components/front-basic-shapes'; +import { + getAccordionShapeSizeRestrictions, + getAppBarShapeSizeRestrictions, + getBarChartShapeSizeRestrictions, + getBreadcrumbShapeSizeRestrictions, + getButtonBarShapeSizeRestrictions, + getCalendarShapeSizeRestrictions, + getHorizontalMenuShapeSizeRestrictions, + getLineChartShapeSizeRestrictions, + getMapChartShapeSizeRestrictions, + getModalShapeSizeRestrictions, + getPieChartShapeSizeRestrictions, + getTableSizeRestrictions, + getTabsBarShapeSizeRestrictions, + getVerticalMenuShapeSizeRestrictions, + getVideoPlayerShapeSizeRestrictions, + // other imports +} from '@/common/components/mock-components/front-rich-components'; +import { + getHeading1SizeRestrictions, + getHeading2SizeRestrictions, + getHeading3SizeRestrictions, + getNormaltextSizeRestrictions, + getParagraphSizeRestrictions, + getSmalltextSizeRestrictions, + // other imports +} from '@/common/components/mock-components/front-text-components'; + +const getMultipleNodeSizeRestrictions = (): ShapeSizeRestrictions => ({ + minWidth: 0, + minHeight: 0, + maxWidth: -1, + maxHeight: -1, + defaultWidth: 0, + defaultHeight: 0, +}); + +// Map associating each ShapeType with its size restriction function +const shapeSizeMap: Record ShapeSizeRestrictions> = { + multiple: getMultipleNodeSizeRestrictions, + label: getLabelSizeRestrictions, + combobox: getComboBoxShapeSizeRestrictions, + input: getInputShapeSizeRestrictions, + toggleswitch: getToggleSwitchShapeSizeRestrictions, + textarea: getTextAreaSizeRestrictions, + datepickerinput: getDatepickerInputShapeSizeRestrictions, + button: getButtonShapeSizeRestrictions, + progressbar: getProgressBarShapeSizeRestrictions, + listbox: getListboxShapeSizeRestrictions, + browser: getBrowserWindowShapeSizeRestrictions, + mobilePhone: getMobilePhoneShapeSizeRestrictions, + tablet: getTabletShapeSizeRestrictions, + modalDialog: getModalDialogShapeSizeRestrictions, + timepickerinput: getTimepickerInputShapeSizeRestrictions, + rectangle: getRectangleShapeSizeRestrictions, + videoPlayer: getVideoPlayerShapeSizeRestrictions, + diamond: getDiamondShapeSizeRestrictions, + line: getlineShapeRestrictions, + accordion: getAccordionShapeSizeRestrictions, + triangle: getTriangleShapeSizeRestrictions, + postit: getPostItShapeSizeRestrictions, + pie: getPieChartShapeSizeRestrictions, + 'horizontal-menu': getHorizontalMenuShapeSizeRestrictions, + 'vertical-menu': getVerticalMenuShapeSizeRestrictions, + breadcrumb: getBreadcrumbShapeSizeRestrictions, + map: getMapChartShapeSizeRestrictions, + circle: getCircleShapeSizeRestrictions, + star: getStarShapeSizeRestrictions, + linechart: getLineChartShapeSizeRestrictions, + heading1: getHeading1SizeRestrictions, + heading2: getHeading2SizeRestrictions, + heading3: getHeading3SizeRestrictions, + normaltext: getNormaltextSizeRestrictions, + smalltext: getSmalltextSizeRestrictions, + paragraph: getParagraphSizeRestrictions, + largeArrow: getLargeArrowShapeSizeRestrictions, + radiobutton: getRadioButtonShapeSizeRestrictions, + checkbox: getCheckboxShapeSizeRestrictions, + icon: getIconShapeSizeRestrictions, + bar: getBarChartShapeSizeRestrictions, + image: getImageShapeSizeRestrictions, + table: getTableSizeRestrictions, + horizontalScrollBar: getHorizontalScrollBarShapeSizeRestrictions, + calendar: getCalendarShapeSizeRestrictions, + verticalScrollBar: getVerticalScrollBarShapeSizeRestrictions, + modal: getModalShapeSizeRestrictions, + tabsBar: getTabsBarShapeSizeRestrictions, + appBar: getAppBarShapeSizeRestrictions, + buttonBar: getButtonBarShapeSizeRestrictions, + tooltip: getTooltipShapeSizeRestrictions, + slider: getSliderShapeSizeRestrictions, +}; + +export default shapeSizeMap; diff --git a/src/pods/canvas/model/shape-size.utils.ts b/src/pods/canvas/model/shape-size.utils.ts new file mode 100644 index 00000000..afc853e5 --- /dev/null +++ b/src/pods/canvas/model/shape-size.utils.ts @@ -0,0 +1,37 @@ +import { ShapeSizeRestrictions, ShapeType, Size } from '@/core/model'; +import shapeSizeMap from './shape-size.mapper'; + +export const getSizeRestrictionFromShape = ( + shapeType: ShapeType +): ShapeSizeRestrictions => { + const getSizeRestriction = shapeSizeMap[shapeType]; + if (getSizeRestriction) { + return getSizeRestriction(); + } else { + console.warn( + `** Shape ${shapeType} has not defined default size, check getDefaultSizeFromShape helper function` + ); + return { + minWidth: 200, + minHeight: 50, + maxWidth: -1, + maxHeight: -1, + defaultWidth: 200, + defaultHeight: 50, + }; + } +}; + +export const getMinSizeFromShape = (shapeType: ShapeType): Size => { + const { minWidth: width, minHeight: height } = + getSizeRestrictionFromShape(shapeType); + + return { width, height }; +}; + +export const getDefaultSizeFromShape = (shapeType: ShapeType): Size => { + const { defaultWidth: width, defaultHeight: height } = + getSizeRestrictionFromShape(shapeType); + + return { width, height }; +}; diff --git a/src/pods/canvas/model/shape.model.ts b/src/pods/canvas/model/shape.model.ts new file mode 100644 index 00000000..24343127 --- /dev/null +++ b/src/pods/canvas/model/shape.model.ts @@ -0,0 +1,35 @@ +import { Coord, ShapeType, ShapeModel, OtherProps } from '@/core/model'; +import { v4 as uuidv4 } from 'uuid'; +import { generateTypeOfTransformer } from './transformer.model'; +import { + doesShapeAllowInlineEdition, + generateDefaultTextValue, + getShapeEditInlineType, +} from './inline-editable.model'; +import { getDefaultSizeFromShape } from './shape-size.utils'; +import { generateDefaultOtherProps } from './shape-other-props.utils'; + +export const createShape = ( + coord: Coord, + shapeType: ShapeType, + otherProps?: OtherProps +): ShapeModel => { + const { x, y } = coord; + const { width, height } = getDefaultSizeFromShape(shapeType); + + const defaultProps = generateDefaultOtherProps(shapeType); + + return { + id: uuidv4(), + x, + y, + width, + height, + type: shapeType, + allowsInlineEdition: doesShapeAllowInlineEdition(shapeType), + typeOfTransformer: generateTypeOfTransformer(shapeType), + text: generateDefaultTextValue(shapeType), + editType: getShapeEditInlineType(shapeType), + otherProps: otherProps ? { ...defaultProps, ...otherProps } : defaultProps, + }; +}; diff --git a/src/pods/canvas/model/transformer.model.ts b/src/pods/canvas/model/transformer.model.ts new file mode 100644 index 00000000..20eaef13 --- /dev/null +++ b/src/pods/canvas/model/transformer.model.ts @@ -0,0 +1,89 @@ +import { ShapeType } from '@/core/model'; + +// Snap model +export const SNAP_THRESHOLD = 5; + +export type SnapLines = { + vertical: number[]; + horizontal: number[]; +}; + +export type SnapType = 'center' | 'start' | 'end'; + +export interface SnapEdge { + guide: number; + offset: number; + snapType: SnapType; +} + +export type SnapEdges = { + vertical: SnapEdge[]; + horizontal: SnapEdge[]; +}; + +export type SnapLineSubset = { + snapLine: number; + diff: number; + snap: SnapType; + offset: number; +}; + +export type ClosestSnapLines = { + vertical: SnapLineSubset | null; + horizontal: SnapLineSubset | null; +}; + +export interface SelectionRect { + x: number; + y: number; + width: number; + height: number; + visible: boolean; +} + +export const generateTypeOfTransformer = (shapeType: ShapeType): string[] => { + switch (shapeType) { + case 'label': + case 'input': + case 'button': + case 'combobox': + case 'line': + case 'listbox': + case 'checkbox': + case 'toggleswitch': + case 'progressbar': + case 'datepickerinput': + case 'timepickerinput': + case 'radiobutton': + case 'horizontal-menu': + case 'breadcrumb': + case 'heading1': + case 'heading2': + case 'heading3': + case 'normaltext': + case 'smalltext': + case 'horizontalScrollBar': + case 'appBar': + case 'buttonBar': + case 'slider': + return ['middle-left', 'middle-right']; + case 'verticalScrollBar': + return ['top-center', 'bottom-center']; + case 'icon': + case 'multiple': + return []; + case 'image': + return ['top-left', 'top-right', 'bottom-left', 'bottom-right']; + default: + return [ + 'top-left', + 'top-center', + 'top-right', + 'middle-left', + 'middle-right', + 'bottom-left', + 'bottom-center', + 'bottom-right', + ]; + } +}; diff --git a/src/pods/canvas/snap.utils.ts b/src/pods/canvas/snap.utils.ts index 11044d54..6b9b40ae 100644 --- a/src/pods/canvas/snap.utils.ts +++ b/src/pods/canvas/snap.utils.ts @@ -5,7 +5,7 @@ import { SnapLineSubset, ClosestSnapLines, SNAP_THRESHOLD, -} from './canvas.model'; +} from './model'; const getAllSnapLinesSingleDirection = ( singleDirectionPossibleSnapLines: number[], diff --git a/src/pods/canvas/use-monitor.business.spec.ts b/src/pods/canvas/use-monitor.business.spec.ts index 9b3cc370..19d6357d 100644 --- a/src/pods/canvas/use-monitor.business.spec.ts +++ b/src/pods/canvas/use-monitor.business.spec.ts @@ -2,7 +2,7 @@ import { ShapeType } from '@/core/model'; import { vi, describe, it, expect } from 'vitest'; import { calculateShapeOffsetToXDropCoordinate } from './use-monitor.business'; -vi.mock('@/pods/canvas/canvas.model', () => ({ +vi.mock('@/pods/canvas/model/shape-size.utils', () => ({ getDefaultSizeFromShape: (shapeType: ShapeType) => { if (shapeType === 'input') { return { diff --git a/src/pods/canvas/use-monitor.business.ts b/src/pods/canvas/use-monitor.business.ts index 35d16c4f..c5bce571 100644 --- a/src/pods/canvas/use-monitor.business.ts +++ b/src/pods/canvas/use-monitor.business.ts @@ -1,5 +1,5 @@ import { ShapeType } from '@/core/model'; -import { getDefaultSizeFromShape } from './canvas.model'; +import { getDefaultSizeFromShape } from './model'; // TODO: #156 Add unit tests to this funcion export const calculateShapeOffsetToXDropCoordinate = ( diff --git a/src/pods/canvas/use-multiple-selection-shape.hook.tsx b/src/pods/canvas/use-multiple-selection-shape.hook.tsx index 4e53fa6c..3e4b745a 100644 --- a/src/pods/canvas/use-multiple-selection-shape.hook.tsx +++ b/src/pods/canvas/use-multiple-selection-shape.hook.tsx @@ -2,7 +2,7 @@ import { ShapeModel, ShapeRefs, Coord } from '@/core/model'; import { SelectionInfo } from '@/core/providers/canvas/canvas.model'; import Konva from 'konva'; import { useState } from 'react'; -import { SelectionRect } from './canvas.model'; +import { SelectionRect } from './model'; import { findFirstShapeInCoords, getSelectedShapesFromSelectionRect, diff --git a/src/pods/canvas/use-multiple-selection.business.ts b/src/pods/canvas/use-multiple-selection.business.ts index 37b82b3a..9a161cc6 100644 --- a/src/pods/canvas/use-multiple-selection.business.ts +++ b/src/pods/canvas/use-multiple-selection.business.ts @@ -1,5 +1,5 @@ import { Coord, ShapeModel, ShapeRefs } from '@/core/model'; -import { SelectionRect } from './canvas.model'; +import { SelectionRect } from './model'; const isShapeInsideSelectionRect = ( shapeRect: SelectionRect, diff --git a/src/pods/canvas/use-snapin.hook.tsx b/src/pods/canvas/use-snapin.hook.tsx index cfe4ba29..51700e22 100644 --- a/src/pods/canvas/use-snapin.hook.tsx +++ b/src/pods/canvas/use-snapin.hook.tsx @@ -1,7 +1,7 @@ import Konva from 'konva'; import { KonvaEventObject } from 'konva/lib/Node'; import invariant from 'tiny-invariant'; -import { ClosestSnapLines, SnapEdges, SnapLines } from './canvas.model'; +import { ClosestSnapLines, SnapEdges, SnapLines } from './model'; import { getClosestSnapLines } from './snap.utils'; import { useState } from 'react'; import { useCanvasContext } from '@/core/providers'; diff --git a/src/pods/canvas/use-transform.hook.ts b/src/pods/canvas/use-transform.hook.ts index 003ffd39..ac812661 100644 --- a/src/pods/canvas/use-transform.hook.ts +++ b/src/pods/canvas/use-transform.hook.ts @@ -2,7 +2,7 @@ import { Box } from 'konva/lib/shapes/Transformer'; import { Coord, Size } from '@/core/model'; import { useEffect } from 'react'; import { useCanvasContext } from '@/core/providers'; -import { getMinSizeFromShape } from './canvas.model'; +import { getMinSizeFromShape } from './model'; import { KonvaEventObject, NodeConfig, Node } from 'konva/lib/Node'; export const useTransform = (