Merge branch 'master' into eslint-fixes

This commit is contained in:
Eric NGUYEN 2023-02-23 13:04:19 +01:00
commit f1c97b25d1
38 changed files with 1456 additions and 480 deletions

View file

@ -3,6 +3,7 @@
"private": true, "private": true,
"version": "v1.0.0", "version": "v1.0.0",
"type": "module", "type": "module",
"postinstall": "npx patch-package",
"scripts": { "scripts": {
"d": "mprocs", "d": "mprocs",
"dev": "vite", "dev": "vite",
@ -22,10 +23,11 @@
"interweave": "^13.0.0", "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.12.1",
"react-window": "^1.8.8", "react-window": "^1.8.8",
"sweetalert2": "^11.7.1", "sweetalert2": "^11.7.1",
"sweetalert2-react-content": "^5.0.7" "sweetalert2-react-content": "^5.0.7",
"transformation-matrix": "^2.14.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/dom": "^8.20.0", "@testing-library/dom": "^8.20.0",
@ -60,5 +62,10 @@
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.1.1", "vite": "^4.1.1",
"vitest": "^0.28.4" "vitest": "^0.28.4"
},
"pnpm": {
"patchedDependencies": {
"@types/react-svg-pan-zoom@3.3.5": "patches/@types__react-svg-pan-zoom@3.3.5.patch"
}
} }
} }

View file

@ -0,0 +1,17 @@
diff --git a/node_modules/@types/react-svg-pan-zoom/index.d.ts b/node_modules/@types/react-svg-pan-zoom/index.d.ts
index a57d545..83ace9f 100644
--- a/node_modules/@types/react-svg-pan-zoom/index.d.ts
+++ b/node_modules/@types/react-svg-pan-zoom/index.d.ts
@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa
export function fitSelection(
value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value;
-export function fitToViewer(value: Value): Value;
+export function fitToViewer(
+ value: Value,
+ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined,
+ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
+): Value;
export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value;

View file

@ -0,0 +1,17 @@
diff --git a/index.d.ts b/index.d.ts
index a57d545d33b2798024b9762d3d3513e58a38e19d..83ace9fc85b7354e128948402a50e00083eacd8c 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa
export function fitSelection(
value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value;
-export function fitToViewer(value: Value): Value;
+export function fitToViewer(
+ value: Value,
+ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined,
+ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
+): Value;
export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value;

23
pnpm-lock.yaml generated
View file

@ -1,5 +1,10 @@
lockfileVersion: 5.4 lockfileVersion: 5.4
patchedDependencies:
'@types/react-svg-pan-zoom@3.3.5':
hash: kv3ctd73j5hnzcxdc2ceiq5wuy
path: patches/@types__react-svg-pan-zoom@3.3.5.patch
specifiers: specifiers:
'@heroicons/react': ^2.0.14 '@heroicons/react': ^2.0.14
'@react-hook/size': ^2.1.2 '@react-hook/size': ^2.1.2
@ -33,12 +38,13 @@ specifiers:
postcss: ^8.4.21 postcss: ^8.4.21
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.12.1
react-window: ^1.8.8 react-window: ^1.8.8
sass: ^1.58.0 sass: ^1.58.0
sweetalert2: ^11.7.1 sweetalert2: ^11.7.1
sweetalert2-react-content: ^5.0.7 sweetalert2-react-content: ^5.0.7
tailwindcss: ^3.2.4 tailwindcss: ^3.2.4
transformation-matrix: ^2.14.0
typescript: ^4.9.5 typescript: ^4.9.5
vite: ^4.1.1 vite: ^4.1.1
vitest: ^0.28.4 vitest: ^0.28.4
@ -49,10 +55,11 @@ dependencies:
interweave: 13.0.0_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.12.1
react-window: 1.8.8_biqbaboplfbrettd7655fr4n2y react-window: 1.8.8_biqbaboplfbrettd7655fr4n2y
sweetalert2: 11.7.1 sweetalert2: 11.7.1
sweetalert2-react-content: 5.0.7_5cbezu6w3tvev2ldv5vdmnpfca sweetalert2-react-content: 5.0.7_5cbezu6w3tvev2ldv5vdmnpfca
transformation-matrix: 2.14.0
devDependencies: devDependencies:
'@testing-library/dom': 8.20.0 '@testing-library/dom': 8.20.0
@ -61,7 +68,7 @@ devDependencies:
'@testing-library/user-event': 14.4.3_yxlyej73nftwmh2fiao7paxmlm '@testing-library/user-event': 14.4.3_yxlyej73nftwmh2fiao7paxmlm
'@types/react': 18.0.27 '@types/react': 18.0.27
'@types/react-dom': 18.0.10 '@types/react-dom': 18.0.10
'@types/react-svg-pan-zoom': 3.3.5 '@types/react-svg-pan-zoom': 3.3.5_kv3ctd73j5hnzcxdc2ceiq5wuy
'@types/react-window': 1.8.5 '@types/react-window': 1.8.5
'@typescript-eslint/eslint-plugin': 5.51.0_b635kmla6dsb4frxfihkw4m47e '@typescript-eslint/eslint-plugin': 5.51.0_b635kmla6dsb4frxfihkw4m47e
'@typescript-eslint/parser': 5.51.0_4vsywjlpuriuw3tl5oq6zy5a64 '@typescript-eslint/parser': 5.51.0_4vsywjlpuriuw3tl5oq6zy5a64
@ -927,11 +934,12 @@ packages:
'@types/react': 18.0.27 '@types/react': 18.0.27
dev: true dev: true
/@types/react-svg-pan-zoom/3.3.5: /@types/react-svg-pan-zoom/3.3.5_kv3ctd73j5hnzcxdc2ceiq5wuy:
resolution: {integrity: sha512-W8GRFCDy7raSDr5OXGjSyvX5KmdWlIQfv0NLa1jfAYVUO4ClVbgorWeAAom7nY3Pl+4h9blXE1Bnu2CW1iMEvQ==} resolution: {integrity: sha512-W8GRFCDy7raSDr5OXGjSyvX5KmdWlIQfv0NLa1jfAYVUO4ClVbgorWeAAom7nY3Pl+4h9blXE1Bnu2CW1iMEvQ==}
dependencies: dependencies:
'@types/react': 18.0.27 '@types/react': 18.0.27
dev: true dev: true
patched: true
/@types/react-window/1.8.5: /@types/react-window/1.8.5:
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==} resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
@ -3453,13 +3461,10 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/react-svg-pan-zoom/3.11.0_react@18.2.0: /react-svg-pan-zoom/3.12.1:
resolution: {integrity: sha512-xK2tpfp4YksHOfyMZH5zXP52ARLSBgkoJgWNJmJ1B+6O1tkuf23TQp7Q4m9GG5IRSK5KWO0JEGEWlNYG9+iiug==} resolution: {integrity: sha512-ug1LHCN5qed56C64xFypr/ClajuMFkig1OKvwJrIgGeSyHOjWM7XGgSgeP3IfHAkNw8QEc6a31ggZRpTijWYRw==}
peerDependencies:
react: '>=17.0.0'
dependencies: dependencies:
prop-types: 15.8.1 prop-types: 15.8.1
react: 18.2.0
transformation-matrix: 2.14.0 transformation-matrix: 2.14.0
dev: false dev: false

View file

@ -1,16 +1,85 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { IPoint } from '../../Interfaces/IPoint'; import { type IPoint } from '../../Interfaces/IPoint';
import { BAR_WIDTH } from '../Bar/Bar'; import { BAR_WIDTH } from '../Bar/Bar';
import { SelectorMode } from '../SVG/SVG';
import { type DrawParams } from '../Viewer/Viewer';
import { RenderContainers, RenderSymbols } from './Renderer';
import { RenderContainerSelector } from './SelectorContainer';
import { RenderSymbolSelector } from './SelectorSymbol';
interface ICanvasProps { interface ICanvasProps {
className?: string
width: number width: number
height: number height: number
draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
className?: string
style?: React.CSSProperties style?: React.CSSProperties
drawParams: DrawParams
}; };
function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject<HTMLCanvasElement> { function Draw(
ctx: CanvasRenderingContext2D,
frameCount: number,
scale: number,
translatePos: IPoint,
{
mainContainer,
selectorMode,
selectedContainer,
selectedSymbol,
containers,
symbols
}: DrawParams
): void {
if (mainContainer === undefined) {
return;
}
const topDim = mainContainer.properties.y;
const leftDim = mainContainer.properties.x;
const rightDim = mainContainer.properties.x + mainContainer.properties.width;
const bottomDim = mainContainer.properties.y + mainContainer.properties.height;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.setTransform(scale, 0, 0, scale, translatePos.x, translatePos.y);
ctx.fillStyle = '#000000';
// Draw containers and symbol dimensions
RenderContainers(
ctx,
mainContainer,
containers,
leftDim, bottomDim, topDim, rightDim, scale);
// Draw symbols and symbol dimensions
RenderSymbols(ctx, symbols, scale);
// Draw selector
switch (selectorMode) {
case SelectorMode.Containers:
RenderContainerSelector(ctx, frameCount, {
containers,
scale,
selected: selectedContainer
});
break;
case SelectorMode.Symbols:
RenderSymbolSelector(ctx, frameCount, {
symbols,
scale,
selected: selectedSymbol
});
break;
}
ctx.restore();
}
function UseCanvas(
draw: (context: CanvasRenderingContext2D,
frameCount: number,
scale: number,
translatePos: IPoint) => void
): React.RefObject<HTMLCanvasElement> {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const frameCount = useRef(0); const frameCount = useRef(0);
const translatePos = useRef({ const translatePos = useRef({
@ -123,8 +192,21 @@ interface Viewer {
viewerHeight: number viewerHeight: number
} }
export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element { export function Canvas({
const canvasRef = UseCanvas(draw); className,
width,
height,
style,
drawParams
}: ICanvasProps): JSX.Element {
const canvasRef = UseCanvas((
...CanvasProps
) => {
Draw(
...CanvasProps,
drawParams
);
});
const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({ const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
viewerWidth: width, viewerWidth: width,

View file

@ -0,0 +1,18 @@
import { type IContainerModel } from '../../Interfaces/IContainerModel';
export function RenderContainer(
ctx: CanvasRenderingContext2D,
container: IContainerModel,
x: number,
y: number
): void {
ctx.save();
ctx.strokeStyle = container.properties.style?.stroke ?? '#000000';
ctx.fillStyle = container.properties.style?.fill ?? '#000000';
ctx.lineWidth = Number(container.properties.style?.strokeWidth ?? 1);
ctx.globalAlpha = Number(container.properties.style?.fillOpacity ?? 1);
ctx.fillRect(x, y, container.properties.width, container.properties.height);
ctx.globalAlpha = Number(container.properties.style?.strokeOpacity ?? 1);
ctx.strokeRect(x, y, container.properties.width, container.properties.height);
ctx.restore();
}

View file

@ -1,4 +1,5 @@
import { NOTCHES_LENGTH } from '../../utils/default'; import { NOTCHES_LENGTH } from '../../utils/default';
import { IDimensionStyle } from '../SVG/Elements/Dimension';
interface IDimensionProps { interface IDimensionProps {
id: string id: string
@ -7,7 +8,7 @@ interface IDimensionProps {
xEnd: number xEnd: number
yEnd: number yEnd: number
text: string text: string
strokeWidth: number style: IDimensionStyle
scale?: number scale?: number
} }
@ -26,8 +27,11 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimensionProps): void { export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimensionProps): void {
const scale = props.scale ?? 1; const scale = props.scale ?? 1;
const strokeStyle = 'black'; const strokeStyle = props.style.color ?? 'black';
const lineWidth = 2 / scale; const lineWidth = (props.style.width ?? 2) / scale;
const dashArray: number[] = props.style.dashArray?.split(' ')
.flatMap(array => array.split(','))
.map(stringValue => parseInt(stringValue)) ?? [];
/// We need to find the points of the notches /// We need to find the points of the notches
// Get the vector of the line // Get the vector of the line
@ -59,6 +63,7 @@ export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimension
ctx.lineWidth = lineWidth; ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeStyle; ctx.strokeStyle = strokeStyle;
ctx.fillStyle = strokeStyle; ctx.fillStyle = strokeStyle;
ctx.setLineDash(dashArray);
ctx.moveTo(startTopX, startTopY); ctx.moveTo(startTopX, startTopY);
ctx.lineTo(startBottomX, startBottomY); ctx.lineTo(startBottomX, startBottomY);
ctx.stroke(); ctx.stroke();
@ -68,6 +73,7 @@ export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimension
ctx.moveTo(endTopX, endTopY); ctx.moveTo(endTopX, endTopY);
ctx.lineTo(endBottomX, endBottomY); ctx.lineTo(endBottomX, endBottomY);
ctx.stroke(); ctx.stroke();
ctx.setLineDash([]);
const textX = (props.xStart + props.xEnd) / 2; const textX = (props.xStart + props.xEnd) / 2;
const textY = (props.yStart + props.yEnd) / 2; const textY = (props.yStart + props.yEnd) / 2;
ctx.font = `${16 / scale}px Verdana`; ctx.font = `${16 / scale}px Verdana`;

View file

@ -1,14 +1,19 @@
import { Orientation } from '../../Enums/Orientation'; import { Orientation } from '../../Enums/Orientation';
import { Position } from '../../Enums/Position'; import { Position } from '../../Enums/Position';
import { IContainerModel } from '../../Interfaces/IContainerModel'; import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS } from '../../utils/default'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import {
SHOW_SELF_DIMENSIONS,
SHOW_BORROWER_DIMENSIONS,
SHOW_CHILDREN_DIMENSIONS,
DIMENSION_MARGIN,
SHOW_SELF_MARGINS_DIMENSIONS, DEFAULT_DIMENSION_SYMBOL_STYLE
} from '../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools'; import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
import { TransformX, TransformY } from '../../utils/svg'; import { TransformX, TransformY } from '../../utils/svg';
import { RenderDimension } from './Dimension'; import { RenderDimension } from './Dimension';
const MODULE_STROKE_WIDTH = 1; export function AddContainerDimensions(
export function AddDimensions(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
container: IContainerModel, container: IContainerModel,
@ -18,7 +23,8 @@ export function AddDimensions(
depth: number depth: number
): void { ): void {
ctx.beginPath(); ctx.beginPath();
if (SHOW_SELF_DIMENSIONS && container.properties.dimensionOptions.selfDimensions.positions.length > 0) { if (SHOW_SELF_DIMENSIONS &&
container.properties.dimensionOptions.selfDimensions.positions.length > 0) {
ActionByPosition( ActionByPosition(
ctx, ctx,
dimMapped, dimMapped,
@ -32,7 +38,24 @@ export function AddDimensions(
); );
} }
if (SHOW_BORROWER_DIMENSIONS && container.properties.dimensionOptions.dimensionWithMarks.positions.length > 0) { if (SHOW_SELF_MARGINS_DIMENSIONS &&
container.properties.dimensionOptions.selfMarginsDimensions.positions.length > 0) {
ActionByPosition(
ctx,
dimMapped,
container.properties.dimensionOptions.selfMarginsDimensions.positions,
AddHorizontalSelfMarginsDimension,
AddVerticalSelfMarginDimension,
[
container,
currentTransform,
scale
]
);
}
if (SHOW_BORROWER_DIMENSIONS &&
container.properties.dimensionOptions.dimensionWithMarks.positions.length > 0) {
ActionByPosition( ActionByPosition(
ctx, ctx,
dimMapped, dimMapped,
@ -48,7 +71,9 @@ export function AddDimensions(
); );
} }
if (SHOW_CHILDREN_DIMENSIONS && container.properties.dimensionOptions.childrenDimensions.positions.length > 0 && container.children.length >= 2) { if (SHOW_CHILDREN_DIMENSIONS &&
container.properties.dimensionOptions.childrenDimensions.positions.length > 0 &&
container.children.length >= 2) {
ActionByPosition( ActionByPosition(
ctx, ctx,
dimMapped, dimMapped,
@ -64,8 +89,33 @@ export function AddDimensions(
} }
} }
export function AddSymbolDimensions(
ctx: CanvasRenderingContext2D,
symbol: ISymbolModel,
scale: number,
depth: number
): void {
if (symbol.isVertical) {
AddVerticalSymbolDimension(
ctx,
symbol,
scale,
depth
);
return;
}
AddHorizontalSymbolDimension(
ctx,
symbol,
scale,
depth
);
}
/** /**
* Fonction that call another function given the positions * Fonction that call another function given the positions
* @param ctx
* @param dimMapped Position mapped depending on the Position enum in order: * @param dimMapped Position mapped depending on the Position enum in order:
* [0:left, 1:bottom, 2:up, 3:right] * [0:left, 1:bottom, 2:up, 3:right]
* @param positions List of positions * @param positions List of positions
@ -108,6 +158,7 @@ function AddHorizontalChildrenDimension(
scale: number scale: number
): void { ): void {
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`; const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1]; const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId); const lastChild = FindContainerById(containers, lastChildId);
@ -115,8 +166,14 @@ function AddHorizontalChildrenDimension(
if (lastChild === undefined) { if (lastChild === undefined) {
return; return;
} }
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference); let xChildrenStart = TransformX(
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference); lastChild.properties.x,
lastChild.properties.width,
lastChild.properties.positionReference);
let xChildrenEnd = TransformX(
lastChild.properties.x,
lastChild.properties.width,
lastChild.properties.positionReference);
// Find the min and max // Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) { for (let i = container.children.length - 2; i >= 0; i--) {
@ -152,9 +209,9 @@ function AddHorizontalChildrenDimension(
xEnd: xChildrenEnd + offset, xEnd: xChildrenEnd + offset,
yStart: yDim, yStart: yDim,
yEnd: yDim, yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH,
text: textChildren, text: textChildren,
scale scale,
style
}); });
} }
@ -168,6 +225,7 @@ function AddVerticalChildrenDimension(
scale: number scale: number
): void { ): void {
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`; const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1]; const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId); const lastChild = FindContainerById(containers, lastChildId);
@ -176,7 +234,10 @@ function AddVerticalChildrenDimension(
return; return;
} }
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference); let yChildrenStart = TransformY(
lastChild.properties.y,
lastChild.properties.height,
lastChild.properties.positionReference);
let yChildrenEnd = yChildrenStart; let yChildrenEnd = yChildrenStart;
// Find the min and max // Find the min and max
@ -218,9 +279,9 @@ function AddVerticalChildrenDimension(
xEnd: xDim, xEnd: xDim,
yStart: yChildrenStart + offset, yStart: yChildrenStart + offset,
yEnd: yChildrenEnd + offset, yEnd: yChildrenEnd + offset,
strokeWidth: MODULE_STROKE_WIDTH,
text: textChildren, text: textChildren,
scale scale,
style
}); });
} }
@ -234,6 +295,7 @@ function AddHorizontalBorrowerDimension(
scale: number scale: number
): void { ): void {
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform); const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const style = container.properties.dimensionOptions.dimensionWithMarks;
const marks = []; // list of vertical lines for the dimension const marks = []; // list of vertical lines for the dimension
for (const { for (const {
container: childContainer, currentTransform: childCurrentTransform container: childContainer, currentTransform: childCurrentTransform
@ -274,9 +336,9 @@ function AddHorizontalBorrowerDimension(
xEnd: next, xEnd: next,
yStart: yDim, yStart: yDim,
yEnd: yDim, yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH,
text: value.toFixed(0), text: value.toFixed(0),
scale scale,
style
}); });
count++; count++;
} }
@ -293,6 +355,7 @@ function AddVerticalBorrowerDimension(
scale: number scale: number
): void { ): void {
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform); const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const style = container.properties.dimensionOptions.dimensionWithMarks;
const marks = []; // list of vertical lines for the dimension const marks = []; // list of vertical lines for the dimension
for (const { for (const {
container: childContainer, currentTransform: childCurrentTransform container: childContainer, currentTransform: childCurrentTransform
@ -338,9 +401,9 @@ function AddVerticalBorrowerDimension(
xEnd: xDim, xEnd: xDim,
yStart: cur, yStart: cur,
yEnd: next, yEnd: next,
strokeWidth: MODULE_STROKE_WIDTH,
text: value.toFixed(0), text: value.toFixed(0),
scale scale,
style
}); });
count++; count++;
} }
@ -354,6 +417,7 @@ function AddVerticalSelfDimension(
currentTransform: [number, number], currentTransform: [number, number],
scale: number scale: number
): void { ): void {
const style = container.properties.dimensionOptions.selfDimensions;
const height = container.properties.height; const height = container.properties.height;
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`; const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1] + height; let yStart = container.properties.y + currentTransform[1] + height;
@ -372,9 +436,9 @@ function AddVerticalSelfDimension(
xEnd: xDim, xEnd: xDim,
yStart, yStart,
yEnd, yEnd,
strokeWidth: MODULE_STROKE_WIDTH,
text: textVert, text: textVert,
scale scale,
style
}); });
} }
@ -385,6 +449,7 @@ function AddHorizontalSelfDimension(
currentTransform: [number, number], currentTransform: [number, number],
scale: number scale: number
): void { ): void {
const style = container.properties.dimensionOptions.selfDimensions;
const width = container.properties.width; const width = container.properties.width;
const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`; const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0]; const xStart = container.properties.x + currentTransform[0];
@ -398,8 +463,180 @@ function AddHorizontalSelfDimension(
yStart: yDim, yStart: yDim,
xEnd, xEnd,
yEnd: yDim, yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH,
text, text,
scale scale,
style
});
}
function AddHorizontalSelfMarginsDimension(
ctx: CanvasRenderingContext2D,
yDim: number,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const left = container.properties.margin.left;
if (left != null) {
const id = `dim-y-margin-left${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0] - left;
const xEnd = xStart + left;
const text = left
.toFixed(0)
.toString();
RenderDimension(ctx, {
id,
xStart,
yStart: yDim,
xEnd,
yEnd: yDim,
text,
scale,
style
});
}
const right = container.properties.margin.right;
if (right != null) {
const id = `dim-y-margin-right${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + container.properties.width + currentTransform[0];
const xEnd = xStart + right;
const text = right
.toFixed(0)
.toString();
RenderDimension(ctx, {
id,
xStart,
yStart: yDim,
xEnd,
yEnd: yDim,
text,
scale,
style
});
}
}
function AddVerticalSelfMarginDimension(
ctx: CanvasRenderingContext2D,
xDim: number,
isRight: boolean,
container: IContainerModel,
currentTransform: [number, number],
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const top = container.properties.margin.top;
if (top != null) {
const idVert = `dim-x-margin-top${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1];
let yEnd = yStart - top;
const textVert = top
.toFixed(0)
.toString();
if (isRight) {
[yStart, yEnd] = [yEnd, yStart];
}
RenderDimension(ctx, {
id: idVert,
xStart: xDim,
yStart,
xEnd: xDim,
yEnd,
text: textVert,
scale,
style
});
}
const bottom = container.properties.margin.bottom;
if (bottom != null) {
const idVert = `dim-x-margin-bottom${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + container.properties.height + bottom + currentTransform[1];
let yEnd = yStart - bottom;
const textVert = bottom
.toFixed(0)
.toString();
if (isRight) {
[yStart, yEnd] = [yEnd, yStart];
}
RenderDimension(ctx, {
id: idVert,
xStart: xDim,
yStart,
xEnd: xDim,
yEnd,
text: textVert,
scale,
style
});
}
}
function AddHorizontalSymbolDimension(
ctx: CanvasRenderingContext2D,
symbol: ISymbolModel,
scale: number,
depth: number
): void {
const width = TransformX(symbol.offset, symbol.width, symbol.config.PositionReference);
if (width == null || width <= 0) {
return;
}
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const text = width
.toFixed(0)
.toString();
RenderDimension(ctx, {
id,
xStart: 0,
yStart: -offset,
xEnd: width,
yEnd: -offset,
text,
scale,
style: DEFAULT_DIMENSION_SYMBOL_STYLE
});
}
function AddVerticalSymbolDimension(
ctx: CanvasRenderingContext2D,
symbol: ISymbolModel,
scale: number,
depth: number
): void {
const height = TransformY(symbol.offset, symbol.height, symbol.config.PositionReference);
if (height == null || height <= 0) {
return;
}
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const text = height
.toFixed(0)
.toString();
RenderDimension(ctx, {
id,
xStart: -offset,
yStart: height,
xEnd: -offset,
yEnd: 0,
text,
scale,
style: DEFAULT_DIMENSION_SYMBOL_STYLE
}); });
} }

View file

@ -0,0 +1,103 @@
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DIMENSION_MARGIN } from '../../utils/default';
import { MakeRecursionDFSIterator } from '../../utils/itertools';
import { RenderContainer } from './Container';
import { AddContainerDimensions, AddSymbolDimensions } from './DimensionLayer';
import { RenderSymbol } from './Symbol';
export function RenderContainers(
ctx: CanvasRenderingContext2D,
root: IContainerModel,
containers: Map<string, IContainerModel>,
leftDim: number,
bottomDim: number,
topDim: number,
rightDim: number,
scale: number
): void {
const it = MakeRecursionDFSIterator(root, containers, 0, [0, 0]);
for (const { container, depth, currentTransform } of it) {
const [x, y] = [
container.properties.x + currentTransform[0],
container.properties.y + currentTransform[1]
];
// Draw container
RenderContainer(ctx, container, x, y);
// Draw dimensions
RenderContainerDimensions(
ctx,
leftDim,
bottomDim,
topDim,
rightDim,
depth,
scale,
containers,
container,
currentTransform
);
}
}
export function RenderContainerDimensions(
ctx: CanvasRenderingContext2D,
leftDim: number,
bottomDim: number,
topDim: number,
rightDim: number,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
ctx.save();
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = leftDim - depthOffset;
const containerTopDim = topDim - depthOffset;
const containerBottomDim = bottomDim + depthOffset;
const containerRightDim = rightDim + depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddContainerDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}
export function RenderSymbols(
ctx: CanvasRenderingContext2D,
symbols: Map<string, ISymbolModel>,
scale: number
): void {
symbols.forEach((symbol: ISymbolModel) => {
RenderSymbol(ctx, symbol);
if (!symbol.showDimension) {
return;
}
// TODO: Implement DimensionManager
AddSymbolDimensions(ctx, symbol, scale, 0);
});
}
export function RenderSymbolDimensions(
ctx: CanvasRenderingContext2D,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
ctx.save();
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = -depthOffset;
const containerTopDim = -depthOffset;
const containerBottomDim = depthOffset;
const containerRightDim = depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddContainerDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}

View file

@ -1,33 +1,26 @@
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { SHOW_SELECTOR_TEXT } from '../../utils/default'; import { SHOW_SELECTOR_TEXT } from '../../utils/default';
import { GetAbsolutePosition } from '../../utils/itertools';
import { RemoveMargin } from '../../utils/svg';
interface ISelectorProps { interface ISelectorProps {
containers: Map<string, IContainerModel> text: string
selected?: IContainerModel x: number
scale?: number y: number
width: number
height: number
scale: number
} }
export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number, props: ISelectorProps): void { export function RenderSelector(
if (props.selected === undefined || props.selected === null) { ctx: CanvasRenderingContext2D,
return; frameCount: number,
} {
text,
const scale = (props.scale ?? 1); x,
let [x, y] = GetAbsolutePosition(props.containers, props.selected); y,
let [width, height] = [ width,
props.selected.properties.width, height,
props.selected.properties.height scale
]; }: ISelectorProps
): void {
({ x, y, width, height } = RemoveMargin(x, y, width, height,
props.selected.properties.margin.left,
props.selected.properties.margin.bottom,
props.selected.properties.margin.top,
props.selected.properties.margin.right
));
const xText = x + width / 2; const xText = x + width / 2;
const yText = y + height / 2; const yText = y + height / 2;
@ -42,7 +35,7 @@ export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number
if (SHOW_SELECTOR_TEXT) { if (SHOW_SELECTOR_TEXT) {
ctx.font = `${16 / scale}px Verdana`; ctx.font = `${16 / scale}px Verdana`;
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.fillText(props.selected.properties.displayedText, xText, yText); ctx.fillText(text, xText, yText);
ctx.textAlign = 'left'; ctx.textAlign = 'left';
} }
} }

View file

@ -0,0 +1,48 @@
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { GetAbsolutePosition } from '../../utils/itertools';
import { RemoveMargin } from '../../utils/svg';
import { RenderSelector } from './Selector';
interface ISelectorProps {
containers: Map<string, IContainerModel>
selected?: IContainerModel
scale?: number
}
export function RenderContainerSelector(
ctx: CanvasRenderingContext2D,
frameCount: number,
props: ISelectorProps
): void {
if (props.selected === undefined || props.selected === null) {
return;
}
const scale = (props.scale ?? 1);
let [x, y] = GetAbsolutePosition(props.containers, props.selected);
let [width, height] = [
props.selected.properties.width,
props.selected.properties.height
];
({ x, y, width, height } = RemoveMargin(x, y, width, height,
props.selected.properties.margin.left,
props.selected.properties.margin.bottom,
props.selected.properties.margin.top,
props.selected.properties.margin.right
));
const text = props.selected.properties.displayedText;
RenderSelector(
ctx,
frameCount,
{
text,
x,
y,
width,
height,
scale
}
);
}

View file

@ -0,0 +1,47 @@
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SYMBOL_MARGIN } from '../../utils/default';
import { RenderSelector } from './Selector';
interface ISelectorProps {
symbols: Map<string, ISymbolModel>
selected?: ISymbolModel
}
export function RenderSymbolSelector(
ctx: CanvasRenderingContext2D,
frameCount: number,
props: ISelectorProps
): void {
if (props.selected === undefined || props.selected === null) {
return;
}
const [width, height] = [
props.selected.width,
props.selected.height
];
let x, y: number;
if (props.selected.isVertical) {
x = -SYMBOL_MARGIN - props.selected.width;
y = props.selected.offset;
} else {
x = props.selected.offset;
y = -SYMBOL_MARGIN - props.selected.height;
}
const text = props.selected.displayedText;
RenderSelector(
ctx,
frameCount,
{
text,
x,
y,
width,
height,
scale: 1
}
);
}

View file

@ -1,12 +1,11 @@
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DIMENSION_MARGIN } from '../../utils/default'; import { SYMBOL_MARGIN } from '../../utils/default';
const IMAGE_CACHE = new Map<string, HTMLImageElement>(); const IMAGE_CACHE = new Map<string, HTMLImageElement>();
export function RenderSymbol( export function RenderSymbol(
symbol: ISymbolModel,
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
scale: number): void { symbol: ISymbolModel): void {
const href = symbol.config.Image.Base64Image ?? symbol.config.Image.Url; const href = symbol.config.Image.Base64Image ?? symbol.config.Image.Url;
if (href === undefined) { if (href === undefined) {
@ -19,27 +18,38 @@ export function RenderSymbol(
newImage.src = href; newImage.src = href;
IMAGE_CACHE.set(href, newImage); IMAGE_CACHE.set(href, newImage);
newImage.onload = () => { newImage.onload = () => {
DrawImage(ctx, scale, newImage, symbol); DrawImage(ctx, newImage, symbol);
}; };
return; return;
} }
DrawImage(ctx, scale, image, symbol); DrawImage(ctx, image, symbol);
} }
function DrawImage( function DrawImage(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
scale: number,
image: HTMLImageElement, image: HTMLImageElement,
symbol: ISymbolModel symbol: ISymbolModel
): void { ): void {
let x, y: number;
if (symbol.isVertical) {
x = -SYMBOL_MARGIN - symbol.width;
y = symbol.offset;
} else {
x = symbol.offset;
y = -SYMBOL_MARGIN - symbol.height;
}
ctx.save(); ctx.save();
ctx.fillStyle = '#000000'; ctx.fillStyle = '#000000';
const width = symbol.width;
const height = symbol.height;
ctx.drawImage( ctx.drawImage(
image, image,
symbol.x, x,
-DIMENSION_MARGIN, y,
symbol.width, width,
symbol.height height
); );
ctx.restore(); ctx.restore();
} }

View file

@ -5,7 +5,8 @@ import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { import {
SHOW_BORROWER_DIMENSIONS, SHOW_BORROWER_DIMENSIONS,
SHOW_CHILDREN_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS,
SHOW_SELF_DIMENSIONS, SHOW_SELF_MARGINS_DIMENSIONS SHOW_SELF_DIMENSIONS,
SHOW_SELF_MARGINS_DIMENSIONS
} from '../../utils/default'; } from '../../utils/default';
import { import {
ApplyWidthMargin, ApplyWidthMargin,
@ -27,8 +28,12 @@ import { OrientationSelector } from '../RadioGroupButtons/OrientationSelector';
import { OrientationCheckboxes } from '../CheckboxGroupButtons/OrientationCheckboxes'; import { OrientationCheckboxes } from '../CheckboxGroupButtons/OrientationCheckboxes';
import { PositionCheckboxes } from '../CheckboxGroupButtons/PositionCheckboxes'; import { PositionCheckboxes } from '../CheckboxGroupButtons/PositionCheckboxes';
import { Category } from '../Category/Category'; import { Category } from '../Category/Category';
import { Orientation } from '../../Enums/Orientation';
import { FindContainerById } from '../../utils/itertools';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
interface IContainerFormProps { interface IContainerFormProps {
containers: Map<string, IContainerModel>
properties: IContainerProperties properties: IContainerProperties
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
@ -36,6 +41,8 @@ interface IContainerFormProps {
export function ContainerForm(props: IContainerFormProps): JSX.Element { export function ContainerForm(props: IContainerFormProps): JSX.Element {
const categoryHeight = 'h-11'; const categoryHeight = 'h-11';
const parent = FindContainerById(props.containers, props.properties.parentId);
const isVertical = parent?.properties.orientation === Orientation.Vertical;
return ( return (
<div className='grid grid-cols-1 gap-y-4 items-center'> <div className='grid grid-cols-1 gap-y-4 items-center'>
<TextInputGroup <TextInputGroup
@ -185,8 +192,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number' type='number'
min={props.properties.minWidth} min={props.properties.minWidth}
max={props.properties.maxWidth} max={props.properties.maxWidth}
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()} value={(RemoveWidthMargin(props.properties.width,
onChange={(value) => { props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right)); }} props.properties.margin.left,
props.properties.margin.right)).toString()}
onChange={(value) => {
props.onChange('width', ApplyWidthMargin(Number(value),
props.properties.margin.left,
props.properties.margin.right));
}}
isDisabled={props.properties.isFlex}/> isDisabled={props.properties.isFlex}/>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-maxWidth`} id={`${props.properties.id}-maxWidth`}
@ -218,8 +231,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number' type='number'
min={props.properties.minHeight} min={props.properties.minHeight}
max={props.properties.maxHeight} max={props.properties.maxHeight}
value={(RemoveWidthMargin(props.properties.height, props.properties.margin.top, props.properties.margin.bottom)).toString()} value={(RemoveWidthMargin(props.properties.height,
onChange={(value) => { props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom)); }} props.properties.margin.top,
props.properties.margin.bottom)).toString()}
onChange={(value) => {
props.onChange('height', ApplyWidthMargin(Number(value),
props.properties.margin.top,
props.properties.margin.bottom));
}}
isDisabled={props.properties.isFlex} isDisabled={props.properties.isFlex}
/> />
<TextInputGroup <TextInputGroup
@ -332,7 +351,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelText={Text({ textId: '@ContainerAlignWithSymbol' })} labelText={Text({ textId: '@ContainerAlignWithSymbol' })}
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
inputs={[...props.symbols.values()].map(symbol => ({ inputs={[...props.symbols.values()].filter(symbol => (symbol.isVertical === isVertical)).map(symbol => ({
key: symbol.id, key: symbol.id,
text: symbol.id, text: symbol.id,
value: symbol.id value: symbol.id
@ -366,7 +385,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='color' type='color'
value={props.properties.dimensionOptions.selfDimensions.color} value={props.properties.dimensionOptions.selfDimensions.color ?? '#000000'}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/> onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-selfDimensions-width`} id={`${props.properties.id}-selfDimensions-width`}
@ -406,7 +425,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='color' type='color'
value={props.properties.dimensionOptions.selfMarginsDimensions.color} value={props.properties.dimensionOptions.selfMarginsDimensions.color ?? '#000000'}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/> onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-selfMarginsDimensions-width`} id={`${props.properties.id}-selfMarginsDimensions-width`}
@ -446,7 +465,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='color' type='color'
value={props.properties.dimensionOptions.childrenDimensions.color} value={props.properties.dimensionOptions.childrenDimensions.color ?? '#000000'}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/> onChange={(e) => { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-childrenDimensions-width`} id={`${props.properties.id}-childrenDimensions-width`}
@ -496,7 +515,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='color' type='color'
value={props.properties.dimensionOptions.dimensionWithMarks.color} value={props.properties.dimensionOptions.dimensionWithMarks.color ?? '#000000'}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/> onChange={(e) => { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-dimensionWithMarks-width`} id={`${props.properties.id}-dimensionWithMarks-width`}
@ -531,16 +550,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'> <div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
<TextInputGroup <InputGroup
id={`${props.properties.id}-stroke`}
labelText={Text({ textId: '@StyleStroke' })} labelText={Text({ textId: '@StyleStroke' })}
inputKey='stroke' inputKey={`${props.properties.id}-stroke`}
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
type='string' type='color'
value={props.properties.style.stroke ?? 'black'} value={props.properties.style.stroke ?? '#000000'}
onChange={(value) => { props.onChange('stroke', value, PropertyType.Style); }} onChange={(e) => { props.onChange('stroke', e.target.value, PropertyType.Style); }}/>
/>
<InputGroup <InputGroup
labelKey={`${props.properties.id}-strokeOpacity`} labelKey={`${props.properties.id}-strokeOpacity`}
labelText={Text({ textId: '@StyleStrokeOpacity' })} labelText={Text({ textId: '@StyleStrokeOpacity' })}
@ -564,16 +581,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
value={(props.properties.style.strokeWidth ?? 1).toString()} value={(props.properties.style.strokeWidth ?? 1).toString()}
onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }} onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
/> />
<TextInputGroup <InputGroup
id={`${props.properties.id}-fill`}
labelText={Text({ textId: '@StyleFill' })} labelText={Text({ textId: '@StyleFill' })}
inputKey='fill' inputKey={`${props.properties.id}-fill`}
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
type='string' type='color'
value={props.properties.style.fill ?? 'black'} value={props.properties.style.fill ?? '#000000'}
onChange={(value) => { props.onChange('fill', value, PropertyType.Style); }} onChange={(e) => { props.onChange('fill', e.target.value, PropertyType.Style); }}/>
/>
<InputGroup <InputGroup
labelKey={`${props.properties.id}-fillOpacity`} labelKey={`${props.properties.id}-fillOpacity`}
labelText={Text({ textId: '@StyleFillOpacity' })} labelText={Text({ textId: '@StyleFillOpacity' })}

View file

@ -1,10 +1,12 @@
import React from 'react'; import React from 'react';
import { PropertyType } from '../../Enums/PropertyType'; import { type PropertyType } from '../../Enums/PropertyType';
import { IContainerProperties } from '../../Interfaces/IContainerProperties'; import { type IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { ContainerForm } from './ContainerForm'; import { ContainerForm } from './ContainerForm';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
interface IPropertiesProps { interface IPropertiesProps {
containers: Map<string, IContainerModel>
properties?: IContainerProperties properties?: IContainerProperties
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
@ -18,6 +20,7 @@ export function ContainerProperties(props: IPropertiesProps): JSX.Element {
return ( return (
<div className='h-full p-3 bg-slate-200 overflow-y-auto'> <div className='h-full p-3 bg-slate-200 overflow-y-auto'>
<ContainerForm <ContainerForm
containers={props.containers}
properties={props.properties} properties={props.properties}
symbols={props.symbols} symbols={props.symbols}
onChange={props.onChange} /> onChange={props.onChange} />

View file

@ -1,10 +1,10 @@
import { IConfiguration } from '../../../Interfaces/IConfiguration'; import { type IConfiguration } from '../../../Interfaces/IConfiguration';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { type IContainerModel } from '../../../Interfaces/IContainerModel';
import { IHistoryState } from '../../../Interfaces/IHistoryState'; import { type IHistoryState } from '../../../Interfaces/IHistoryState';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { GetDefaultSymbolModel } from '../../../utils/default'; import { GetDefaultSymbolModel } from '../../../utils/default';
import { FindContainerById } from '../../../utils/itertools'; import { FindContainerById } from '../../../utils/itertools';
import { RestoreX } from '../../../utils/svg'; import {RestoreX, RestoreY} from '../../../utils/svg';
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor'; import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor';
import { AddContainers } from './AddContainer'; import { AddContainers } from './AddContainer';
@ -32,7 +32,12 @@ export function AddSymbol(
const newSymbols = structuredClone(current.symbols); const newSymbols = structuredClone(current.symbols);
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig); const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig);
const containers = structuredClone(current.containers); const containers = structuredClone(current.containers);
newSymbol.x = RestoreX(newSymbol.x, newSymbol.width, newSymbol.config.PositionReference);
if (newSymbol.isVertical) {
newSymbol.offset = RestoreY(newSymbol.offset, newSymbol.height, newSymbol.config.PositionReference);
} else {
newSymbol.offset = RestoreX(newSymbol.offset, newSymbol.width, newSymbol.config.PositionReference);
}
newSymbols.set(newSymbol.id, newSymbol); newSymbols.set(newSymbol.id, newSymbol);

View file

@ -1,16 +1,39 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { type IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { ApplyParentTransform, FindContainerById } from '../../../utils/itertools'; import { ApplyParentTransform, FindContainerById } from '../../../utils/itertools';
import { RestoreX, TransformX } from '../../../utils/svg'; import { RestoreX, RestoreY, TransformX, TransformY } from '../../../utils/svg';
export function ApplySymbol(containers: Map<string, IContainerModel>, container: IContainerModel, symbol: ISymbolModel): IContainerModel { export function ApplySymbol(containers: Map<string, IContainerModel>,
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.PositionReference); container: IContainerModel,
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.positionReference); symbol: ISymbolModel): IContainerModel {
const parent = FindContainerById(containers, container.properties.parentId); if (symbol.isVertical) {
let x = 0; container.properties.y = TransformY(symbol.offset,
if (parent !== undefined && parent !== null) { symbol.height,
([x] = ApplyParentTransform(containers, parent, container.properties.x, 0)); symbol.config.PositionReference);
container.properties.y = RestoreY(container.properties.y,
container.properties.height,
container.properties.positionReference);
const parent = FindContainerById(containers, container.properties.parentId);
let y = 0;
if (parent !== undefined && parent !== null) {
([,y] = ApplyParentTransform(containers, parent, 0, container.properties.y));
}
container.properties.y = y;
return container;
} else {
container.properties.x = TransformX(
symbol.offset,
symbol.width,
symbol.config.PositionReference);
container.properties.x = RestoreX(container.properties.x,
container.properties.width,
container.properties.positionReference);
const parent = FindContainerById(containers, container.properties.parentId);
let x = 0;
if (parent !== undefined && parent !== null) {
([x] = ApplyParentTransform(containers, parent, container.properties.x, 0));
}
container.properties.x = x;
return container;
} }
container.properties.x = x;
return container;
} }

View file

@ -1,21 +1,22 @@
import * as React from 'react'; import * as React from 'react';
import { useState } from 'react';
import useSize from '@react-hook/size'; import useSize from '@react-hook/size';
import { FixedSizeList as List } from 'react-window'; import { FixedSizeList as List } from 'react-window';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { ContainerProperties } from '../ContainerProperties/ContainerProperties'; import { ContainerProperties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel'; import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools'; import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType'; import { type PropertyType } from '../../Enums/PropertyType';
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar'; import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
import { Text } from '../Text/Text'; import { Text } from '../Text/Text';
import { ExtendedSidebar } from '../UI/UI';
interface IElementsSideBarProps { interface IElementsSidebarProps {
containers: Map<string, IContainerModel> containers: Map<string, IContainerModel>
mainContainer: IContainerModel mainContainer: IContainerModel
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
selectedContainer: IContainerModel | undefined selectedContainer: IContainerModel | undefined
selectedExtendedSidebar: ExtendedSidebar
onPropertyChange: ( onPropertyChange: (
key: string, key: string,
value: string | number | boolean | number[], value: string | number | boolean | number[],
@ -23,8 +24,7 @@ interface IElementsSideBarProps {
) => void ) => void
selectContainer: (containerId: string) => void selectContainer: (containerId: string) => void
addContainer: (index: number, type: string, parent: string) => void addContainer: (index: number, type: string, parent: string) => void
isExpanded: boolean onExpandChange: (value: ExtendedSidebar) => void
onExpandChange: () => void
} }
function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void { function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void {
@ -124,11 +124,10 @@ function HandleOnDrop(
} }
} }
export function ElementsSideBar(props: IElementsSideBarProps): JSX.Element { export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
// States // States
const divRef = React.useRef<HTMLDivElement>(null); const divRef = React.useRef<HTMLDivElement>(null);
const [,height] = useSize(divRef); const [,height] = useSize(divRef);
const [showProperties, setShowProperties] = useState(props.isExpanded);
// Render // Render
const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true); const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true);
@ -167,18 +166,28 @@ export function ElementsSideBar(props: IElementsSideBarProps): JSX.Element {
return ( return (
<div className='flex flex-row h-full w-full' > <div className='flex flex-row h-full w-full' >
{showProperties && {props.selectedExtendedSidebar === ExtendedSidebar.Property &&
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'> <div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
<ContainerProperties <ContainerProperties
properties={props.selectedContainer?.properties} containers ={props.containers}
symbols={props.symbols} properties={props.selectedContainer?.properties}
onChange={props.onPropertyChange} symbols={props.symbols}
/> onChange={props.onPropertyChange}
</div> />
</div>
} }
<div className='flex w-64' ref={divRef}> <div className='flex w-64' ref={divRef}>
<div className='w-6'> <div className='w-6'>
<ToggleSideBar title={Text({ textId: '@Properties' })} checked={showProperties} onChange={(newValue) => { setShowProperties(newValue); props.onExpandChange(); }} /> <ToggleSideBar
title={Text({ textId: '@Properties' })}
checked={props.selectedExtendedSidebar === ExtendedSidebar.Property}
onClick={() => {
const newValue = props.selectedExtendedSidebar !== ExtendedSidebar.Property
? ExtendedSidebar.Property
: ExtendedSidebar.None;
props.onExpandChange(newValue);
}}
/>
</div> </div>
<List <List
itemCount={containers.length} itemCount={containers.length}
@ -223,16 +232,18 @@ function ElementsListRow(
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400'; : 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
return <button type="button" return <button type="button"
className={`transition-all border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre className={`transition-all border-blue-500
text-left text-sm font-medium flex items-center align-middle group ${container.properties.type} ${buttonSelectedClass}`} hover:shadow-lg elements-sidebar-row whitespace-pre
text-left text-sm font-medium flex items-center align-middle group
${container.properties.type} ${buttonSelectedClass}`}
id={key} id={key}
key={key} key={key}
style={style} style={style}
title={container.properties.warning} title={container.properties.warning}
onClick={() => selectContainer(container.properties.id)} onClick={() => { selectContainer(container.properties.id); }}
onDrop={(event) => HandleOnDrop(event, containers, mainContainer, addContainer)} onDrop={(event) => { HandleOnDrop(event, containers, mainContainer, addContainer); }}
onDragOver={(event) => HandleDragOver(event, mainContainer)} onDragOver={(event) => { HandleDragOver(event, mainContainer); }}
onDragLeave={(event) => HandleDragLeave(event)} onDragLeave={(event) => { HandleDragLeave(event); }}
> >
{verticalBars} {verticalBars}
{text} {text}

View file

@ -31,7 +31,7 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
export function Dimension(props: IDimensionProps): JSX.Element { export function Dimension(props: IDimensionProps): JSX.Element {
const scale = props.scale ?? 1; const scale = props.scale ?? 1;
const style: React.CSSProperties = { const style: React.CSSProperties = {
stroke: props.style.color, stroke: props.style.color ?? '#000000',
strokeWidth: (props.style.width ?? 2) / scale, strokeWidth: (props.style.width ?? 2) / scale,
strokeDasharray: props.style.dashArray strokeDasharray: props.style.dashArray
}; };

View file

@ -2,6 +2,7 @@ import * as React from 'react';
import { Orientation } from '../../../Enums/Orientation'; import { Orientation } from '../../../Enums/Orientation';
import { Position } from '../../../Enums/Position'; import { Position } from '../../../Enums/Position';
import { import {
DEFAULT_DIMENSION_SYMBOL_STYLE,
DIMENSION_MARGIN, DIMENSION_MARGIN,
SHOW_BORROWER_DIMENSIONS, SHOW_BORROWER_DIMENSIONS,
SHOW_CHILDREN_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS,
@ -10,7 +11,7 @@ import {
} from '../../../utils/default'; } from '../../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools'; import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg'; import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension, type IDimensionStyle } from './Dimension'; import { Dimension } from './Dimension';
import { type IContainerModel } from '../../../Interfaces/IContainerModel'; import { type IContainerModel } from '../../../Interfaces/IContainerModel';
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
@ -41,14 +42,12 @@ function ActionByPosition(
positions.forEach((position: Position) => { positions.forEach((position: Position) => {
const dim = dimMapped[position]; const dim = dimMapped[position];
switch (position) { switch (position) {
case Position.Right:
case Position.Left: case Position.Left:
case Position.Right: { verticalAction(dim, false, ...params);
const isRight = position === Position.Right;
verticalAction(dim, isRight, ...params);
break; break;
}
case Position.Down:
case Position.Up: case Position.Up:
case Position.Down:
horizontalAction(dim, ...params); horizontalAction(dim, ...params);
break; break;
} }
@ -94,7 +93,8 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
); );
} }
if (SHOW_SELF_MARGINS_DIMENSIONS && container.properties.dimensionOptions.selfMarginsDimensions.positions.length > 0) { if (SHOW_SELF_MARGINS_DIMENSIONS &&
container.properties.dimensionOptions.selfMarginsDimensions.positions.length > 0) {
ActionByPosition( ActionByPosition(
dimMapped, dimMapped,
container.properties.dimensionOptions.selfMarginsDimensions.positions, container.properties.dimensionOptions.selfMarginsDimensions.positions,
@ -127,7 +127,9 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
); );
} }
if (SHOW_CHILDREN_DIMENSIONS && container.properties.dimensionOptions.childrenDimensions.positions.length > 0 && container.children.length >= 2) { if (SHOW_CHILDREN_DIMENSIONS &&
container.properties.dimensionOptions.childrenDimensions.positions.length > 0 &&
container.children.length >= 2) {
ActionByPosition( ActionByPosition(
dimMapped, dimMapped,
container.properties.dimensionOptions.childrenDimensions.positions, container.properties.dimensionOptions.childrenDimensions.positions,
@ -144,29 +146,26 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
} }
} }
let startDepthSymbols: number = 0; // TODO: Implement DimensionManager
for (const symbol of symbols) { symbols.forEach((symbol) => {
if (symbol[1].showDimension) { if (symbol.showDimension) {
startDepthSymbols++; if (symbol.isVertical) {
AddHorizontalSymbolDimension( AddVerticalSymbolDimension(symbol, dimensions, scale, 0);
symbol[1], } else {
dimensions, AddHorizontalSymbolDimension(symbol, dimensions, scale, 0);
scale, }
startDepthSymbols
);
} }
} });
return dimensions; return dimensions;
} }
function AddHorizontalSymbolDimension( function AddHorizontalSymbolDimension(symbol: ISymbolModel,
symbol: ISymbolModel,
dimensions: React.ReactNode[], dimensions: React.ReactNode[],
scale: number, scale: number,
depth: number depth: number
): void { ): void {
const width = symbol.x + (symbol.width / 2); const width = TransformX(symbol.offset, symbol.width, symbol.config.PositionReference);
if (width != null && width > 0) { if (width != null && width > 0) {
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`; const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
@ -174,11 +173,6 @@ function AddHorizontalSymbolDimension(
const text = width const text = width
.toFixed(0) .toFixed(0)
.toString(); .toString();
// TODO: Put this in default.ts
const defaultDimensionSymbolStyle: IDimensionStyle = {
color: 'black'
};
dimensions.push( dimensions.push(
<Dimension <Dimension
key={id} key={id}
@ -189,7 +183,35 @@ function AddHorizontalSymbolDimension(
yEnd={-offset} yEnd={-offset}
text={text} text={text}
scale={scale} scale={scale}
style={defaultDimensionSymbolStyle}/> style={DEFAULT_DIMENSION_SYMBOL_STYLE}/>
);
}
}
function AddVerticalSymbolDimension(symbol: ISymbolModel,
dimensions: React.ReactNode[],
scale: number,
depth: number
): void {
const height = TransformY(symbol.offset, symbol.height, symbol.config.PositionReference);
if (height != null && height > 0) {
const id = `dim-x-margin-left${symbol.height.toFixed(0)}-${symbol.id}`;
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const text = height
.toFixed(0)
.toString();
dimensions.push(
<Dimension
key={id}
id={id}
xStart={-offset}
yStart={height}
xEnd={-offset}
yEnd={0}
text={text}
scale={scale}
style={DEFAULT_DIMENSION_SYMBOL_STYLE}/>
); );
} }
} }
@ -227,8 +249,12 @@ function AddHorizontalChildrenDimension(
return; return;
} }
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference); let xChildrenStart = TransformX(lastChild.properties.x,
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference); lastChild.properties.width,
lastChild.properties.positionReference);
let xChildrenEnd = TransformX(lastChild.properties.x,
lastChild.properties.width,
lastChild.properties.positionReference);
// Find the min and max // Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) { for (let i = container.children.length - 2; i >= 0; i--) {
@ -290,8 +316,12 @@ function AddVerticalChildrenDimension(
return; return;
} }
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference); let yChildrenStart = TransformY(lastChild.properties.y,
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference); lastChild.properties.height,
lastChild.properties.positionReference);
let yChildrenEnd = TransformY(lastChild.properties.y,
lastChild.properties.height,
lastChild.properties.positionReference);
// Find the min and max // Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) { for (let i = container.children.length - 2; i >= 0; i--) {
@ -537,7 +567,7 @@ function AddHorizontalSelfMarginsDimension(
): void { ): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions; const style = container.properties.dimensionOptions.selfMarginsDimensions;
const left = container.properties.margin.left; const left = container.properties.margin.left;
if (left != null) { if (left != null && left > 0) {
const id = `dim-y-margin-left${yDim.toFixed(0)}-${container.properties.id}`; const id = `dim-y-margin-left${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0] - left; const xStart = container.properties.x + currentTransform[0] - left;
const xEnd = xStart + left; const xEnd = xStart + left;
@ -559,7 +589,7 @@ function AddHorizontalSelfMarginsDimension(
} }
const right = container.properties.margin.right; const right = container.properties.margin.right;
if (right != null) { if (right != null && right > 0) {
const id = `dim-y-margin-right${yDim.toFixed(0)}-${container.properties.id}`; const id = `dim-y-margin-right${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + container.properties.width + currentTransform[0]; const xStart = container.properties.x + container.properties.width + currentTransform[0];
const xEnd = xStart + right; const xEnd = xStart + right;
@ -591,7 +621,7 @@ function AddVerticalSelfMarginDimension(
): void { ): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions; const style = container.properties.dimensionOptions.selfMarginsDimensions;
const top = container.properties.margin.top; const top = container.properties.margin.top;
if (top != null) { if (top != null && top > 0) {
const idVert = `dim-x-margin-top${xDim.toFixed(0)}-${container.properties.id}`; const idVert = `dim-x-margin-top${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1]; let yStart = container.properties.y + currentTransform[1];
let yEnd = yStart - top; let yEnd = yStart - top;
@ -617,7 +647,7 @@ function AddVerticalSelfMarginDimension(
); );
} }
const bottom = container.properties.margin.bottom; const bottom = container.properties.margin.bottom;
if (bottom != null) { if (bottom != null && bottom > 0) {
const idVert = `dim-x-margin-bottom${xDim.toFixed(0)}-${container.properties.id}`; const idVert = `dim-x-margin-bottom${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + container.properties.height + bottom + currentTransform[1]; let yStart = container.properties.y + container.properties.height + bottom + currentTransform[1];
let yEnd = yStart - bottom; let yEnd = yStart - bottom;

View file

@ -1,13 +1,12 @@
import '../Selector.scss'; import '../Selector.scss';
import * as React from 'react'; import * as React from 'react';
import { SYMBOL_MARGIN } from '../../../../utils/default'; import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../../../utils/default';
import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel';
import { Selector } from '../Selector/Selector'; import { Selector } from '../Selector/Selector';
interface ISelectorSymbolProps { interface ISelectorSymbolProps {
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
selected?: ISymbolModel selected?: ISymbolModel
scale?: number
} }
export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element { export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
@ -18,30 +17,27 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
); );
} }
const scale = (props.scale ?? 1); let x, y: number;
const [width, height] = [
props.selected.width / scale,
props.selected.height / scale
];
const [x, y] = [ const scaledHeight = props.selected.height;
props.selected.x + props.selected.width / 2, const scaledWidth = props.selected.width;
-SYMBOL_MARGIN - height];
const style: React.CSSProperties = { if (props.selected.isVertical) {
transform: 'translateX(-50%)', x = -SYMBOL_MARGIN - props.selected.width;
transformBox: 'fill-box' y = props.selected.offset;
}; } else {
x = props.selected.offset;
y = -SYMBOL_MARGIN - props.selected.height;
}
return ( return (
<Selector <Selector
text={props.selected.displayedText} text={props.selected.displayedText}
x={x} x={x}
y={y} y={y}
width={width} width={scaledWidth}
height={height} height={scaledHeight}
scale={scale} scale={1}
style={style}
/> />
); );
} }

View file

@ -1,7 +1,7 @@
import { Interweave } from 'interweave'; import { Interweave } from 'interweave';
import * as React from 'react'; import * as React from 'react';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../../utils/default'; import { SYMBOL_MARGIN } from '../../../utils/default';
interface ISymbolProps { interface ISymbolProps {
model: ISymbolModel model: ISymbolModel
@ -12,11 +12,22 @@ export function Symbol(props: ISymbolProps): JSX.Element {
const href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url; const href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url;
const hasSVG = props.model.config.Image.Svg !== undefined && const hasSVG = props.model.config.Image.Svg !== undefined &&
props.model.config.Image.Svg !== null; props.model.config.Image.Svg !== null;
let x, y: number;
if (props.model.isVertical) {
x = -SYMBOL_MARGIN - props.model.width;
y = props.model.offset;
} else {
x = props.model.offset;
y = -SYMBOL_MARGIN - props.model.height;
}
if (hasSVG) { if (hasSVG) {
return ( return (
<g <g
x={props.model.x} x={x}
y={-DIMENSION_MARGIN / props.scale} y={y}
> >
<Interweave <Interweave
noWrap={true} noWrap={true}
@ -31,14 +42,9 @@ export function Symbol(props: ISymbolProps): JSX.Element {
<image <image
href={href} href={href}
preserveAspectRatio="none" preserveAspectRatio="none"
style={{ x={x}
fill: 'none', y={y}
transform: 'translateY(-100%) translateX(-50%)', height={props.model.height}
transformBox: 'fill-box' width={props.model.width} />
}}
x={props.model.x + props.model.width / 2}
y={-SYMBOL_MARGIN}
height={props.model.height / props.scale}
width={props.model.width / props.scale} />
); );
} }

View file

@ -1,13 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value } from 'react-svg-pan-zoom'; import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value, ALIGN_CENTER } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container'; import { Container } from './Elements/Container';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer'; import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer';
import { MAX_FRAMERATE } from '../../utils/default'; import { MAX_FRAMERATE } from '../../utils/default';
import { SymbolLayer } from './Elements/SymbolLayer'; import { SymbolLayer } from './Elements/SymbolLayer';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DimensionLayer } from './Elements/DimensionLayer'; import { DimensionLayer } from './Elements/DimensionLayer';
import { SelectorSymbol } from './Elements/SelectorSymbol/SelectorSymbol'; import { SelectorSymbol } from './Elements/SelectorSymbol/SelectorSymbol';
import { type IToolbarProps, Toolbar } from './SVGReactPanZoom/ui-toolbar/toolbar';
import { type DrawParams } from '../Viewer/Viewer';
interface ISVGProps { interface ISVGProps {
className?: string className?: string
@ -15,12 +15,7 @@ interface ISVGProps {
viewerHeight: number viewerHeight: number
width: number width: number
height: number height: number
containers: Map<string, IContainerModel> drawParams: DrawParams
children: IContainerModel
selectedContainer?: IContainerModel
symbols: Map<string, ISymbolModel>
selectedSymbol?: ISymbolModel
selectorMode: SelectorMode
selectContainer: (containerId: string) => void selectContainer: (containerId: string) => void
} }
@ -33,6 +28,14 @@ export enum SelectorMode {
export const ID = 'svg'; export const ID = 'svg';
export function SVG(props: ISVGProps): JSX.Element { export function SVG(props: ISVGProps): JSX.Element {
const {
mainContainer,
selectorMode,
selectedContainer,
selectedSymbol,
containers,
symbols
} = props.drawParams;
const [tool, setTool] = React.useState<Tool>(TOOL_PAN); const [tool, setTool] = React.useState<Tool>(TOOL_PAN);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const [value, setValue] = React.useState<Value>({} as Value); const [value, setValue] = React.useState<Value>({} as Value);
@ -58,27 +61,27 @@ export function SVG(props: ISVGProps): JSX.Element {
}; };
const children: React.ReactNode | React.ReactNode[] = <Container const children: React.ReactNode | React.ReactNode[] = <Container
key={`container-${props.children.properties.id}`} key={`container-${mainContainer.properties.id}`}
containers={props.containers} containers={containers}
model={props.children} model={mainContainer}
depth={0} depth={0}
scale={scale} scale={scale}
selectContainer={props.selectContainer} selectContainer={props.selectContainer}
/>; />;
function Selector(): JSX.Element { function Selector(): JSX.Element {
switch (props.selectorMode) { switch (selectorMode) {
case SelectorMode.Containers: case SelectorMode.Containers:
return <SelectorContainer return <SelectorContainer
containers={props.containers} containers={containers}
scale={scale} scale={scale}
selected={props.selectedContainer} selected={selectedContainer}
/>; />;
case SelectorMode.Symbols: case SelectorMode.Symbols:
return <SelectorSymbol return <SelectorSymbol
symbols={props.symbols} symbols={symbols}
scale={scale} scale={scale}
selected={props.selectedSymbol} selected={selectedSymbol}
/>; />;
default: default:
return <></>; return <></>;
@ -109,6 +112,9 @@ export function SVG(props: ISVGProps): JSX.Element {
const value = event as Value; const value = event as Value;
setScale(value.a); setScale(value.a);
}} }}
onDoubleClick={() => {
svgViewer?.current?.setPointOnViewerCenter(props.width / 2, props.height / 2, 0.8);
}}
background={'#ffffff'} background={'#ffffff'}
defaultTool='pan' defaultTool='pan'
miniatureProps={{ miniatureProps={{
@ -117,11 +123,24 @@ export function SVG(props: ISVGProps): JSX.Element {
width: 120, width: 120,
height: 120 height: 120
}} }}
customToolbar={(props: IToolbarProps) => (
<Toolbar
{...props}
SVGAlignX={ALIGN_CENTER}
SVGAlignY={ALIGN_CENTER}
fittingScale={0.8}
/>
)}
> >
<svg {...properties}> <svg {...properties}>
{children} {children}
<DimensionLayer containers={props.containers} symbols={props.symbols} scale={scale} root={props.children} /> <DimensionLayer
<SymbolLayer scale={scale} symbols={props.symbols} /> containers={containers}
symbols={symbols}
scale={scale}
root={mainContainer}
/>
<SymbolLayer scale={scale} symbols={symbols} />
<Selector /> <Selector />
</svg> </svg>
</ReactSVGPanZoom> </ReactSVGPanZoom>
@ -130,7 +149,8 @@ export function SVG(props: ISVGProps): JSX.Element {
} }
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void { function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void {
React.useEffect(() => { React.useCallback(() => {
svgViewer?.current?.fitToViewer(); // TODO: Fix this
svgViewer?.current?.setPointOnViewerCenter(width / 2, height / 2, 0.8);
}, [svgViewer, width, height]); }, [svgViewer, width, height]);
} }

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 https://github.com/chrvadala
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,81 @@
import React from 'react';
import { POSITION_TOP, POSITION_BOTTOM } from 'react-svg-pan-zoom';
interface IToolbarButtonProps {
title: string
name: string
toolbarPosition: string
activeColor: string
onClick: (event: React.MouseEvent | React.TouchEvent) => void
active: boolean
children: JSX.Element | JSX.Element[]
}
interface IToolbarButtonState {
hover: boolean
}
export class ToolbarButton extends React.Component<IToolbarButtonProps, IToolbarButtonState> {
public state: IToolbarButtonState;
constructor(props: IToolbarButtonProps) {
super(props);
this.state = { hover: false };
}
change(event: (React.MouseEvent | React.TouchEvent)): void {
event.preventDefault();
event.stopPropagation();
switch (event.type) {
case 'mouseenter':
case 'touchstart':
this.setState({ hover: true });
break;
case 'mouseleave':
case 'touchend':
case 'touchcancel':
this.setState({ hover: false });
break;
default:
// noop
}
}
render(): JSX.Element {
const style = {
display: 'block',
width: '24px',
height: '24px',
margin: [POSITION_TOP, POSITION_BOTTOM].includes(this.props.toolbarPosition) ? '2px 1px' : '1px 2px',
color: this.props.active || this.state.hover ? this.props.activeColor : '#FFF',
transition: 'color 200ms ease',
background: 'none',
padding: '0px',
border: '0px',
outline: '0px',
cursor: 'pointer'
};
return (
<button
onMouseEnter={e => { this.change(e); }}
onMouseLeave={e => { this.change(e); }}
onTouchStart={e => {
this.change(e);
this.props.onClick(e);
}}
onTouchEnd={e => { this.change(e); }}
onTouchCancel={e => { this.change(e); }}
onClick={this.props.onClick}
style={style}
title={this.props.title}
name={this.props.name}
type="button"
>{this.props.children}</button>
);
}
}

View file

@ -0,0 +1,188 @@
import {
ArrowsPointingOutIcon,
CursorArrowRaysIcon,
HandRaisedIcon,
MagnifyingGlassMinusIcon,
MagnifyingGlassPlusIcon
} from '@heroicons/react/24/outline';
import React from 'react';
import { applyToPoint, fromObject, inverse, scale, transform, translate } from 'transformation-matrix';
import {
fitToViewer,
POSITION_TOP,
POSITION_BOTTOM,
POSITION_LEFT,
POSITION_RIGHT,
TOOL_NONE,
TOOL_PAN,
TOOL_ZOOM_IN,
TOOL_ZOOM_OUT, ALIGN_LEFT, ALIGN_TOP,
type Value,
type Tool,
type ALIGN_BOTTOM,
type ALIGN_CENTER,
type ALIGN_RIGHT,
type ToolbarPosition
} from 'react-svg-pan-zoom';
import { ToolbarButton } from './toolbar-button';
export interface IToolbarProps {
tool: Tool
value: Value
onChangeValue: (value: Value) => void
onChangeTool: (tool: Tool) => void
activeToolColor?: string
position?: ToolbarPosition | undefined
SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined
SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
fittingScale?: number | undefined
}
/**
* Change value
* @param value
* @param patch
* @param action
* @returns {Object}
*/
function set(value: Value, patch: object, action = null): Value {
value = Object.assign({}, value, patch, { lastAction: action });
return Object.freeze(value);
}
/**
* Export x,y coords relative to SVG
* @param value
* @param viewerX
* @param viewerY
* @returns {*|{x, y}|{x: number, y: number}}
*/
function getSVGPoint(value: Value, viewerX: number, viewerY: number): PointObjectNotation {
const matrix = fromObject(value);
const inverseMatrix = inverse(matrix);
return applyToPoint(inverseMatrix, { x: viewerX, y: viewerY });
}
export function zoom(
value: Value,
SVGPointX: number,
SVGPointY: number,
scaleFactor: number
): Value {
const matrix = transform(
fromObject(value),
translate(SVGPointX, SVGPointY),
scale(scaleFactor, scaleFactor),
translate(-SVGPointX, -SVGPointY)
);
return set(value, {
...matrix
});
}
export function Toolbar({
tool,
value,
onChangeValue,
onChangeTool,
activeToolColor = '#1CA6FC',
position = POSITION_RIGHT,
SVGAlignX = ALIGN_LEFT,
SVGAlignY = ALIGN_TOP,
fittingScale = undefined
}: IToolbarProps): JSX.Element {
function handleChangeTool(event: React.MouseEvent | React.TouchEvent, tool: Tool): void {
onChangeTool(tool);
event.stopPropagation();
event.preventDefault();
};
function handleFit(event: React.MouseEvent | React.TouchEvent): void {
let fittedValue: Value = fitToViewer(value, SVGAlignX, SVGAlignY);
if (fittingScale !== undefined) {
const { viewerWidth, viewerHeight } = fittedValue;
const SVGPoint = getSVGPoint(value, viewerWidth / 2, viewerHeight / 2);
fittedValue = zoom(fittedValue, SVGPoint.x, SVGPoint.y, fittingScale);
}
onChangeValue(fittedValue);
event.stopPropagation();
event.preventDefault();
};
const isHorizontal = [POSITION_TOP, POSITION_BOTTOM].includes(position);
const style: React.CSSProperties = {
// position
position: 'absolute',
transform: [POSITION_TOP, POSITION_BOTTOM].includes(position) ? 'translate(-50%, 0px)' : 'none',
top: [POSITION_LEFT, POSITION_RIGHT, POSITION_TOP].includes(position) ? '5px' : 'unset',
left: [POSITION_TOP, POSITION_BOTTOM].includes(position) ? '50%' : (POSITION_LEFT === position ? '5px' : 'unset'),
right: [POSITION_RIGHT].includes(position) ? '5px' : 'unset',
bottom: [POSITION_BOTTOM].includes(position) ? '5px' : 'unset',
// inner styling
backgroundColor: 'rgba(19, 20, 22, 0.90)',
borderRadius: '2px',
display: 'flex',
flexDirection: isHorizontal ? 'row' : 'column',
padding: isHorizontal ? '1px 2px' : '2px 1px'
};
return (
<div style={style} role="toolbar">
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_NONE}
activeColor={activeToolColor}
name="unselect-tools"
title="Selection"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_NONE); } }>
<CursorArrowRaysIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_PAN}
activeColor={activeToolColor}
name="select-tool-pan"
title="Pan"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_PAN); } }>
<HandRaisedIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_ZOOM_IN}
activeColor={activeToolColor}
name="select-tool-zoom-in"
title="Zoom in"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_ZOOM_IN); } }>
<MagnifyingGlassPlusIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_ZOOM_OUT}
activeColor={activeToolColor}
name="select-tool-zoom-out"
title="Zoom out"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_ZOOM_OUT); } }>
<MagnifyingGlassMinusIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={false}
activeColor={activeToolColor}
name="fit-to-viewer"
title="Fit to viewer"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleFit(event); } }>
<ArrowsPointingOutIcon/>
</ToolbarButton>
</div>
);
}

View file

@ -4,16 +4,16 @@ import './ToggleSideBar.scss';
interface IToggleSidebarProps { interface IToggleSidebarProps {
title: string title: string
checked: boolean checked: boolean
onChange: (newValue: boolean) => void onClick: () => void
} }
export function ToggleSideBar({ title, checked, onChange }: IToggleSidebarProps): JSX.Element { export function ToggleSideBar({ title, checked, onClick }: IToggleSidebarProps): JSX.Element {
return ( return (
<div className={`${(checked ? 'bg-slate-400 hover:bg-slate-500' : 'bg-slate-300 hover:bg-slate-400')}`}> <div className={`${(checked ? 'bg-slate-400 hover:bg-slate-500' : 'bg-slate-300 hover:bg-slate-400')}`}>
<button <button
className={'w-full py-2'} className={'w-full py-2'}
type='button' type='button'
onClick={() => onChange(!checked)} onClick={onClick}
> >
<p className='text-vertical'>{title} <p className='text-vertical'>{title}
</p> </p>

View file

@ -1,11 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { RestoreX, TransformX } from '../../utils/svg'; import { RestoreX, RestoreY, TransformX, TransformY } from '../../utils/svg';
import { InputGroup } from '../InputGroup/InputGroup'; import { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup'; import { TextInputGroup } from '../InputGroup/TextInputGroup';
import { Text } from '../Text/Text'; import { Text } from '../Text/Text';
import { PropertyType } from '../../Enums/PropertyType';
import { ToggleButton } from '../ToggleButton/ToggleButton'; import { ToggleButton } from '../ToggleButton/ToggleButton';
import { type PositionReference } from '../../Enums/PositionReference';
interface ISymbolFormProps { interface ISymbolFormProps {
symbol: ISymbolModel symbol: ISymbolModel
@ -13,6 +13,29 @@ interface ISymbolFormProps {
onChange: (key: string, value: string | number | boolean) => void onChange: (key: string, value: string | number | boolean) => void
} }
function Restore(offset: number,
isVertical: boolean,
height: number,
width: number,
position: PositionReference | undefined): number {
if (isVertical) {
return RestoreY(offset, height, position);
} else {
return RestoreX(offset, width, position);
}
}
function Transform(offset: number,
isVertical: boolean,
height: number,
width: number,
position: PositionReference | undefined): number {
if (isVertical) {
return TransformY(offset, height, position);
} else {
return TransformX(offset, width, position);
}
}
export function SymbolForm(props: ISymbolFormProps): JSX.Element { export function SymbolForm(props: ISymbolFormProps): JSX.Element {
return ( return (
<div className='grid grid-cols-2 gap-y-4'> <div className='grid grid-cols-2 gap-y-4'>
@ -32,16 +55,34 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
inputClassName='' inputClassName=''
type='string' type='string'
value={props.symbol.displayedText} value={props.symbol.displayedText}
onChange={(value) => props.onChange('displayedText', value)} /> onChange={(value) => { props.onChange('displayedText', value); }} />
<TextInputGroup <TextInputGroup
id='x' id='offset'
labelText={Text({ textId: '@SymbolX' })} labelText={Text({ textId: '@SymbolOffset' })}
inputKey='x' inputKey='offset'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='number' type='number'
value={TransformX(props.symbol.x, props.symbol.width, props.symbol.config.PositionReference).toString()} value={Transform(props.symbol.offset,
onChange={(value) => props.onChange('x', RestoreX(Number(value), props.symbol.width, props.symbol.config.PositionReference))} /> props.symbol.isVertical,
props.symbol.height,
props.symbol.width,
props.symbol.config.PositionReference).toString()}
onChange={(value) => {
props.onChange('offset',
Restore(Number(value),
props.symbol.isVertical,
props.symbol.height,
props.symbol.width,
props.symbol.config.PositionReference));
}} />
<ToggleButton
labelText={Text({ textId: '@IsVertical' })}
inputKey='isVertical'
labelClassName=''
inputClassName=''
checked={props.symbol.isVertical}
onChange={(e) => { props.onChange('isVertical', e.target.checked); }}/>
<TextInputGroup <TextInputGroup
id='height' id='height'
labelText={Text({ textId: '@SymbolHeight' })} labelText={Text({ textId: '@SymbolHeight' })}
@ -51,7 +92,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
type='number' type='number'
min={0} min={0}
value={props.symbol.height.toString()} value={props.symbol.height.toString()}
onChange={(value) => props.onChange('height', Number(value))} /> onChange={(value) => { props.onChange('height', Number(value)); }} />
<TextInputGroup <TextInputGroup
id='width' id='width'
labelText={Text({ textId: '@SymbolWidth' })} labelText={Text({ textId: '@SymbolWidth' })}
@ -61,14 +102,14 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
type='number' type='number'
min={0} min={0}
value={props.symbol.width.toString()} value={props.symbol.width.toString()}
onChange={(value) => props.onChange('width', Number(value))} /> onChange={(value) => { props.onChange('width', Number(value)); }} />
<ToggleButton <ToggleButton
labelText={Text({ textId: '@ShowDimension' })} labelText={Text({ textId: '@ShowDimension' })}
inputKey='showDimension' inputKey='showDimension'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
checked={props.symbol.showDimension} checked={props.symbol.showDimension}
onChange={(e) => props.onChange('showDimension', e.target.checked)}/> onChange={(e) => { props.onChange('showDimension', e.target.checked); }}/>
</div> </div>
); );
} }

View file

@ -5,39 +5,47 @@ import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SymbolProperties } from '../SymbolProperties/SymbolProperties'; import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar'; import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
import { Text } from '../Text/Text'; import { Text } from '../Text/Text';
import { useState } from 'react'; import { ExtendedSidebar } from '../UI/UI';
interface ISymbolsSidebarProps { interface ISymbolsSidebarProps {
selectedSymbolId: string selectedSymbolId: string
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
selectedExtendedSidebar: ExtendedSidebar
onPropertyChange: (key: string, value: string | number | boolean) => void onPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void selectSymbol: (symbolId: string) => void
isExpanded: boolean onExpandChange: (value: ExtendedSidebar) => void
onExpandChange: (isExpanded: boolean) => void
} }
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element { export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
// States // States
const divRef = React.useRef<HTMLDivElement>(null); const divRef = React.useRef<HTMLDivElement>(null);
const height = useSize(divRef)[1]; const height = useSize(divRef)[1];
const [showProperties, setShowProperties] = useState(props.isExpanded);
// Render // Render
const symbols = [...props.symbols.values()]; const symbols = [...props.symbols.values()];
const selectedSymbol = props.symbols.get(props.selectedSymbolId); const selectedSymbol = props.symbols.get(props.selectedSymbolId);
return ( return (
<div className='flex flex-row h-full w-full'> <div className='flex flex-row h-full w-full'>
{showProperties && <div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'> {props.selectedExtendedSidebar === ExtendedSidebar.Property &&
{(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>} <div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
<SymbolProperties {(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>}
symbol={selectedSymbol} <SymbolProperties
symbols={props.symbols} symbol={selectedSymbol}
onChange={props.onPropertyChange} symbols={props.symbols}
/> onChange={props.onPropertyChange}
</div>} />
</div>}
<div className={'flex w-64'} ref={divRef}> <div className={'flex w-64'} ref={divRef}>
<div className='w-6'> <div className='w-6'>
<ToggleSideBar title={Text({ textId: '@Properties' })} checked={showProperties} onChange={(newValue) => { setShowProperties(newValue); props.onExpandChange(newValue); }} /> <ToggleSideBar
title={Text({ textId: '@Properties' })}
checked={props.selectedExtendedSidebar === ExtendedSidebar.Property}
onClick={() => {
const newValue = props.selectedExtendedSidebar !== ExtendedSidebar.Property
? ExtendedSidebar.Property
: ExtendedSidebar.None;
props.onExpandChange(newValue);
}} />
</div> </div>
<List <List
itemCount={symbols.length} itemCount={symbols.length}

View file

@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { ElementsSideBar } from '../ElementsList/ElementsSideBar'; import { ElementsSidebar } from '../ElementsSidebar/ElementsSidebar';
import { History } from '../History/History'; import { History } from '../History/History';
import { Bar, BAR_WIDTH } from '../Bar/Bar'; import { Bar, BAR_WIDTH } from '../Bar/Bar';
import { Symbols } from '../Symbols/Symbols'; import { Symbols } from '../Symbols/Symbols';
import { SymbolsSidebar } from '../SymbolsList/SymbolsSidebar'; import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
import { type PropertyType } from '../../Enums/PropertyType'; import { type PropertyType } from '../../Enums/PropertyType';
import { Messages } from '../Messages/Messages'; import { Messages } from '../Messages/Messages';
import { Sidebar } from '../Sidebar/Sidebar'; import { Sidebar } from '../Sidebar/Sidebar';
@ -17,8 +17,8 @@ import { FindContainerById } from '../../utils/itertools';
import { type IEditorState } from '../../Interfaces/IEditorState'; import { type IEditorState } from '../../Interfaces/IEditorState';
import { GetCurrentHistoryState } from '../Editor/Editor'; import { GetCurrentHistoryState } from '../Editor/Editor';
import { Text } from '../Text/Text'; import { Text } from '../Text/Text';
import { IReplaceContainer } from '../../Interfaces/IReplaceContainer'; import { type IReplaceContainer } from '../../Interfaces/IReplaceContainer';
import { Dispatch } from 'react'; import { type Dispatch } from 'react';
export interface IUIProps { export interface IUIProps {
editorState: IEditorState editorState: IEditorState
@ -42,14 +42,17 @@ export interface IUIProps {
export enum SidebarType { export enum SidebarType {
None, None,
Components, Components,
ComponentsExpanded,
Symbols, Symbols,
SymbolsExpanded,
History, History,
Messages, Messages,
Settings Settings
} }
export enum ExtendedSidebar {
None,
Property
}
function UseSetOrToggleSidebar( function UseSetOrToggleSidebar(
selectedSidebar: SidebarType, selectedSidebar: SidebarType,
setSelectedSidebar: React.Dispatch<React.SetStateAction<SidebarType>> setSelectedSidebar: React.Dispatch<React.SetStateAction<SidebarType>>
@ -66,6 +69,10 @@ function UseSetOrToggleSidebar(
export function UI({ editorState, replaceContainer, setReplaceContainer, ...methods }: IUIProps): JSX.Element { export function UI({ editorState, replaceContainer, setReplaceContainer, ...methods }: IUIProps): JSX.Element {
const [selectedSidebar, setSelectedSidebar] = React.useState<SidebarType>(SidebarType.Components); const [selectedSidebar, setSelectedSidebar] = React.useState<SidebarType>(SidebarType.Components);
const [
selectedExtendedSidebar,
setSelectedExtendedSidebarType
] = React.useState<ExtendedSidebar>(ExtendedSidebar.None);
const [messages, setMessages] = React.useState<IMessage[]>([]); const [messages, setMessages] = React.useState<IMessage[]>([]);
const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep); const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep);
@ -115,7 +122,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
replaceContainer={replaceContainer} replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>; setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' }); rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar rightChildren = <ElementsSidebar
containers={current.containers} containers={current.containers}
mainContainer={mainContainer} mainContainer={mainContainer}
symbols={current.symbols} symbols={current.symbols}
@ -123,32 +130,11 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
onPropertyChange={methods.onPropertyChange} onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer} selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt} addContainer={methods.addContainerAt}
isExpanded ={false} selectedExtendedSidebar={selectedExtendedSidebar}
onExpandChange={() => { setOrToggleSidebar(SidebarType.ComponentsExpanded); } } onExpandChange={(value) => { setSelectedExtendedSidebarType(value); } }
/>;
break;
case SidebarType.ComponentsExpanded:
leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components
selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers}
categories={configuration.Categories}
buttonOnClick={methods.addOrReplaceContainer}
replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
selectedContainer={selectedContainer}
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Components); } }
/>; />;
break; break;
case SidebarType.Symbols: case SidebarType.Symbols:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' }); leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols leftChildren = <Symbols
@ -161,24 +147,8 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
symbols={current.symbols} symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange} onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol} selectSymbol={methods.selectSymbol}
isExpanded ={false} selectedExtendedSidebar={selectedExtendedSidebar}
onExpandChange={() => { setOrToggleSidebar(SidebarType.SymbolsExpanded); } } onExpandChange={(value) => { setSelectedExtendedSidebarType(value); } }
/>;
break;
case SidebarType.SymbolsExpanded:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
componentOptions={configuration.AvailableSymbols}
buttonOnClick={methods.addSymbol}
/>;
rightSidebarTitle = Text({ textId: '@SymbolsRight' });
rightChildren = <SymbolsSidebar
selectedSymbolId={current.selectedSymbolId}
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Symbols); }}
/>; />;
break; break;
@ -210,8 +180,10 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
} }
const isLeftSidebarOpen = selectedSidebar !== SidebarType.None; const isLeftSidebarOpen = selectedSidebar !== SidebarType.None;
const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols; const isRightSidebarOpen = [SidebarType.Components, SidebarType.Symbols]
const isRightSidebarOpenExpanded = selectedSidebar === SidebarType.ComponentsExpanded || selectedSidebar === SidebarType.SymbolsExpanded; .includes(selectedSidebar);
const isRightSidebarOpenExpanded = isRightSidebarOpen &&
selectedExtendedSidebar !== ExtendedSidebar.None;
const isLeftSidebarOpenClasses = new Set<string>([ const isLeftSidebarOpenClasses = new Set<string>([
'left-sidebar', 'left-sidebar',
@ -252,8 +224,8 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
} }
const clickRestrictionsClasses = replaceContainer.isReplacing ? 'pointer-events-none opacity-50' : ''; const clickRestrictionsClasses = replaceContainer.isReplacing ? 'pointer-events-none opacity-50' : '';
const isComponentsOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.ComponentsExpanded; const isComponentsOpen = selectedSidebar === SidebarType.Components;
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols || selectedSidebar === SidebarType.SymbolsExpanded; const isSymbolsOpen = selectedSidebar === SidebarType.Symbols;
return ( return (
<> <>
@ -265,18 +237,10 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
isMessagesOpen={selectedSidebar === SidebarType.Messages} isMessagesOpen={selectedSidebar === SidebarType.Messages}
isSettingsOpen={selectedSidebar === SidebarType.Settings} isSettingsOpen={selectedSidebar === SidebarType.Settings}
toggleComponents={() => { toggleComponents={() => {
if (selectedSidebar === SidebarType.ComponentsExpanded) { setOrToggleSidebar(SidebarType.Components);
setOrToggleSidebar(SidebarType.ComponentsExpanded);
} else {
setOrToggleSidebar(SidebarType.Components);
}
} } } }
toggleSymbols={() => { toggleSymbols={() => {
if (selectedSidebar === SidebarType.SymbolsExpanded) { setOrToggleSidebar(SidebarType.Symbols);
setOrToggleSidebar(SidebarType.SymbolsExpanded);
} else {
setOrToggleSidebar(SidebarType.Symbols);
}
} } } }
toggleTimeline={() => { toggleTimeline={() => {
setOrToggleSidebar(SidebarType.History); setOrToggleSidebar(SidebarType.History);

View file

@ -1,15 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { type IContainerModel } from '../../Interfaces/IContainerModel'; import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type IHistoryState } from '../../Interfaces/IHistoryState'; import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { type IPoint } from '../../Interfaces/IPoint'; import { USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
import { DIMENSION_MARGIN, USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default'; import { FindContainerById } from '../../utils/itertools';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { BAR_WIDTH } from '../Bar/Bar'; import { BAR_WIDTH } from '../Bar/Bar';
import { Canvas } from '../Canvas/Canvas'; import { Canvas } from '../Canvas/Canvas';
import { AddDimensions } from '../Canvas/DimensionLayer';
import { RenderSelector } from '../Canvas/Selector';
import { SelectorMode, SVG } from '../SVG/SVG'; import { SelectorMode, SVG } from '../SVG/SVG';
import { RenderSymbol } from '../Canvas/Symbol';
import { useState } from 'react'; import { useState } from 'react';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
@ -17,27 +13,36 @@ interface IViewerProps {
className: string className: string
current: IHistoryState current: IHistoryState
selectedContainer: IContainerModel | undefined selectedContainer: IContainerModel | undefined
selectContainer: (containerId: string) => void
selectedSymbol: ISymbolModel | undefined selectedSymbol: ISymbolModel | undefined
margin: number margin: number
isComponentsOpen: boolean isComponentsOpen: boolean
isSymbolsOpen: boolean isSymbolsOpen: boolean
selectContainer: (containerId: string) => void
}
export interface DrawParams {
mainContainer: IContainerModel
selectorMode: SelectorMode
selectedContainer: IContainerModel | undefined
selectedSymbol: ISymbolModel | undefined
containers: Map<string, IContainerModel>
symbols: Map<string, ISymbolModel>
}
function computeWidth(margin: number): number {
return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
} }
export function Viewer({ export function Viewer({
className, className,
current, current,
selectedContainer, selectedContainer,
selectContainer,
selectedSymbol, selectedSymbol,
margin, margin,
isComponentsOpen, isComponentsOpen,
isSymbolsOpen isSymbolsOpen,
selectContainer
}: IViewerProps): JSX.Element { }: IViewerProps): JSX.Element {
function computeWidth(margin: number): number {
return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
}
const [windowSize, setWindowSize] = useState([ const [windowSize, setWindowSize] = useState([
computeWidth(margin), computeWidth(margin),
window.innerHeight window.innerHeight
@ -78,64 +83,22 @@ export function Viewer({
selectorMode = SelectorMode.Symbols; selectorMode = SelectorMode.Symbols;
} }
const drawParams: DrawParams = {
mainContainer,
selectorMode,
selectedContainer,
selectedSymbol,
containers: current.containers,
symbols: current.symbols
};
if (USE_EXPERIMENTAL_CANVAS_API) { if (USE_EXPERIMENTAL_CANVAS_API) {
function Draw(ctx: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint): void {
if (mainContainer === undefined) {
return;
}
const topDim = mainContainer.properties.y;
const leftDim = mainContainer.properties.x;
const rightDim = mainContainer.properties.x + mainContainer.properties.width;
const bottomDim = mainContainer.properties.y + mainContainer.properties.height;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.setTransform(scale, 0, 0, scale, translatePos.x, translatePos.y);
ctx.fillStyle = '#000000';
const it = MakeRecursionDFSIterator(mainContainer, current.containers, 0, [0, 0]);
for (const { container, depth, currentTransform } of it) {
const [x, y] = [
container.properties.x + currentTransform[0],
container.properties.y + currentTransform[1]
];
// Draw container
RenderContainers(ctx, container, x, y);
// Draw dimensions
RenderDimensions(
ctx,
leftDim,
bottomDim,
topDim,
rightDim,
depth,
scale,
current.containers,
container,
currentTransform
);
// Draw selector
RenderSelector(ctx, frameCount, {
containers: current.containers,
scale,
selected: selectedContainer
});
}
// Draw symbols
RenderSymbols(current, ctx, scale);
ctx.restore();
}
return ( return (
<Canvas <Canvas
draw={Draw} className={className}
className={`ml-16 ${className}`}
width={window.innerWidth - BAR_WIDTH} width={window.innerWidth - BAR_WIDTH}
height={window.innerHeight} height={window.innerHeight}
drawParams={drawParams}
/> />
); );
} }
@ -147,58 +110,8 @@ export function Viewer({
viewerHeight={windowSize[1]} viewerHeight={windowSize[1]}
width={mainContainer.properties.width} width={mainContainer.properties.width}
height={mainContainer.properties.height} height={mainContainer.properties.height}
containers={current.containers} drawParams={drawParams}
selectedContainer={selectedContainer}
symbols={current.symbols}
selectedSymbol={selectedSymbol}
selectorMode={selectorMode}
selectContainer={selectContainer} selectContainer={selectContainer}
> />
{mainContainer}
</SVG>
); );
} }
function RenderSymbols(
current: IHistoryState,
ctx: CanvasRenderingContext2D,
scale: number
): void {
current.symbols.forEach((symbol) => {
RenderSymbol(symbol, ctx, scale);
});
}
function RenderDimensions(
ctx: CanvasRenderingContext2D,
leftDim: number,
bottomDim: number,
topDim: number,
rightDim: number,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
ctx.save();
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = leftDim - depthOffset;
const containerTopDim = topDim - depthOffset;
const containerBottomDim = bottomDim + depthOffset;
const containerRightDim = rightDim + depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}
function RenderContainers(ctx: CanvasRenderingContext2D, container: IContainerModel, x: number, y: number): void {
ctx.save();
ctx.strokeStyle = container.properties.style?.stroke ?? '#000000';
ctx.fillStyle = container.properties.style?.fill ?? '#000000';
ctx.lineWidth = Number(container.properties.style?.strokeWidth ?? 1);
ctx.globalAlpha = Number(container.properties.style?.fillOpacity ?? 1);
ctx.fillRect(x, y, container.properties.width, container.properties.height);
ctx.globalAlpha = Number(container.properties.style?.strokeOpacity ?? 1);
ctx.strokeRect(x, y, container.properties.width, container.properties.height);
ctx.restore();
}

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { PositionReference } from '../Enums/PositionReference'; import { type PositionReference } from '../Enums/PositionReference';
import { IAvailableContainer } from './IAvailableContainer'; import { type IAvailableContainer } from './IAvailableContainer';
import { IImage } from './IImage'; import { type IImage } from './IImage';
/** /**
* Model of available symbol to configure the application */ * Model of available symbol to configure the application */
@ -13,9 +13,9 @@ export interface IAvailableSymbol {
/** displayed text */ /** displayed text */
DisplayedText?: string DisplayedText?: string
X?: number isVertical?: boolean
Y?: number offset?: number
Width?: number Width?: number

View file

@ -13,10 +13,10 @@ export interface ISymbolModel {
/** Configuration of the symbol */ /** Configuration of the symbol */
config: IAvailableSymbol config: IAvailableSymbol
/** Horizontal offset */ isVertical: boolean
x: number
// TODO: Implement Y and verticality /** offset */
offset: number
/** Width */ /** Width */
width: number width: number

View file

@ -22,6 +22,7 @@
"@DeleteContainerTitle": "Delete the container", "@DeleteContainerTitle": "Delete the container",
"@DeleteSymbol": "Delete", "@DeleteSymbol": "Delete",
"@DeleteSymbolTitle": "Delete the container", "@DeleteSymbolTitle": "Delete the container",
"@ReplaceByContainer": "Replace by component",
"@ExportAsJSON": "Export as JSON", "@ExportAsJSON": "Export as JSON",
"@ExportAsSVG": "Export as SVG", "@ExportAsSVG": "Export as SVG",
@ -75,7 +76,9 @@
"@SymbolName": "Name", "@SymbolName": "Name",
"@SymbolDisplayedText": "Displayed text", "@SymbolDisplayedText": "Displayed text",
"@SymbolX": "x", "@SymbolOffset" : "Offset",
"@IsVertical" : "Vertical",
"@ShowDimension" : "Dimension",
"@SymbolHeight": "Height", "@SymbolHeight": "Height",
"@SymbolWidth": "Width" "@SymbolWidth": "Width"
} }

View file

@ -9,7 +9,7 @@
"@Symbols": "Symboles", "@Symbols": "Symboles",
"@SymbolsLeft": "Symboles", "@SymbolsLeft": "Symboles",
"@SymbolsRight": "Symboles", "@SymbolsRight": "Symboles",
"@NoSymbolSelected": "Pas de symbol sélectionné", "@NoSymbolSelected": "Pas de symbole sélectionné",
"@Timeline": "Chronologie", "@Timeline": "Chronologie",
"@Messages": "Messages", "@Messages": "Messages",
"@Settings": "Paramètres", "@Settings": "Paramètres",
@ -22,6 +22,7 @@
"@DeleteContainerTitle": "Supprimer le conteneur", "@DeleteContainerTitle": "Supprimer le conteneur",
"@DeleteSymbol": "Supprimer", "@DeleteSymbol": "Supprimer",
"@DeleteSymbolTitle": "Supprimer le symbole", "@DeleteSymbolTitle": "Supprimer le symbole",
"@ReplaceByContainer": "Replacer par un composant",
"@ExportAsJSON": "Exporter en JSON", "@ExportAsJSON": "Exporter en JSON",
"@ExportAsSVG": "Exporter en SVG", "@ExportAsSVG": "Exporter en SVG",
@ -75,7 +76,9 @@
"@SymbolName": "Nom", "@SymbolName": "Nom",
"@SymbolDisplayedText": "Texte affiché", "@SymbolDisplayedText": "Texte affiché",
"@SymbolX": "x", "@SymbolOffset" : "Décalage",
"@IsVertical" : "Vertical",
"@ShowDimension" : "Cotation",
"@SymbolHeight": "Hauteur", "@SymbolHeight": "Hauteur",
"@SymbolWidth": "Largeur" "@SymbolWidth": "Largeur"
} }

View file

@ -2,12 +2,14 @@
"@StartFromScratch": "Partir de zéro", "@StartFromScratch": "Partir de zéro",
"@LoadConfigFile": "Charger un fichier de configuration", "@LoadConfigFile": "Charger un fichier de configuration",
"@GoBack": "Revenir", "@GoBack": "Revenir",
"@Properties": "Propriétés",
"@Components": "Composants", "@Components": "Composants",
"@Elements": "Éléments", "@Elements": "Éléments",
"@Symbols": "Ancrages", "@Symbols": "Ancrages",
"@SymbolsLeft": "Ancrages", "@SymbolsLeft": "Ancrages",
"@SymbolsRight": "Ancrages", "@SymbolsRight": "Ancrages",
"@NoSymbolSelected": "Pas d'ancre sélectionnée",
"@Timeline": "Chronologie", "@Timeline": "Chronologie",
"@Messages": "Messages", "@Messages": "Messages",
"@Settings": "Paramètres", "@Settings": "Paramètres",
@ -61,9 +63,18 @@
"@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants", "@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants",
"@ContainerMarkPosition": "Marquer la position pour les parents", "@ContainerMarkPosition": "Marquer la position pour les parents",
"@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués", "@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués",
"@ContainerShowMarginsDimension": "Afficher les cotations des jeux",
"@ContainerStyle": "Style", "@ContainerStyle": "Style",
"@StyleStrokeColor": "Couleur du tracé",
"@StyleStrokeDashArray": "Tableau de traits",
"@StyleStroke": "Tracé",
"@StyleStrokeOpacity": "Opacité du tracé",
"@StyleStrokeWidth": "Epaisseur du tracé",
"@StyleFill": "Remplissage",
"@StyleFillOpacity": "Opacité du remplissage",
"@SymbolName": "Nom", "@SymbolName": "Nom",
"@SymbolDisplayedText": "Texte affiché",
"@SymbolX": "x", "@SymbolX": "x",
"@SymbolHeight": "Hauteur", "@SymbolHeight": "Hauteur",
"@SymbolWidth": "Largeur" "@SymbolWidth": "Largeur"

View file

@ -9,6 +9,7 @@ import { type ISymbolModel } from '../Interfaces/ISymbolModel';
import { Orientation } from '../Enums/Orientation'; import { Orientation } from '../Enums/Orientation';
import { AppState } from '../Enums/AppState'; import { AppState } from '../Enums/AppState';
import { type IDimensionOptions } from '../Interfaces/IDimensionOptions'; import { type IDimensionOptions } from '../Interfaces/IDimensionOptions';
import { type IDimensionStyle } from '../Components/SVG/Elements/Dimension';
/// EDITOR DEFAULTS /// /// EDITOR DEFAULTS ///
@ -69,10 +70,14 @@ export const SHOW_BORROWER_DIMENSIONS = true;
export const DIMENSION_MARGIN = 50; export const DIMENSION_MARGIN = 50;
export const SYMBOL_MARGIN = 25; export const SYMBOL_MARGIN = 25;
export const NOTCHES_LENGTH = 10; export const NOTCHES_LENGTH = 10;
export const DEFAULT_DIMENSION_SYMBOL_STYLE: IDimensionStyle = {
color: '#000000'
};
/// SYMBOL DEFAULTS /// /// SYMBOL DEFAULTS ///
export const DEFAULT_SYMBOL_WIDTH = 32; export const DEFAULT_SYMBOL_WIDTH = 32;
export const DEFAULT_SYMBOL_IS_VERTICAL = false;
export const DEFAULT_SYMBOL_HEIGHT = 32; export const DEFAULT_SYMBOL_HEIGHT = 32;
/** /**
@ -270,7 +275,7 @@ export function GetDefaultContainerProps(type: string,
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false, hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
dimensionOptions: { dimensionOptions: {
childrenDimensions: { childrenDimensions: {
...containerConfig.DimensionOptions?.selfDimensions, ...containerConfig.DimensionOptions?.childrenDimensions,
positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? [] positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? []
}, },
selfDimensions: { selfDimensions: {
@ -304,7 +309,8 @@ export function GetDefaultSymbolModel(name: string,
displayedText: symbolConfig.DisplayedText ?? id, displayedText: symbolConfig.DisplayedText ?? id,
type: name, type: name,
config: structuredClone(symbolConfig), config: structuredClone(symbolConfig),
x: 0, offset: 0,
isVertical: symbolConfig.isVertical ?? DEFAULT_SYMBOL_IS_VERTICAL,
width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH, width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH,
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT, height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
linkedContainers: new Set(), linkedContainers: new Set(),

View file

@ -81,11 +81,10 @@ const GetSVGLayoutConfiguration = () => {
Style: { Style: {
fillOpacity: 1, fillOpacity: 1,
strokeWidth: 2, strokeWidth: 2,
stroke: 'red', stroke: '#ff0000',
fill: '#d3c9b7', fill: '#d3c9b7',
}, },
ShowSelfDimensions: [0, 2], IsFlex: true,
ShowDimensionWithMarks: [1, 3],
Category: "Stuff" Category: "Stuff"
}, },
{ {
@ -97,11 +96,10 @@ const GetSVGLayoutConfiguration = () => {
Style: { Style: {
fillOpacity: 1, fillOpacity: 1,
strokeWidth: 2, strokeWidth: 2,
stroke: 'red', stroke: '#ff0000',
fill: '#d3c9b7', fill: '#d3c9b7',
}, },
ShowSelfDimensions: [0, 2], IsFlex: true,
ShowDimensionWithMarks: [1, 3],
Category: "Stuff" Category: "Stuff"
}, },
{ {
@ -117,10 +115,11 @@ const GetSVGLayoutConfiguration = () => {
Style: { Style: {
fillOpacity: 1, fillOpacity: 1,
strokeWidth: 2, strokeWidth: 2,
stroke: 'green', stroke: '#00ff00',
fill: 'white' fill: '#ffffff'
}, },
Category: "Stuff", Category: "Stuff",
IsFlex: true,
Actions: [ Actions: [
{ {
Id: "Insert", Id: "Insert",
@ -173,6 +172,7 @@ const GetSVGLayoutConfiguration = () => {
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line> <line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
` `
, ,
IsFlex: true,
Actions: [ Actions: [
{ {
Id: "SplitRemplissage", Id: "SplitRemplissage",
@ -254,22 +254,24 @@ const GetSVGLayoutConfiguration = () => {
Type: '200', Type: '200',
MaxWidth: 500, MaxWidth: 500,
MinWidth: 200, MinWidth: 200,
IsFlex: true,
Style: { Style: {
fillOpacity: 1, fillOpacity: 1,
strokeWidth: 2, strokeWidth: 2,
stroke: 'blue', stroke: '#0000ff',
fill: 'blue', fill: '#0000ff',
} }
}, },
{ {
Type: '400', Type: '400',
MaxWidth: 500, MaxWidth: 500,
MinWidth: 400, MinWidth: 400,
IsFlex: true,
Style: { Style: {
fillOpacity: 1, fillOpacity: 1,
strokeWidth: 2, strokeWidth: 2,
stroke: 'red', stroke: '#ff0000',
fill: 'red', fill: '#ff0000',
} }
} }
], ],
@ -284,8 +286,8 @@ const GetSVGLayoutConfiguration = () => {
Svg: null, Svg: null,
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg' Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
}, },
Name: 'Poteau structure', Name: 'Poteau CenterCenter',
PositionReference: 1, PositionReference: 4,
AssociatedContainer: { AssociatedContainer: {
Type: 'Montant' Type: 'Montant'
} }
@ -295,12 +297,48 @@ const GetSVGLayoutConfiguration = () => {
Height: 32, Height: 32,
Image: { Image: {
Base64Image: null, Base64Image: null,
Name: 'Arrow', Name: 'ArrowTopLeft',
Svg: null, Svg: null,
Url: './images/arrow-down.svg' Url: './images/arrow-down.svg'
}, },
Name: 'Arrow', Name: 'ArrowTopLeft',
PositionReference: 1 PositionReference: 0
},
{
Width: 32,
Height: 32,
Image: {
Base64Image: null,
Name: 'ArrowTopRight',
Svg: null,
Url: './images/arrow-down.svg'
},
Name: 'ArrowTopRight',
PositionReference: 2
},
{
Width: 32,
Height: 32,
Image: {
Base64Image: null,
Name: 'ArrowCenterRight',
Svg: null,
Url: './images/arrow-down.svg'
},
Name: 'ArrowCenterRight',
PositionReference: 5
},
{
Width: 32,
Height: 32,
Image: {
Base64Image: null,
Name: 'ArrowBottomRight',
Svg: null,
Url: './images/arrow-down.svg'
},
Name: 'ArrowBottomRight',
PositionReference: 8
}, },
{ {
Width: 32, Width: 32,
@ -329,10 +367,10 @@ const GetSVGLayoutConfiguration = () => {
MainContainer: { MainContainer: {
Type: 'main', Type: 'main',
Width: 800, Width: 800,
Height: 800, Height: 200,
Orientation: 1, Orientation: 0,
Style: { Style: {
stroke: 'black', stroke: '#000000',
strokeWidth: 2, strokeWidth: 2,
fillOpacity: 0 fillOpacity: 0
} }