diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9c5ea7c..22e05f9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -31,6 +31,7 @@ module.exports = { '@typescript-eslint/semi': ['warn', 'always'], 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/ban-types': ['error'], 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks 'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies } diff --git a/package.json b/package.json index 5fdcfa7..b4ae68f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@heroicons/react": "^1.0.6", + "interweave": "^13.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-svg-pan-zoom": "^3.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be1cc19..74ffac8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,7 @@ specifiers: eslint-plugin-promise: ^6.0.0 eslint-plugin-react: ^7.30.1 eslint-plugin-react-hooks: ^4.6.0 + interweave: ^13.0.0 jsdom: ^20.0.0 postcss: ^8.4.14 react: ^18.2.0 @@ -38,6 +39,7 @@ specifiers: dependencies: '@heroicons/react': 1.0.6_react@18.2.0 + interweave: 13.0.0_react@18.2.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-svg-pan-zoom: 3.11.0_react@18.2.0 @@ -1522,6 +1524,10 @@ packages: engines: {node: '>=6'} dev: true + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + /escape-string-regexp/1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -2177,6 +2183,15 @@ packages: side-channel: 1.0.4 dev: true + /interweave/13.0.0_react@18.2.0: + resolution: {integrity: sha512-Mckwj+ix/VtrZu1bRBIIohwrsXj12ZTvJCoYUMZlJmgtvIaQCj0i77eSZ63ckbA1TsPrz2VOvLW9/kTgm5d+mw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + escape-html: 1.0.3 + react: 18.2.0 + dev: false + /is-bigint/1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index 80e592e..e4e7c0d 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -218,7 +218,9 @@ export function AddContainer( isRigidBody: false, isAnchor: false, XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left, - style: containerConfig.Style + customSVG: containerConfig.CustomSVG, + style: containerConfig.Style, + userData: containerConfig.UserData }; // Create the container diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 5565ec1..b6431fb 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -1,9 +1,11 @@ import * as React from 'react'; +import { Interweave, Node } from 'interweave'; import { XPositionReference } from '../../../Enums/XPositionReference'; import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { DIMENSION_MARGIN } from '../../../utils/default'; import { getDepth } from '../../../utils/itertools'; import { Dimension } from './Dimension'; +import IProperties from '../../../Interfaces/IProperties'; interface IContainerProps { model: IContainerModel @@ -33,6 +35,14 @@ export const Container: React.FC = (props: IContainerProps) => props.model.properties.style ); + const svg = (props.model.properties.customSVG != null) + ? CreateReactCustomSVG(props.model.properties.customSVG, props.model.properties) + : ( + ); // Dimension props const depth = getDepth(props.model); const dimensionMargin = DIMENSION_MARGIN * (depth + 1); @@ -79,12 +89,7 @@ export const Container: React.FC = (props: IContainerProps) => text={text} /> { dimensionChildren } - - + { svg } transform(node, children, props)} + />; +} + +function transform(node: HTMLElement, children: Node[], props: IProperties): React.ReactNode { + const supportedTags = ['line', 'path', 'rect']; + if (supportedTags.includes(node.tagName.toLowerCase())) { + const attributes: {[att: string]: string | object | null} = {}; + node.getAttributeNames().forEach(attName => { + const attributeValue = node.getAttribute(attName); + if (attributeValue === null) { + attributes[attName] = attributeValue; + return; + } + + if (attributeValue.startsWith('{userData.') && attributeValue.endsWith('}')) { + // support for userData + if (props.userData === undefined) { + return undefined; + } + + const userDataKey = attributeValue.replace(/userData\./, ''); + + const prop = Object.entries(props.userData).find(([key]) => `{${key}}` === userDataKey); + if (prop !== undefined) { + attributes[camelize(attName)] = prop[1]; + return; + } + } + + if (attributeValue.startsWith('{{') && attributeValue.endsWith('}}')) { + // support for object + const stringObject = attributeValue.slice(1, -1); + const object: JSON = JSON.parse(stringObject); + attributes[camelize(attName)] = object; + return; + } + + const prop = Object.entries(props).find(([key]) => `{${key}}` === attributeValue); + if (prop !== undefined) { + attributes[camelize(attName)] = prop[1]; + return; + } + attributes[camelize(attName)] = attributeValue; + }); + return React.createElement(node.tagName.toLowerCase(), attributes, children); + } + return undefined; +} + +function camelize(str: string): any { + return str.split('-').map((word, index) => index > 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word).join(''); +} diff --git a/src/Interfaces/IAvailableContainer.ts b/src/Interfaces/IAvailableContainer.ts index 2f4a011..800af5d 100644 --- a/src/Interfaces/IAvailableContainer.ts +++ b/src/Interfaces/IAvailableContainer.ts @@ -11,5 +11,7 @@ export interface IAvailableContainer { DefaultY?: number AddMethod?: AddMethod XPositionReference?: XPositionReference + CustomSVG?: string Style: React.CSSProperties + UserData?: object } diff --git a/src/Interfaces/IProperties.ts b/src/Interfaces/IProperties.ts index 2513d74..38153ce 100644 --- a/src/Interfaces/IProperties.ts +++ b/src/Interfaces/IProperties.ts @@ -20,5 +20,7 @@ export default interface IProperties { isRigidBody: boolean isAnchor: boolean XPositionReference: XPositionReference + customSVG?: string style?: React.CSSProperties + userData?: object } diff --git a/test-server/http.js b/test-server/http.js index 2c7d5f8..75c8628 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -76,11 +76,23 @@ const GetSVGLayoutConfiguration = () => { }, { Type: 'Remplissage', + CustomSVG: ` + + + + + ` + , Style: { fillOpacity: 1, - strokeWidth: 2, - stroke: '#bfdbfe', + strokeWidth: 1, fill: '#bfdbfe' + }, + UserData: { + styleLine: { + transform: "scaleY(0.5) translateY(100%)", + transformBox: "fill-box" + } } }, {