Merge branch 'master' into eslint-fixes
This commit is contained in:
commit
f1c97b25d1
38 changed files with 1456 additions and 480 deletions
11
package.json
11
package.json
|
@ -3,6 +3,7 @@
|
|||
"private": true,
|
||||
"version": "v1.0.0",
|
||||
"type": "module",
|
||||
"postinstall": "npx patch-package",
|
||||
"scripts": {
|
||||
"d": "mprocs",
|
||||
"dev": "vite",
|
||||
|
@ -22,10 +23,11 @@
|
|||
"interweave": "^13.0.0",
|
||||
"react": "^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",
|
||||
"sweetalert2": "^11.7.1",
|
||||
"sweetalert2-react-content": "^5.0.7"
|
||||
"sweetalert2-react-content": "^5.0.7",
|
||||
"transformation-matrix": "^2.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
|
@ -60,5 +62,10 @@
|
|||
"typescript": "^4.9.5",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@types/react-svg-pan-zoom@3.3.5": "patches/@types__react-svg-pan-zoom@3.3.5.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
patches/@types+react-svg-pan-zoom+3.3.5.patch
Normal file
17
patches/@types+react-svg-pan-zoom+3.3.5.patch
Normal 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;
|
||||
|
17
patches/@types__react-svg-pan-zoom@3.3.5.patch
Normal file
17
patches/@types__react-svg-pan-zoom@3.3.5.patch
Normal 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
23
pnpm-lock.yaml
generated
|
@ -1,5 +1,10 @@
|
|||
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:
|
||||
'@heroicons/react': ^2.0.14
|
||||
'@react-hook/size': ^2.1.2
|
||||
|
@ -33,12 +38,13 @@ specifiers:
|
|||
postcss: ^8.4.21
|
||||
react: ^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
|
||||
sass: ^1.58.0
|
||||
sweetalert2: ^11.7.1
|
||||
sweetalert2-react-content: ^5.0.7
|
||||
tailwindcss: ^3.2.4
|
||||
transformation-matrix: ^2.14.0
|
||||
typescript: ^4.9.5
|
||||
vite: ^4.1.1
|
||||
vitest: ^0.28.4
|
||||
|
@ -49,10 +55,11 @@ dependencies:
|
|||
interweave: 13.0.0_react@18.2.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-svg-pan-zoom: 3.11.0_react@18.2.0
|
||||
react-svg-pan-zoom: 3.12.1
|
||||
react-window: 1.8.8_biqbaboplfbrettd7655fr4n2y
|
||||
sweetalert2: 11.7.1
|
||||
sweetalert2-react-content: 5.0.7_5cbezu6w3tvev2ldv5vdmnpfca
|
||||
transformation-matrix: 2.14.0
|
||||
|
||||
devDependencies:
|
||||
'@testing-library/dom': 8.20.0
|
||||
|
@ -61,7 +68,7 @@ devDependencies:
|
|||
'@testing-library/user-event': 14.4.3_yxlyej73nftwmh2fiao7paxmlm
|
||||
'@types/react': 18.0.27
|
||||
'@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
|
||||
'@typescript-eslint/eslint-plugin': 5.51.0_b635kmla6dsb4frxfihkw4m47e
|
||||
'@typescript-eslint/parser': 5.51.0_4vsywjlpuriuw3tl5oq6zy5a64
|
||||
|
@ -927,11 +934,12 @@ packages:
|
|||
'@types/react': 18.0.27
|
||||
dev: true
|
||||
|
||||
/@types/react-svg-pan-zoom/3.3.5:
|
||||
/@types/react-svg-pan-zoom/3.3.5_kv3ctd73j5hnzcxdc2ceiq5wuy:
|
||||
resolution: {integrity: sha512-W8GRFCDy7raSDr5OXGjSyvX5KmdWlIQfv0NLa1jfAYVUO4ClVbgorWeAAom7nY3Pl+4h9blXE1Bnu2CW1iMEvQ==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.27
|
||||
dev: true
|
||||
patched: true
|
||||
|
||||
/@types/react-window/1.8.5:
|
||||
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
|
||||
|
@ -3453,13 +3461,10 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/react-svg-pan-zoom/3.11.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-xK2tpfp4YksHOfyMZH5zXP52ARLSBgkoJgWNJmJ1B+6O1tkuf23TQp7Q4m9GG5IRSK5KWO0JEGEWlNYG9+iiug==}
|
||||
peerDependencies:
|
||||
react: '>=17.0.0'
|
||||
/react-svg-pan-zoom/3.12.1:
|
||||
resolution: {integrity: sha512-ug1LHCN5qed56C64xFypr/ClajuMFkig1OKvwJrIgGeSyHOjWM7XGgSgeP3IfHAkNw8QEc6a31ggZRpTijWYRw==}
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
transformation-matrix: 2.14.0
|
||||
dev: false
|
||||
|
||||
|
|
|
@ -1,16 +1,85 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import { IPoint } from '../../Interfaces/IPoint';
|
||||
import { type IPoint } from '../../Interfaces/IPoint';
|
||||
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 {
|
||||
className?: string
|
||||
width: number
|
||||
height: number
|
||||
draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
|
||||
className?: string
|
||||
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 frameCount = useRef(0);
|
||||
const translatePos = useRef({
|
||||
|
@ -123,8 +192,21 @@ interface Viewer {
|
|||
viewerHeight: number
|
||||
}
|
||||
|
||||
export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element {
|
||||
const canvasRef = UseCanvas(draw);
|
||||
export function Canvas({
|
||||
className,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
drawParams
|
||||
}: ICanvasProps): JSX.Element {
|
||||
const canvasRef = UseCanvas((
|
||||
...CanvasProps
|
||||
) => {
|
||||
Draw(
|
||||
...CanvasProps,
|
||||
drawParams
|
||||
);
|
||||
});
|
||||
|
||||
const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
|
||||
viewerWidth: width,
|
||||
|
|
18
src/Components/Canvas/Container.ts
Normal file
18
src/Components/Canvas/Container.ts
Normal 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();
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { NOTCHES_LENGTH } from '../../utils/default';
|
||||
import { IDimensionStyle } from '../SVG/Elements/Dimension';
|
||||
|
||||
interface IDimensionProps {
|
||||
id: string
|
||||
|
@ -7,7 +8,7 @@ interface IDimensionProps {
|
|||
xEnd: number
|
||||
yEnd: number
|
||||
text: string
|
||||
strokeWidth: number
|
||||
style: IDimensionStyle
|
||||
scale?: number
|
||||
}
|
||||
|
||||
|
@ -26,8 +27,11 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
|
|||
|
||||
export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimensionProps): void {
|
||||
const scale = props.scale ?? 1;
|
||||
const strokeStyle = 'black';
|
||||
const lineWidth = 2 / scale;
|
||||
const strokeStyle = props.style.color ?? 'black';
|
||||
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
|
||||
// Get the vector of the line
|
||||
|
@ -59,6 +63,7 @@ export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimension
|
|||
ctx.lineWidth = lineWidth;
|
||||
ctx.strokeStyle = strokeStyle;
|
||||
ctx.fillStyle = strokeStyle;
|
||||
ctx.setLineDash(dashArray);
|
||||
ctx.moveTo(startTopX, startTopY);
|
||||
ctx.lineTo(startBottomX, startBottomY);
|
||||
ctx.stroke();
|
||||
|
@ -68,6 +73,7 @@ export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimension
|
|||
ctx.moveTo(endTopX, endTopY);
|
||||
ctx.lineTo(endBottomX, endBottomY);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
const textX = (props.xStart + props.xEnd) / 2;
|
||||
const textY = (props.yStart + props.yEnd) / 2;
|
||||
ctx.font = `${16 / scale}px Verdana`;
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import { Orientation } from '../../Enums/Orientation';
|
||||
import { Position } from '../../Enums/Position';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS } from '../../utils/default';
|
||||
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
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 { TransformX, TransformY } from '../../utils/svg';
|
||||
import { RenderDimension } from './Dimension';
|
||||
|
||||
const MODULE_STROKE_WIDTH = 1;
|
||||
|
||||
export function AddDimensions(
|
||||
export function AddContainerDimensions(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
containers: Map<string, IContainerModel>,
|
||||
container: IContainerModel,
|
||||
|
@ -18,7 +23,8 @@ export function AddDimensions(
|
|||
depth: number
|
||||
): void {
|
||||
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(
|
||||
ctx,
|
||||
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(
|
||||
ctx,
|
||||
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(
|
||||
ctx,
|
||||
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
|
||||
* @param ctx
|
||||
* @param dimMapped Position mapped depending on the Position enum in order:
|
||||
* [0:left, 1:bottom, 2:up, 3:right]
|
||||
* @param positions List of positions
|
||||
|
@ -108,6 +158,7 @@ function AddHorizontalChildrenDimension(
|
|||
scale: number
|
||||
): void {
|
||||
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 lastChild = FindContainerById(containers, lastChildId);
|
||||
|
@ -115,8 +166,14 @@ function AddHorizontalChildrenDimension(
|
|||
if (lastChild === undefined) {
|
||||
return;
|
||||
}
|
||||
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
let xChildrenStart = TransformX(
|
||||
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
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
|
@ -152,9 +209,9 @@ function AddHorizontalChildrenDimension(
|
|||
xEnd: xChildrenEnd + offset,
|
||||
yStart: yDim,
|
||||
yEnd: yDim,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: textChildren,
|
||||
scale
|
||||
scale,
|
||||
style
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -168,6 +225,7 @@ function AddVerticalChildrenDimension(
|
|||
scale: number
|
||||
): void {
|
||||
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 lastChild = FindContainerById(containers, lastChildId);
|
||||
|
@ -176,7 +234,10 @@ function AddVerticalChildrenDimension(
|
|||
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;
|
||||
|
||||
// Find the min and max
|
||||
|
@ -218,9 +279,9 @@ function AddVerticalChildrenDimension(
|
|||
xEnd: xDim,
|
||||
yStart: yChildrenStart + offset,
|
||||
yEnd: yChildrenEnd + offset,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: textChildren,
|
||||
scale
|
||||
scale,
|
||||
style
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -234,6 +295,7 @@ function AddHorizontalBorrowerDimension(
|
|||
scale: number
|
||||
): void {
|
||||
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
|
||||
const style = container.properties.dimensionOptions.dimensionWithMarks;
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
container: childContainer, currentTransform: childCurrentTransform
|
||||
|
@ -274,9 +336,9 @@ function AddHorizontalBorrowerDimension(
|
|||
xEnd: next,
|
||||
yStart: yDim,
|
||||
yEnd: yDim,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: value.toFixed(0),
|
||||
scale
|
||||
scale,
|
||||
style
|
||||
});
|
||||
count++;
|
||||
}
|
||||
|
@ -293,6 +355,7 @@ function AddVerticalBorrowerDimension(
|
|||
scale: number
|
||||
): void {
|
||||
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
|
||||
const style = container.properties.dimensionOptions.dimensionWithMarks;
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
container: childContainer, currentTransform: childCurrentTransform
|
||||
|
@ -338,9 +401,9 @@ function AddVerticalBorrowerDimension(
|
|||
xEnd: xDim,
|
||||
yStart: cur,
|
||||
yEnd: next,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: value.toFixed(0),
|
||||
scale
|
||||
scale,
|
||||
style
|
||||
});
|
||||
count++;
|
||||
}
|
||||
|
@ -354,6 +417,7 @@ function AddVerticalSelfDimension(
|
|||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const style = container.properties.dimensionOptions.selfDimensions;
|
||||
const height = container.properties.height;
|
||||
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
|
||||
let yStart = container.properties.y + currentTransform[1] + height;
|
||||
|
@ -372,9 +436,9 @@ function AddVerticalSelfDimension(
|
|||
xEnd: xDim,
|
||||
yStart,
|
||||
yEnd,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: textVert,
|
||||
scale
|
||||
scale,
|
||||
style
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -385,6 +449,7 @@ function AddHorizontalSelfDimension(
|
|||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const style = container.properties.dimensionOptions.selfDimensions;
|
||||
const width = container.properties.width;
|
||||
const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`;
|
||||
const xStart = container.properties.x + currentTransform[0];
|
||||
|
@ -398,8 +463,180 @@ function AddHorizontalSelfDimension(
|
|||
yStart: yDim,
|
||||
xEnd,
|
||||
yEnd: yDim,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
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
|
||||
});
|
||||
}
|
||||
|
|
103
src/Components/Canvas/Renderer.ts
Normal file
103
src/Components/Canvas/Renderer.ts
Normal 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();
|
||||
}
|
|
@ -1,33 +1,26 @@
|
|||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { SHOW_SELECTOR_TEXT } from '../../utils/default';
|
||||
import { GetAbsolutePosition } from '../../utils/itertools';
|
||||
import { RemoveMargin } from '../../utils/svg';
|
||||
|
||||
interface ISelectorProps {
|
||||
containers: Map<string, IContainerModel>
|
||||
selected?: IContainerModel
|
||||
scale?: number
|
||||
text: string
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
scale: number
|
||||
}
|
||||
|
||||
export function RenderSelector(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
|
||||
));
|
||||
|
||||
export function RenderSelector(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
frameCount: number,
|
||||
{
|
||||
text,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
scale
|
||||
}: ISelectorProps
|
||||
): void {
|
||||
const xText = x + width / 2;
|
||||
const yText = y + height / 2;
|
||||
|
||||
|
@ -42,7 +35,7 @@ export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number
|
|||
if (SHOW_SELECTOR_TEXT) {
|
||||
ctx.font = `${16 / scale}px Verdana`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(props.selected.properties.displayedText, xText, yText);
|
||||
ctx.fillText(text, xText, yText);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
}
|
||||
|
|
48
src/Components/Canvas/SelectorContainer.ts
Normal file
48
src/Components/Canvas/SelectorContainer.ts
Normal 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
|
||||
}
|
||||
);
|
||||
}
|
47
src/Components/Canvas/SelectorSymbol.ts
Normal file
47
src/Components/Canvas/SelectorSymbol.ts
Normal 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
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { DIMENSION_MARGIN } from '../../utils/default';
|
||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { SYMBOL_MARGIN } from '../../utils/default';
|
||||
|
||||
const IMAGE_CACHE = new Map<string, HTMLImageElement>();
|
||||
|
||||
export function RenderSymbol(
|
||||
symbol: ISymbolModel,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
scale: number): void {
|
||||
symbol: ISymbolModel): void {
|
||||
const href = symbol.config.Image.Base64Image ?? symbol.config.Image.Url;
|
||||
|
||||
if (href === undefined) {
|
||||
|
@ -19,27 +18,38 @@ export function RenderSymbol(
|
|||
newImage.src = href;
|
||||
IMAGE_CACHE.set(href, newImage);
|
||||
newImage.onload = () => {
|
||||
DrawImage(ctx, scale, newImage, symbol);
|
||||
DrawImage(ctx, newImage, symbol);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
DrawImage(ctx, scale, image, symbol);
|
||||
DrawImage(ctx, image, symbol);
|
||||
}
|
||||
|
||||
function DrawImage(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
scale: number,
|
||||
image: HTMLImageElement,
|
||||
symbol: ISymbolModel
|
||||
): 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.fillStyle = '#000000';
|
||||
const width = symbol.width;
|
||||
const height = symbol.height;
|
||||
ctx.drawImage(
|
||||
image,
|
||||
symbol.x,
|
||||
-DIMENSION_MARGIN,
|
||||
symbol.width,
|
||||
symbol.height
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
ctx.restore();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
|||
import {
|
||||
SHOW_BORROWER_DIMENSIONS,
|
||||
SHOW_CHILDREN_DIMENSIONS,
|
||||
SHOW_SELF_DIMENSIONS, SHOW_SELF_MARGINS_DIMENSIONS
|
||||
SHOW_SELF_DIMENSIONS,
|
||||
SHOW_SELF_MARGINS_DIMENSIONS
|
||||
} from '../../utils/default';
|
||||
import {
|
||||
ApplyWidthMargin,
|
||||
|
@ -27,8 +28,12 @@ import { OrientationSelector } from '../RadioGroupButtons/OrientationSelector';
|
|||
import { OrientationCheckboxes } from '../CheckboxGroupButtons/OrientationCheckboxes';
|
||||
import { PositionCheckboxes } from '../CheckboxGroupButtons/PositionCheckboxes';
|
||||
import { Category } from '../Category/Category';
|
||||
import { Orientation } from '../../Enums/Orientation';
|
||||
import { FindContainerById } from '../../utils/itertools';
|
||||
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
|
||||
interface IContainerFormProps {
|
||||
containers: Map<string, IContainerModel>
|
||||
properties: IContainerProperties
|
||||
symbols: Map<string, ISymbolModel>
|
||||
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
|
||||
|
@ -36,6 +41,8 @@ interface IContainerFormProps {
|
|||
|
||||
export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||
const categoryHeight = 'h-11';
|
||||
const parent = FindContainerById(props.containers, props.properties.parentId);
|
||||
const isVertical = parent?.properties.orientation === Orientation.Vertical;
|
||||
return (
|
||||
<div className='grid grid-cols-1 gap-y-4 items-center'>
|
||||
<TextInputGroup
|
||||
|
@ -185,8 +192,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
type='number'
|
||||
min={props.properties.minWidth}
|
||||
max={props.properties.maxWidth}
|
||||
value={(RemoveWidthMargin(props.properties.width, 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)); }}
|
||||
value={(RemoveWidthMargin(props.properties.width,
|
||||
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}/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-maxWidth`}
|
||||
|
@ -218,8 +231,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
type='number'
|
||||
min={props.properties.minHeight}
|
||||
max={props.properties.maxHeight}
|
||||
value={(RemoveWidthMargin(props.properties.height, 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)); }}
|
||||
value={(RemoveWidthMargin(props.properties.height,
|
||||
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}
|
||||
/>
|
||||
<TextInputGroup
|
||||
|
@ -332,7 +351,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelText={Text({ textId: '@ContainerAlignWithSymbol' })}
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
inputs={[...props.symbols.values()].map(symbol => ({
|
||||
inputs={[...props.symbols.values()].filter(symbol => (symbol.isVertical === isVertical)).map(symbol => ({
|
||||
key: symbol.id,
|
||||
text: symbol.id,
|
||||
value: symbol.id
|
||||
|
@ -366,7 +385,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
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); }}/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-selfDimensions-width`}
|
||||
|
@ -406,7 +425,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
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); }}/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-selfMarginsDimensions-width`}
|
||||
|
@ -446,7 +465,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
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); }}/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-childrenDimensions-width`}
|
||||
|
@ -496,7 +515,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
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); }}/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-dimensionWithMarks-width`}
|
||||
|
@ -531,16 +550,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-stroke`}
|
||||
<InputGroup
|
||||
labelText={Text({ textId: '@StyleStroke' })}
|
||||
inputKey='stroke'
|
||||
inputKey={`${props.properties.id}-stroke`}
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
type='string'
|
||||
value={props.properties.style.stroke ?? 'black'}
|
||||
onChange={(value) => { props.onChange('stroke', value, PropertyType.Style); }}
|
||||
/>
|
||||
type='color'
|
||||
value={props.properties.style.stroke ?? '#000000'}
|
||||
onChange={(e) => { props.onChange('stroke', e.target.value, PropertyType.Style); }}/>
|
||||
<InputGroup
|
||||
labelKey={`${props.properties.id}-strokeOpacity`}
|
||||
labelText={Text({ textId: '@StyleStrokeOpacity' })}
|
||||
|
@ -564,16 +581,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
value={(props.properties.style.strokeWidth ?? 1).toString()}
|
||||
onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
|
||||
/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-fill`}
|
||||
<InputGroup
|
||||
labelText={Text({ textId: '@StyleFill' })}
|
||||
inputKey='fill'
|
||||
inputKey={`${props.properties.id}-fill`}
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
type='string'
|
||||
value={props.properties.style.fill ?? 'black'}
|
||||
onChange={(value) => { props.onChange('fill', value, PropertyType.Style); }}
|
||||
/>
|
||||
type='color'
|
||||
value={props.properties.style.fill ?? '#000000'}
|
||||
onChange={(e) => { props.onChange('fill', e.target.value, PropertyType.Style); }}/>
|
||||
<InputGroup
|
||||
labelKey={`${props.properties.id}-fillOpacity`}
|
||||
labelText={Text({ textId: '@StyleFillOpacity' })}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import { PropertyType } from '../../Enums/PropertyType';
|
||||
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { type PropertyType } from '../../Enums/PropertyType';
|
||||
import { type IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { ContainerForm } from './ContainerForm';
|
||||
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
|
||||
interface IPropertiesProps {
|
||||
containers: Map<string, IContainerModel>
|
||||
properties?: IContainerProperties
|
||||
symbols: Map<string, ISymbolModel>
|
||||
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
|
||||
|
@ -18,6 +20,7 @@ export function ContainerProperties(props: IPropertiesProps): JSX.Element {
|
|||
return (
|
||||
<div className='h-full p-3 bg-slate-200 overflow-y-auto'>
|
||||
<ContainerForm
|
||||
containers={props.containers}
|
||||
properties={props.properties}
|
||||
symbols={props.symbols}
|
||||
onChange={props.onChange} />
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { type IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { type IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { GetDefaultSymbolModel } from '../../../utils/default';
|
||||
import { FindContainerById } from '../../../utils/itertools';
|
||||
import { RestoreX } from '../../../utils/svg';
|
||||
import {RestoreX, RestoreY} from '../../../utils/svg';
|
||||
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
||||
import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor';
|
||||
import { AddContainers } from './AddContainer';
|
||||
|
@ -32,7 +32,12 @@ export function AddSymbol(
|
|||
const newSymbols = structuredClone(current.symbols);
|
||||
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig);
|
||||
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);
|
||||
|
||||
|
|
|
@ -1,16 +1,39 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
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 {
|
||||
container.properties.x = TransformX(symbol.x, 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));
|
||||
export function ApplySymbol(containers: Map<string, IContainerModel>,
|
||||
container: IContainerModel,
|
||||
symbol: ISymbolModel): IContainerModel {
|
||||
if (symbol.isVertical) {
|
||||
container.properties.y = TransformY(symbol.offset,
|
||||
symbol.height,
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import useSize from '@react-hook/size';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
||||
import { ContainerProperties } from '../ContainerProperties/ContainerProperties';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { PropertyType } from '../../Enums/PropertyType';
|
||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { type PropertyType } from '../../Enums/PropertyType';
|
||||
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
|
||||
import { Text } from '../Text/Text';
|
||||
import { ExtendedSidebar } from '../UI/UI';
|
||||
|
||||
interface IElementsSideBarProps {
|
||||
interface IElementsSidebarProps {
|
||||
containers: Map<string, IContainerModel>
|
||||
mainContainer: IContainerModel
|
||||
symbols: Map<string, ISymbolModel>
|
||||
selectedContainer: IContainerModel | undefined
|
||||
selectedExtendedSidebar: ExtendedSidebar
|
||||
onPropertyChange: (
|
||||
key: string,
|
||||
value: string | number | boolean | number[],
|
||||
|
@ -23,8 +24,7 @@ interface IElementsSideBarProps {
|
|||
) => void
|
||||
selectContainer: (containerId: string) => void
|
||||
addContainer: (index: number, type: string, parent: string) => void
|
||||
isExpanded: boolean
|
||||
onExpandChange: () => void
|
||||
onExpandChange: (value: ExtendedSidebar) => 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
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
const [,height] = useSize(divRef);
|
||||
const [showProperties, setShowProperties] = useState(props.isExpanded);
|
||||
|
||||
// Render
|
||||
const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true);
|
||||
|
@ -167,18 +166,28 @@ export function ElementsSideBar(props: IElementsSideBarProps): JSX.Element {
|
|||
|
||||
return (
|
||||
<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'>
|
||||
<ContainerProperties
|
||||
properties={props.selectedContainer?.properties}
|
||||
symbols={props.symbols}
|
||||
onChange={props.onPropertyChange}
|
||||
/>
|
||||
</div>
|
||||
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
|
||||
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
|
||||
<ContainerProperties
|
||||
containers ={props.containers}
|
||||
properties={props.selectedContainer?.properties}
|
||||
symbols={props.symbols}
|
||||
onChange={props.onPropertyChange}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div className='flex w-64' ref={divRef}>
|
||||
<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>
|
||||
<List
|
||||
itemCount={containers.length}
|
||||
|
@ -223,16 +232,18 @@ function ElementsListRow(
|
|||
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
|
||||
|
||||
return <button type="button"
|
||||
className={`transition-all border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium flex items-center align-middle group ${container.properties.type} ${buttonSelectedClass}`}
|
||||
className={`transition-all border-blue-500
|
||||
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}
|
||||
key={key}
|
||||
style={style}
|
||||
title={container.properties.warning}
|
||||
onClick={() => selectContainer(container.properties.id)}
|
||||
onDrop={(event) => HandleOnDrop(event, containers, mainContainer, addContainer)}
|
||||
onDragOver={(event) => HandleDragOver(event, mainContainer)}
|
||||
onDragLeave={(event) => HandleDragLeave(event)}
|
||||
onClick={() => { selectContainer(container.properties.id); }}
|
||||
onDrop={(event) => { HandleOnDrop(event, containers, mainContainer, addContainer); }}
|
||||
onDragOver={(event) => { HandleDragOver(event, mainContainer); }}
|
||||
onDragLeave={(event) => { HandleDragLeave(event); }}
|
||||
>
|
||||
{verticalBars}
|
||||
{text}
|
|
@ -31,7 +31,7 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
|
|||
export function Dimension(props: IDimensionProps): JSX.Element {
|
||||
const scale = props.scale ?? 1;
|
||||
const style: React.CSSProperties = {
|
||||
stroke: props.style.color,
|
||||
stroke: props.style.color ?? '#000000',
|
||||
strokeWidth: (props.style.width ?? 2) / scale,
|
||||
strokeDasharray: props.style.dashArray
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
|||
import { Orientation } from '../../../Enums/Orientation';
|
||||
import { Position } from '../../../Enums/Position';
|
||||
import {
|
||||
DEFAULT_DIMENSION_SYMBOL_STYLE,
|
||||
DIMENSION_MARGIN,
|
||||
SHOW_BORROWER_DIMENSIONS,
|
||||
SHOW_CHILDREN_DIMENSIONS,
|
||||
|
@ -10,7 +11,7 @@ import {
|
|||
} from '../../../utils/default';
|
||||
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
|
||||
import { TransformX, TransformY } from '../../../utils/svg';
|
||||
import { Dimension, type IDimensionStyle } from './Dimension';
|
||||
import { Dimension } from './Dimension';
|
||||
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
|
||||
|
@ -41,14 +42,12 @@ function ActionByPosition(
|
|||
positions.forEach((position: Position) => {
|
||||
const dim = dimMapped[position];
|
||||
switch (position) {
|
||||
case Position.Right:
|
||||
case Position.Left:
|
||||
case Position.Right: {
|
||||
const isRight = position === Position.Right;
|
||||
verticalAction(dim, isRight, ...params);
|
||||
verticalAction(dim, false, ...params);
|
||||
break;
|
||||
}
|
||||
case Position.Down:
|
||||
case Position.Up:
|
||||
case Position.Down:
|
||||
horizontalAction(dim, ...params);
|
||||
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(
|
||||
dimMapped,
|
||||
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(
|
||||
dimMapped,
|
||||
container.properties.dimensionOptions.childrenDimensions.positions,
|
||||
|
@ -144,29 +146,26 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
|
|||
}
|
||||
}
|
||||
|
||||
let startDepthSymbols: number = 0;
|
||||
for (const symbol of symbols) {
|
||||
if (symbol[1].showDimension) {
|
||||
startDepthSymbols++;
|
||||
AddHorizontalSymbolDimension(
|
||||
symbol[1],
|
||||
dimensions,
|
||||
scale,
|
||||
startDepthSymbols
|
||||
);
|
||||
// TODO: Implement DimensionManager
|
||||
symbols.forEach((symbol) => {
|
||||
if (symbol.showDimension) {
|
||||
if (symbol.isVertical) {
|
||||
AddVerticalSymbolDimension(symbol, dimensions, scale, 0);
|
||||
} else {
|
||||
AddHorizontalSymbolDimension(symbol, dimensions, scale, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
function AddHorizontalSymbolDimension(
|
||||
symbol: ISymbolModel,
|
||||
function AddHorizontalSymbolDimension(symbol: ISymbolModel,
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
depth: number
|
||||
): void {
|
||||
const width = symbol.x + (symbol.width / 2);
|
||||
const width = TransformX(symbol.offset, symbol.width, symbol.config.PositionReference);
|
||||
if (width != null && width > 0) {
|
||||
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
|
||||
|
||||
|
@ -174,11 +173,6 @@ function AddHorizontalSymbolDimension(
|
|||
const text = width
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
|
||||
// TODO: Put this in default.ts
|
||||
const defaultDimensionSymbolStyle: IDimensionStyle = {
|
||||
color: 'black'
|
||||
};
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
|
@ -189,7 +183,35 @@ function AddHorizontalSymbolDimension(
|
|||
yEnd={-offset}
|
||||
text={text}
|
||||
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;
|
||||
}
|
||||
|
||||
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
let xChildrenStart = TransformX(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
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
|
@ -290,8 +316,12 @@ function AddVerticalChildrenDimension(
|
|||
return;
|
||||
}
|
||||
|
||||
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
||||
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
||||
let yChildrenStart = TransformY(lastChild.properties.y,
|
||||
lastChild.properties.height,
|
||||
lastChild.properties.positionReference);
|
||||
let yChildrenEnd = TransformY(lastChild.properties.y,
|
||||
lastChild.properties.height,
|
||||
lastChild.properties.positionReference);
|
||||
|
||||
// Find the min and max
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
|
@ -537,7 +567,7 @@ function AddHorizontalSelfMarginsDimension(
|
|||
): void {
|
||||
const style = container.properties.dimensionOptions.selfMarginsDimensions;
|
||||
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 xStart = container.properties.x + currentTransform[0] - left;
|
||||
const xEnd = xStart + left;
|
||||
|
@ -559,7 +589,7 @@ function AddHorizontalSelfMarginsDimension(
|
|||
}
|
||||
|
||||
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 xStart = container.properties.x + container.properties.width + currentTransform[0];
|
||||
const xEnd = xStart + right;
|
||||
|
@ -591,7 +621,7 @@ function AddVerticalSelfMarginDimension(
|
|||
): void {
|
||||
const style = container.properties.dimensionOptions.selfMarginsDimensions;
|
||||
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}`;
|
||||
let yStart = container.properties.y + currentTransform[1];
|
||||
let yEnd = yStart - top;
|
||||
|
@ -617,7 +647,7 @@ function AddVerticalSelfMarginDimension(
|
|||
);
|
||||
}
|
||||
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}`;
|
||||
let yStart = container.properties.y + container.properties.height + bottom + currentTransform[1];
|
||||
let yEnd = yStart - bottom;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import '../Selector.scss';
|
||||
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 { Selector } from '../Selector/Selector';
|
||||
|
||||
interface ISelectorSymbolProps {
|
||||
symbols: Map<string, ISymbolModel>
|
||||
selected?: ISymbolModel
|
||||
scale?: number
|
||||
}
|
||||
|
||||
export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
||||
|
@ -18,30 +17,27 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
const scale = (props.scale ?? 1);
|
||||
const [width, height] = [
|
||||
props.selected.width / scale,
|
||||
props.selected.height / scale
|
||||
];
|
||||
let x, y: number;
|
||||
|
||||
const [x, y] = [
|
||||
props.selected.x + props.selected.width / 2,
|
||||
-SYMBOL_MARGIN - height];
|
||||
const scaledHeight = props.selected.height;
|
||||
const scaledWidth = props.selected.width;
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
transform: 'translateX(-50%)',
|
||||
transformBox: 'fill-box'
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
return (
|
||||
<Selector
|
||||
text={props.selected.displayedText}
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
scale={scale}
|
||||
style={style}
|
||||
width={scaledWidth}
|
||||
height={scaledHeight}
|
||||
scale={1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Interweave } from 'interweave';
|
||||
import * as React from 'react';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../../utils/default';
|
||||
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { SYMBOL_MARGIN } from '../../../utils/default';
|
||||
|
||||
interface ISymbolProps {
|
||||
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 hasSVG = props.model.config.Image.Svg !== undefined &&
|
||||
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) {
|
||||
return (
|
||||
<g
|
||||
x={props.model.x}
|
||||
y={-DIMENSION_MARGIN / props.scale}
|
||||
x={x}
|
||||
y={y}
|
||||
>
|
||||
<Interweave
|
||||
noWrap={true}
|
||||
|
@ -31,14 +42,9 @@ export function Symbol(props: ISymbolProps): JSX.Element {
|
|||
<image
|
||||
href={href}
|
||||
preserveAspectRatio="none"
|
||||
style={{
|
||||
fill: 'none',
|
||||
transform: 'translateY(-100%) translateX(-50%)',
|
||||
transformBox: 'fill-box'
|
||||
}}
|
||||
x={props.model.x + props.model.width / 2}
|
||||
y={-SYMBOL_MARGIN}
|
||||
height={props.model.height / props.scale}
|
||||
width={props.model.width / props.scale} />
|
||||
x={x}
|
||||
y={y}
|
||||
height={props.model.height}
|
||||
width={props.model.width} />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
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 { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer';
|
||||
import { MAX_FRAMERATE } from '../../utils/default';
|
||||
import { SymbolLayer } from './Elements/SymbolLayer';
|
||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { DimensionLayer } from './Elements/DimensionLayer';
|
||||
import { SelectorSymbol } from './Elements/SelectorSymbol/SelectorSymbol';
|
||||
import { type IToolbarProps, Toolbar } from './SVGReactPanZoom/ui-toolbar/toolbar';
|
||||
import { type DrawParams } from '../Viewer/Viewer';
|
||||
|
||||
interface ISVGProps {
|
||||
className?: string
|
||||
|
@ -15,12 +15,7 @@ interface ISVGProps {
|
|||
viewerHeight: number
|
||||
width: number
|
||||
height: number
|
||||
containers: Map<string, IContainerModel>
|
||||
children: IContainerModel
|
||||
selectedContainer?: IContainerModel
|
||||
symbols: Map<string, ISymbolModel>
|
||||
selectedSymbol?: ISymbolModel
|
||||
selectorMode: SelectorMode
|
||||
drawParams: DrawParams
|
||||
selectContainer: (containerId: string) => void
|
||||
}
|
||||
|
||||
|
@ -33,6 +28,14 @@ export enum SelectorMode {
|
|||
export const ID = 'svg';
|
||||
|
||||
export function SVG(props: ISVGProps): JSX.Element {
|
||||
const {
|
||||
mainContainer,
|
||||
selectorMode,
|
||||
selectedContainer,
|
||||
selectedSymbol,
|
||||
containers,
|
||||
symbols
|
||||
} = props.drawParams;
|
||||
const [tool, setTool] = React.useState<Tool>(TOOL_PAN);
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
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
|
||||
key={`container-${props.children.properties.id}`}
|
||||
containers={props.containers}
|
||||
model={props.children}
|
||||
key={`container-${mainContainer.properties.id}`}
|
||||
containers={containers}
|
||||
model={mainContainer}
|
||||
depth={0}
|
||||
scale={scale}
|
||||
selectContainer={props.selectContainer}
|
||||
/>;
|
||||
|
||||
function Selector(): JSX.Element {
|
||||
switch (props.selectorMode) {
|
||||
switch (selectorMode) {
|
||||
case SelectorMode.Containers:
|
||||
return <SelectorContainer
|
||||
containers={props.containers}
|
||||
containers={containers}
|
||||
scale={scale}
|
||||
selected={props.selectedContainer}
|
||||
selected={selectedContainer}
|
||||
/>;
|
||||
case SelectorMode.Symbols:
|
||||
return <SelectorSymbol
|
||||
symbols={props.symbols}
|
||||
symbols={symbols}
|
||||
scale={scale}
|
||||
selected={props.selectedSymbol}
|
||||
selected={selectedSymbol}
|
||||
/>;
|
||||
default:
|
||||
return <></>;
|
||||
|
@ -109,6 +112,9 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
const value = event as Value;
|
||||
setScale(value.a);
|
||||
}}
|
||||
onDoubleClick={() => {
|
||||
svgViewer?.current?.setPointOnViewerCenter(props.width / 2, props.height / 2, 0.8);
|
||||
}}
|
||||
background={'#ffffff'}
|
||||
defaultTool='pan'
|
||||
miniatureProps={{
|
||||
|
@ -117,11 +123,24 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
width: 120,
|
||||
height: 120
|
||||
}}
|
||||
customToolbar={(props: IToolbarProps) => (
|
||||
<Toolbar
|
||||
{...props}
|
||||
SVGAlignX={ALIGN_CENTER}
|
||||
SVGAlignY={ALIGN_CENTER}
|
||||
fittingScale={0.8}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<svg {...properties}>
|
||||
{children}
|
||||
<DimensionLayer containers={props.containers} symbols={props.symbols} scale={scale} root={props.children} />
|
||||
<SymbolLayer scale={scale} symbols={props.symbols} />
|
||||
<DimensionLayer
|
||||
containers={containers}
|
||||
symbols={symbols}
|
||||
scale={scale}
|
||||
root={mainContainer}
|
||||
/>
|
||||
<SymbolLayer scale={scale} symbols={symbols} />
|
||||
<Selector />
|
||||
</svg>
|
||||
</ReactSVGPanZoom>
|
||||
|
@ -130,7 +149,8 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
}
|
||||
|
||||
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void {
|
||||
React.useEffect(() => {
|
||||
svgViewer?.current?.fitToViewer();
|
||||
React.useCallback(() => {
|
||||
// TODO: Fix this
|
||||
svgViewer?.current?.setPointOnViewerCenter(width / 2, height / 2, 0.8);
|
||||
}, [svgViewer, width, height]);
|
||||
}
|
||||
|
|
21
src/Components/SVG/SVGReactPanZoom/LICENSE
Normal file
21
src/Components/SVG/SVGReactPanZoom/LICENSE
Normal 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.
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
188
src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx
Normal file
188
src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -4,16 +4,16 @@ import './ToggleSideBar.scss';
|
|||
interface IToggleSidebarProps {
|
||||
title: string
|
||||
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 (
|
||||
<div className={`${(checked ? 'bg-slate-400 hover:bg-slate-500' : 'bg-slate-300 hover:bg-slate-400')}`}>
|
||||
<button
|
||||
className={'w-full py-2'}
|
||||
type='button'
|
||||
onClick={() => onChange(!checked)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<p className='text-vertical'>{title}
|
||||
</p>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { RestoreX, TransformX } from '../../utils/svg';
|
||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { RestoreX, RestoreY, TransformX, TransformY } from '../../utils/svg';
|
||||
import { InputGroup } from '../InputGroup/InputGroup';
|
||||
import { TextInputGroup } from '../InputGroup/TextInputGroup';
|
||||
import { Text } from '../Text/Text';
|
||||
import { PropertyType } from '../../Enums/PropertyType';
|
||||
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
||||
import { type PositionReference } from '../../Enums/PositionReference';
|
||||
|
||||
interface ISymbolFormProps {
|
||||
symbol: ISymbolModel
|
||||
|
@ -13,6 +13,29 @@ interface ISymbolFormProps {
|
|||
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 {
|
||||
return (
|
||||
<div className='grid grid-cols-2 gap-y-4'>
|
||||
|
@ -32,16 +55,34 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
|||
inputClassName=''
|
||||
type='string'
|
||||
value={props.symbol.displayedText}
|
||||
onChange={(value) => props.onChange('displayedText', value)} />
|
||||
onChange={(value) => { props.onChange('displayedText', value); }} />
|
||||
<TextInputGroup
|
||||
id='x'
|
||||
labelText={Text({ textId: '@SymbolX' })}
|
||||
inputKey='x'
|
||||
id='offset'
|
||||
labelText={Text({ textId: '@SymbolOffset' })}
|
||||
inputKey='offset'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
value={TransformX(props.symbol.x, props.symbol.width, props.symbol.config.PositionReference).toString()}
|
||||
onChange={(value) => props.onChange('x', RestoreX(Number(value), props.symbol.width, props.symbol.config.PositionReference))} />
|
||||
value={Transform(props.symbol.offset,
|
||||
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
|
||||
id='height'
|
||||
labelText={Text({ textId: '@SymbolHeight' })}
|
||||
|
@ -51,7 +92,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
|||
type='number'
|
||||
min={0}
|
||||
value={props.symbol.height.toString()}
|
||||
onChange={(value) => props.onChange('height', Number(value))} />
|
||||
onChange={(value) => { props.onChange('height', Number(value)); }} />
|
||||
<TextInputGroup
|
||||
id='width'
|
||||
labelText={Text({ textId: '@SymbolWidth' })}
|
||||
|
@ -61,14 +102,14 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
|||
type='number'
|
||||
min={0}
|
||||
value={props.symbol.width.toString()}
|
||||
onChange={(value) => props.onChange('width', Number(value))} />
|
||||
onChange={(value) => { props.onChange('width', Number(value)); }} />
|
||||
<ToggleButton
|
||||
labelText={Text({ textId: '@ShowDimension' })}
|
||||
inputKey='showDimension'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
checked={props.symbol.showDimension}
|
||||
onChange={(e) => props.onChange('showDimension', e.target.checked)}/>
|
||||
onChange={(e) => { props.onChange('showDimension', e.target.checked); }}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,39 +5,47 @@ import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
|||
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
|
||||
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
|
||||
import { Text } from '../Text/Text';
|
||||
import { useState } from 'react';
|
||||
import { ExtendedSidebar } from '../UI/UI';
|
||||
|
||||
interface ISymbolsSidebarProps {
|
||||
selectedSymbolId: string
|
||||
symbols: Map<string, ISymbolModel>
|
||||
selectedExtendedSidebar: ExtendedSidebar
|
||||
onPropertyChange: (key: string, value: string | number | boolean) => void
|
||||
selectSymbol: (symbolId: string) => void
|
||||
isExpanded: boolean
|
||||
onExpandChange: (isExpanded: boolean) => void
|
||||
onExpandChange: (value: ExtendedSidebar) => void
|
||||
}
|
||||
|
||||
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
||||
// States
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
const height = useSize(divRef)[1];
|
||||
const [showProperties, setShowProperties] = useState(props.isExpanded);
|
||||
// Render
|
||||
const symbols = [...props.symbols.values()];
|
||||
|
||||
const selectedSymbol = props.symbols.get(props.selectedSymbolId);
|
||||
return (
|
||||
<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'>
|
||||
{(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>}
|
||||
<SymbolProperties
|
||||
symbol={selectedSymbol}
|
||||
symbols={props.symbols}
|
||||
onChange={props.onPropertyChange}
|
||||
/>
|
||||
</div>}
|
||||
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
|
||||
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
|
||||
{(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>}
|
||||
<SymbolProperties
|
||||
symbol={selectedSymbol}
|
||||
symbols={props.symbols}
|
||||
onChange={props.onPropertyChange}
|
||||
/>
|
||||
</div>}
|
||||
<div className={'flex w-64'} ref={divRef}>
|
||||
<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>
|
||||
<List
|
||||
itemCount={symbols.length}
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { ElementsSideBar } from '../ElementsList/ElementsSideBar';
|
||||
import { ElementsSidebar } from '../ElementsSidebar/ElementsSidebar';
|
||||
import { History } from '../History/History';
|
||||
import { Bar, BAR_WIDTH } from '../Bar/Bar';
|
||||
import { Symbols } from '../Symbols/Symbols';
|
||||
import { SymbolsSidebar } from '../SymbolsList/SymbolsSidebar';
|
||||
import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
|
||||
import { type PropertyType } from '../../Enums/PropertyType';
|
||||
import { Messages } from '../Messages/Messages';
|
||||
import { Sidebar } from '../Sidebar/Sidebar';
|
||||
|
@ -17,8 +17,8 @@ import { FindContainerById } from '../../utils/itertools';
|
|||
import { type IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { GetCurrentHistoryState } from '../Editor/Editor';
|
||||
import { Text } from '../Text/Text';
|
||||
import { IReplaceContainer } from '../../Interfaces/IReplaceContainer';
|
||||
import { Dispatch } from 'react';
|
||||
import { type IReplaceContainer } from '../../Interfaces/IReplaceContainer';
|
||||
import { type Dispatch } from 'react';
|
||||
|
||||
export interface IUIProps {
|
||||
editorState: IEditorState
|
||||
|
@ -42,14 +42,17 @@ export interface IUIProps {
|
|||
export enum SidebarType {
|
||||
None,
|
||||
Components,
|
||||
ComponentsExpanded,
|
||||
Symbols,
|
||||
SymbolsExpanded,
|
||||
History,
|
||||
Messages,
|
||||
Settings
|
||||
}
|
||||
|
||||
export enum ExtendedSidebar {
|
||||
None,
|
||||
Property
|
||||
}
|
||||
|
||||
function UseSetOrToggleSidebar(
|
||||
selectedSidebar: SidebarType,
|
||||
setSelectedSidebar: React.Dispatch<React.SetStateAction<SidebarType>>
|
||||
|
@ -66,6 +69,10 @@ function UseSetOrToggleSidebar(
|
|||
|
||||
export function UI({ editorState, replaceContainer, setReplaceContainer, ...methods }: IUIProps): JSX.Element {
|
||||
const [selectedSidebar, setSelectedSidebar] = React.useState<SidebarType>(SidebarType.Components);
|
||||
const [
|
||||
selectedExtendedSidebar,
|
||||
setSelectedExtendedSidebarType
|
||||
] = React.useState<ExtendedSidebar>(ExtendedSidebar.None);
|
||||
|
||||
const [messages, setMessages] = React.useState<IMessage[]>([]);
|
||||
const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep);
|
||||
|
@ -115,7 +122,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
|
|||
replaceContainer={replaceContainer}
|
||||
setReplaceContainer={setReplaceContainer}/>;
|
||||
rightSidebarTitle = Text({ textId: '@Elements' });
|
||||
rightChildren = <ElementsSideBar
|
||||
rightChildren = <ElementsSidebar
|
||||
containers={current.containers}
|
||||
mainContainer={mainContainer}
|
||||
symbols={current.symbols}
|
||||
|
@ -123,32 +130,11 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
|
|||
onPropertyChange={methods.onPropertyChange}
|
||||
selectContainer={methods.selectContainer}
|
||||
addContainer={methods.addContainerAt}
|
||||
isExpanded ={false}
|
||||
onExpandChange={() => { setOrToggleSidebar(SidebarType.ComponentsExpanded); } }
|
||||
/>;
|
||||
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); } }
|
||||
selectedExtendedSidebar={selectedExtendedSidebar}
|
||||
onExpandChange={(value) => { setSelectedExtendedSidebarType(value); } }
|
||||
/>;
|
||||
break;
|
||||
|
||||
case SidebarType.Symbols:
|
||||
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
|
||||
leftChildren = <Symbols
|
||||
|
@ -161,24 +147,8 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
|
|||
symbols={current.symbols}
|
||||
onPropertyChange={methods.onSymbolPropertyChange}
|
||||
selectSymbol={methods.selectSymbol}
|
||||
isExpanded ={false}
|
||||
onExpandChange={() => { setOrToggleSidebar(SidebarType.SymbolsExpanded); } }
|
||||
/>;
|
||||
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); }}
|
||||
selectedExtendedSidebar={selectedExtendedSidebar}
|
||||
onExpandChange={(value) => { setSelectedExtendedSidebarType(value); } }
|
||||
/>;
|
||||
break;
|
||||
|
||||
|
@ -210,8 +180,10 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
|
|||
}
|
||||
|
||||
const isLeftSidebarOpen = selectedSidebar !== SidebarType.None;
|
||||
const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols;
|
||||
const isRightSidebarOpenExpanded = selectedSidebar === SidebarType.ComponentsExpanded || selectedSidebar === SidebarType.SymbolsExpanded;
|
||||
const isRightSidebarOpen = [SidebarType.Components, SidebarType.Symbols]
|
||||
.includes(selectedSidebar);
|
||||
const isRightSidebarOpenExpanded = isRightSidebarOpen &&
|
||||
selectedExtendedSidebar !== ExtendedSidebar.None;
|
||||
|
||||
const isLeftSidebarOpenClasses = new Set<string>([
|
||||
'left-sidebar',
|
||||
|
@ -252,8 +224,8 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
|
|||
}
|
||||
|
||||
const clickRestrictionsClasses = replaceContainer.isReplacing ? 'pointer-events-none opacity-50' : '';
|
||||
const isComponentsOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.ComponentsExpanded;
|
||||
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols || selectedSidebar === SidebarType.SymbolsExpanded;
|
||||
const isComponentsOpen = selectedSidebar === SidebarType.Components;
|
||||
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -265,18 +237,10 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
|
|||
isMessagesOpen={selectedSidebar === SidebarType.Messages}
|
||||
isSettingsOpen={selectedSidebar === SidebarType.Settings}
|
||||
toggleComponents={() => {
|
||||
if (selectedSidebar === SidebarType.ComponentsExpanded) {
|
||||
setOrToggleSidebar(SidebarType.ComponentsExpanded);
|
||||
} else {
|
||||
setOrToggleSidebar(SidebarType.Components);
|
||||
}
|
||||
setOrToggleSidebar(SidebarType.Components);
|
||||
} }
|
||||
toggleSymbols={() => {
|
||||
if (selectedSidebar === SidebarType.SymbolsExpanded) {
|
||||
setOrToggleSidebar(SidebarType.SymbolsExpanded);
|
||||
} else {
|
||||
setOrToggleSidebar(SidebarType.Symbols);
|
||||
}
|
||||
setOrToggleSidebar(SidebarType.Symbols);
|
||||
} }
|
||||
toggleTimeline={() => {
|
||||
setOrToggleSidebar(SidebarType.History);
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { type IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { type IPoint } from '../../Interfaces/IPoint';
|
||||
import { DIMENSION_MARGIN, USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
|
||||
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
|
||||
import { USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
|
||||
import { FindContainerById } from '../../utils/itertools';
|
||||
import { BAR_WIDTH } from '../Bar/Bar';
|
||||
import { Canvas } from '../Canvas/Canvas';
|
||||
import { AddDimensions } from '../Canvas/DimensionLayer';
|
||||
import { RenderSelector } from '../Canvas/Selector';
|
||||
import { SelectorMode, SVG } from '../SVG/SVG';
|
||||
import { RenderSymbol } from '../Canvas/Symbol';
|
||||
import { useState } from 'react';
|
||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
|
||||
|
@ -17,27 +13,36 @@ interface IViewerProps {
|
|||
className: string
|
||||
current: IHistoryState
|
||||
selectedContainer: IContainerModel | undefined
|
||||
selectContainer: (containerId: string) => void
|
||||
selectedSymbol: ISymbolModel | undefined
|
||||
margin: number
|
||||
isComponentsOpen: 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({
|
||||
className,
|
||||
current,
|
||||
selectedContainer,
|
||||
selectContainer,
|
||||
selectedSymbol,
|
||||
margin,
|
||||
isComponentsOpen,
|
||||
isSymbolsOpen
|
||||
isSymbolsOpen,
|
||||
selectContainer
|
||||
}: IViewerProps): JSX.Element {
|
||||
function computeWidth(margin: number): number {
|
||||
return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
|
||||
}
|
||||
|
||||
const [windowSize, setWindowSize] = useState([
|
||||
computeWidth(margin),
|
||||
window.innerHeight
|
||||
|
@ -78,64 +83,22 @@ export function Viewer({
|
|||
selectorMode = SelectorMode.Symbols;
|
||||
}
|
||||
|
||||
const drawParams: DrawParams = {
|
||||
mainContainer,
|
||||
selectorMode,
|
||||
selectedContainer,
|
||||
selectedSymbol,
|
||||
containers: current.containers,
|
||||
symbols: current.symbols
|
||||
};
|
||||
|
||||
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 (
|
||||
<Canvas
|
||||
draw={Draw}
|
||||
className={`ml-16 ${className}`}
|
||||
className={className}
|
||||
width={window.innerWidth - BAR_WIDTH}
|
||||
height={window.innerHeight}
|
||||
drawParams={drawParams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -147,58 +110,8 @@ export function Viewer({
|
|||
viewerHeight={windowSize[1]}
|
||||
width={mainContainer.properties.width}
|
||||
height={mainContainer.properties.height}
|
||||
containers={current.containers}
|
||||
selectedContainer={selectedContainer}
|
||||
symbols={current.symbols}
|
||||
selectedSymbol={selectedSymbol}
|
||||
selectorMode={selectorMode}
|
||||
drawParams={drawParams}
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { PositionReference } from '../Enums/PositionReference';
|
||||
import { IAvailableContainer } from './IAvailableContainer';
|
||||
import { IImage } from './IImage';
|
||||
import { type PositionReference } from '../Enums/PositionReference';
|
||||
import { type IAvailableContainer } from './IAvailableContainer';
|
||||
import { type IImage } from './IImage';
|
||||
|
||||
/**
|
||||
* Model of available symbol to configure the application */
|
||||
|
@ -13,9 +13,9 @@ export interface IAvailableSymbol {
|
|||
/** displayed text */
|
||||
DisplayedText?: string
|
||||
|
||||
X?: number
|
||||
isVertical?: boolean
|
||||
|
||||
Y?: number
|
||||
offset?: number
|
||||
|
||||
Width?: number
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@ export interface ISymbolModel {
|
|||
/** Configuration of the symbol */
|
||||
config: IAvailableSymbol
|
||||
|
||||
/** Horizontal offset */
|
||||
x: number
|
||||
isVertical: boolean
|
||||
|
||||
// TODO: Implement Y and verticality
|
||||
/** offset */
|
||||
offset: number
|
||||
|
||||
/** Width */
|
||||
width: number
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"@DeleteContainerTitle": "Delete the container",
|
||||
"@DeleteSymbol": "Delete",
|
||||
"@DeleteSymbolTitle": "Delete the container",
|
||||
"@ReplaceByContainer": "Replace by component",
|
||||
|
||||
"@ExportAsJSON": "Export as JSON",
|
||||
"@ExportAsSVG": "Export as SVG",
|
||||
|
@ -75,7 +76,9 @@
|
|||
|
||||
"@SymbolName": "Name",
|
||||
"@SymbolDisplayedText": "Displayed text",
|
||||
"@SymbolX": "x",
|
||||
"@SymbolOffset" : "Offset",
|
||||
"@IsVertical" : "Vertical",
|
||||
"@ShowDimension" : "Dimension",
|
||||
"@SymbolHeight": "Height",
|
||||
"@SymbolWidth": "Width"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"@Symbols": "Symboles",
|
||||
"@SymbolsLeft": "Symboles",
|
||||
"@SymbolsRight": "Symboles",
|
||||
"@NoSymbolSelected": "Pas de symbol sélectionné",
|
||||
"@NoSymbolSelected": "Pas de symbole sélectionné",
|
||||
"@Timeline": "Chronologie",
|
||||
"@Messages": "Messages",
|
||||
"@Settings": "Paramètres",
|
||||
|
@ -22,6 +22,7 @@
|
|||
"@DeleteContainerTitle": "Supprimer le conteneur",
|
||||
"@DeleteSymbol": "Supprimer",
|
||||
"@DeleteSymbolTitle": "Supprimer le symbole",
|
||||
"@ReplaceByContainer": "Replacer par un composant",
|
||||
|
||||
"@ExportAsJSON": "Exporter en JSON",
|
||||
"@ExportAsSVG": "Exporter en SVG",
|
||||
|
@ -75,7 +76,9 @@
|
|||
|
||||
"@SymbolName": "Nom",
|
||||
"@SymbolDisplayedText": "Texte affiché",
|
||||
"@SymbolX": "x",
|
||||
"@SymbolOffset" : "Décalage",
|
||||
"@IsVertical" : "Vertical",
|
||||
"@ShowDimension" : "Cotation",
|
||||
"@SymbolHeight": "Hauteur",
|
||||
"@SymbolWidth": "Largeur"
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
"@StartFromScratch": "Partir de zéro",
|
||||
"@LoadConfigFile": "Charger un fichier de configuration",
|
||||
"@GoBack": "Revenir",
|
||||
"@Properties": "Propriétés",
|
||||
|
||||
"@Components": "Composants",
|
||||
"@Elements": "Éléments",
|
||||
"@Symbols": "Ancrages",
|
||||
"@SymbolsLeft": "Ancrages",
|
||||
"@SymbolsRight": "Ancrages",
|
||||
"@NoSymbolSelected": "Pas d'ancre sélectionnée",
|
||||
"@Timeline": "Chronologie",
|
||||
"@Messages": "Messages",
|
||||
"@Settings": "Paramètres",
|
||||
|
@ -61,9 +63,18 @@
|
|||
"@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants",
|
||||
"@ContainerMarkPosition": "Marquer la position pour les parents",
|
||||
"@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués",
|
||||
"@ContainerShowMarginsDimension": "Afficher les cotations des jeux",
|
||||
"@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",
|
||||
"@SymbolDisplayedText": "Texte affiché",
|
||||
"@SymbolX": "x",
|
||||
"@SymbolHeight": "Hauteur",
|
||||
"@SymbolWidth": "Largeur"
|
||||
|
|
|
@ -9,6 +9,7 @@ import { type ISymbolModel } from '../Interfaces/ISymbolModel';
|
|||
import { Orientation } from '../Enums/Orientation';
|
||||
import { AppState } from '../Enums/AppState';
|
||||
import { type IDimensionOptions } from '../Interfaces/IDimensionOptions';
|
||||
import { type IDimensionStyle } from '../Components/SVG/Elements/Dimension';
|
||||
|
||||
/// EDITOR DEFAULTS ///
|
||||
|
||||
|
@ -69,10 +70,14 @@ export const SHOW_BORROWER_DIMENSIONS = true;
|
|||
export const DIMENSION_MARGIN = 50;
|
||||
export const SYMBOL_MARGIN = 25;
|
||||
export const NOTCHES_LENGTH = 10;
|
||||
export const DEFAULT_DIMENSION_SYMBOL_STYLE: IDimensionStyle = {
|
||||
color: '#000000'
|
||||
};
|
||||
|
||||
/// SYMBOL DEFAULTS ///
|
||||
|
||||
export const DEFAULT_SYMBOL_WIDTH = 32;
|
||||
export const DEFAULT_SYMBOL_IS_VERTICAL = false;
|
||||
export const DEFAULT_SYMBOL_HEIGHT = 32;
|
||||
|
||||
/**
|
||||
|
@ -270,7 +275,7 @@ export function GetDefaultContainerProps(type: string,
|
|||
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
|
||||
dimensionOptions: {
|
||||
childrenDimensions: {
|
||||
...containerConfig.DimensionOptions?.selfDimensions,
|
||||
...containerConfig.DimensionOptions?.childrenDimensions,
|
||||
positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? []
|
||||
},
|
||||
selfDimensions: {
|
||||
|
@ -304,7 +309,8 @@ export function GetDefaultSymbolModel(name: string,
|
|||
displayedText: symbolConfig.DisplayedText ?? id,
|
||||
type: name,
|
||||
config: structuredClone(symbolConfig),
|
||||
x: 0,
|
||||
offset: 0,
|
||||
isVertical: symbolConfig.isVertical ?? DEFAULT_SYMBOL_IS_VERTICAL,
|
||||
width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH,
|
||||
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
|
||||
linkedContainers: new Set(),
|
||||
|
|
|
@ -81,11 +81,10 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
stroke: 'red',
|
||||
stroke: '#ff0000',
|
||||
fill: '#d3c9b7',
|
||||
},
|
||||
ShowSelfDimensions: [0, 2],
|
||||
ShowDimensionWithMarks: [1, 3],
|
||||
IsFlex: true,
|
||||
Category: "Stuff"
|
||||
},
|
||||
{
|
||||
|
@ -97,11 +96,10 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
stroke: 'red',
|
||||
stroke: '#ff0000',
|
||||
fill: '#d3c9b7',
|
||||
},
|
||||
ShowSelfDimensions: [0, 2],
|
||||
ShowDimensionWithMarks: [1, 3],
|
||||
IsFlex: true,
|
||||
Category: "Stuff"
|
||||
},
|
||||
{
|
||||
|
@ -117,10 +115,11 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
stroke: 'green',
|
||||
fill: 'white'
|
||||
stroke: '#00ff00',
|
||||
fill: '#ffffff'
|
||||
},
|
||||
Category: "Stuff",
|
||||
IsFlex: true,
|
||||
Actions: [
|
||||
{
|
||||
Id: "Insert",
|
||||
|
@ -173,6 +172,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||
`
|
||||
,
|
||||
IsFlex: true,
|
||||
Actions: [
|
||||
{
|
||||
Id: "SplitRemplissage",
|
||||
|
@ -254,22 +254,24 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Type: '200',
|
||||
MaxWidth: 500,
|
||||
MinWidth: 200,
|
||||
IsFlex: true,
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
stroke: 'blue',
|
||||
fill: 'blue',
|
||||
stroke: '#0000ff',
|
||||
fill: '#0000ff',
|
||||
}
|
||||
},
|
||||
{
|
||||
Type: '400',
|
||||
MaxWidth: 500,
|
||||
MinWidth: 400,
|
||||
IsFlex: true,
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
stroke: 'red',
|
||||
fill: 'red',
|
||||
stroke: '#ff0000',
|
||||
fill: '#ff0000',
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -284,8 +286,8 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Svg: null,
|
||||
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
|
||||
},
|
||||
Name: 'Poteau structure',
|
||||
PositionReference: 1,
|
||||
Name: 'Poteau CenterCenter',
|
||||
PositionReference: 4,
|
||||
AssociatedContainer: {
|
||||
Type: 'Montant'
|
||||
}
|
||||
|
@ -295,12 +297,48 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Height: 32,
|
||||
Image: {
|
||||
Base64Image: null,
|
||||
Name: 'Arrow',
|
||||
Name: 'ArrowTopLeft',
|
||||
Svg: null,
|
||||
Url: './images/arrow-down.svg'
|
||||
},
|
||||
Name: 'Arrow',
|
||||
PositionReference: 1
|
||||
Name: 'ArrowTopLeft',
|
||||
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,
|
||||
|
@ -329,10 +367,10 @@ const GetSVGLayoutConfiguration = () => {
|
|||
MainContainer: {
|
||||
Type: 'main',
|
||||
Width: 800,
|
||||
Height: 800,
|
||||
Orientation: 1,
|
||||
Height: 200,
|
||||
Orientation: 0,
|
||||
Style: {
|
||||
stroke: 'black',
|
||||
stroke: '#000000',
|
||||
strokeWidth: 2,
|
||||
fillOpacity: 0
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue