import { action, computed, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../fields/Document"; import { FieldWaiting } from "../../fields/Field"; import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField"; import { KeyStore } from "../../fields/KeyStore"; import { Utils } from "../../Utils"; import { Transform } from "../util/Transform"; import "./InkingCanvas.scss"; import { InkingControl } from "./InkingControl"; import { InkingStroke } from "./InkingStroke"; import React = require("react"); interface InkCanvasProps { getScreenTransform: () => Transform; Document: Document; } @observer export class InkingCanvas extends React.Component { static InkOffset: number = 50000; private _currentStrokeId: string = ""; public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean { return stroke.pathData.reduce((inside, val) => inside || (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset && selRect.top < val.y - InkingCanvas.InkOffset && selRect.top + selRect.height > val.y - InkingCanvas.InkOffset) , false); } @computed get inkData(): StrokeMap { let map = this.props.Document.GetT(KeyStore.Ink, InkField); if (!map || map === FieldWaiting) { return new Map; } return new Map(map.Data); } set inkData(value: StrokeMap) { this.props.Document.SetDataOnPrototype(KeyStore.Ink, value, InkField); } @action onPointerDown = (e: React.PointerEvent): void => { if (e.button != 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) { return; } document.addEventListener("pointermove", this.onPointerMove, true); document.addEventListener("pointerup", this.onPointerUp, true); e.stopPropagation(); e.preventDefault(); if (InkingControl.Instance.selectedTool != InkTool.Eraser) { // start the new line, saves a uuid to represent the field of the stroke this._currentStrokeId = Utils.GenerateGuid(); this.inkData.set(this._currentStrokeId, { pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)], color: InkingControl.Instance.selectedColor, width: InkingControl.Instance.selectedWidth, tool: InkingControl.Instance.selectedTool, page: this.props.Document.GetNumber(KeyStore.CurPage, -1) }); } } onPointerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onPointerMove, true); document.removeEventListener("pointerup", this.onPointerUp, true); e.stopPropagation(); e.preventDefault(); } @action onPointerMove = (e: PointerEvent): void => { e.stopPropagation() e.preventDefault(); if (InkingControl.Instance.selectedTool != InkTool.Eraser) { let data = this.inkData; // add points to new line as it is being drawn let strokeData = data.get(this._currentStrokeId); if (strokeData) { strokeData.pathData.push(this.relativeCoordinatesForEvent(e.clientX, e.clientY)); data.set(this._currentStrokeId, strokeData); } this.inkData = data; } } relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => { let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey); x += InkingCanvas.InkOffset; y += InkingCanvas.InkOffset; return { x, y }; } @action removeLine = (id: string): void => { let data = this.inkData; data.delete(id); this.inkData = data; } @computed get drawnPaths() { // parse data from server let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { if (strokeData.page == -1 || strokeData.page == curPage) paths.push() return paths; }, [] as JSX.Element[]); return [ {paths.filter(path => path.props.tool == InkTool.Highlighter)} , {paths.filter(path => path.props.tool != InkTool.Highlighter)} ]; } render() { let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect"; return (
{(this.props.children as any)() /* bcz: is there a better way to know that children is a function? */} {this.drawnPaths}
) } }