module SketchToolbar = { @module("./SketchToolbar.jsx") @react.component external make: ( ~state: UseSketch.sketchState, ~onSetTool: UseSketch.tool => unit, ~onSetColor: UseSketch.color => unit, ~onClear: unit => unit, ~onSave: unit => unit, ~onClose: unit => unit, ) => React.element = "default" } type point = {x: float, y: float} @val external document: {..} = "document" external asCanvas: Dom.element => {..} = "%identity" @react.component let make = ( ~isActive: bool, ~tool: UseSketch.tool, ~color: UseSketch.color, ~onSave: unit => unit, ~onClear: unit => unit, ~onClose: unit => unit, ~onSetTool: UseSketch.tool => unit, ~onSetColor: UseSketch.color => unit, ) => { let canvasRef: React.ref> = React.useRef(Nullable.null) let isDrawing = React.useRef(false) let lastPoint = React.useRef(None) React.useEffect1(() => { switch canvasRef.current->Nullable.toOption { | None => () | Some(el) => let canvas = asCanvas(el) let parent = canvas["parentElement"] canvas["width"] = parent["offsetWidth"] canvas["height"] = parent["offsetHeight"] } None }, [isActive]) let getPos = (e: JsxEvent.Mouse.t, canvas) => { let rect = canvas["getBoundingClientRect"]() { x: Float.fromInt(JsxEvent.Mouse.clientX(e)) -. rect["left"], y: Float.fromInt(JsxEvent.Mouse.clientY(e)) -. rect["top"], } } let startDraw = (e: JsxEvent.Mouse.t) => { switch canvasRef.current->Nullable.toOption { | None => () | Some(el) => let canvas = asCanvas(el) isDrawing.current = true let pos = getPos(e, canvas) lastPoint.current = Some(pos) if tool === UseSketch.Highlighter { let ctx = canvas["getContext"]("2d") let hex = UseSketch.colorToHex(color) let width = UseSketch.toolToWidth(tool, 2.0) ctx["globalAlpha"] = 0.25 ctx["globalCompositeOperation"] = "source-over" ctx["strokeStyle"] = hex ctx["lineWidth"] = width ctx["lineCap"] = "round" ctx["lineJoin"] = "round" let _ = ctx["beginPath"]() let _ = ctx["moveTo"](pos.x, pos.y) } else if tool === UseSketch.Text { let text = canvas["ownerDocument"]["defaultView"]["prompt"]("Enter text:", "") switch Nullable.toOption(text) { | None => () | Some(t) => let ctx = canvas["getContext"]("2d") let hex = UseSketch.colorToHex(color) ctx["globalAlpha"] = 1.0 ctx["fillStyle"] = hex ctx["font"] = "bold 16px sans-serif" let _ = ctx["fillText"](t, pos.x, pos.y) } } } } let draw = (e: JsxEvent.Mouse.t) => { if isDrawing.current && tool !== UseSketch.Text { switch canvasRef.current->Nullable.toOption { | None => () | Some(el) => let canvas = asCanvas(el) switch lastPoint.current { | None => () | Some(last) => let ctx = canvas["getContext"]("2d") let pos = getPos(e, canvas) let hex = UseSketch.colorToHex(color) let width = UseSketch.toolToWidth(tool, 2.0) switch tool { | UseSketch.Highlighter => let _ = ctx["lineTo"](pos.x, pos.y) let _ = ctx["stroke"]() | _ => ctx["globalAlpha"] = 1.0 ctx["strokeStyle"] = hex ctx["lineWidth"] = width ctx["lineCap"] = "round" ctx["lineJoin"] = "round" let _ = ctx["beginPath"]() let _ = ctx["moveTo"](last.x, last.y) let _ = ctx["quadraticCurveTo"]( last.x +. (pos.x -. last.x) /. 2.0, last.y +. (pos.y -. last.y) /. 2.0, pos.x, pos.y, ) let _ = ctx["stroke"]() } lastPoint.current = Some(pos) } } } } let stopDraw = (_e: JsxEvent.Mouse.t) => { if tool === UseSketch.Highlighter { switch canvasRef.current->Nullable.toOption { | None => () | Some(el) => let canvas = asCanvas(el) let ctx = canvas["getContext"]("2d") let _ = ctx["stroke"]() ctx["globalAlpha"] = 1.0 ctx["globalCompositeOperation"] = "source-over" } } isDrawing.current = false lastPoint.current = None } let handleClear = () => { switch canvasRef.current->Nullable.toOption { | None => () | Some(el) => let canvas = asCanvas(el) let ctx = canvas["getContext"]("2d") let _ = ctx["clearRect"](0, 0, canvas["width"], canvas["height"]) onClear() } } let handleSave = () => { switch canvasRef.current->Nullable.toOption { | None => () | Some(el) => let canvas = asCanvas(el) let dataUrl = canvas["toDataURL"]("image/png") let link = document["createElement"]("a") link["href"] = dataUrl link["download"] = "sketch.png" let _ = link["click"]() onSave() } } if !isActive { React.null } else {
ReactDOM.Ref.domRef} width="100%" height="100%" className="w-full h-full cursor-crosshair" onMouseDown={startDraw} onMouseMove={draw} onMouseUp={stopDraw} onMouseLeave={stopDraw} />
} }