diff options
| author | Sam Wilkins <abdullah_ahmed@brown.edu> | 2019-05-08 23:16:24 -0400 |
|---|---|---|
| committer | Sam Wilkins <abdullah_ahmed@brown.edu> | 2019-05-08 23:16:24 -0400 |
| commit | 508410d810f61b8f8602dedbbba3e0736c1f5ba3 (patch) | |
| tree | b6de0f335ed187f7e795025ba9a304b3146fc6a2 /src/client/views/collections/collectionFreeForm | |
| parent | 086391b7e45ed4b3cb29602a776f5812f142fff2 (diff) | |
| parent | 890d43c23c5aaafc8ebc2978cd1ad2e19bfcd830 (diff) | |
Working implementation of remote cursors
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
6 files changed, 138 insertions, 106 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index b34e0856e..cbfbb1d2c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, IReactionDisposer, reaction } from "mobx"; +import { computed, IReactionDisposer, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Utils } from "../../../../Utils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -7,7 +7,7 @@ import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); -import { Doc } from "../../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../../new_fields/Doc"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; import { listSpec } from "../../../../new_fields/Schema"; import { List } from "../../../../new_fields/List"; @@ -18,59 +18,57 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP _brushReactionDisposer?: IReactionDisposer; componentDidMount() { - this._brushReactionDisposer = reaction(() => Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).map(doc => NumCast(doc.x)), + this._brushReactionDisposer = reaction( () => { - let views = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => StrCast(doc.backgroundLayout, "").indexOf("istogram") !== -1); - for (let i = 0; i < views.length; i++) { - for (let j = 0; j < views.length; j++) { - let srcDoc = views[j]; - let dstDoc = views[i]; + let doclist = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + return { doclist: doclist ? doclist : [], xs: doclist instanceof List ? doclist.map(d => d instanceof Doc && d.x) : [] }; + }, + async () => { + let doclist = await DocListCast(this.props.Document[this.props.fieldKey]); + let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : []; + views.forEach((dstDoc, i) => { + views.forEach((srcDoc, j) => { + let dstTarg = dstDoc; + let srcTarg = srcDoc; let x1 = NumCast(srcDoc.x); - let x1w = NumCast(srcDoc.width, -1); let x2 = NumCast(dstDoc.x); + let x1w = NumCast(srcDoc.width, -1); let x2w = NumCast(dstDoc.width, -1); - if (x1w < 0 || x2w < 0 || i === j) { - continue; - } - let dstTarg = dstDoc; - let srcTarg = srcDoc; - let findBrush = (field: List<Doc>) => field.findIndex(brush => { - let bdocs = brush ? Cast(brush.brushingDocs, listSpec(Doc), []) : []; - return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false); - }); - let brushAction = (field: List<Doc>) => { - let found = findBrush(field); - if (found !== -1) { - console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title); - field.splice(found, 1); - } - }; - if (Math.abs(x1 + x1w - x2) < 20) { - let linkDoc: Doc = new Doc(); - linkDoc.title = "Histogram Brush"; - linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title); - linkDoc.brushingDocs = new List([dstTarg, srcTarg]); - - brushAction = (field: List<Doc>) => { - if (findBrush(field) === -1) { - console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); - (findBrush(field) === -1) && field.push(linkDoc); + if (x1w < 0 || x2w < 0 || i === j) { } + else { + let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => { + let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined; + return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false; + }); + let brushAction = (field: (Doc | Promise<Doc>)[]) => { + let found = findBrush(field); + if (found !== -1) { + console.log("REMOVE BRUSH " + srcTarg.title + " " + dstTarg.title); + field.splice(found, 1); } }; - } - let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc)); - if (dstBrushDocs === undefined) { - dstTarg.brushingDocs = dstBrushDocs = new List<Doc>(); - } - let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc)); - if (srcBrushDocs === undefined) { - srcTarg.brushingDocs = srcBrushDocs = new List<Doc>(); - } - brushAction(dstBrushDocs); - brushAction(srcBrushDocs); + if (Math.abs(x1 + x1w - x2) < 20) { + let linkDoc: Doc = new Doc(); + linkDoc.title = "Histogram Brush"; + linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title); + linkDoc.brushingDocs = new List([dstTarg, srcTarg]); - } - } + brushAction = (field: (Doc | Promise<Doc>)[]) => { + if (findBrush(field) === -1) { + console.log("ADD BRUSH " + srcTarg.title + " " + dstTarg.title); + field.push(linkDoc); + } + }; + } + let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []); + let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []); + if (dstBrushDocs === undefined) dstTarg.brushingDocs = dstBrushDocs = new List<Doc>(); + else brushAction(dstBrushDocs); + if (srcBrushDocs === undefined) srcTarg.brushingDocs = srcBrushDocs = new List<Doc>(); + else brushAction(srcBrushDocs); + } + }) + }) }); } componentWillUnmount() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index c22f430ac..642118d75 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -22,11 +22,8 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV } let cursors = Cast(doc.cursors, listSpec(CursorField)); - if (!cursors) { - doc.cursors = cursors = new List<CursorField>(); - } - return cursors.filter(cursor => cursor.data.metadata.id !== id); + return (cursors || []).filter(cursor => cursor.data.metadata.id !== id); } private crosshairs?: HTMLCanvasElement; @@ -62,7 +59,6 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV } } - @computed get sharedCursors() { return this.getCursors().map(c => { let m = c.data.metadata; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index cb849b325..063c9e2cf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -37,7 +37,9 @@ border-radius: $border-radius; box-sizing: border-box; position: absolute; - overflow: hidden; + .marqueeView { + overflow: hidden; + } top: 0; left: 0; width: 100%; @@ -61,7 +63,9 @@ border-radius: $border-radius; box-sizing: border-box; position:absolute; - overflow: hidden; + .marqueeView { + overflow: hidden; + } top: 0; left: 0; width: 100%; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 59f7fa442..7ab09987f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -110,15 +110,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { - let childSelected = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), [] as Doc[]).filter(doc => doc).reduce((childSelected, doc) => { - var dv = DocumentManager.Instance.getDocumentView(doc); - return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false); - }, false); if ((CollectionFreeFormView.RIGHT_BTN_DRAG && (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || - (e.button === 0 && e.altKey)) && (childSelected || this.props.active()))) || + (e.button === 0 && e.altKey)) && this.props.active())) || (!CollectionFreeFormView.RIGHT_BTN_DRAG && - ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && (childSelected || this.props.active())))) { + ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -233,7 +229,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); }).forEach((doc, index) => doc.zIndex = index + 1); doc.zIndex = docs.length + 1; - return doc; } focusDocument = (doc: Doc) => { @@ -265,7 +260,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, parentActive: this.props.active, - whenActiveChanged: this.props.active, + whenActiveChanged: this.props.whenActiveChanged, bringToFront: this.bringToFront, }; } @@ -274,7 +269,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { get views() { let curPage = FieldValue(this.Document.curPage, -1); let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => { - if (!FieldValue(doc)) return prev; + if (!(doc instanceof Doc)) return prev; var page = NumCast(doc.page, -1); if (page === curPage || page === -1) { let minim = Cast(doc.isMinimized, "boolean"); @@ -316,20 +311,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { </CollectionFreeFormLinksView> <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> </CollectionFreeFormViewPannableContents> - <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} /> </MarqueeView> + <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} /> </div> ); } } @observer -class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> { +class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { @computed get overlayView() { - let overlayLayout = Cast(this.props.Document.overlayLayout, "string", ""); - return !overlayLayout ? (null) : - (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"} - isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />); + return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"} + isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />); } render() { return this.overlayView; @@ -339,10 +332,8 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> { @observer class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { @computed get backgroundView() { - let backgroundLayout = Cast(this.props.Document.backgroundLayout, "string", ""); - return !backgroundLayout ? (null) : - (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} - isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />); + return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} + isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />); } render() { return this.backgroundView; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index ae0a9fd48..6e8ec8662 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -21,6 +21,6 @@ white-space:nowrap; } .marquee-legend::after { - content: "Press: C (collection), or Delete" + content: "Press: c (collection), s (summary), r (replace) or Delete" } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index a9e627188..37428e88b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import { Docs } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; @@ -13,7 +13,6 @@ import { Utils } from "../../../../Utils"; import { Doc } from "../../../../new_fields/Doc"; import { NumCast, Cast } from "../../../../new_fields/Types"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; -import { Templates } from "../../Templates"; import { List } from "../../../../new_fields/List"; interface MarqueeViewProps { @@ -48,12 +47,42 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._visible = false; } + @undoBatch @action onKeyPress = (e: KeyboardEvent) => { //make textbox and add it to this collection let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); - let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); - this.props.addLiveTextDocument(newBox); + if (e.key === "q" && e.ctrlKey) { + e.preventDefault(); + (async () => { + let text = await navigator.clipboard.readText(); + let ns = text.split("\n").filter(t => t != "\r"); + for (let i = 0; i < ns.length - 1; i++) { + if (ns[i].trim() === "") { + ns.splice(i, 1); + continue; + } + while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") || + ns[i].endsWith(";\r") || ns[i].endsWith(";") || + ns[i].endsWith(".\r") || ns[i].endsWith(".") || + ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) { + let sub = ns[i].endsWith("\r") ? 1 : 0; + let br = ns[i + 1].trim() === ""; + ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft()); + if (br) break; + } + } + ns.map(line => { + let indent = line.search(/\S|$/); + let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); + this.props.addDocument(newBox, false); + y += 40 * this.props.getTransform().Scale; + }) + })(); + } else { + let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); + this.props.addLiveTextDocument(newBox); + } e.stopPropagation(); } @action @@ -67,6 +96,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> document.addEventListener("pointermove", this.onPointerMove, true); document.addEventListener("pointerup", this.onPointerUp, true); document.addEventListener("keydown", this.marqueeCommand, true); + e.stopPropagation(); } if (e.altKey) { e.preventDefault(); @@ -146,20 +176,28 @@ export class MarqueeView extends React.Component<MarqueeViewProps> if (ink) { this.marqueeInkDelete(ink.inkData); } + SelectionManager.DeselectAll(); this.cleanupInteractions(false); e.stopPropagation(); } - if (e.key === "c" || e.key === "r" || e.key === "e") { + if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e") { this._commandExecuted = true; e.stopPropagation(); let bounds = this.Bounds; let selected = this.marqueeSelect().map(d => { - if (e.key !== "r") { + if (e.key === "s") { + let dCopy = Doc.MakeCopy(d); + dCopy.x = NumCast(d.x) - bounds.left - bounds.width / 2; + dCopy.y = NumCast(d.y) - bounds.top - bounds.height / 2; + dCopy.page = -1; + return dCopy; + } + else if (e.key !== "r") { this.props.removeDocument(d); + d.x = NumCast(d.x) - bounds.left - bounds.width / 2; + d.y = NumCast(d.y) - bounds.top - bounds.height / 2; + d.page = -1; } - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; return d; }); let ink = Cast(this.props.container.props.Document.ink, InkField); @@ -175,21 +213,23 @@ export class MarqueeView extends React.Component<MarqueeViewProps> width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined, - title: "a nested collection" + title: "a nested collection", }); this.marqueeInkDelete(inkData); // SelectionManager.DeselectAll(); - if (e.key === "r") { - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" }); - summary.maximizedDocs = new List<Doc>(selected); - // summary.doc1 = selected[0]; - // if (selected.length > 1) - // summary.doc2 = selected[1]; - // summary.templates = new List<string>([Templates.Summary.Layout]); - this.props.addLiveTextDocument(summary); + if (e.key === "s" || e.key === "r") { e.preventDefault(); let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top); + let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" }); + + if (e.key === "s") { + summary.proto!.maximizeOnRight = true; + newCollection.proto!.summaryDoc = summary; + selected = [newCollection]; + } + summary.proto!.maximizedDocs = new List<Doc>(selected); + summary.proto!.isButton = true; selected.map(maximizedDoc => { let maxx = NumCast(maximizedDoc.x, undefined); let maxy = NumCast(maximizedDoc.y, undefined); @@ -197,25 +237,28 @@ export class MarqueeView extends React.Component<MarqueeViewProps> let maxh = NumCast(maximizedDoc.height, undefined); maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]); }); + this.props.addLiveTextDocument(summary); } else { this.props.addDocument(newCollection, false); + SelectionManager.DeselectAll(); + this.props.selectDocuments([newCollection]); } this.cleanupInteractions(false); - } - if (e.key === "s") { - this._commandExecuted = true; - e.stopPropagation(); - e.preventDefault(); - let bounds = this.Bounds; - let selected = this.marqueeSelect(); - SelectionManager.DeselectAll(); - let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" }); - this.props.addLiveTextDocument(summary); - selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!)); + } else + if (e.key === "s") { + // this._commandExecuted = true; + // e.stopPropagation(); + // e.preventDefault(); + // let bounds = this.Bounds; + // let selected = this.marqueeSelect(); + // SelectionManager.DeselectAll(); + // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" }); + // this.props.addLiveTextDocument(summary); + // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!)); - this.cleanupInteractions(false); - } + // this.cleanupInteractions(false); + } } @action marqueeInkSelect(ink: Map<any, any>) { |
