155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
import * as React from 'react';
|
|
import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value, ALIGN_CENTER } from 'react-svg-pan-zoom';
|
|
import { Container } from './Elements/Container';
|
|
import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer';
|
|
import { MAX_FRAMERATE } from '../../utils/default';
|
|
import { SymbolLayer } from './Elements/SymbolLayer';
|
|
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
|
|
viewerWidth: number
|
|
viewerHeight: number
|
|
width: number
|
|
height: number
|
|
drawParams: DrawParams
|
|
selectContainer: (containerId: string) => void
|
|
}
|
|
|
|
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);
|
|
const [scale, setScale] = React.useState<number>(value.a ?? 1);
|
|
|
|
const svgViewer = React.useRef<ReactSVGPanZoom>(null);
|
|
|
|
// Framerate limiter
|
|
const delta = React.useRef(0);
|
|
const timer = React.useRef(performance.now());
|
|
const renderCounter = React.useRef(0);
|
|
// Debug: FPS counter
|
|
// const startTimer = React.useRef(Date.now());
|
|
// console.log(renderCounter.current / ((Date.now() - startTimer.current) / 1000));
|
|
|
|
UseFitOnce(svgViewer, props.width, props.height);
|
|
|
|
const xmlns = '<http://www.w3.org/2000/svg>';
|
|
const properties = {
|
|
width: props.width,
|
|
height: props.height,
|
|
xmlns
|
|
};
|
|
|
|
const children: React.ReactNode | React.ReactNode[] = <Container
|
|
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}
|
|
selected={selectedSymbol}
|
|
/>;
|
|
default:
|
|
return <></>;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div id={ID} className={props.className}>
|
|
<ReactSVGPanZoom
|
|
ref={svgViewer}
|
|
width={props.viewerWidth}
|
|
height={props.viewerHeight}
|
|
tool={tool} onChangeTool={setTool}
|
|
value={value} onChangeValue={(value: Value) => {
|
|
// Framerate limiter
|
|
const newTimer = performance.now();
|
|
delta.current += (newTimer - timer.current) / 1000;
|
|
timer.current = newTimer;
|
|
if (delta.current <= (1 / MAX_FRAMERATE)) {
|
|
return;
|
|
}
|
|
|
|
renderCounter.current = renderCounter.current + 1;
|
|
delta.current = delta.current % (1 / MAX_FRAMERATE);
|
|
setValue(value);
|
|
}}
|
|
onZoom={(event: unknown) => {
|
|
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={{
|
|
position: 'left',
|
|
background: '#616264',
|
|
width: 120,
|
|
height: 120
|
|
}}
|
|
customToolbar={(props: IToolbarProps) => (
|
|
<Toolbar
|
|
{...props}
|
|
SVGAlignX={ALIGN_CENTER}
|
|
SVGAlignY={ALIGN_CENTER}
|
|
fittingScale={0.8}
|
|
/>
|
|
)}
|
|
>
|
|
<svg {...properties}>
|
|
{children}
|
|
<DimensionLayer
|
|
containers={containers}
|
|
symbols={symbols}
|
|
scale={scale}
|
|
root={mainContainer}
|
|
/>
|
|
<SymbolLayer scale={scale} symbols={symbols} />
|
|
<Selector />
|
|
</svg>
|
|
</ReactSVGPanZoom>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void {
|
|
React.useCallback(() => {
|
|
// TODO: Fix this
|
|
svgViewer?.current?.setPointOnViewerCenter(width / 2, height / 2, 0.8);
|
|
}, [svgViewer, width, height]);
|
|
}
|