diff --git a/public/rich-components/fab-button.svg b/public/rich-components/fab-button.svg new file mode 100644 index 00000000..728ecd63 --- /dev/null +++ b/public/rich-components/fab-button.svg @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/common/components/mock-components/front-components/icon/icon-shape.business.ts b/src/common/components/mock-components/front-components/icon/icon-shape.business.ts index 9378b3e3..71b7c376 100644 --- a/src/common/components/mock-components/front-components/icon/icon-shape.business.ts +++ b/src/common/components/mock-components/front-components/icon/icon-shape.business.ts @@ -1,20 +1,5 @@ import { IconSize } from '@/core/model'; -export const loadSvgWithFill = async (url: string, fillColor: string) => { - const response = await fetch(url); - const svgText = await response.text(); - - const modifiedSvg = svgText.replace(/fill="[^"]*"/g, `fill="${fillColor}"`); - - const svgBlob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); - const objectURL = URL.createObjectURL(svgBlob); - - const img = new window.Image(); - img.src = objectURL; - - return img; -}; - export const returnIconSize = (iconSize: IconSize): number[] => { switch (iconSize) { case 'XS': diff --git a/src/common/components/mock-components/front-components/icon/icon-shape.tsx b/src/common/components/mock-components/front-components/icon/icon-shape.tsx index f268a9ae..86af4394 100644 --- a/src/common/components/mock-components/front-components/icon/icon-shape.tsx +++ b/src/common/components/mock-components/front-components/icon/icon-shape.tsx @@ -7,7 +7,8 @@ import { useModalDialogContext } from '@/core/providers/model-dialog-providers/m import { IconModal } from '@/pods/properties/components/icon-selector/modal'; import { useCanvasContext } from '@/core/providers'; import { useGroupShapeProps } from '../../mock-components.utils'; -import { loadSvgWithFill, returnIconSize } from './icon-shape.business'; +import { returnIconSize } from './icon-shape.business'; +import { loadSvgWithFill } from '@/common/utils/svg.utils'; const iconShapeRestrictions: ShapeSizeRestrictions = { minWidth: 25, diff --git a/src/common/components/mock-components/front-rich-components/fab-button/fab-button.tsx b/src/common/components/mock-components/front-rich-components/fab-button/fab-button.tsx new file mode 100644 index 00000000..29c11261 --- /dev/null +++ b/src/common/components/mock-components/front-rich-components/fab-button/fab-button.tsx @@ -0,0 +1,96 @@ +import { BASE_ICONS_URL, ShapeSizeRestrictions, ShapeType } from '@/core/model'; +import { forwardRef, useEffect, useState } from 'react'; +import { Circle, Group, Image } from 'react-konva'; +import { ShapeProps } from '../../shape.model'; +import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; +import { useShapeProps } from '@/common/components/shapes/use-shape-props.hook'; +import { useGroupShapeProps } from '../../mock-components.utils'; +import { BASIC_SHAPE } from '../../front-components/shape.const'; +import { IconModal } from '@/pods/properties/components/icon-selector/modal'; +import { useModalDialogContext } from '@/core/providers/model-dialog-providers/model-dialog.provider'; +import { useCanvasContext } from '@/core/providers'; +import { loadSvgWithFill } from '@/common/utils/svg.utils'; + +const fabButtonShapeRestrictions: ShapeSizeRestrictions = { + minWidth: 25, + minHeight: 25, + maxWidth: -1, + maxHeight: -1, + defaultWidth: 85, + defaultHeight: 85, +}; + +const shapeType: ShapeType = 'fabButton'; + +export const getFabButtonShapeSizeRestrictions = (): ShapeSizeRestrictions => + fabButtonShapeRestrictions; + +export const FabButtonShape = forwardRef((props, ref) => { + const { x, y, width, height, id, onSelected, otherProps, ...shapeProps } = + props; + + const [iconImage, setIconImage] = useState(null); + + const { openModal } = useModalDialogContext(); + const { selectionInfo } = useCanvasContext(); + const { updateOtherPropsOnSelected } = selectionInfo; + + const restrictedSize = fitSizeToShapeSizeRestrictions( + fabButtonShapeRestrictions, + width, + height + ); + const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; + + const radius = Math.min(restrictedWidth, restrictedHeight) / 2; + const center = radius; + + const iconInfo = otherProps?.icon; + const iconSize = radius * 1.2; + const iconStroke = otherProps?.stroke || '#ffffff'; + + const { fill } = useShapeProps(otherProps, BASIC_SHAPE); + const commonGroupProps = useGroupShapeProps( + props, + restrictedSize, + shapeType, + ref + ); + + const handleDoubleClick = () => { + if (iconInfo) { + openModal( + updateOtherPropsOnSelected('icon', icon)} + />, + 'Choose Icon' + ); + } + }; + + useEffect(() => { + if (iconInfo?.filename) { + loadSvgWithFill(`${BASE_ICONS_URL}${iconInfo.filename}`, iconStroke).then( + img => setIconImage(img) + ); + } + }, [iconInfo?.filename, iconStroke]); + + return ( + + {/* Background Circle */} + + {/* Icon */} + {iconImage && ( + + )} + + ); +}); diff --git a/src/common/components/mock-components/front-rich-components/index.ts b/src/common/components/mock-components/front-rich-components/index.ts index 6664c0ae..525b3d37 100644 --- a/src/common/components/mock-components/front-rich-components/index.ts +++ b/src/common/components/mock-components/front-rich-components/index.ts @@ -18,3 +18,4 @@ export * from './loading-indicator'; export * from './videoconference'; export * from './togglelightdark-shape'; export * from './gauge/gauge'; +export * from './fab-button/fab-button'; diff --git a/src/common/utils/svg.utils.ts b/src/common/utils/svg.utils.ts new file mode 100644 index 00000000..573c60b5 --- /dev/null +++ b/src/common/utils/svg.utils.ts @@ -0,0 +1,14 @@ +export const loadSvgWithFill = async (url: string, fillColor: string) => { + const response = await fetch(url); + const svgText = await response.text(); + + const modifiedSvg = svgText.replace(/fill="[^"]*"/g, `fill="${fillColor}"`); + + const svgBlob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); + const objectURL = URL.createObjectURL(svgBlob); + + const img = new window.Image(); + img.src = objectURL; + + return img; +}; diff --git a/src/core/model/index.ts b/src/core/model/index.ts index 97dd6e3f..6885155c 100644 --- a/src/core/model/index.ts +++ b/src/core/model/index.ts @@ -84,7 +84,8 @@ export type ShapeType = | 'rectangleLow' | 'circleLow' | 'textScribbled' - | 'paragraphScribbled'; + | 'paragraphScribbled' + | 'fabButton'; export const ShapeDisplayName: Record = { multiple: 'multiple', @@ -158,6 +159,7 @@ export const ShapeDisplayName: Record = { circleLow: 'Circle', textScribbled: 'Text Scribbled', paragraphScribbled: 'Paragraph Scribbled', + fabButton: 'Fab Button', }; export type EditType = 'input' | 'textarea' | 'imageupload'; diff --git a/src/pods/canvas/model/shape-other-props.utils.ts b/src/pods/canvas/model/shape-other-props.utils.ts index d10ff84b..fe238aea 100644 --- a/src/pods/canvas/model/shape-other-props.utils.ts +++ b/src/pods/canvas/model/shape-other-props.utils.ts @@ -75,6 +75,17 @@ export const generateDefaultOtherProps = ( stroke: '#808080', textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT, }; + case 'fabButton': + return { + icon: { + name: 'chat', + filename: 'chat.svg', + searchTerms: ['chat', 'message', 'conversation', 'chatting'], + categories: ['IT'], + }, + stroke: '#ffffff', + backgroundColor: '#A9A9A9', + }; case 'buttonBar': return { stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR, diff --git a/src/pods/canvas/model/shape-size.mapper.ts b/src/pods/canvas/model/shape-size.mapper.ts index e4f8fd74..397dddd5 100644 --- a/src/pods/canvas/model/shape-size.mapper.ts +++ b/src/pods/canvas/model/shape-size.mapper.ts @@ -64,6 +64,7 @@ import { getVideoPlayerShapeSizeRestrictions, getVideoconferenceShapeSizeRestrictions, getGaugeShapeSizeRestrictions, + getFabButtonShapeSizeRestrictions, // other imports } from '@/common/components/mock-components/front-rich-components'; import { @@ -171,6 +172,7 @@ const shapeSizeMap: Record ShapeSizeRestrictions> = { circleLow: getCircleLowShapeSizeRestrictions, textScribbled: getTextScribbledShapeRestrictions, paragraphScribbled: getParagraphScribbledShapeRestrictions, + fabButton: getFabButtonShapeSizeRestrictions, }; export default shapeSizeMap; diff --git a/src/pods/canvas/shape-renderer/index.tsx b/src/pods/canvas/shape-renderer/index.tsx index 7b06e31c..b591c9f9 100644 --- a/src/pods/canvas/shape-renderer/index.tsx +++ b/src/pods/canvas/shape-renderer/index.tsx @@ -48,6 +48,7 @@ import { renderCalendar, renderAppBar, renderLoadingIndicator, + renderFabButton, } from './simple-rich-components'; import { renderDiamond, @@ -209,6 +210,8 @@ export const renderShapeComponent = ( return renderLoadingIndicator(shape, shapeRenderedProps); case 'videoconference': return renderVideoconference(shape, shapeRenderedProps); + case 'fabButton': + return renderFabButton(shape, shapeRenderedProps); case 'gauge': return renderGauge(shape, shapeRenderedProps); case 'imagePlaceholder': diff --git a/src/pods/canvas/shape-renderer/simple-rich-components/fab-button.renderer.tsx b/src/pods/canvas/shape-renderer/simple-rich-components/fab-button.renderer.tsx new file mode 100644 index 00000000..32c88469 --- /dev/null +++ b/src/pods/canvas/shape-renderer/simple-rich-components/fab-button.renderer.tsx @@ -0,0 +1,32 @@ +import { ShapeRendererProps } from '../model'; +import { ShapeModel } from '@/core/model'; +import { FabButtonShape } from '@/common/components/mock-components/front-rich-components/fab-button/fab-button'; + +export const renderFabButton = ( + shape: ShapeModel, + shapeRenderedProps: ShapeRendererProps +) => { + const { handleSelected, shapeRefs, handleDragEnd, handleTransform } = + shapeRenderedProps; + + return ( + + ); +}; diff --git a/src/pods/canvas/shape-renderer/simple-rich-components/index.ts b/src/pods/canvas/shape-renderer/simple-rich-components/index.ts index 81078779..440b7ba4 100644 --- a/src/pods/canvas/shape-renderer/simple-rich-components/index.ts +++ b/src/pods/canvas/shape-renderer/simple-rich-components/index.ts @@ -20,3 +20,4 @@ export * from './video-player.renderer'; export * from './audio-player.renderer'; export * from './loading-indicator.renderer'; export * from './videoconference.renderer'; +export * from './fab-button.renderer'; diff --git a/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts b/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts index c8ceb040..439b4f92 100644 --- a/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts +++ b/src/pods/galleries/rich-components-gallery/rich-components-gallery-data/index.ts @@ -36,4 +36,8 @@ export const mockRichComponentsCollection: ItemInfo[] = [ thumbnailSrc: '/rich-components/videoconference.svg', type: 'videoconference', }, + { + thumbnailSrc: '/rich-components/fab-button.svg', + type: 'fabButton', + }, ];