Merge branch 'master' into master.7306.symbolY
# Conflicts: # src/Components/Canvas/Symbol.ts # src/Components/SVG/Elements/DimensionLayer.tsx # src/Components/SVG/Elements/SelectorSymbol/SelectorSymbol.tsx
This commit is contained in:
commit
d72c2266f3
52 changed files with 1967 additions and 913 deletions
|
@ -1,100 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||
import { TransformX } from '../../../utils/svg';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
containers: Map<string, IContainerModel>
|
||||
roots: IContainerModel | IContainerModel[] | null
|
||||
scale?: number
|
||||
}
|
||||
|
||||
function GetDimensionsNodes(
|
||||
containers: Map<string, IContainerModel>,
|
||||
root: IContainerModel,
|
||||
scale: number
|
||||
): React.ReactNode[] {
|
||||
const it = MakeBFSIterator(root, containers);
|
||||
const dimensions: React.ReactNode[] = [];
|
||||
let currentDepth = 0;
|
||||
let min = Infinity;
|
||||
let max = -Infinity;
|
||||
let lastY = 0;
|
||||
for (const { container, depth } of it) {
|
||||
if (currentDepth !== depth) {
|
||||
AddNewDimension(currentDepth, min, max, lastY, scale, '#000000', dimensions);
|
||||
|
||||
currentDepth = depth;
|
||||
min = Infinity;
|
||||
max = -Infinity;
|
||||
}
|
||||
|
||||
const absoluteX = GetAbsolutePosition(containers, container)[0];
|
||||
const x = TransformX(absoluteX, container.properties.width, container.properties.positionReference);
|
||||
lastY = container.properties.y + container.properties.height;
|
||||
if (x < min) {
|
||||
min = x;
|
||||
}
|
||||
|
||||
if (x > max) {
|
||||
max = x;
|
||||
}
|
||||
}
|
||||
|
||||
AddNewDimension(currentDepth, min, max, lastY, scale, '#000000', dimensions);
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A layer containing all dimension
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export function DepthDimensionLayer(props: IDimensionLayerProps): JSX.Element {
|
||||
let dimensions: React.ReactNode[] = [];
|
||||
const scale = props.scale ?? 1;
|
||||
if (Array.isArray(props.roots)) {
|
||||
props.roots.forEach(child => {
|
||||
dimensions.concat(GetDimensionsNodes(props.containers, child, scale));
|
||||
});
|
||||
} else if (props.roots !== null) {
|
||||
dimensions = GetDimensionsNodes(props.containers, props.roots, scale);
|
||||
}
|
||||
return (
|
||||
<g>
|
||||
{dimensions}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, scale: number, color: string, dimensions: React.ReactNode[]): void {
|
||||
const id = `dim-depth-${currentDepth}`;
|
||||
const xStart = min;
|
||||
const xEnd = max;
|
||||
const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1)) / scale;
|
||||
const width = xEnd - xStart;
|
||||
const text = width
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
|
||||
if (width === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
yStart={y}
|
||||
xEnd={xEnd}
|
||||
yEnd={y}
|
||||
text={text}
|
||||
scale={scale}
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { type IDimensionOptions } from '../../../Interfaces/IDimensionOptions';
|
||||
import { NOTCHES_LENGTH } from '../../../utils/default';
|
||||
|
||||
export type IDimensionStyle = Omit<IDimensionOptions, 'positions'>;
|
||||
|
||||
interface IDimensionProps {
|
||||
id: string
|
||||
xStart: number
|
||||
|
@ -8,7 +11,7 @@ interface IDimensionProps {
|
|||
xEnd: number
|
||||
yEnd: number
|
||||
text: string
|
||||
color: string
|
||||
style: IDimensionStyle
|
||||
scale?: number
|
||||
}
|
||||
|
||||
|
@ -28,8 +31,9 @@ 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.color,
|
||||
strokeWidth: 2 / scale
|
||||
stroke: props.style.color,
|
||||
strokeWidth: (props.style.width ?? 2) / scale,
|
||||
strokeDasharray: props.style.dashArray
|
||||
};
|
||||
|
||||
/// We need to find the points of the notches
|
||||
|
@ -79,9 +83,11 @@ export function Dimension(props: IDimensionProps): JSX.Element {
|
|||
x2={endBottomX}
|
||||
y2={endBottomY}
|
||||
style={style}/>
|
||||
<text textAnchor={'middle'} alignmentBaseline={'central'}
|
||||
<text
|
||||
x={textX}
|
||||
y={textY}
|
||||
textAnchor={'middle'}
|
||||
alignmentBaseline={'central'}
|
||||
style={{
|
||||
transform: `rotate(${rotation}turn) scale(${1 / scale})`,
|
||||
transformOrigin: `${textX}px ${textY}px`
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import { Orientation } from '../../../Enums/Orientation';
|
||||
import { Position } from '../../../Enums/Position';
|
||||
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import {
|
||||
DIMENSION_MARGIN,
|
||||
SHOW_BORROWER_DIMENSIONS,
|
||||
|
@ -11,7 +10,8 @@ import {
|
|||
} from '../../../utils/default';
|
||||
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
|
||||
import { TransformX, TransformY } from '../../../utils/svg';
|
||||
import { Dimension } from './Dimension';
|
||||
import { Dimension, type IDimensionStyle } from './Dimension';
|
||||
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
|
@ -252,10 +252,10 @@ function AddHorizontalChildrenDimension(
|
|||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
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);
|
||||
|
@ -305,7 +305,7 @@ function AddHorizontalChildrenDimension(
|
|||
yEnd={yDim}
|
||||
text={textChildren}
|
||||
scale={scale}
|
||||
color={color}/>);
|
||||
style={style}/>);
|
||||
}
|
||||
|
||||
function AddVerticalChildrenDimension(
|
||||
|
@ -315,10 +315,10 @@ function AddVerticalChildrenDimension(
|
|||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
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);
|
||||
|
@ -373,7 +373,7 @@ function AddVerticalChildrenDimension(
|
|||
yEnd={yChildrenEnd + offset}
|
||||
text={textChildren}
|
||||
scale={scale}
|
||||
color={color}
|
||||
style={style}
|
||||
/>);
|
||||
}
|
||||
|
||||
|
@ -384,9 +384,9 @@ function AddHorizontalBorrowerDimension(
|
|||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
scale: number
|
||||
): void {
|
||||
const style = container.properties.dimensionOptions.dimensionWithMarks;
|
||||
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
|
@ -431,7 +431,7 @@ function AddHorizontalBorrowerDimension(
|
|||
yEnd={yDim}
|
||||
text={value.toFixed(0)}
|
||||
scale={scale}
|
||||
color={color}/>);
|
||||
style={style}/>);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
@ -444,9 +444,9 @@ function AddVerticalBorrowerDimension(
|
|||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
scale: number
|
||||
): void {
|
||||
const style = container.properties.dimensionOptions.dimensionWithMarks;
|
||||
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
|
@ -496,7 +496,7 @@ function AddVerticalBorrowerDimension(
|
|||
yEnd={next}
|
||||
text={value.toFixed(0)}
|
||||
scale={scale}
|
||||
color={color}/>);
|
||||
style={style}/>);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
@ -507,9 +507,9 @@ function AddVerticalSelfDimension(
|
|||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
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;
|
||||
|
@ -532,7 +532,7 @@ function AddVerticalSelfDimension(
|
|||
yEnd={yEnd}
|
||||
text={textVert}
|
||||
scale={scale}
|
||||
color={color}/>
|
||||
style={style}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -541,9 +541,9 @@ function AddHorizontalSelfDimension(
|
|||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
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];
|
||||
|
@ -561,7 +561,7 @@ function AddHorizontalSelfDimension(
|
|||
yEnd={yDim}
|
||||
text={text}
|
||||
scale={scale}
|
||||
color={color}/>
|
||||
style={style}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -570,9 +570,9 @@ function AddHorizontalSelfMarginsDimension(
|
|||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
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}`;
|
||||
|
@ -591,7 +591,7 @@ function AddHorizontalSelfMarginsDimension(
|
|||
yEnd={yDim}
|
||||
text={text}
|
||||
scale={scale}
|
||||
color={color}/>
|
||||
style={style}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -613,7 +613,7 @@ function AddHorizontalSelfMarginsDimension(
|
|||
yEnd={yDim}
|
||||
text={text}
|
||||
scale={scale}
|
||||
color={color}/>
|
||||
style={style}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -624,9 +624,9 @@ function AddVerticalSelfMarginDimension(
|
|||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
color: string
|
||||
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}`;
|
||||
|
@ -650,7 +650,7 @@ function AddVerticalSelfMarginDimension(
|
|||
yEnd={yEnd}
|
||||
text={textVert}
|
||||
scale={scale}
|
||||
color={color}/>
|
||||
style={style}/>
|
||||
);
|
||||
}
|
||||
const bottom = container.properties.margin.bottom;
|
||||
|
@ -676,7 +676,44 @@ function AddVerticalSelfMarginDimension(
|
|||
yEnd={yEnd}
|
||||
text={textVert}
|
||||
scale={scale}
|
||||
color={color}/>
|
||||
style={style}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function AddHorizontalSymbolDimension(
|
||||
symbol: ISymbolModel,
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number,
|
||||
depth: number
|
||||
): void {
|
||||
const width = symbol.x + (symbol.width / 2);
|
||||
|
||||
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();
|
||||
|
||||
// TODO: Put this in default.ts
|
||||
const defaultDimensionSymbolStyle: IDimensionStyle = {
|
||||
color: 'black'
|
||||
};
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={0}
|
||||
yStart={-offset}
|
||||
xEnd={width}
|
||||
yEnd={-offset}
|
||||
text={text}
|
||||
scale={scale}
|
||||
style={defaultDimensionSymbolStyle}/>
|
||||
);
|
||||
}
|
||||
|
|
54
src/Components/SVG/Elements/Selector/Selector.tsx
Normal file
54
src/Components/SVG/Elements/Selector/Selector.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import '../Selector.scss';
|
||||
import * as React from 'react';
|
||||
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
|
||||
|
||||
interface ISelectorProps {
|
||||
text: string
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
scale: number
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export function Selector({ text, x, y, width, height, scale, style: overrideStyle }: ISelectorProps): JSX.Element {
|
||||
const xText = x + width / 2;
|
||||
const yText = y + height / 2;
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
stroke: '#3B82F6',
|
||||
strokeWidth: 4 / scale,
|
||||
fillOpacity: 0,
|
||||
transitionProperty: 'all',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transitionDuration: '150ms',
|
||||
animation: 'fadein 750ms ease-in alternate infinite',
|
||||
...overrideStyle
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
style={style}
|
||||
>
|
||||
</rect>
|
||||
{SHOW_SELECTOR_TEXT
|
||||
? <text
|
||||
x={xText}
|
||||
y={yText}
|
||||
style={{
|
||||
transform: `scale(${1 / scale}) translateX(-50%)`,
|
||||
transformBox: 'fill-box'
|
||||
}}
|
||||
>
|
||||
{ text }
|
||||
</text>
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import '../Selector.scss';
|
||||
import * as React from 'react';
|
||||
import { type IContainerModel } from '../../../../Interfaces/IContainerModel';
|
||||
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
|
||||
import { GetAbsolutePosition } from '../../../../utils/itertools';
|
||||
import { RemoveMargin } from '../../../../utils/svg';
|
||||
import { Selector } from '../Selector/Selector';
|
||||
|
||||
interface ISelectorContainerProps {
|
||||
containers: Map<string, IContainerModel>
|
||||
|
@ -33,41 +33,14 @@ export function SelectorContainer(props: ISelectorContainerProps): JSX.Element {
|
|||
props.selected.properties.margin.right
|
||||
));
|
||||
|
||||
const xText = x + width / 2;
|
||||
const yText = y + height / 2;
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
stroke: '#3B82F6',
|
||||
strokeWidth: 4 / scale,
|
||||
fillOpacity: 0,
|
||||
transitionProperty: 'all',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transitionDuration: '150ms',
|
||||
animation: 'fadein 750ms ease-in alternate infinite'
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
style={style}
|
||||
>
|
||||
</rect>
|
||||
{SHOW_SELECTOR_TEXT
|
||||
? <text
|
||||
x={xText}
|
||||
y={yText}
|
||||
style={{
|
||||
transform: `scale(${1 / scale}) translateX(-50%)`,
|
||||
transformBox: 'fill-box'
|
||||
}}
|
||||
>
|
||||
{props.selected.properties.displayedText}
|
||||
</text>
|
||||
: null}
|
||||
</>
|
||||
<Selector
|
||||
text={props.selected.properties.displayedText}
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
scale={scale}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import '../Selector.scss';
|
||||
import * as React from 'react';
|
||||
import { SHOW_SELECTOR_TEXT, SYMBOL_MARGIN } from '../../../../utils/default';
|
||||
import { SYMBOL_MARGIN } from '../../../../utils/default';
|
||||
import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel';
|
||||
import { Selector } from '../Selector/Selector';
|
||||
|
||||
interface ISelectorSymbolProps {
|
||||
symbols: Map<string, ISymbolModel>
|
||||
|
@ -19,7 +20,7 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
|||
|
||||
const scale = (props.scale ?? 1);
|
||||
const [width, height] = [
|
||||
props.selected.width,
|
||||
props.selected.width / scale,
|
||||
props.selected.height / scale
|
||||
];
|
||||
|
||||
|
@ -29,45 +30,28 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
|||
x = -SYMBOL_MARGIN;
|
||||
y = props.selected.offset;
|
||||
} else {
|
||||
x = props.selected.offset;
|
||||
y = -SYMBOL_MARGIN - height;
|
||||
[x,y] = [
|
||||
props.selected.offset + props.selected.width / 2,
|
||||
-SYMBOL_MARGIN - height]
|
||||
}
|
||||
|
||||
const xText = x + width / 2;
|
||||
const yText = y + height / 2;
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
stroke: '#3B82F6',
|
||||
strokeWidth: 4 / scale,
|
||||
fillOpacity: 0,
|
||||
transitionProperty: 'all',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transitionDuration: '150ms',
|
||||
animation: 'fadein 750ms ease-in alternate infinite'
|
||||
transform: 'translateX(-50%)',
|
||||
transformBox: 'fill-box'
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
style={style}
|
||||
>
|
||||
</rect>
|
||||
{SHOW_SELECTOR_TEXT
|
||||
? <text
|
||||
x={xText}
|
||||
y={yText}
|
||||
style={{
|
||||
transform: `scale(${1 / scale}) translateX(-50%)`,
|
||||
transformBox: 'fill-box'
|
||||
}}
|
||||
>
|
||||
{props.selected.displayedText}
|
||||
</text>
|
||||
: null}
|
||||
</>
|
||||
<Selector
|
||||
text={props.selected.displayedText}
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
scale={scale}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +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 { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer';
|
||||
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
|
||||
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
|
||||
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
|
||||
|
@ -16,19 +15,27 @@ interface ISVGProps {
|
|||
viewerHeight: number
|
||||
width: number
|
||||
height: number
|
||||
containers: Map<string, IContainerModel>
|
||||
children: IContainerModel
|
||||
selectedContainer?: IContainerModel
|
||||
symbols: Map<string, ISymbolModel>
|
||||
selectedSymbol?: ISymbolModel
|
||||
drawParams: DrawParams
|
||||
selectContainer: (containerId: string) => void
|
||||
isComponentsOpen: boolean
|
||||
isSymbolsOpen: boolean
|
||||
}
|
||||
|
||||
export enum SelectorMode {
|
||||
Nothing,
|
||||
Containers,
|
||||
Symbols
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -54,14 +61,33 @@ 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 (selectorMode) {
|
||||
case SelectorMode.Containers:
|
||||
return <SelectorContainer
|
||||
containers={containers}
|
||||
scale={scale}
|
||||
selected={selectedContainer}
|
||||
/>;
|
||||
case SelectorMode.Symbols:
|
||||
return <SelectorSymbol
|
||||
symbols={symbols}
|
||||
scale={scale}
|
||||
selected={selectedSymbol}
|
||||
/>;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div id={ID} className={props.className}>
|
||||
<ReactSVGPanZoom
|
||||
|
@ -86,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={{
|
||||
|
@ -94,17 +123,25 @@ 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}
|
||||
{SHOW_DIMENSIONS_PER_DEPTH
|
||||
? <DepthDimensionLayer containers={props.containers} scale={scale} roots={props.children} />
|
||||
: null}
|
||||
<DimensionLayer containers={props.containers} symbols={props.symbols} scale={scale} root={props.children} />
|
||||
<SymbolLayer scale={scale} symbols={props.symbols} />
|
||||
{/* leave this at the end so it can be removed during the svg export */}
|
||||
{ props.isComponentsOpen ? <SelectorContainer containers={props.containers} scale={scale} selected={props.selectedContainer} /> : null }
|
||||
{ props.isSymbolsOpen ? <SelectorSymbol symbols={props.symbols} scale={scale} selected={props.selectedSymbol} /> : null }
|
||||
<DimensionLayer
|
||||
containers={containers}
|
||||
symbols={symbols}
|
||||
scale={scale}
|
||||
root={mainContainer}
|
||||
/>
|
||||
<SymbolLayer scale={scale} symbols={symbols} />
|
||||
<Selector />
|
||||
</svg>
|
||||
</ReactSVGPanZoom>
|
||||
</div>
|
||||
|
@ -112,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>
|
||||
);
|
||||
}
|
||||
}
|
164
src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx
Normal file
164
src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx
Normal file
|
@ -0,0 +1,164 @@
|
|||
import {
|
||||
ArrowsPointingOutIcon,
|
||||
CursorArrowRaysIcon,
|
||||
HandRaisedIcon,
|
||||
MagnifyingGlassMinusIcon,
|
||||
MagnifyingGlassPlusIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import React from 'react';
|
||||
import { fromObject, 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 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 matrix = transform(
|
||||
fromObject(fittedValue),
|
||||
translate(viewerWidth, viewerHeight),
|
||||
scale(fittingScale, fittingScale),
|
||||
translate(-viewerWidth, -viewerHeight)
|
||||
);
|
||||
|
||||
fittedValue = set(fittedValue, {
|
||||
...matrix
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue