diff --git a/package.json b/package.json index f49dfef..cbfaf5d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "react-svg-pan-zoom": "^3.12.1", "react-window": "^1.8.8", "sweetalert2": "^11.7.1", - "sweetalert2-react-content": "^5.0.7" + "sweetalert2-react-content": "^5.0.7", + "transformation-matrix": "^2.14.0" }, "devDependencies": { "@testing-library/dom": "^8.20.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 922995b..e3b088c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,7 @@ specifiers: sweetalert2: ^11.7.1 sweetalert2-react-content: ^5.0.7 tailwindcss: ^3.2.4 + transformation-matrix: ^2.14.0 typescript: ^4.9.5 vite: ^4.1.1 vitest: ^0.28.4 @@ -53,6 +54,7 @@ dependencies: react-window: 1.8.8_biqbaboplfbrettd7655fr4n2y sweetalert2: 11.7.1 sweetalert2-react-content: 5.0.7_5cbezu6w3tvev2ldv5vdmnpfca + transformation-matrix: 2.14.0 devDependencies: '@testing-library/dom': 8.20.0 diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index 69cd0d0..90bbcc8 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value } from 'react-svg-pan-zoom'; +import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value, ALIGN_CENTER } from 'react-svg-pan-zoom'; import { Container } from './Elements/Container'; import { IContainerModel } from '../../Interfaces/IContainerModel'; import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer'; @@ -8,7 +8,7 @@ import { SymbolLayer } from './Elements/SymbolLayer'; import { type ISymbolModel } from '../../Interfaces/ISymbolModel'; import { DimensionLayer } from './Elements/DimensionLayer'; import { SelectorSymbol } from './Elements/SelectorSymbol/SelectorSymbol'; -import { Toolbar } from './SVGReactPanZoom/ui-toolbar/toolbar'; +import { IToolbarProps, Toolbar } from './SVGReactPanZoom/ui-toolbar/toolbar'; interface ISVGProps { className?: string @@ -121,7 +121,14 @@ export function SVG(props: ISVGProps): JSX.Element { width: 120, height: 120 }} - customToolbar={Toolbar} + customToolbar={(props: IToolbarProps) => ( + + )} > {children} @@ -135,7 +142,7 @@ export function SVG(props: ISVGProps): JSX.Element { } function UseFitOnce(svgViewer: React.RefObject, width: number, height: number): void { - React.useEffect(() => { + React.useCallback(() => { // TODO: Fix this svgViewer?.current?.setPointOnViewerCenter(width / 2, height / 2, 0.8); }, [svgViewer, width, height]); diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx index 39a801e..83bb4b0 100644 --- a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx @@ -1,5 +1,12 @@ -import { ArrowsPointingOutIcon, CursorArrowRaysIcon, HandRaisedIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'; +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, @@ -10,9 +17,7 @@ import { TOOL_NONE, TOOL_PAN, TOOL_ZOOM_IN, - TOOL_ZOOM_OUT, - ALIGN_LEFT, - ALIGN_TOP, + TOOL_ZOOM_OUT, ALIGN_LEFT, ALIGN_TOP, type Value, type Tool, type ALIGN_BOTTOM, @@ -22,7 +27,7 @@ import { } from 'react-svg-pan-zoom'; import { ToolbarButton } from './toolbar-button'; -interface IToolbarProps { +export interface IToolbarProps { tool: Tool value: Value onChangeValue: (value: Value) => void @@ -31,6 +36,19 @@ interface IToolbarProps { 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({ @@ -41,7 +59,8 @@ export function Toolbar({ activeToolColor = '#1CA6FC', position = POSITION_RIGHT, SVGAlignX = ALIGN_LEFT, - SVGAlignY = ALIGN_TOP + SVGAlignY = ALIGN_TOP, + fittingScale = undefined }: IToolbarProps): JSX.Element { function handleChangeTool(event: React.MouseEvent | React.TouchEvent, tool: Tool): void { onChangeTool(tool); @@ -50,7 +69,22 @@ export function Toolbar({ }; function handleFit(event: React.MouseEvent | React.TouchEvent): void { - onChangeValue(fitToViewer(value, SVGAlignX, SVGAlignY)); + 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(); };