All checks were successful
continuous-integration/drone/push Build is passing
- Displayed text - Enable Shortcuts - Hide text - Disable Dimension - Depth dimension Reviewed-on: https://git.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react/pulls/37
213 lines
7 KiB
TypeScript
213 lines
7 KiB
TypeScript
import * as React from 'react';
|
|
import { Interweave, Node } from 'interweave';
|
|
import { XPositionReference } from '../../../Enums/XPositionReference';
|
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
|
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
|
import { getDepth } from '../../../utils/itertools';
|
|
import { Dimension } from './Dimension';
|
|
import IProperties from '../../../Interfaces/IProperties';
|
|
|
|
interface IContainerProps {
|
|
model: IContainerModel
|
|
}
|
|
|
|
/**
|
|
* Render the container
|
|
* @returns Render the container
|
|
*/
|
|
export const Container: React.FC<IContainerProps> = (props: IContainerProps) => {
|
|
const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
|
|
const xText = props.model.properties.width / 2;
|
|
const yText = props.model.properties.height / 2;
|
|
|
|
const transform = `translate(${props.model.properties.x}, ${props.model.properties.y})`;
|
|
|
|
// g style
|
|
const defaultStyle: React.CSSProperties = {
|
|
transitionProperty: 'all',
|
|
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
transitionDuration: '150ms'
|
|
};
|
|
|
|
// Rect style
|
|
const style = Object.assign(
|
|
JSON.parse(JSON.stringify(defaultStyle)),
|
|
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
|
|
const depth = getDepth(props.model);
|
|
const dimensionMargin = DIMENSION_MARGIN * (depth + 1);
|
|
const id = `dim-${props.model.properties.id}`;
|
|
const xStart: number = 0;
|
|
const xEnd = props.model.properties.width;
|
|
const y = -dimensionMargin;
|
|
const strokeWidth = 1;
|
|
const text = (props.model.properties.width ?? 0).toString();
|
|
|
|
let dimensionChildren: JSX.Element | null = null;
|
|
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
|
const {
|
|
childrenId,
|
|
xChildrenStart,
|
|
xChildrenEnd,
|
|
yChildren,
|
|
textChildren
|
|
} = GetChildrenDimensionProps(props, dimensionMargin);
|
|
dimensionChildren = <Dimension
|
|
id={childrenId}
|
|
xStart={xChildrenStart}
|
|
xEnd={xChildrenEnd}
|
|
yStart={yChildren}
|
|
yEnd={yChildren}
|
|
strokeWidth={strokeWidth}
|
|
text={textChildren}
|
|
/>;
|
|
}
|
|
|
|
return (
|
|
<g
|
|
style={defaultStyle}
|
|
transform={transform}
|
|
key={`container-${props.model.properties.id}`}
|
|
>
|
|
{ SHOW_PARENT_DIMENSION
|
|
? <Dimension
|
|
id={id}
|
|
xStart={xStart}
|
|
xEnd={xEnd}
|
|
yStart={y}
|
|
yEnd={y}
|
|
strokeWidth={strokeWidth}
|
|
text={text}
|
|
/>
|
|
: null
|
|
}
|
|
{ dimensionChildren }
|
|
{ svg }
|
|
{ SHOW_TEXT
|
|
? <text
|
|
x={xText}
|
|
y={yText}
|
|
>
|
|
{props.model.properties.displayedText}
|
|
</text>
|
|
: null }
|
|
{ containersElements }
|
|
</g>
|
|
);
|
|
};
|
|
|
|
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number):
|
|
{ childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
|
|
const childrenId = `dim-children-${props.model.properties.id}`;
|
|
|
|
const lastChild = props.model.children[props.model.children.length - 1];
|
|
let xChildrenStart = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference);
|
|
let xChildrenEnd = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference);
|
|
|
|
// Find the min and max
|
|
for (let i = props.model.children.length - 2; i >= 0; i--) {
|
|
const child = props.model.children[i];
|
|
const left = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference);
|
|
if (left < xChildrenStart) {
|
|
xChildrenStart = left;
|
|
}
|
|
const right = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference);
|
|
if (right > xChildrenEnd) {
|
|
xChildrenEnd = right;
|
|
}
|
|
}
|
|
|
|
const yChildren = props.model.properties.height + dimensionMargin;
|
|
const textChildren = (xChildrenEnd - xChildrenStart).toString();
|
|
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
|
|
}
|
|
|
|
export function transformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
|
let transformedX = x;
|
|
if (xPositionReference === XPositionReference.Center) {
|
|
transformedX += width / 2;
|
|
} else if (xPositionReference === XPositionReference.Right) {
|
|
transformedX += width;
|
|
}
|
|
return transformedX;
|
|
}
|
|
|
|
export function restoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
|
let transformedX = x;
|
|
if (xPositionReference === XPositionReference.Center) {
|
|
transformedX -= width / 2;
|
|
} else if (xPositionReference === XPositionReference.Right) {
|
|
transformedX -= width;
|
|
}
|
|
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('');
|
|
}
|