146 lines
3.8 KiB
TypeScript
146 lines
3.8 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { IPoint } from '../../Interfaces/IPoint';
|
|
import { BAR_WIDTH } from '../Bar/Bar';
|
|
|
|
interface ICanvasProps {
|
|
width: number
|
|
height: number
|
|
draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
|
|
className?: string
|
|
style?: React.CSSProperties
|
|
};
|
|
|
|
function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject<HTMLCanvasElement> {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
const frameCount = useRef(0);
|
|
const translatePos = useRef({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
|
|
const startDragOffset = useRef({
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
|
|
const scale = useRef(1.0);
|
|
const scaleMultiplier = useRef(0.8);
|
|
const dragging = useRef(false);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
|
|
let animationFrameId = 0;
|
|
if (canvas === null) {
|
|
return;
|
|
}
|
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
// add event listeners to handle screen drag
|
|
function MouseDown(evt: MouseEvent): void {
|
|
dragging.current = true;
|
|
startDragOffset.current.x = evt.clientX - translatePos.current.x;
|
|
startDragOffset.current.y = evt.clientY - translatePos.current.y;
|
|
}
|
|
|
|
function MouseUp(evt: MouseEvent): void {
|
|
dragging.current = false;
|
|
}
|
|
|
|
function MouseMove(evt: MouseEvent): void {
|
|
if (!dragging.current || context === null) {
|
|
return;
|
|
}
|
|
|
|
translatePos.current.x = evt.clientX - startDragOffset.current.x;
|
|
translatePos.current.y = evt.clientY - startDragOffset.current.y;
|
|
frameCount.current++;
|
|
draw(context, frameCount.current, scale.current, translatePos.current);
|
|
}
|
|
function HandleScroll(evt: WheelEvent): void {
|
|
if (context === null) {
|
|
return;
|
|
}
|
|
|
|
if (evt.deltaY >= 0) {
|
|
scale.current *= scaleMultiplier.current;
|
|
} else {
|
|
scale.current /= scaleMultiplier.current;
|
|
}
|
|
|
|
draw(context, frameCount.current, scale.current, translatePos.current);
|
|
evt.preventDefault();
|
|
}
|
|
canvas.addEventListener('mousedown', MouseDown);
|
|
window.addEventListener('mouseup', MouseUp);
|
|
canvas.addEventListener('mousemove', MouseMove);
|
|
canvas.addEventListener('wheel', HandleScroll);
|
|
|
|
function Render(): void {
|
|
if (context === null) {
|
|
return;
|
|
}
|
|
|
|
frameCount.current++;
|
|
draw(context, frameCount.current, scale.current, translatePos.current);
|
|
animationFrameId = window.requestAnimationFrame(Render);
|
|
}
|
|
|
|
Render();
|
|
|
|
return () => {
|
|
window.cancelAnimationFrame(animationFrameId);
|
|
canvas.removeEventListener('mousedown', MouseDown);
|
|
window.removeEventListener('mouseup', MouseUp);
|
|
canvas.removeEventListener('mousemove', MouseMove);
|
|
canvas.removeEventListener('wheel', HandleScroll);
|
|
};
|
|
}, [draw]);
|
|
|
|
return canvasRef;
|
|
}
|
|
|
|
function UseSVGAutoResizer(
|
|
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
|
|
): void {
|
|
React.useEffect(() => {
|
|
function OnResize(): void {
|
|
setViewer({
|
|
viewerWidth: window.innerWidth - BAR_WIDTH,
|
|
viewerHeight: window.innerHeight
|
|
});
|
|
}
|
|
window.addEventListener('resize', OnResize);
|
|
|
|
return () => {
|
|
window.removeEventListener('resize', OnResize);
|
|
};
|
|
});
|
|
}
|
|
|
|
interface Viewer {
|
|
viewerWidth: number
|
|
viewerHeight: number
|
|
}
|
|
|
|
export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element {
|
|
const canvasRef = UseCanvas(draw);
|
|
|
|
const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
|
|
viewerWidth: width,
|
|
viewerHeight: height
|
|
});
|
|
|
|
UseSVGAutoResizer(setViewer);
|
|
|
|
return (
|
|
<canvas
|
|
ref={canvasRef}
|
|
style={style}
|
|
width={viewerWidth}
|
|
height={viewerHeight}
|
|
className={className}
|
|
/>
|
|
);
|
|
}
|