From 1e71e67bc27d0016088a288492aa9d1ba02b749b Mon Sep 17 00:00:00 2001 From: rohitsharma120582 <86062128+rohitsharma120582@users.noreply.github.com> Date: Fri, 26 May 2023 15:37:35 +0530 Subject: [PATCH] Feature/MN-80/modify-svg-dynamically (#496) Add custom styling to SVG overlays --------- Co-authored-by: souyahia-monk --- .idea/monkjs.iml | 2 + .../camera/src/components/Capture/capture.js | 22 +++++--- .../components/Overlay/SVGElementMapper.js | 54 +++++++++++++++++++ .../src/components/Overlay/hooks/index.js | 4 ++ .../Overlay/hooks/useCustomSVGAttributes.js | 21 ++++++++ .../components/Overlay/hooks/useInnerHTML.js | 11 ++++ .../hooks/useJSXTransformAttributes.js | 35 ++++++++++++ .../components/Overlay/hooks/useXMLParser.js | 5 ++ .../camera/src/components/Overlay/index.js | 31 +++++++---- 9 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 packages/camera/src/components/Overlay/SVGElementMapper.js create mode 100644 packages/camera/src/components/Overlay/hooks/index.js create mode 100644 packages/camera/src/components/Overlay/hooks/useCustomSVGAttributes.js create mode 100644 packages/camera/src/components/Overlay/hooks/useInnerHTML.js create mode 100644 packages/camera/src/components/Overlay/hooks/useJSXTransformAttributes.js create mode 100644 packages/camera/src/components/Overlay/hooks/useXMLParser.js diff --git a/.idea/monkjs.iml b/.idea/monkjs.iml index 68b2fb5e4..f1618258e 100644 --- a/.idea/monkjs.iml +++ b/.idea/monkjs.iml @@ -13,6 +13,8 @@ + + diff --git a/packages/camera/src/components/Capture/capture.js b/packages/camera/src/components/Capture/capture.js index 79a7d61cd..056a1da8f 100644 --- a/packages/camera/src/components/Capture/capture.js +++ b/packages/camera/src/components/Capture/capture.js @@ -1,19 +1,19 @@ +import { MonitoringStatus, SentryOperation, SentryTag, SentryTransaction, useMonitoring } from '@monkvision/corejs'; import { utils } from '@monkvision/toolkit'; -import { useMonitoring, MonitoringStatus, SentryTransaction, SentryOperation, SentryTag } from '@monkvision/corejs'; import PropTypes from 'prop-types'; -import React, { forwardRef, useCallback, useEffect, useRef, useImperativeHandle, useMemo, useState } from 'react'; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActivityIndicator, Platform, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; import { useMediaQuery } from 'react-responsive'; import Constants from '../../const'; +import useEventStorage from '../../hooks/useEventStorage'; import log from '../../utils/log'; import AddDamageModal from '../AddDamageModal'; -import AddDamageOverlay from '../AddDamageOverlay'; import AddDamageHelpModal from '../AddDamageModal/AddDamageHelpModal'; -import useEventStorage from '../../hooks/useEventStorage'; +import AddDamageOverlay from '../AddDamageOverlay'; import Camera from '../Camera'; import CloseEarlyConfirmModal from '../CloseEarlyConfirmModal'; import Controls from '../Controls'; @@ -30,7 +30,8 @@ import { useSetPictureAsync, useStartUploadAsync, useTakePictureAsync, - useTitle, useUploadAdditionalDamage, + useTitle, + useUploadAdditionalDamage, } from './hooks'; const AddDamageStatus = { @@ -126,6 +127,7 @@ const Capture = forwardRef(({ onStartUploadPicture, onFinishUploadPicture, orientationBlockerProps, + overlayPathStyles, primaryColor, sightsContainerStyle, style, @@ -573,6 +575,12 @@ const Capture = forwardRef(({ ) : null} {(isReady && overlay && loading === false @@ -598,7 +606,7 @@ const Capture = forwardRef(({ ) : null} - ), [isReady, loading, overlay, overlaySize, primaryColor, addDamageStatus]); + ), [isReady, loading, overlay, overlaySize, primaryColor, addDamageStatus, overlayPathStyles]); if (enableComplianceCheck && (endTour || (tourHasFinished && complianceHasFulfilledAll))) { return ( @@ -804,6 +812,7 @@ Capture.propTypes = { onUploadsChange: PropTypes.func, onWarningMessage: PropTypes.func, orientationBlockerProps: PropTypes.shape({ title: PropTypes.string }), + overlayPathStyles: PropTypes.object, primaryColor: PropTypes.string, resolutionOptions: PropTypes.shape({ QHDDelay: PropTypes.number, @@ -916,6 +925,7 @@ Capture.defaultProps = { onReady: () => {}, onStartUploadPicture: () => {}, orientationBlockerProps: null, + overlayPathStyles: {}, primaryColor: '#FFF', resolutionOptions: undefined, sightsContainerStyle: {}, diff --git a/packages/camera/src/components/Overlay/SVGElementMapper.js b/packages/camera/src/components/Overlay/SVGElementMapper.js new file mode 100644 index 000000000..f08d7c63e --- /dev/null +++ b/packages/camera/src/components/Overlay/SVGElementMapper.js @@ -0,0 +1,54 @@ +/* eslint-disable react/no-array-index-key */ +import PropTypes from 'prop-types'; +import React, { useMemo } from 'react'; + +import { useInnerHTML, useJSXTransformAttributes, useCustomSVGAttributes } from './hooks'; + +export default function SVGElementMapper({ + element, + rootStyles, + pathStyles, +}) { + const Tag = useMemo(() => element.tagName, [element]); + const innerHTML = useInnerHTML({ element }); + const transformedAttributes = useJSXTransformAttributes(element); + const customAttributes = useCustomSVGAttributes({ + element, + rootStyles, + pathStyles, + }); + const attributes = useMemo(() => ({ + ...transformedAttributes, + ...customAttributes, + style: { + ...transformedAttributes?.style ?? {}, + ...customAttributes?.style ?? {}, + }, + }), []); + const children = useMemo(() => [...element.children], [element]); + + return ( + + {innerHTML} + {children.map((child, id) => ( + + ))} + + ); +} + +SVGElementMapper.propTypes = { + element: PropTypes.any.isRequired, + pathStyles: PropTypes.object, + rootStyles: PropTypes.object, +}; + +SVGElementMapper.defaultProps = { + pathStyles: {}, + rootStyles: {}, +}; diff --git a/packages/camera/src/components/Overlay/hooks/index.js b/packages/camera/src/components/Overlay/hooks/index.js new file mode 100644 index 000000000..ec0ce776d --- /dev/null +++ b/packages/camera/src/components/Overlay/hooks/index.js @@ -0,0 +1,4 @@ +export { default as useInnerHTML } from './useInnerHTML'; +export { default as useJSXTransformAttributes } from './useJSXTransformAttributes'; +export { default as useCustomSVGAttributes } from './useCustomSVGAttributes'; +export { default as useXMLParser } from './useXMLParser'; diff --git a/packages/camera/src/components/Overlay/hooks/useCustomSVGAttributes.js b/packages/camera/src/components/Overlay/hooks/useCustomSVGAttributes.js new file mode 100644 index 000000000..1ec681576 --- /dev/null +++ b/packages/camera/src/components/Overlay/hooks/useCustomSVGAttributes.js @@ -0,0 +1,21 @@ +import { useMemo } from 'react'; + +export default function useCustomSVGAttributes({ + element, + rootStyles, + pathStyles, +}) { + return useMemo(() => { + const elementTag = element.tagName; + + if (elementTag === 'svg') { + return { style: rootStyles ?? {} }; + } + + return { style: pathStyles ?? {} }; + }, [ + element, + rootStyles, + pathStyles, + ]); +} diff --git a/packages/camera/src/components/Overlay/hooks/useInnerHTML.js b/packages/camera/src/components/Overlay/hooks/useInnerHTML.js new file mode 100644 index 000000000..2f58c3b4c --- /dev/null +++ b/packages/camera/src/components/Overlay/hooks/useInnerHTML.js @@ -0,0 +1,11 @@ +import { useMemo } from 'react'; + +export default function useInnerHTML({ element }) { + return useMemo(() => { + if (element.tagName === 'style' && !!element.innerHTML) { + return element.innerHTML; + } + + return null; + }, [element]); +} diff --git a/packages/camera/src/components/Overlay/hooks/useJSXTransformAttributes.js b/packages/camera/src/components/Overlay/hooks/useJSXTransformAttributes.js new file mode 100644 index 000000000..64b784184 --- /dev/null +++ b/packages/camera/src/components/Overlay/hooks/useJSXTransformAttributes.js @@ -0,0 +1,35 @@ +import { useMemo } from 'react'; + +function tranformJsxAttribute(key, value) { + switch (key) { + case 'class': + return { key: 'className', value }; + case 'xml:space': + return { key: 'xmlSpace', value }; + case 'style': + return value.split(';') + .map((style) => style.split(':')) + .reduce((prev, curr) => { + const [styleKey, styleValue] = curr; + const transformedKey = styleKey.replace(/-./g, (css) => css.toUpperCase()[1]); + return { + ...prev, + [transformedKey]: styleValue, + }; + }, {}); + default: + return { key, value }; + } +} + +export default function useJSXTransformAttributes(element) { + return useMemo(() => element + .getAttributeNames() + .reduce((prev, attr) => { + const { key, value } = tranformJsxAttribute(attr, element.getAttribute(attr)); + return { + ...prev, + [key]: value, + }; + }, {}), [element]); +} diff --git a/packages/camera/src/components/Overlay/hooks/useXMLParser.js b/packages/camera/src/components/Overlay/hooks/useXMLParser.js new file mode 100644 index 000000000..9e4bef1a0 --- /dev/null +++ b/packages/camera/src/components/Overlay/hooks/useXMLParser.js @@ -0,0 +1,5 @@ +import { useMemo } from 'react'; + +export default function useXMLParser(xml) { + return useMemo(() => new DOMParser().parseFromString(xml, 'text/xml'), [xml]); +} diff --git a/packages/camera/src/components/Overlay/index.js b/packages/camera/src/components/Overlay/index.js index cdb1f391a..146099b27 100644 --- a/packages/camera/src/components/Overlay/index.js +++ b/packages/camera/src/components/Overlay/index.js @@ -1,31 +1,42 @@ -import React, { useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; -import { Image } from 'react-native'; +import React, { useEffect, useMemo } from 'react'; import log from '../../utils/log'; -export default function Overlay({ label, svg, ...passThoughProps }) { - const base64 = useMemo(() => btoa(unescape(encodeURIComponent(svg))), [svg]); +import { useXMLParser } from './hooks'; +import SVGElementMapper from './SVGElementMapper'; + +export default function Overlay({ label, svg, rootStyles, pathStyles }) { + const doc = useXMLParser(svg); + const svgElement = useMemo(() => { + const svgElm = doc.children[0]; + if (svgElm.tagName !== 'svg') { + throw new Error('Invalid Overlay SVG: expected tag as the first children of XML document.'); + } + return svgElm; + }, [doc]); useEffect(() => { log(['[Event] Loading sight', label]); }, [label]); return ( - ); } Overlay.propTypes = { label: PropTypes.string, + pathStyles: PropTypes.object, + rootStyles: PropTypes.object, svg: PropTypes.string.isRequired, }; Overlay.defaultProps = { label: '', + pathStyles: {}, + rootStyles: {}, };