From b3338b0167051800ec59b159c56faf92951e6d32 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Fri, 17 Feb 2023 11:53:19 +0100 Subject: [PATCH] Add custom toolbar --- package.json | 1 + patches/@types+react-svg-pan-zoom+3.3.5.patch | 17 +++ src/Components/SVG/SVG.tsx | 2 + src/Components/SVG/SVGReactPanZoom/LICENSE | 21 +++ .../ui-toolbar/icon-cursor.tsx | 12 ++ .../SVGReactPanZoom/ui-toolbar/icon-fit.tsx | 12 ++ .../SVGReactPanZoom/ui-toolbar/icon-pan.tsx | 12 ++ .../ui-toolbar/icon-zoom-in.tsx | 15 ++ .../ui-toolbar/icon-zoom-out.tsx | 12 ++ .../ui-toolbar/toolbar-button.tsx | 81 +++++++++++ .../SVGReactPanZoom/ui-toolbar/toolbar.tsx | 134 ++++++++++++++++++ 11 files changed, 319 insertions(+) create mode 100644 patches/@types+react-svg-pan-zoom+3.3.5.patch create mode 100644 src/Components/SVG/SVGReactPanZoom/LICENSE create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-cursor.tsx create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-fit.tsx create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-pan.tsx create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-in.tsx create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-out.tsx create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar-button.tsx create mode 100644 src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx diff --git a/package.json b/package.json index ff96249..5cdc210 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "private": true, "version": "v1.0.0", "type": "module", + "postinstall": "npx patch-package", "scripts": { "d": "mprocs", "dev": "vite", diff --git a/patches/@types+react-svg-pan-zoom+3.3.5.patch b/patches/@types+react-svg-pan-zoom+3.3.5.patch new file mode 100644 index 0000000..b7fc8cc --- /dev/null +++ b/patches/@types+react-svg-pan-zoom+3.3.5.patch @@ -0,0 +1,17 @@ +diff --git a/node_modules/@types/react-svg-pan-zoom/index.d.ts b/node_modules/@types/react-svg-pan-zoom/index.d.ts +index a57d545..83ace9f 100644 +--- a/node_modules/@types/react-svg-pan-zoom/index.d.ts ++++ b/node_modules/@types/react-svg-pan-zoom/index.d.ts +@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa + export function fitSelection( + value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value; + +-export function fitToViewer(value: Value): Value; ++export function fitToViewer( ++ value: Value, ++ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined, ++ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined ++): Value; + + export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value; + diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index c56ce18..742bb67 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -8,6 +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'; interface ISVGProps { className?: string @@ -120,6 +121,7 @@ export function SVG(props: ISVGProps): JSX.Element { width: 120, height: 120 }} + customToolbar={Toolbar} > {children} diff --git a/src/Components/SVG/SVGReactPanZoom/LICENSE b/src/Components/SVG/SVGReactPanZoom/LICENSE new file mode 100644 index 0000000..2f083c0 --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/LICENSE @@ -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. \ No newline at end of file diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-cursor.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-cursor.tsx new file mode 100644 index 0000000..554dc8e --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-cursor.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +// credits https://materialdesignicons.com/icon/cursor-default-outline + +export function IconCursor(): JSX.Element { + return ( + + + + ); +} diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-fit.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-fit.tsx new file mode 100644 index 0000000..5ad1472 --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-fit.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +//credits https://materialdesignicons.com/icon/cursor-default-outline + +export function IconFit(): JSX.Element { + return ( + + + + ); +} diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-pan.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-pan.tsx new file mode 100644 index 0000000..ad74de5 --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-pan.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +//https://materialdesignicons.com/icon/cursor-move + +export function IconPan(): JSX.Element { + return ( + + + + ); +} diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-in.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-in.tsx new file mode 100644 index 0000000..40a2edc --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-in.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +//https://material.io/icons/#ic_zoom_in + +export function IconZoomIn(): JSX.Element { + return ( + + + + + + + ); +} diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-out.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-out.tsx new file mode 100644 index 0000000..0ccb97b --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-zoom-out.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +//https://material.io/icons/#ic_zoom_out + +export function IconZoomOut(): JSX.Element { + return ( + + + + ) +} diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar-button.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar-button.tsx new file mode 100644 index 0000000..91fed61 --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar-button.tsx @@ -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 { + 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 ( + + ); + } +} diff --git a/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx new file mode 100644 index 0000000..21d5a6b --- /dev/null +++ b/src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx @@ -0,0 +1,134 @@ +import React from 'react'; + +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 { IconCursor } from './icon-cursor'; +import { IconFit } from './icon-fit'; +import { IconPan } from './icon-pan'; +import { IconZoomIn } from './icon-zoom-in'; +import { IconZoomOut } from './icon-zoom-out'; +import { ToolbarButton } from './toolbar-button'; + +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 +} + +export function Toolbar({ + tool, + value, + onChangeValue, + onChangeTool, + activeToolColor = '#1CA6FC', + position = POSITION_RIGHT, + SVGAlignX = ALIGN_LEFT, + SVGAlignY = ALIGN_TOP +}: 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 { + onChangeValue(fitToViewer(value, SVGAlignX, SVGAlignY)); + 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 ( +
+ { handleChangeTool(event, TOOL_NONE); } }> + + + + { handleChangeTool(event, TOOL_PAN); } }> + + + + { handleChangeTool(event, TOOL_ZOOM_IN); } }> + + + + { handleChangeTool(event, TOOL_ZOOM_OUT); } }> + + + + { handleFit(event); } }> + + +
+ ); +}