import React, { useEffect, useRef } from 'react' const baseCanvasStyle: React.CSSProperties = { position: 'absolute', left: 0, top: 0 } const getDrawingContextFromCanvasRef = (canvasRef: React.MutableRefObject, ) => { if (!canvasRef || typeof canvasRef === 'function') return undefined const canvas = canvasRef.current const ctxt = canvas && canvas.getContext('2d') if (!ctxt) return undefined return ctxt } export type DrawFn = (ctxt: CanvasRenderingContext2D, data: T) => void /** * @param width Pixel width of canvas element. * @param height Pixel height of canvas element. * @param vOffsetPx Optional; pixels to reposition this canvas relative to the top of the parent element. * @param hOffsetPx Optional; pixels to reposition this canvas relative to the top of its parent element. * @param draw A function mapping a CanvasRenderingContext2D and a generically-typed backing-data object to void; * this will be called every time the backing data changes. The function should draw the input data. * @param drawData The data backing this Canvas view. */ export interface BaseCanvasProps { width: number height: number vOffsetPx?: number hOffsetPx?: number draw: DrawFn drawData: T } // the ', ' in the type parameter is so the parser knows it isn't // an unclosed HTML tag. Can also use 'extends {}' for this, but this is more idiomatic. /** * Creates a canvas object with a specified data type and draw function that draws that data type, * along with wiring to ensure the draw function is called every time the underlying data changes. * A graphical-element View in an MVC pattern. * * @param props Dimensions and positioning parameters, plus typed drawing data and drawing function. * @returns A Canvas element which automatically redraws when its content data changes. */ const BaseCanvas = (props: BaseCanvasProps) => { const { width, height, vOffsetPx, hOffsetPx, draw, drawData } = props const canvasRef = useRef(null) useEffect(() => { const ctxt = getDrawingContextFromCanvasRef(canvasRef) ctxt && ctxt.canvas && draw(ctxt, drawData) }, [draw, canvasRef, drawData]) const topPosition = vOffsetPx ? {top: vOffsetPx} : {} const leftPosition = hOffsetPx ? {left: hOffsetPx} : {} const style = {...baseCanvasStyle, ...topPosition, ...leftPosition } return } export default BaseCanvas