diff options
| author | bob <bcz@cs.brown.edu> | 2019-04-03 11:45:15 -0400 |
|---|---|---|
| committer | bob <bcz@cs.brown.edu> | 2019-04-03 11:45:15 -0400 |
| commit | bb7d5a26ec68f283c5adb42d4d6554253de7176f (patch) | |
| tree | 7efa817102ba54e916601611f608dd4bd761a437 /src/client/views/collections/collectionFreeForm/MarqueeView.tsx | |
| parent | 43a0768690caa89c606dd5d296d3cc8825c1702b (diff) | |
| parent | c406c8d123ce0aa9d63fb8a4dd90adfe83d2889d (diff) | |
merged with master
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx new file mode 100644 index 000000000..e2239c8be --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -0,0 +1,201 @@ +import { action, computed, observable, trace } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { FieldWaiting } from "../../../../fields/Field"; +import { InkField, StrokeData } from "../../../../fields/InkField"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { Documents } from "../../../documents/Documents"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { Transform } from "../../../util/Transform"; +import { InkingCanvas } from "../../InkingCanvas"; +import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import "./MarqueeView.scss"; +import { PreviewCursor } from "./PreviewCursor"; +import React = require("react"); + +interface MarqueeViewProps { + getContainerTransform: () => Transform; + getTransform: () => Transform; + container: CollectionFreeFormView; + addDocument: (doc: Document, allowDuplicates: false) => boolean; + activeDocuments: () => Document[]; + selectDocuments: (docs: Document[]) => void; + removeDocument: (doc: Document) => boolean; +} + +@observer +export class MarqueeView extends React.Component<MarqueeViewProps> +{ + @observable _lastX: number = 0; + @observable _lastY: number = 0; + @observable _downX: number = 0; + @observable _downY: number = 0; + @observable _used: boolean = false; + @observable _visible: boolean = false; + static DRAG_THRESHOLD = 4; + + @action + cleanupInteractions = (all: boolean = false) => { + if (all) { + document.removeEventListener("pointermove", this.onPointerMove, true) + document.removeEventListener("pointerup", this.onPointerUp, true); + } else { + this._used = true; + } + document.removeEventListener("keydown", this.marqueeCommand, true); + this._visible = false; + } + + @action + onPointerDown = (e: React.PointerEvent): void => { + if (e.buttons == 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { + this._downX = this._lastX = e.pageX; + this._downY = this._lastY = e.pageY; + this._used = false; + document.addEventListener("pointermove", this.onPointerMove, true) + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("keydown", this.marqueeCommand, true); + } + } + + @action + onPointerMove = (e: PointerEvent): void => { + this._lastX = e.pageX; + this._lastY = e.pageY; + if (!e.cancelBubble) { + if (!this._used && e.buttons == 1 && !e.altKey && !e.metaKey && + (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) { + this._visible = true; + } + e.stopPropagation(); + e.preventDefault(); + } + } + + @action + onPointerUp = (e: PointerEvent): void => { + this.cleanupInteractions(true); + this._visible = false; + let mselect = this.marqueeSelect(); + if (!e.shiftKey) { + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + } + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + } + + intersectRect(r1: { left: number, top: number, width: number, height: number }, + r2: { left: number, top: number, width: number, height: number }) { + return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); + } + + @computed + get Bounds() { + let left = this._downX < this._lastX ? this._downX : this._lastX; + let top = this._downY < this._lastY ? this._downY : this._lastY; + let topLeft = this.props.getTransform().transformPoint(left, top); + let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) } + } + + @action + marqueeCommand = (e: KeyboardEvent) => { + if (e.key == "Backspace" || e.key == "Delete") { + this.marqueeSelect().map(d => this.props.removeDocument(d)); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting) { + this.marqueeInkDelete(ink.Data); + } + this.cleanupInteractions(); + } + if (e.key == "c") { + let bounds = this.Bounds; + let selected = this.marqueeSelect().map(d => { + this.props.removeDocument(d); + d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2); + d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2); + d.SetNumber(KeyStore.Page, -1); + d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); + return d; + }); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + let inkData = ink && ink != FieldWaiting ? ink.Data : undefined; + //setTimeout(() => { + let newCollection = Documents.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panx: 0, + pany: 0, + width: bounds.width, + height: bounds.height, + backgroundColor: "Transparent", + ink: inkData ? this.marqueeInkSelect(inkData) : undefined, + title: "a nested collection" + }); + this.props.addDocument(newCollection, false); + this.marqueeInkDelete(inkData); + // }, 100); + this.cleanupInteractions(); + } + } + @action + marqueeInkSelect(ink: Map<any, any>) { + let idata = new Map(); + let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin. + let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2); + ink.forEach((value: StrokeData, key: string, map: any) => { + if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { + idata.set(key, + { + pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), + color: value.color, + width: value.width, + tool: value.tool, + page: -1 + }); + } + }); + return idata; + } + + @action + marqueeInkDelete(ink?: Map<any, any>) { + // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB. + // ink.forEach((value: StrokeData, key: string, map: any) => + // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key)); + + if (ink) { + let idata = new Map(); + ink.forEach((value: StrokeData, key: string, map: any) => + !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); + this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField); + } + } + + marqueeSelect() { + let selRect = this.Bounds; + let selection: Document[] = []; + this.props.activeDocuments().map(doc => { + var x = doc.GetNumber(KeyStore.X, 0); + var y = doc.GetNumber(KeyStore.Y, 0); + var w = doc.GetNumber(KeyStore.Width, 0); + var h = doc.GetNumber(KeyStore.Height, 0); + if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) + selection.push(doc) + }) + return selection; + } + + @computed + get marqueeDiv() { + let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); + let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} /> + } + + render() { + return <div className="marqueeView" onPointerDown={this.onPointerDown}> + {this.props.children} + {!this._visible ? (null) : this.marqueeDiv} + </div>; + } +}
\ No newline at end of file |
