Add custom toolbar

This commit is contained in:
Eric NGUYEN 2023-02-17 11:53:19 +01:00
parent 4053763e44
commit b3338b0167
11 changed files with 319 additions and 0 deletions

View file

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

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

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

View file

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

View file

@ -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>
)
}

View file

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

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