svg-layout-designer-react/src/Components/SVG/SVG.tsx

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]);
}