Add custom toolbar
This commit is contained in:
parent
4053763e44
commit
b3338b0167
11 changed files with 319 additions and 0 deletions
|
@ -3,6 +3,7 @@
|
|||
"private": true,
|
||||
"version": "v1.0.0",
|
||||
"type": "module",
|
||||
"postinstall": "npx patch-package",
|
||||
"scripts": {
|
||||
"d": "mprocs",
|
||||
"dev": "vite",
|
||||
|
|
17
patches/@types+react-svg-pan-zoom+3.3.5.patch
Normal file
17
patches/@types+react-svg-pan-zoom+3.3.5.patch
Normal file
|
@ -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;
|
||||
|
|
@ -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}
|
||||
>
|
||||
<svg {...properties}>
|
||||
{children}
|
||||
|
|
21
src/Components/SVG/SVGReactPanZoom/LICENSE
Normal file
21
src/Components/SVG/SVGReactPanZoom/LICENSE
Normal file
|
@ -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.
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
// credits https://materialdesignicons.com/icon/cursor-default-outline
|
||||
|
||||
export function IconCursor(): JSX.Element {
|
||||
return (
|
||||
<svg width={24} height={24} stroke="currentColor">
|
||||
<path
|
||||
d="M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
12
src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-fit.tsx
Normal file
12
src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-fit.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
//credits https://materialdesignicons.com/icon/cursor-default-outline
|
||||
|
||||
export function IconFit(): JSX.Element {
|
||||
return (
|
||||
<svg width={24} height={24} stroke="currentColor">
|
||||
<path
|
||||
d="M15 3l2.3 2.3-2.89 2.87 1.42 1.42L18.7 6.7 21 9V3zM3 9l2.3-2.3 2.87 2.89 1.42-1.42L6.7 5.3 9 3H3zm6 12l-2.3-2.3 2.89-2.87-1.42-1.42L5.3 17.3 3 15v6zm12-6l-2.3 2.3-2.87-2.89-1.42 1.42 2.89 2.87L15 21h6z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
12
src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-pan.tsx
Normal file
12
src/Components/SVG/SVGReactPanZoom/ui-toolbar/icon-pan.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
//https://materialdesignicons.com/icon/cursor-move
|
||||
|
||||
export function IconPan(): JSX.Element {
|
||||
return (
|
||||
<svg width={24} height={24} stroke="currentColor">
|
||||
<path
|
||||
d="M13,6V11H18V7.75L22.25,12L18,16.25V13H13V18H16.25L12,22.25L7.75,18H11V13H6V16.25L1.75,12L6,7.75V11H11V6H7.75L12,1.75L16.25,6H13Z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
//https://material.io/icons/#ic_zoom_in
|
||||
|
||||
export function IconZoomIn(): JSX.Element {
|
||||
return (
|
||||
<svg width={24} height={24} stroke="currentColor">
|
||||
<g>
|
||||
<path
|
||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
||||
<path d="M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
//https://material.io/icons/#ic_zoom_out
|
||||
|
||||
export function IconZoomOut(): JSX.Element {
|
||||
return (
|
||||
<svg width={24} height={24} stroke="currentColor">
|
||||
<path
|
||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -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<IToolbarButtonProps, IToolbarButtonState> {
|
||||
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 (
|
||||
<button
|
||||
onMouseEnter={e => { this.change(e); }}
|
||||
onMouseLeave={e => { this.change(e); }}
|
||||
|
||||
onTouchStart={e => {
|
||||
this.change(e);
|
||||
this.props.onClick(e);
|
||||
}}
|
||||
onTouchEnd={e => { this.change(e); }}
|
||||
onTouchCancel={e => { this.change(e); }}
|
||||
|
||||
onClick={this.props.onClick}
|
||||
|
||||
style={style}
|
||||
title={this.props.title}
|
||||
name={this.props.name}
|
||||
type="button"
|
||||
>{this.props.children}</button>
|
||||
);
|
||||
}
|
||||
}
|
134
src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx
Normal file
134
src/Components/SVG/SVGReactPanZoom/ui-toolbar/toolbar.tsx
Normal file
|
@ -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 (
|
||||
<div style={style} role="toolbar">
|
||||
<ToolbarButton
|
||||
toolbarPosition={position}
|
||||
active={tool === TOOL_NONE}
|
||||
activeColor={activeToolColor}
|
||||
name="unselect-tools"
|
||||
title="Selection"
|
||||
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_NONE); } }>
|
||||
<IconCursor/>
|
||||
</ToolbarButton>
|
||||
|
||||
<ToolbarButton
|
||||
toolbarPosition={position}
|
||||
active={tool === TOOL_PAN}
|
||||
activeColor={activeToolColor}
|
||||
name="select-tool-pan"
|
||||
title="Pan"
|
||||
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_PAN); } }>
|
||||
<IconPan/>
|
||||
</ToolbarButton>
|
||||
|
||||
<ToolbarButton
|
||||
toolbarPosition={position}
|
||||
active={tool === TOOL_ZOOM_IN}
|
||||
activeColor={activeToolColor}
|
||||
name="select-tool-zoom-in"
|
||||
title="Zoom in"
|
||||
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_ZOOM_IN); } }>
|
||||
<IconZoomIn/>
|
||||
</ToolbarButton>
|
||||
|
||||
<ToolbarButton
|
||||
toolbarPosition={position}
|
||||
active={tool === TOOL_ZOOM_OUT}
|
||||
activeColor={activeToolColor}
|
||||
name="select-tool-zoom-out"
|
||||
title="Zoom out"
|
||||
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_ZOOM_OUT); } }>
|
||||
<IconZoomOut/>
|
||||
</ToolbarButton>
|
||||
|
||||
<ToolbarButton
|
||||
toolbarPosition={position}
|
||||
active={false}
|
||||
activeColor={activeToolColor}
|
||||
name="fit-to-viewer"
|
||||
title="Fit to viewer"
|
||||
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleFit(event); } }>
|
||||
<IconFit/>
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue