Merged PR 18: Add support for custom SVG
Some checks failed
continuous-integration/drone/push Build is failing

Add support for custom SVG
Add userData back into IProperties
Added library interweave
Update example
This commit is contained in:
Eric Nguyen 2022-08-17 13:43:24 +00:00
parent 82eae4971e
commit e96e4f123b
8 changed files with 109 additions and 9 deletions

View file

@ -31,6 +31,7 @@ module.exports = {
'@typescript-eslint/semi': ['warn', 'always'], '@typescript-eslint/semi': ['warn', 'always'],
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/ban-types': ['error'],
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies 'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
} }

View file

@ -14,6 +14,7 @@
}, },
"dependencies": { "dependencies": {
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"interweave": "^13.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-svg-pan-zoom": "^3.11.0", "react-svg-pan-zoom": "^3.11.0",

15
pnpm-lock.yaml generated
View file

@ -24,6 +24,7 @@ specifiers:
eslint-plugin-promise: ^6.0.0 eslint-plugin-promise: ^6.0.0
eslint-plugin-react: ^7.30.1 eslint-plugin-react: ^7.30.1
eslint-plugin-react-hooks: ^4.6.0 eslint-plugin-react-hooks: ^4.6.0
interweave: ^13.0.0
jsdom: ^20.0.0 jsdom: ^20.0.0
postcss: ^8.4.14 postcss: ^8.4.14
react: ^18.2.0 react: ^18.2.0
@ -38,6 +39,7 @@ specifiers:
dependencies: dependencies:
'@heroicons/react': 1.0.6_react@18.2.0 '@heroicons/react': 1.0.6_react@18.2.0
interweave: 13.0.0_react@18.2.0
react: 18.2.0 react: 18.2.0
react-dom: 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 react-svg-pan-zoom: 3.11.0_react@18.2.0
@ -1522,6 +1524,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/escape-html/1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/escape-string-regexp/1.0.5: /escape-string-regexp/1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -2177,6 +2183,15 @@ packages:
side-channel: 1.0.4 side-channel: 1.0.4
dev: true 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: /is-bigint/1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
dependencies: dependencies:

View file

@ -218,7 +218,9 @@ export function AddContainer(
isRigidBody: false, isRigidBody: false,
isAnchor: false, isAnchor: false,
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left, XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
style: containerConfig.Style customSVG: containerConfig.CustomSVG,
style: containerConfig.Style,
userData: containerConfig.UserData
}; };
// Create the container // Create the container

View file

@ -1,9 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { Interweave, Node } from 'interweave';
import { XPositionReference } from '../../../Enums/XPositionReference'; import { XPositionReference } from '../../../Enums/XPositionReference';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default'; import { DIMENSION_MARGIN } from '../../../utils/default';
import { getDepth } from '../../../utils/itertools'; import { getDepth } from '../../../utils/itertools';
import { Dimension } from './Dimension'; import { Dimension } from './Dimension';
import IProperties from '../../../Interfaces/IProperties';
interface IContainerProps { interface IContainerProps {
model: IContainerModel model: IContainerModel
@ -33,6 +35,14 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
props.model.properties.style props.model.properties.style
); );
const svg = (props.model.properties.customSVG != null)
? CreateReactCustomSVG(props.model.properties.customSVG, props.model.properties)
: (<rect
width={props.model.properties.width}
height={props.model.properties.height}
style={style}
>
</rect>);
// Dimension props // Dimension props
const depth = getDepth(props.model); const depth = getDepth(props.model);
const dimensionMargin = DIMENSION_MARGIN * (depth + 1); const dimensionMargin = DIMENSION_MARGIN * (depth + 1);
@ -79,12 +89,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
text={text} text={text}
/> />
{ dimensionChildren } { dimensionChildren }
<rect { svg }
width={props.model.properties.width}
height={props.model.properties.height}
style={style}
>
</rect>
<text <text
x={xText} x={xText}
y={yText} y={yText}
@ -141,3 +146,63 @@ export function restoreX(x: number, width: number, xPositionReference = XPositio
} }
return transformedX; return transformedX;
} }
function CreateReactCustomSVG(customSVG: string, props: IProperties): React.ReactNode {
return <Interweave
tagName='g'
disableLineBreaks={true}
content={customSVG}
allowElements={true}
transform={(node, children) => 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('');
}

View file

@ -11,5 +11,7 @@ export interface IAvailableContainer {
DefaultY?: number DefaultY?: number
AddMethod?: AddMethod AddMethod?: AddMethod
XPositionReference?: XPositionReference XPositionReference?: XPositionReference
CustomSVG?: string
Style: React.CSSProperties Style: React.CSSProperties
UserData?: object
} }

View file

@ -20,5 +20,7 @@ export default interface IProperties {
isRigidBody: boolean isRigidBody: boolean
isAnchor: boolean isAnchor: boolean
XPositionReference: XPositionReference XPositionReference: XPositionReference
customSVG?: string
style?: React.CSSProperties style?: React.CSSProperties
userData?: object
} }

View file

@ -76,11 +76,23 @@ const GetSVGLayoutConfiguration = () => {
}, },
{ {
Type: 'Remplissage', Type: 'Remplissage',
CustomSVG: `
<rect width="{width}" height="{height}" style="{style}"></rect>
<rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
<line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
`
,
Style: { Style: {
fillOpacity: 1, fillOpacity: 1,
strokeWidth: 2, strokeWidth: 1,
stroke: '#bfdbfe',
fill: '#bfdbfe' fill: '#bfdbfe'
},
UserData: {
styleLine: {
transform: "scaleY(0.5) translateY(100%)",
transformBox: "fill-box"
}
} }
}, },
{ {