Merged PR 375: Symbol Y
Implement y axis in symbol Related work items: #7306
This commit is contained in:
commit
9e9a5f3f52
20 changed files with 423 additions and 208 deletions
|
@ -2,10 +2,15 @@ import { Orientation } from '../../Enums/Orientation';
|
||||||
import { Position } from '../../Enums/Position';
|
import { Position } from '../../Enums/Position';
|
||||||
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, DIMENSION_MARGIN, SHOW_SELF_MARGINS_DIMENSIONS } from '../../utils/default';
|
import {
|
||||||
|
SHOW_SELF_DIMENSIONS,
|
||||||
|
SHOW_BORROWER_DIMENSIONS,
|
||||||
|
SHOW_CHILDREN_DIMENSIONS,
|
||||||
|
DIMENSION_MARGIN,
|
||||||
|
SHOW_SELF_MARGINS_DIMENSIONS, DEFAULT_DIMENSION_SYMBOL_STYLE
|
||||||
|
} from '../../utils/default';
|
||||||
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
|
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
|
||||||
import { TransformX, TransformY } from '../../utils/svg';
|
import { TransformX, TransformY } from '../../utils/svg';
|
||||||
import { type IDimensionStyle } from '../SVG/Elements/Dimension';
|
|
||||||
import { RenderDimension } from './Dimension';
|
import { RenderDimension } from './Dimension';
|
||||||
|
|
||||||
export function AddContainerDimensions(
|
export function AddContainerDimensions(
|
||||||
|
@ -90,6 +95,16 @@ export function AddSymbolDimensions(
|
||||||
scale: number,
|
scale: number,
|
||||||
depth: number
|
depth: number
|
||||||
): void {
|
): void {
|
||||||
|
if (symbol.isVertical) {
|
||||||
|
AddVerticalSymbolDimension(
|
||||||
|
ctx,
|
||||||
|
symbol,
|
||||||
|
scale,
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AddHorizontalSymbolDimension(
|
AddHorizontalSymbolDimension(
|
||||||
ctx,
|
ctx,
|
||||||
symbol,
|
symbol,
|
||||||
|
@ -100,6 +115,7 @@ export function AddSymbolDimensions(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction that call another function given the positions
|
* Fonction that call another function given the positions
|
||||||
|
* @param ctx
|
||||||
* @param dimMapped Position mapped depending on the Position enum in order:
|
* @param dimMapped Position mapped depending on the Position enum in order:
|
||||||
* [0:left, 1:bottom, 2:up, 3:right]
|
* [0:left, 1:bottom, 2:up, 3:right]
|
||||||
* @param positions List of positions
|
* @param positions List of positions
|
||||||
|
@ -569,7 +585,7 @@ function AddHorizontalSymbolDimension(
|
||||||
scale: number,
|
scale: number,
|
||||||
depth: number
|
depth: number
|
||||||
): void {
|
): void {
|
||||||
const width = symbol.x + (symbol.width / 2);
|
const width = TransformX(symbol.offset, symbol.width, symbol.config.PositionReference);
|
||||||
|
|
||||||
if (width == null || width <= 0) {
|
if (width == null || width <= 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -582,11 +598,6 @@ function AddHorizontalSymbolDimension(
|
||||||
.toFixed(0)
|
.toFixed(0)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
// TODO: Put this in default.ts
|
|
||||||
const defaultDimensionSymbolStyle: IDimensionStyle = {
|
|
||||||
color: 'black'
|
|
||||||
};
|
|
||||||
|
|
||||||
RenderDimension(ctx, {
|
RenderDimension(ctx, {
|
||||||
id,
|
id,
|
||||||
xStart: 0,
|
xStart: 0,
|
||||||
|
@ -595,6 +606,37 @@ function AddHorizontalSymbolDimension(
|
||||||
yEnd: -offset,
|
yEnd: -offset,
|
||||||
text,
|
text,
|
||||||
scale,
|
scale,
|
||||||
style: defaultDimensionSymbolStyle
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,16 +71,15 @@ export function RenderSymbols(
|
||||||
symbols: Map<string, ISymbolModel>,
|
symbols: Map<string, ISymbolModel>,
|
||||||
scale: number
|
scale: number
|
||||||
): void {
|
): void {
|
||||||
let count = 0;
|
|
||||||
symbols.forEach((symbol: ISymbolModel) => {
|
symbols.forEach((symbol: ISymbolModel) => {
|
||||||
RenderSymbol(ctx, symbol, scale);
|
RenderSymbol(ctx, symbol);
|
||||||
|
|
||||||
if (!symbol.showDimension) {
|
if (!symbol.showDimension) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddSymbolDimensions(ctx, symbol, scale, count);
|
// TODO: Implement DimensionManager
|
||||||
count++;
|
AddSymbolDimensions(ctx, symbol, scale, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { RenderSelector } from './Selector';
|
||||||
interface ISelectorProps {
|
interface ISelectorProps {
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
selected?: ISymbolModel
|
selected?: ISymbolModel
|
||||||
scale?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RenderSymbolSelector(
|
export function RenderSymbolSelector(
|
||||||
|
@ -17,15 +16,20 @@ export function RenderSymbolSelector(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scale = (props.scale ?? 1);
|
|
||||||
const [width, height] = [
|
const [width, height] = [
|
||||||
props.selected.width / scale,
|
props.selected.width,
|
||||||
props.selected.height / scale
|
props.selected.height
|
||||||
];
|
];
|
||||||
|
|
||||||
const [x, y] = [
|
let x, y: number;
|
||||||
props.selected.x + props.selected.width / 2,
|
|
||||||
-SYMBOL_MARGIN - height];
|
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;
|
const text = props.selected.displayedText;
|
||||||
RenderSelector(
|
RenderSelector(
|
||||||
|
@ -37,7 +41,7 @@ export function RenderSymbolSelector(
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
scale
|
scale: 1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../utils/default';
|
import { SYMBOL_MARGIN } from '../../utils/default';
|
||||||
|
|
||||||
const IMAGE_CACHE = new Map<string, HTMLImageElement>();
|
const IMAGE_CACHE = new Map<string, HTMLImageElement>();
|
||||||
|
|
||||||
export function RenderSymbol(
|
export function RenderSymbol(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
symbol: ISymbolModel,
|
symbol: ISymbolModel): void {
|
||||||
scale: number): void {
|
|
||||||
const href = symbol.config.Image.Base64Image ?? symbol.config.Image.Url;
|
const href = symbol.config.Image.Base64Image ?? symbol.config.Image.Url;
|
||||||
|
|
||||||
if (href === undefined) {
|
if (href === undefined) {
|
||||||
|
@ -19,28 +18,36 @@ export function RenderSymbol(
|
||||||
newImage.src = href;
|
newImage.src = href;
|
||||||
IMAGE_CACHE.set(href, newImage);
|
IMAGE_CACHE.set(href, newImage);
|
||||||
newImage.onload = () => {
|
newImage.onload = () => {
|
||||||
DrawImage(ctx, scale, newImage, symbol);
|
DrawImage(ctx, newImage, symbol);
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawImage(ctx, scale, image, symbol);
|
DrawImage(ctx, image, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawImage(
|
function DrawImage(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
scale: number,
|
|
||||||
image: HTMLImageElement,
|
image: HTMLImageElement,
|
||||||
symbol: ISymbolModel
|
symbol: ISymbolModel
|
||||||
): void {
|
): void {
|
||||||
|
let x, y: number;
|
||||||
|
if (symbol.isVertical) {
|
||||||
|
x = -SYMBOL_MARGIN - symbol.width;
|
||||||
|
y = symbol.offset;
|
||||||
|
} else {
|
||||||
|
x = symbol.offset;
|
||||||
|
y = -SYMBOL_MARGIN - symbol.height;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.fillStyle = '#000000';
|
ctx.fillStyle = '#000000';
|
||||||
const width = symbol.width / scale;
|
const width = symbol.width;
|
||||||
const height = symbol.height / scale;
|
const height = symbol.height;
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
image,
|
image,
|
||||||
symbol.x + symbol.width / 2,
|
x,
|
||||||
-SYMBOL_MARGIN - height,
|
y,
|
||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import {
|
import {
|
||||||
SHOW_BORROWER_DIMENSIONS,
|
SHOW_BORROWER_DIMENSIONS,
|
||||||
SHOW_CHILDREN_DIMENSIONS,
|
SHOW_CHILDREN_DIMENSIONS,
|
||||||
SHOW_SELF_DIMENSIONS, SHOW_SELF_MARGINS_DIMENSIONS
|
SHOW_SELF_DIMENSIONS,
|
||||||
|
SHOW_SELF_MARGINS_DIMENSIONS
|
||||||
} from '../../utils/default';
|
} from '../../utils/default';
|
||||||
import {
|
import {
|
||||||
ApplyWidthMargin,
|
ApplyWidthMargin,
|
||||||
|
@ -27,8 +28,12 @@ import { OrientationSelector } from '../RadioGroupButtons/OrientationSelector';
|
||||||
import { OrientationCheckboxes } from '../CheckboxGroupButtons/OrientationCheckboxes';
|
import { OrientationCheckboxes } from '../CheckboxGroupButtons/OrientationCheckboxes';
|
||||||
import { PositionCheckboxes } from '../CheckboxGroupButtons/PositionCheckboxes';
|
import { PositionCheckboxes } from '../CheckboxGroupButtons/PositionCheckboxes';
|
||||||
import { Category } from '../Category/Category';
|
import { Category } from '../Category/Category';
|
||||||
|
import { Orientation } from '../../Enums/Orientation';
|
||||||
|
import { FindContainerById } from '../../utils/itertools';
|
||||||
|
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
|
|
||||||
interface IContainerFormProps {
|
interface IContainerFormProps {
|
||||||
|
containers: Map<string, IContainerModel>
|
||||||
properties: IContainerProperties
|
properties: IContainerProperties
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
|
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
|
||||||
|
@ -36,6 +41,8 @@ interface IContainerFormProps {
|
||||||
|
|
||||||
export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
const categoryHeight = 'h-11';
|
const categoryHeight = 'h-11';
|
||||||
|
const parent = FindContainerById(props.containers, props.properties.parentId);
|
||||||
|
const isVertical = parent?.properties.orientation === Orientation.Vertical;
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-1 gap-y-4 items-center'>
|
<div className='grid grid-cols-1 gap-y-4 items-center'>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
|
@ -185,8 +192,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
type='number'
|
type='number'
|
||||||
min={props.properties.minWidth}
|
min={props.properties.minWidth}
|
||||||
max={props.properties.maxWidth}
|
max={props.properties.maxWidth}
|
||||||
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
|
value={(RemoveWidthMargin(props.properties.width,
|
||||||
onChange={(value) => { props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right)); }}
|
props.properties.margin.left,
|
||||||
|
props.properties.margin.right)).toString()}
|
||||||
|
onChange={(value) => {
|
||||||
|
props.onChange('width', ApplyWidthMargin(Number(value),
|
||||||
|
props.properties.margin.left,
|
||||||
|
props.properties.margin.right));
|
||||||
|
}}
|
||||||
isDisabled={props.properties.isFlex}/>
|
isDisabled={props.properties.isFlex}/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id={`${props.properties.id}-maxWidth`}
|
id={`${props.properties.id}-maxWidth`}
|
||||||
|
@ -218,8 +231,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
type='number'
|
type='number'
|
||||||
min={props.properties.minHeight}
|
min={props.properties.minHeight}
|
||||||
max={props.properties.maxHeight}
|
max={props.properties.maxHeight}
|
||||||
value={(RemoveWidthMargin(props.properties.height, props.properties.margin.top, props.properties.margin.bottom)).toString()}
|
value={(RemoveWidthMargin(props.properties.height,
|
||||||
onChange={(value) => { props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom)); }}
|
props.properties.margin.top,
|
||||||
|
props.properties.margin.bottom)).toString()}
|
||||||
|
onChange={(value) => {
|
||||||
|
props.onChange('height', ApplyWidthMargin(Number(value),
|
||||||
|
props.properties.margin.top,
|
||||||
|
props.properties.margin.bottom));
|
||||||
|
}}
|
||||||
isDisabled={props.properties.isFlex}
|
isDisabled={props.properties.isFlex}
|
||||||
/>
|
/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
|
@ -332,7 +351,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
labelText={Text({ textId: '@ContainerAlignWithSymbol' })}
|
labelText={Text({ textId: '@ContainerAlignWithSymbol' })}
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
inputs={[...props.symbols.values()].map(symbol => ({
|
inputs={[...props.symbols.values()].filter(symbol => (symbol.isVertical === isVertical)).map(symbol => ({
|
||||||
key: symbol.id,
|
key: symbol.id,
|
||||||
text: symbol.id,
|
text: symbol.id,
|
||||||
value: symbol.id
|
value: symbol.id
|
||||||
|
@ -366,7 +385,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='color'
|
type='color'
|
||||||
value={props.properties.dimensionOptions.selfDimensions.color}
|
value={props.properties.dimensionOptions.selfDimensions.color ?? '#000000'}
|
||||||
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/>
|
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id={`${props.properties.id}-selfDimensions-width`}
|
id={`${props.properties.id}-selfDimensions-width`}
|
||||||
|
@ -406,7 +425,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='color'
|
type='color'
|
||||||
value={props.properties.dimensionOptions.selfMarginsDimensions.color}
|
value={props.properties.dimensionOptions.selfMarginsDimensions.color ?? '#000000'}
|
||||||
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/>
|
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id={`${props.properties.id}-selfMarginsDimensions-width`}
|
id={`${props.properties.id}-selfMarginsDimensions-width`}
|
||||||
|
@ -446,7 +465,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='color'
|
type='color'
|
||||||
value={props.properties.dimensionOptions.childrenDimensions.color}
|
value={props.properties.dimensionOptions.childrenDimensions.color ?? '#000000'}
|
||||||
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/>
|
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id={`${props.properties.id}-childrenDimensions-width`}
|
id={`${props.properties.id}-childrenDimensions-width`}
|
||||||
|
@ -496,7 +515,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='color'
|
type='color'
|
||||||
value={props.properties.dimensionOptions.dimensionWithMarks.color}
|
value={props.properties.dimensionOptions.dimensionWithMarks.color ?? '#000000'}
|
||||||
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/>
|
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id={`${props.properties.id}-dimensionWithMarks-width`}
|
id={`${props.properties.id}-dimensionWithMarks-width`}
|
||||||
|
@ -531,16 +550,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
heightClass={`${categoryHeight}`}
|
heightClass={`${categoryHeight}`}
|
||||||
>
|
>
|
||||||
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
|
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
|
||||||
<TextInputGroup
|
<InputGroup
|
||||||
id={`${props.properties.id}-stroke`}
|
|
||||||
labelText={Text({ textId: '@StyleStroke' })}
|
labelText={Text({ textId: '@StyleStroke' })}
|
||||||
inputKey='stroke'
|
inputKey={`${props.properties.id}-stroke`}
|
||||||
labelClassName='col-span-2'
|
labelClassName='col-span-2'
|
||||||
inputClassName='col-span-3'
|
inputClassName='col-span-3'
|
||||||
type='string'
|
type='color'
|
||||||
value={props.properties.style.stroke ?? 'black'}
|
value={props.properties.style.stroke ?? '#000000'}
|
||||||
onChange={(value) => { props.onChange('stroke', value, PropertyType.Style); }}
|
onChange={(e) => { props.onChange('stroke', e.target.value, PropertyType.Style); }}/>
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelKey={`${props.properties.id}-strokeOpacity`}
|
labelKey={`${props.properties.id}-strokeOpacity`}
|
||||||
labelText={Text({ textId: '@StyleStrokeOpacity' })}
|
labelText={Text({ textId: '@StyleStrokeOpacity' })}
|
||||||
|
@ -564,16 +581,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
value={(props.properties.style.strokeWidth ?? 1).toString()}
|
value={(props.properties.style.strokeWidth ?? 1).toString()}
|
||||||
onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
|
onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
|
||||||
/>
|
/>
|
||||||
<TextInputGroup
|
<InputGroup
|
||||||
id={`${props.properties.id}-fill`}
|
|
||||||
labelText={Text({ textId: '@StyleFill' })}
|
labelText={Text({ textId: '@StyleFill' })}
|
||||||
inputKey='fill'
|
inputKey={`${props.properties.id}-fill`}
|
||||||
labelClassName='col-span-2'
|
labelClassName='col-span-2'
|
||||||
inputClassName='col-span-3'
|
inputClassName='col-span-3'
|
||||||
type='string'
|
type='color'
|
||||||
value={props.properties.style.fill ?? 'black'}
|
value={props.properties.style.fill ?? '#000000'}
|
||||||
onChange={(value) => { props.onChange('fill', value, PropertyType.Style); }}
|
onChange={(e) => { props.onChange('fill', e.target.value, PropertyType.Style); }}/>
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelKey={`${props.properties.id}-fillOpacity`}
|
labelKey={`${props.properties.id}-fillOpacity`}
|
||||||
labelText={Text({ textId: '@StyleFillOpacity' })}
|
labelText={Text({ textId: '@StyleFillOpacity' })}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
import { type PropertyType } from '../../Enums/PropertyType';
|
||||||
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
import { type IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { ContainerForm } from './ContainerForm';
|
import { ContainerForm } from './ContainerForm';
|
||||||
|
import { type IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
|
|
||||||
interface IPropertiesProps {
|
interface IPropertiesProps {
|
||||||
|
containers: Map<string, IContainerModel>
|
||||||
properties?: IContainerProperties
|
properties?: IContainerProperties
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
|
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
|
||||||
|
@ -18,6 +20,7 @@ export function ContainerProperties(props: IPropertiesProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='h-full p-3 bg-slate-200 overflow-y-auto'>
|
<div className='h-full p-3 bg-slate-200 overflow-y-auto'>
|
||||||
<ContainerForm
|
<ContainerForm
|
||||||
|
containers={props.containers}
|
||||||
properties={props.properties}
|
properties={props.properties}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
onChange={props.onChange} />
|
onChange={props.onChange} />
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
import { type IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
import { type IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { GetDefaultSymbolModel } from '../../../utils/default';
|
import { GetDefaultSymbolModel } from '../../../utils/default';
|
||||||
import { FindContainerById } from '../../../utils/itertools';
|
import { FindContainerById } from '../../../utils/itertools';
|
||||||
import { RestoreX } from '../../../utils/svg';
|
import {RestoreX, RestoreY} from '../../../utils/svg';
|
||||||
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
||||||
import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor';
|
import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor';
|
||||||
import { AddContainers } from './AddContainer';
|
import { AddContainers } from './AddContainer';
|
||||||
|
@ -32,7 +32,12 @@ export function AddSymbol(
|
||||||
const newSymbols = structuredClone(current.symbols);
|
const newSymbols = structuredClone(current.symbols);
|
||||||
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig);
|
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig);
|
||||||
const containers = structuredClone(current.containers);
|
const containers = structuredClone(current.containers);
|
||||||
newSymbol.x = RestoreX(newSymbol.x, newSymbol.width, newSymbol.config.PositionReference);
|
|
||||||
|
if (newSymbol.isVertical) {
|
||||||
|
newSymbol.offset = RestoreY(newSymbol.offset, newSymbol.height, newSymbol.config.PositionReference);
|
||||||
|
} else {
|
||||||
|
newSymbol.offset = RestoreX(newSymbol.offset, newSymbol.width, newSymbol.config.PositionReference);
|
||||||
|
}
|
||||||
|
|
||||||
newSymbols.set(newSymbol.id, newSymbol);
|
newSymbols.set(newSymbol.id, newSymbol);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,33 @@
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { ApplyParentTransform, FindContainerById } from '../../../utils/itertools';
|
import { ApplyParentTransform, FindContainerById } from '../../../utils/itertools';
|
||||||
import { RestoreX, TransformX } from '../../../utils/svg';
|
import { RestoreX, RestoreY, TransformX, TransformY } from '../../../utils/svg';
|
||||||
|
|
||||||
export function ApplySymbol(containers: Map<string, IContainerModel>, container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
export function ApplySymbol(containers: Map<string, IContainerModel>,
|
||||||
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.PositionReference);
|
container: IContainerModel,
|
||||||
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.positionReference);
|
symbol: ISymbolModel): IContainerModel {
|
||||||
|
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);
|
const parent = FindContainerById(containers, container.properties.parentId);
|
||||||
let x = 0;
|
let x = 0;
|
||||||
if (parent !== undefined && parent !== null) {
|
if (parent !== undefined && parent !== null) {
|
||||||
|
@ -14,3 +36,4 @@ export function ApplySymbol(containers: Map<string, IContainerModel>, container:
|
||||||
container.properties.x = x;
|
container.properties.x = x;
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -169,6 +169,7 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
||||||
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
|
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
|
||||||
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
|
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
|
||||||
<ContainerProperties
|
<ContainerProperties
|
||||||
|
containers ={props.containers}
|
||||||
properties={props.selectedContainer?.properties}
|
properties={props.selectedContainer?.properties}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
onChange={props.onPropertyChange}
|
onChange={props.onPropertyChange}
|
||||||
|
@ -231,8 +232,10 @@ function ElementsListRow(
|
||||||
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
|
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
|
||||||
|
|
||||||
return <button type="button"
|
return <button type="button"
|
||||||
className={`transition-all border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre
|
className={`transition-all border-blue-500
|
||||||
text-left text-sm font-medium flex items-center align-middle group ${container.properties.type} ${buttonSelectedClass}`}
|
hover:shadow-lg elements-sidebar-row whitespace-pre
|
||||||
|
text-left text-sm font-medium flex items-center align-middle group
|
||||||
|
${container.properties.type} ${buttonSelectedClass}`}
|
||||||
id={key}
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
style={style}
|
style={style}
|
||||||
|
|
|
@ -31,7 +31,7 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
|
||||||
export function Dimension(props: IDimensionProps): JSX.Element {
|
export function Dimension(props: IDimensionProps): JSX.Element {
|
||||||
const scale = props.scale ?? 1;
|
const scale = props.scale ?? 1;
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
stroke: props.style.color,
|
stroke: props.style.color ?? '#000000',
|
||||||
strokeWidth: (props.style.width ?? 2) / scale,
|
strokeWidth: (props.style.width ?? 2) / scale,
|
||||||
strokeDasharray: props.style.dashArray
|
strokeDasharray: props.style.dashArray
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||||
import { Orientation } from '../../../Enums/Orientation';
|
import { Orientation } from '../../../Enums/Orientation';
|
||||||
import { Position } from '../../../Enums/Position';
|
import { Position } from '../../../Enums/Position';
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_DIMENSION_SYMBOL_STYLE,
|
||||||
DIMENSION_MARGIN,
|
DIMENSION_MARGIN,
|
||||||
SHOW_BORROWER_DIMENSIONS,
|
SHOW_BORROWER_DIMENSIONS,
|
||||||
SHOW_CHILDREN_DIMENSIONS,
|
SHOW_CHILDREN_DIMENSIONS,
|
||||||
|
@ -10,7 +11,7 @@ import {
|
||||||
} from '../../../utils/default';
|
} from '../../../utils/default';
|
||||||
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
|
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
|
||||||
import { TransformX, TransformY } from '../../../utils/svg';
|
import { TransformX, TransformY } from '../../../utils/svg';
|
||||||
import { Dimension, type IDimensionStyle } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
|
|
||||||
|
@ -41,14 +42,12 @@ function ActionByPosition(
|
||||||
positions.forEach((position: Position) => {
|
positions.forEach((position: Position) => {
|
||||||
const dim = dimMapped[position];
|
const dim = dimMapped[position];
|
||||||
switch (position) {
|
switch (position) {
|
||||||
|
case Position.Right:
|
||||||
case Position.Left:
|
case Position.Left:
|
||||||
case Position.Right: {
|
verticalAction(dim, false, ...params);
|
||||||
const isRight = position === Position.Right;
|
|
||||||
verticalAction(dim, isRight, ...params);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case Position.Down:
|
|
||||||
case Position.Up:
|
case Position.Up:
|
||||||
|
case Position.Down:
|
||||||
horizontalAction(dim, ...params);
|
horizontalAction(dim, ...params);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +93,8 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SHOW_SELF_MARGINS_DIMENSIONS && container.properties.dimensionOptions.selfMarginsDimensions.positions.length > 0) {
|
if (SHOW_SELF_MARGINS_DIMENSIONS &&
|
||||||
|
container.properties.dimensionOptions.selfMarginsDimensions.positions.length > 0) {
|
||||||
ActionByPosition(
|
ActionByPosition(
|
||||||
dimMapped,
|
dimMapped,
|
||||||
container.properties.dimensionOptions.selfMarginsDimensions.positions,
|
container.properties.dimensionOptions.selfMarginsDimensions.positions,
|
||||||
|
@ -127,7 +127,9 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SHOW_CHILDREN_DIMENSIONS && container.properties.dimensionOptions.childrenDimensions.positions.length > 0 && container.children.length >= 2) {
|
if (SHOW_CHILDREN_DIMENSIONS &&
|
||||||
|
container.properties.dimensionOptions.childrenDimensions.positions.length > 0 &&
|
||||||
|
container.children.length >= 2) {
|
||||||
ActionByPosition(
|
ActionByPosition(
|
||||||
dimMapped,
|
dimMapped,
|
||||||
container.properties.dimensionOptions.childrenDimensions.positions,
|
container.properties.dimensionOptions.childrenDimensions.positions,
|
||||||
|
@ -144,24 +146,76 @@ function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let startDepthSymbols: number = 0;
|
// TODO: Implement DimensionManager
|
||||||
symbols.forEach((symbol: ISymbolModel) => {
|
symbols.forEach((symbol) => {
|
||||||
if (!symbol.showDimension) {
|
if (symbol.showDimension) {
|
||||||
return;
|
if (symbol.isVertical) {
|
||||||
|
AddVerticalSymbolDimension(symbol, dimensions, scale, 0);
|
||||||
|
} else {
|
||||||
|
AddHorizontalSymbolDimension(symbol, dimensions, scale, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startDepthSymbols++;
|
|
||||||
AddHorizontalSymbolDimension(
|
|
||||||
symbol,
|
|
||||||
dimensions,
|
|
||||||
scale,
|
|
||||||
startDepthSymbols
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AddHorizontalSymbolDimension(symbol: ISymbolModel,
|
||||||
|
dimensions: React.ReactNode[],
|
||||||
|
scale: number,
|
||||||
|
depth: number
|
||||||
|
): void {
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
|
||||||
|
const text = width
|
||||||
|
.toFixed(0)
|
||||||
|
.toString();
|
||||||
|
dimensions.push(
|
||||||
|
<Dimension
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
xStart={0}
|
||||||
|
yStart={-offset}
|
||||||
|
xEnd={width}
|
||||||
|
yEnd={-offset}
|
||||||
|
text={text}
|
||||||
|
scale={scale}
|
||||||
|
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}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A layer containing all dimension
|
* A layer containing all dimension
|
||||||
* @param props
|
* @param props
|
||||||
|
@ -195,8 +249,12 @@ function AddHorizontalChildrenDimension(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
let xChildrenStart = TransformX(lastChild.properties.x,
|
||||||
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
lastChild.properties.width,
|
||||||
|
lastChild.properties.positionReference);
|
||||||
|
let xChildrenEnd = TransformX(lastChild.properties.x,
|
||||||
|
lastChild.properties.width,
|
||||||
|
lastChild.properties.positionReference);
|
||||||
|
|
||||||
// Find the min and max
|
// Find the min and max
|
||||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||||
|
@ -258,8 +316,12 @@ function AddVerticalChildrenDimension(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
let yChildrenStart = TransformY(lastChild.properties.y,
|
||||||
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
lastChild.properties.height,
|
||||||
|
lastChild.properties.positionReference);
|
||||||
|
let yChildrenEnd = TransformY(lastChild.properties.y,
|
||||||
|
lastChild.properties.height,
|
||||||
|
lastChild.properties.positionReference);
|
||||||
|
|
||||||
// Find the min and max
|
// Find the min and max
|
||||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||||
|
@ -611,40 +673,3 @@ function AddVerticalSelfMarginDimension(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import '../Selector.scss';
|
import '../Selector.scss';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { SYMBOL_MARGIN } from '../../../../utils/default';
|
import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../../../utils/default';
|
||||||
import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel';
|
||||||
import { Selector } from '../Selector/Selector';
|
import { Selector } from '../Selector/Selector';
|
||||||
|
|
||||||
interface ISelectorSymbolProps {
|
interface ISelectorSymbolProps {
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
selected?: ISymbolModel
|
selected?: ISymbolModel
|
||||||
scale?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
||||||
|
@ -18,30 +17,27 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const scale = (props.scale ?? 1);
|
let x, y: number;
|
||||||
const [width, height] = [
|
|
||||||
props.selected.width / scale,
|
|
||||||
props.selected.height / scale
|
|
||||||
];
|
|
||||||
|
|
||||||
const [x, y] = [
|
const scaledHeight = props.selected.height;
|
||||||
props.selected.x + props.selected.width / 2,
|
const scaledWidth = props.selected.width;
|
||||||
-SYMBOL_MARGIN - height];
|
|
||||||
|
|
||||||
const style: React.CSSProperties = {
|
if (props.selected.isVertical) {
|
||||||
transform: 'translateX(-50%)',
|
x = -SYMBOL_MARGIN - props.selected.width;
|
||||||
transformBox: 'fill-box'
|
y = props.selected.offset;
|
||||||
};
|
} else {
|
||||||
|
x = props.selected.offset;
|
||||||
|
y = -SYMBOL_MARGIN - props.selected.height;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Selector
|
<Selector
|
||||||
text={props.selected.displayedText}
|
text={props.selected.displayedText}
|
||||||
x={x}
|
x={x}
|
||||||
y={y}
|
y={y}
|
||||||
width={width}
|
width={scaledWidth}
|
||||||
height={height}
|
height={scaledHeight}
|
||||||
scale={scale}
|
scale={1}
|
||||||
style={style}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Interweave } from 'interweave';
|
import { Interweave } from 'interweave';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../../utils/default';
|
import { SYMBOL_MARGIN } from '../../../utils/default';
|
||||||
|
|
||||||
interface ISymbolProps {
|
interface ISymbolProps {
|
||||||
model: ISymbolModel
|
model: ISymbolModel
|
||||||
|
@ -12,11 +12,22 @@ export function Symbol(props: ISymbolProps): JSX.Element {
|
||||||
const href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url;
|
const href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url;
|
||||||
const hasSVG = props.model.config.Image.Svg !== undefined &&
|
const hasSVG = props.model.config.Image.Svg !== undefined &&
|
||||||
props.model.config.Image.Svg !== null;
|
props.model.config.Image.Svg !== null;
|
||||||
|
|
||||||
|
let x, y: number;
|
||||||
|
|
||||||
|
if (props.model.isVertical) {
|
||||||
|
x = -SYMBOL_MARGIN - props.model.width;
|
||||||
|
y = props.model.offset;
|
||||||
|
} else {
|
||||||
|
x = props.model.offset;
|
||||||
|
y = -SYMBOL_MARGIN - props.model.height;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasSVG) {
|
if (hasSVG) {
|
||||||
return (
|
return (
|
||||||
<g
|
<g
|
||||||
x={props.model.x}
|
x={x}
|
||||||
y={-DIMENSION_MARGIN / props.scale}
|
y={y}
|
||||||
>
|
>
|
||||||
<Interweave
|
<Interweave
|
||||||
noWrap={true}
|
noWrap={true}
|
||||||
|
@ -31,14 +42,9 @@ export function Symbol(props: ISymbolProps): JSX.Element {
|
||||||
<image
|
<image
|
||||||
href={href}
|
href={href}
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
style={{
|
x={x}
|
||||||
fill: 'none',
|
y={y}
|
||||||
transform: 'translateY(-100%) translateX(-50%)',
|
height={props.model.height}
|
||||||
transformBox: 'fill-box'
|
width={props.model.width} />
|
||||||
}}
|
|
||||||
x={props.model.x + props.model.width / 2}
|
|
||||||
y={-SYMBOL_MARGIN}
|
|
||||||
height={props.model.height / props.scale}
|
|
||||||
width={props.model.width / props.scale} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { RestoreX, TransformX } from '../../utils/svg';
|
import { RestoreX, RestoreY, TransformX, TransformY } from '../../utils/svg';
|
||||||
import { InputGroup } from '../InputGroup/InputGroup';
|
import { InputGroup } from '../InputGroup/InputGroup';
|
||||||
import { TextInputGroup } from '../InputGroup/TextInputGroup';
|
import { TextInputGroup } from '../InputGroup/TextInputGroup';
|
||||||
import { Text } from '../Text/Text';
|
import { Text } from '../Text/Text';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
|
||||||
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
||||||
|
import { type PositionReference } from '../../Enums/PositionReference';
|
||||||
|
|
||||||
interface ISymbolFormProps {
|
interface ISymbolFormProps {
|
||||||
symbol: ISymbolModel
|
symbol: ISymbolModel
|
||||||
|
@ -13,6 +13,29 @@ interface ISymbolFormProps {
|
||||||
onChange: (key: string, value: string | number | boolean) => void
|
onChange: (key: string, value: string | number | boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Restore(offset: number,
|
||||||
|
isVertical: boolean,
|
||||||
|
height: number,
|
||||||
|
width: number,
|
||||||
|
position: PositionReference | undefined): number {
|
||||||
|
if (isVertical) {
|
||||||
|
return RestoreY(offset, height, position);
|
||||||
|
} else {
|
||||||
|
return RestoreX(offset, width, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function Transform(offset: number,
|
||||||
|
isVertical: boolean,
|
||||||
|
height: number,
|
||||||
|
width: number,
|
||||||
|
position: PositionReference | undefined): number {
|
||||||
|
if (isVertical) {
|
||||||
|
return TransformY(offset, height, position);
|
||||||
|
} else {
|
||||||
|
return TransformX(offset, width, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-2 gap-y-4'>
|
<div className='grid grid-cols-2 gap-y-4'>
|
||||||
|
@ -32,16 +55,34 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.symbol.displayedText}
|
value={props.symbol.displayedText}
|
||||||
onChange={(value) => props.onChange('displayedText', value)} />
|
onChange={(value) => { props.onChange('displayedText', value); }} />
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id='x'
|
id='offset'
|
||||||
labelText={Text({ textId: '@SymbolX' })}
|
labelText={Text({ textId: '@SymbolOffset' })}
|
||||||
inputKey='x'
|
inputKey='offset'
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
value={TransformX(props.symbol.x, props.symbol.width, props.symbol.config.PositionReference).toString()}
|
value={Transform(props.symbol.offset,
|
||||||
onChange={(value) => props.onChange('x', RestoreX(Number(value), props.symbol.width, props.symbol.config.PositionReference))} />
|
props.symbol.isVertical,
|
||||||
|
props.symbol.height,
|
||||||
|
props.symbol.width,
|
||||||
|
props.symbol.config.PositionReference).toString()}
|
||||||
|
onChange={(value) => {
|
||||||
|
props.onChange('offset',
|
||||||
|
Restore(Number(value),
|
||||||
|
props.symbol.isVertical,
|
||||||
|
props.symbol.height,
|
||||||
|
props.symbol.width,
|
||||||
|
props.symbol.config.PositionReference));
|
||||||
|
}} />
|
||||||
|
<ToggleButton
|
||||||
|
labelText={Text({ textId: '@IsVertical' })}
|
||||||
|
inputKey='isVertical'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
checked={props.symbol.isVertical}
|
||||||
|
onChange={(e) => { props.onChange('isVertical', e.target.checked); }}/>
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id='height'
|
id='height'
|
||||||
labelText={Text({ textId: '@SymbolHeight' })}
|
labelText={Text({ textId: '@SymbolHeight' })}
|
||||||
|
@ -51,7 +92,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={props.symbol.height.toString()}
|
value={props.symbol.height.toString()}
|
||||||
onChange={(value) => props.onChange('height', Number(value))} />
|
onChange={(value) => { props.onChange('height', Number(value)); }} />
|
||||||
<TextInputGroup
|
<TextInputGroup
|
||||||
id='width'
|
id='width'
|
||||||
labelText={Text({ textId: '@SymbolWidth' })}
|
labelText={Text({ textId: '@SymbolWidth' })}
|
||||||
|
@ -61,14 +102,14 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={props.symbol.width.toString()}
|
value={props.symbol.width.toString()}
|
||||||
onChange={(value) => props.onChange('width', Number(value))} />
|
onChange={(value) => { props.onChange('width', Number(value)); }} />
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
labelText={Text({ textId: '@ShowDimension' })}
|
labelText={Text({ textId: '@ShowDimension' })}
|
||||||
inputKey='showDimension'
|
inputKey='showDimension'
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
checked={props.symbol.showDimension}
|
checked={props.symbol.showDimension}
|
||||||
onChange={(e) => props.onChange('showDimension', e.target.checked)}/>
|
onChange={(e) => { props.onChange('showDimension', e.target.checked); }}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { PositionReference } from '../Enums/PositionReference';
|
import { type PositionReference } from '../Enums/PositionReference';
|
||||||
import { IAvailableContainer } from './IAvailableContainer';
|
import { type IAvailableContainer } from './IAvailableContainer';
|
||||||
import { IImage } from './IImage';
|
import { type IImage } from './IImage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model of available symbol to configure the application */
|
* Model of available symbol to configure the application */
|
||||||
|
@ -13,9 +13,9 @@ export interface IAvailableSymbol {
|
||||||
/** displayed text */
|
/** displayed text */
|
||||||
DisplayedText?: string
|
DisplayedText?: string
|
||||||
|
|
||||||
X?: number
|
isVertical?: boolean
|
||||||
|
|
||||||
Y?: number
|
offset?: number
|
||||||
|
|
||||||
Width?: number
|
Width?: number
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ export interface ISymbolModel {
|
||||||
/** Configuration of the symbol */
|
/** Configuration of the symbol */
|
||||||
config: IAvailableSymbol
|
config: IAvailableSymbol
|
||||||
|
|
||||||
/** Horizontal offset */
|
isVertical: boolean
|
||||||
x: number
|
|
||||||
|
|
||||||
// TODO: Implement Y and verticality
|
/** offset */
|
||||||
|
offset: number
|
||||||
|
|
||||||
/** Width */
|
/** Width */
|
||||||
width: number
|
width: number
|
||||||
|
|
|
@ -76,7 +76,9 @@
|
||||||
|
|
||||||
"@SymbolName": "Name",
|
"@SymbolName": "Name",
|
||||||
"@SymbolDisplayedText": "Displayed text",
|
"@SymbolDisplayedText": "Displayed text",
|
||||||
"@SymbolX": "x",
|
"@SymbolOffset" : "Offset",
|
||||||
|
"@IsVertical" : "Vertical",
|
||||||
|
"@ShowDimension" : "Dimension",
|
||||||
"@SymbolHeight": "Height",
|
"@SymbolHeight": "Height",
|
||||||
"@SymbolWidth": "Width"
|
"@SymbolWidth": "Width"
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,9 @@
|
||||||
|
|
||||||
"@SymbolName": "Nom",
|
"@SymbolName": "Nom",
|
||||||
"@SymbolDisplayedText": "Texte affiché",
|
"@SymbolDisplayedText": "Texte affiché",
|
||||||
"@SymbolX": "x",
|
"@SymbolOffset" : "Décalage",
|
||||||
|
"@IsVertical" : "Vertical",
|
||||||
|
"@ShowDimension" : "Cotation",
|
||||||
"@SymbolHeight": "Hauteur",
|
"@SymbolHeight": "Hauteur",
|
||||||
"@SymbolWidth": "Largeur"
|
"@SymbolWidth": "Largeur"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { type ISymbolModel } from '../Interfaces/ISymbolModel';
|
||||||
import { Orientation } from '../Enums/Orientation';
|
import { Orientation } from '../Enums/Orientation';
|
||||||
import { AppState } from '../Enums/AppState';
|
import { AppState } from '../Enums/AppState';
|
||||||
import { type IDimensionOptions } from '../Interfaces/IDimensionOptions';
|
import { type IDimensionOptions } from '../Interfaces/IDimensionOptions';
|
||||||
|
import { type IDimensionStyle } from '../Components/SVG/Elements/Dimension';
|
||||||
|
|
||||||
/// EDITOR DEFAULTS ///
|
/// EDITOR DEFAULTS ///
|
||||||
|
|
||||||
|
@ -69,10 +70,14 @@ export const SHOW_BORROWER_DIMENSIONS = true;
|
||||||
export const DIMENSION_MARGIN = 50;
|
export const DIMENSION_MARGIN = 50;
|
||||||
export const SYMBOL_MARGIN = 25;
|
export const SYMBOL_MARGIN = 25;
|
||||||
export const NOTCHES_LENGTH = 10;
|
export const NOTCHES_LENGTH = 10;
|
||||||
|
export const DEFAULT_DIMENSION_SYMBOL_STYLE: IDimensionStyle = {
|
||||||
|
color: '#000000'
|
||||||
|
};
|
||||||
|
|
||||||
/// SYMBOL DEFAULTS ///
|
/// SYMBOL DEFAULTS ///
|
||||||
|
|
||||||
export const DEFAULT_SYMBOL_WIDTH = 32;
|
export const DEFAULT_SYMBOL_WIDTH = 32;
|
||||||
|
export const DEFAULT_SYMBOL_IS_VERTICAL = false;
|
||||||
export const DEFAULT_SYMBOL_HEIGHT = 32;
|
export const DEFAULT_SYMBOL_HEIGHT = 32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -270,7 +275,7 @@ export function GetDefaultContainerProps(type: string,
|
||||||
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
|
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
|
||||||
dimensionOptions: {
|
dimensionOptions: {
|
||||||
childrenDimensions: {
|
childrenDimensions: {
|
||||||
...containerConfig.DimensionOptions?.selfDimensions,
|
...containerConfig.DimensionOptions?.childrenDimensions,
|
||||||
positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? []
|
positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? []
|
||||||
},
|
},
|
||||||
selfDimensions: {
|
selfDimensions: {
|
||||||
|
@ -304,7 +309,8 @@ export function GetDefaultSymbolModel(name: string,
|
||||||
displayedText: symbolConfig.DisplayedText ?? id,
|
displayedText: symbolConfig.DisplayedText ?? id,
|
||||||
type: name,
|
type: name,
|
||||||
config: structuredClone(symbolConfig),
|
config: structuredClone(symbolConfig),
|
||||||
x: 0,
|
offset: 0,
|
||||||
|
isVertical: symbolConfig.isVertical ?? DEFAULT_SYMBOL_IS_VERTICAL,
|
||||||
width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH,
|
width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH,
|
||||||
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
|
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
|
||||||
linkedContainers: new Set(),
|
linkedContainers: new Set(),
|
||||||
|
|
|
@ -81,7 +81,7 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'red',
|
stroke: '#ff0000',
|
||||||
fill: '#d3c9b7',
|
fill: '#d3c9b7',
|
||||||
},
|
},
|
||||||
IsFlex: true,
|
IsFlex: true,
|
||||||
|
@ -96,7 +96,7 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'red',
|
stroke: '#ff0000',
|
||||||
fill: '#d3c9b7',
|
fill: '#d3c9b7',
|
||||||
},
|
},
|
||||||
IsFlex: true,
|
IsFlex: true,
|
||||||
|
@ -115,8 +115,8 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'green',
|
stroke: '#00ff00',
|
||||||
fill: 'white'
|
fill: '#ffffff'
|
||||||
},
|
},
|
||||||
Category: "Stuff",
|
Category: "Stuff",
|
||||||
IsFlex: true,
|
IsFlex: true,
|
||||||
|
@ -258,8 +258,8 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'blue',
|
stroke: '#0000ff',
|
||||||
fill: 'blue',
|
fill: '#0000ff',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -270,8 +270,8 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'red',
|
stroke: '#ff0000',
|
||||||
fill: 'red',
|
fill: '#ff0000',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -286,8 +286,8 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Svg: null,
|
Svg: null,
|
||||||
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
|
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
|
||||||
},
|
},
|
||||||
Name: 'Poteau structure',
|
Name: 'Poteau CenterCenter',
|
||||||
PositionReference: 1,
|
PositionReference: 4,
|
||||||
AssociatedContainer: {
|
AssociatedContainer: {
|
||||||
Type: 'Montant'
|
Type: 'Montant'
|
||||||
}
|
}
|
||||||
|
@ -297,13 +297,49 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Height: 32,
|
Height: 32,
|
||||||
Image: {
|
Image: {
|
||||||
Base64Image: null,
|
Base64Image: null,
|
||||||
Name: 'Arrow',
|
Name: 'ArrowTopLeft',
|
||||||
Svg: null,
|
Svg: null,
|
||||||
Url: './images/arrow-down.svg'
|
Url: './images/arrow-down.svg'
|
||||||
},
|
},
|
||||||
Name: 'Arrow',
|
Name: 'ArrowTopLeft',
|
||||||
PositionReference: 0
|
PositionReference: 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Width: 32,
|
||||||
|
Height: 32,
|
||||||
|
Image: {
|
||||||
|
Base64Image: null,
|
||||||
|
Name: 'ArrowTopRight',
|
||||||
|
Svg: null,
|
||||||
|
Url: './images/arrow-down.svg'
|
||||||
|
},
|
||||||
|
Name: 'ArrowTopRight',
|
||||||
|
PositionReference: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Width: 32,
|
||||||
|
Height: 32,
|
||||||
|
Image: {
|
||||||
|
Base64Image: null,
|
||||||
|
Name: 'ArrowCenterRight',
|
||||||
|
Svg: null,
|
||||||
|
Url: './images/arrow-down.svg'
|
||||||
|
},
|
||||||
|
Name: 'ArrowCenterRight',
|
||||||
|
PositionReference: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Width: 32,
|
||||||
|
Height: 32,
|
||||||
|
Image: {
|
||||||
|
Base64Image: null,
|
||||||
|
Name: 'ArrowBottomRight',
|
||||||
|
Svg: null,
|
||||||
|
Url: './images/arrow-down.svg'
|
||||||
|
},
|
||||||
|
Name: 'ArrowBottomRight',
|
||||||
|
PositionReference: 8
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Width: 32,
|
Width: 32,
|
||||||
Height: 32,
|
Height: 32,
|
||||||
|
@ -334,7 +370,7 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Height: 200,
|
Height: 200,
|
||||||
Orientation: 0,
|
Orientation: 0,
|
||||||
Style: {
|
Style: {
|
||||||
stroke: 'black',
|
stroke: '#000000',
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
fillOpacity: 0
|
fillOpacity: 0
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue