From c3eea60fa586e8ff9cf59497c041044fd0143bb1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 21:03:17 -0400 Subject: organized collectionfreeformview. fixed issues with showing links --- .../collectionFreeForm/CollectionFreeFormView.tsx | 416 +++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx new file mode 100644 index 000000000..1f90b0d46 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -0,0 +1,416 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { FieldWaiting } from "../../../../fields/Field"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { ListField } from "../../../../fields/ListField"; +import { TextField } from "../../../../fields/TextField"; +import { DragManager } from "../../../util/DragManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { InkingCanvas } from "../../InkingCanvas"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentContentsView } from "../../nodes/DocumentContentsView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; +import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; +import { CollectionViewBase } from "../CollectionViewBase"; +import { MarqueeView } from "./MarqueeView"; +import { PreviewCursor } from "./PreviewCursor"; +import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import "./CollectionFreeFormView.scss"; +import React = require("react"); +import v5 = require("uuid/v5"); + +@observer +export class CollectionFreeFormView extends CollectionViewBase { + public _canvasRef = React.createRef(); + private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) + + public addLiveTextBox = (newBox: Document) => { + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + this._selectOnLoaded = newBox.Id; + //set text to be the typed key and get focus on text box + this.props.addDocument(newBox, false); + //remove cursor from screen + this.PreviewCursorVisible = false; + } + + public selectDocuments = (docs: Document[]) => { + this.props.CollectionView.SelectedDocs.length = 0; + docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id)); + } + + public getActiveDocuments = () => { + var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); + const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); + let active: Document[] = []; + if (lvalue && lvalue != FieldWaiting) { + lvalue.Data.map(doc => { + var page = doc.GetNumber(KeyStore.Page, -1); + if (page == curPage || page == -1) { + active.push(doc); + } + }) + } + + return active; + } + + //determines whether the blinking cursor for indicating whether a text will be made on key down is visible + @observable public PreviewCursorVisible: boolean = false; + @observable public MarqueeVisible = false; + @observable public DownX: number = 0; + @observable public DownY: number = 0; + @observable private _lastX: number = 0; + @observable private _lastY: number = 0; + + @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } + @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } + @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } + @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? + @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } + @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } + @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } + @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections + @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections + + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (super.drop(e, de)) { + if (de.data instanceof DragManager.DocumentDragData) { + let screenX = de.x - (de.data.xOffset as number || 0); + let screenY = de.y - (de.data.yOffset as number || 0); + const [x, y] = this.getTransform().transformPoint(screenX, screenY); + de.data.droppedDocument.SetNumber(KeyStore.X, x); + de.data.droppedDocument.SetNumber(KeyStore.Y, y); + if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) { + de.data.droppedDocument.SetNumber(KeyStore.Width, 300); + de.data.droppedDocument.SetNumber(KeyStore.Height, 300); + } + this.bringToFront(de.data.droppedDocument); + } + return true; + } + return false; + } + + + @action + cleanupInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this.MarqueeVisible = false; + } + + @action + onPointerDown = (e: React.PointerEvent): void => { + this.PreviewCursorVisible = false; + if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + this._lastX = this.DownX = e.pageX; + this._lastY = this.DownY = e.pageY; + } + } + + @action + onPointerUp = (e: PointerEvent): void => { + e.stopPropagation(); + + if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) { + //show preview text cursor on tap + this.PreviewCursorVisible = true; + //select is not already selected + if (!this.props.isSelected()) { + this.props.select(false); + } + } + this.cleanupInteractions(); + } + + @action + onPointerMove = (e: PointerEvent): void => { + if (!e.cancelBubble && this.props.active()) { + if (e.buttons == 1 && !e.altKey && !e.metaKey) { + this.MarqueeVisible = true; + } + if (this.MarqueeVisible) { + e.stopPropagation(); + e.preventDefault(); + } + else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { + let x = this.props.Document.GetNumber(KeyStore.PanX, 0); + let y = this.props.Document.GetNumber(KeyStore.PanY, 0); + let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); + this.SetPan(x - dx, y - dy); + this._lastX = e.pageX; + this._lastY = e.pageY; + e.stopPropagation(); + e.preventDefault(); + } + } + } + + @action + onPointerWheel = (e: React.WheelEvent): void => { + this.props.select(false); + e.stopPropagation(); + e.preventDefault(); + let coefficient = 1000; + + if (e.ctrlKey) { + var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); + const coefficient = 1000; + let deltaScale = (1 - (e.deltaY / coefficient)); + this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale); + this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale); + e.stopPropagation(); + e.preventDefault(); + } else { + // if (modes[e.deltaMode] == 'pixels') coefficient = 50; + // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height?? + let transform = this.getTransform(); + + let deltaScale = (1 - (e.deltaY / coefficient)); + if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) + deltaScale = 1 / this.zoomScaling; + let [x, y] = transform.transformPoint(e.clientX, e.clientY); + + let localTransform = this.getLocalTransform() + localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y) + // console.log(localTransform) + + this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale); + this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale); + } + } + + @action + private SetPan(panX: number, panY: number) { + var x1 = this.getLocalTransform().inverse().Scale; + const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX)); + const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY)); + this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); + this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); + } + + @action + onDrop = (e: React.DragEvent): void => { + var pt = this.getTransform().transformPoint(e.pageX, e.pageY); + super.onDrop(e, { x: pt[0], y: pt[1] }); + } + + onDragOver = (): void => { + } + + @action + bringToFront(doc: Document) { + const { fieldKey: fieldKey, Document: Document } = this.props; + + const value: Document[] = Document.GetList(fieldKey, []).slice(); + value.sort((doc1, doc2) => { + if (doc1 === doc) { + return 1; + } + if (doc2 === doc) { + return -1; + } + return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0); + }).map((doc, index) => { + doc.SetNumber(KeyStore.ZIndex, index + 1) + }); + } + + @computed get backgroundLayout(): string | undefined { + let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField); + if (field && field !== FieldWaiting) { + return field.Data; + } + } + @computed get overlayLayout(): string | undefined { + let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField); + if (field && field !== FieldWaiting) { + return field.Data; + } + } + + focusDocument = (doc: Document) => { + let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2; + let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2; + this.SetPan(x, y); + this.props.focus(this.props.Document); + } + + getDocumentViewProps(document: Document): DocumentViewProps { + return { + Document: document, + AddDocument: this.props.addDocument, + RemoveDocument: this.props.removeDocument, + ScreenToLocalTransform: this.getTransform, + isTopMost: false, + SelectOnLoad: document.Id == this._selectOnLoaded, + PanelWidth: document.Width, + PanelHeight: document.Height, + ContentScaling: this.noScaling, + ContainingCollectionView: this.props.CollectionView, + focus: this.focusDocument + } + } + + @computed + get views() { + var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); + const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); + if (lvalue && lvalue != FieldWaiting) { + return lvalue.Data.map(doc => { + if (!doc) return null; + var page = doc.GetNumber(KeyStore.Page, 0); + return (page != curPage && page != 0) ? (null) : + (); + }) + } + return null; + } + + @computed + get backgroundView() { + return !this.backgroundLayout ? (null) : + ( false} select={() => { }} />); + } + @computed + get overlayView() { + return !this.overlayLayout ? (null) : + ( false} select={() => { }} />); + } + + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) + getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) + getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); + noScaling = () => 1; + + //when focus is lost, this will remove the preview cursor + @action + onBlur = (): void => { + this.PreviewCursorVisible = false; + } + + private crosshairs?: HTMLCanvasElement; + drawCrosshairs = (backgroundColor: string) => { + if (this.crosshairs) { + let c = this.crosshairs; + let ctx = c.getContext('2d'); + if (ctx) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, 20, 20); + + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; + + ctx.beginPath(); + + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); + + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); + + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); + + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); + + ctx.stroke(); + + // ctx.font = "10px Arial"; + // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); + } + } + } + + render() { + let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; + + const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); + const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); + // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; + // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; + // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY)); + + return ( +
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} + onWheel={this.onPointerWheel} + onDrop={this.onDrop.bind(this)} + onDragOver={this.onDragOver} + onBlur={this.onBlur} + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }} + tabIndex={0} + ref={this.createDropTarget}> +
+ {this.backgroundView} + + + {this.views} + + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( +
+ { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

{email[0].toUpperCase()}

+
+ ); + } + })} +
+ + {this.overlayView} + +
+ ); + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From eb220da697e2383b1e368dee743613158994746e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 31 Mar 2019 17:58:11 -0400 Subject: restructured marquee and preview cursor to not need reactions. fixed some inking bugs. --- src/client/views/InkingCanvas.tsx | 25 ++- .../CollectionFreeFormLinksView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.scss | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 199 ++++++++------------- .../collectionFreeForm/MarqueeView.scss | 6 + .../collections/collectionFreeForm/MarqueeView.tsx | 169 +++++++++-------- .../collectionFreeForm/PreviewCursor.scss | 7 +- .../collectionFreeForm/PreviewCursor.tsx | 71 +++++--- 8 files changed, 245 insertions(+), 234 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 36a8834a0..8fb74ec48 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -1,16 +1,15 @@ -import { observer } from "mobx-react"; -import { observable } from "mobx"; import { action, computed } from "mobx"; -import { InkingControl } from "./InkingControl"; -import React = require("react"); -import { Transform } from "../util/Transform"; +import { observer } from "mobx-react"; import { Document } from "../../fields/Document"; -import { KeyStore } from "../../fields/KeyStore"; +import { FieldWaiting } from "../../fields/Field"; import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField"; -import { InkingStroke } from "./InkingStroke"; -import "./InkingCanvas.scss" +import { KeyStore } from "../../fields/KeyStore"; import { Utils } from "../../Utils"; -import { FieldWaiting } from "../../fields/Field"; +import { Transform } from "../util/Transform"; +import "./InkingCanvas.scss"; +import { InkingControl } from "./InkingControl"; +import { InkingStroke } from "./InkingStroke"; +import React = require("react"); interface InkCanvasProps { getScreenTransform: () => Transform; @@ -71,8 +70,7 @@ export class InkingCanvas extends React.Component { // start the new line, saves a uuid to represent the field of the stroke this._idGenerator = Utils.GenerateGuid(); - let data = this.inkData; - data.set(this._idGenerator, + this.inkData.set(this._idGenerator, { pathData: [point], color: InkingControl.Instance.selectedColor, @@ -80,7 +78,6 @@ export class InkingCanvas extends React.Component { tool: InkingControl.Instance.selectedTool, page: this.props.Document.GetNumber(KeyStore.CurPage, -1) }); - this.inkData = data; this._isDrawing = true; } @@ -163,9 +160,7 @@ export class InkingCanvas extends React.Component { }) return ( - -
+
{paths} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index f793b3a49..ea392eaea 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -96,6 +96,7 @@ export class CollectionFreeFormLinksView extends React.Component {this.uniqueConnections.map(c => )} + {this.props.children} ); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 215a75243..c979b27a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -6,6 +6,7 @@ left: 0; width: 100%; height: 100%; + transform-origin: left top; .inking-canvas { transform-origin: 50000px 50000px; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1f90b0d46..cdc5bdfb0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,7 +3,6 @@ import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; import { KeyStore } from "../../../../fields/KeyStore"; -import { ListField } from "../../../../fields/ListField"; import { TextField } from "../../../../fields/TextField"; import { DragManager } from "../../../util/DragManager"; import { Transform } from "../../../util/Transform"; @@ -14,10 +13,9 @@ import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; import { CollectionViewBase } from "../CollectionViewBase"; -import { MarqueeView } from "./MarqueeView"; -import { PreviewCursor } from "./PreviewCursor"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import "./CollectionFreeFormView.scss"; +import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); @@ -27,12 +25,15 @@ export class CollectionFreeFormView extends CollectionViewBase { private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) public addLiveTextBox = (newBox: Document) => { - // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself and receive text input this._selectOnLoaded = newBox.Id; - //set text to be the typed key and get focus on text box - this.props.addDocument(newBox, false); - //remove cursor from screen - this.PreviewCursorVisible = false; + this.addDocument(newBox, false); + } + + public addDocument = (newBox: Document, allowDuplicates: boolean) => { + let added = this.props.addDocument(newBox, false); + this.bringToFront(newBox); + return added; } public selectDocuments = (docs: Document[]) => { @@ -42,23 +43,15 @@ export class CollectionFreeFormView extends CollectionViewBase { public getActiveDocuments = () => { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); - let active: Document[] = []; - if (lvalue && lvalue != FieldWaiting) { - lvalue.Data.map(doc => { - var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) { - active.push(doc); - } - }) - } - - return active; + return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => { + var page = doc.GetNumber(KeyStore.Page, -1); + if (page == curPage || page == -1) { + active.push(doc); + } + return active; + }, [] as Document[]); } - //determines whether the blinking cursor for indicating whether a text will be made on key down is visible - @observable public PreviewCursorVisible: boolean = false; - @observable public MarqueeVisible = false; @observable public DownX: number = 0; @observable public DownY: number = 0; @observable private _lastX: number = 0; @@ -100,12 +93,10 @@ export class CollectionFreeFormView extends CollectionViewBase { cleanupInteractions = () => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.MarqueeVisible = false; } @action onPointerDown = (e: React.PointerEvent): void => { - this.PreviewCursorVisible = false; if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); @@ -120,28 +111,13 @@ export class CollectionFreeFormView extends CollectionViewBase { onPointerUp = (e: PointerEvent): void => { e.stopPropagation(); - if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) { - //show preview text cursor on tap - this.PreviewCursorVisible = true; - //select is not already selected - if (!this.props.isSelected()) { - this.props.select(false); - } - } this.cleanupInteractions(); } @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - if (e.buttons == 1 && !e.altKey && !e.metaKey) { - this.MarqueeVisible = true; - } - if (this.MarqueeVisible) { - e.stopPropagation(); - e.preventDefault(); - } - else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { + if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { let x = this.props.Document.GetNumber(KeyStore.PanX, 0); let y = this.props.Document.GetNumber(KeyStore.PanY, 0); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); @@ -264,16 +240,12 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get views() { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); - if (lvalue && lvalue != FieldWaiting) { - return lvalue.Data.map(doc => { - if (!doc) return null; - var page = doc.GetNumber(KeyStore.Page, 0); - return (page != curPage && page != 0) ? (null) : - (); - }) - } - return null; + return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { + var page = doc.GetNumber(KeyStore.Page, -1); + if (page == curPage || page == -1) + prev.push(); + return prev; + }, [] as JSX.Element[]) } @computed @@ -290,15 +262,10 @@ export class CollectionFreeFormView extends CollectionViewBase { } getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) - getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) + getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; - //when focus is lost, this will remove the preview cursor - @action - onBlur = (): void => { - this.PreviewCursorVisible = false; - } private crosshairs?: HTMLCanvasElement; drawCrosshairs = (backgroundColor: string) => { @@ -345,71 +312,63 @@ export class CollectionFreeFormView extends CollectionViewBase { return (
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} - onWheel={this.onPointerWheel} - onDrop={this.onDrop.bind(this)} - onDragOver={this.onDragOver} - onBlur={this.onBlur} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }} - tabIndex={0} - ref={this.createDropTarget}> -
- {this.backgroundView} - - - {this.views} - - {super.getCursors().map(entry => { - if (entry.Data.length > 0) { - let id = entry.Data[0][0]; - let email = entry.Data[0][1]; - let point = entry.Data[1]; - this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") - return ( -
- { if (el) this.crosshairs = el }} - width={20} - height={20} - style={{ - position: 'absolute', - width: "20px", - height: "20px", - opacity: 0.5, - borderRadius: "50%", - border: "2px solid black" - }} - /> -

super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} + onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} tabIndex={0} ref={this.createDropTarget}> + +

+ {this.backgroundView} + + {this.views} + + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( +
{email[0].toUpperCase()}

-
- ); - } - })} -
- - {this.overlayView} - + > + { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

{email[0].toUpperCase()}

+
+ ); + } + })} +
+ {this.overlayView} +
); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 6d9a79344..1ee3b244b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,8 +1,14 @@ .marqueeView { + position: absolute; + width:100%; + height:100%; +} +.marquee { border-style: dashed; box-sizing: border-box; position: absolute; border-width: 1px; border-color: black; + pointer-events: none; } \ 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 2692690dd..2b0e7d228 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,62 +1,58 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; -import { FieldWaiting, Opt } from "../../../../fields/Field"; +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"); -import { InkField, StrokeData } from "../../../../fields/InkField"; -import { Utils } from "../../../../Utils"; -import { InkingCanvas } from "../../InkingCanvas"; interface MarqueeViewProps { - getMarqueeTransform: () => Transform; + getContainerTransform: () => Transform; getTransform: () => Transform; container: CollectionFreeFormView; addDocument: (doc: Document, allowDuplicates: false) => boolean; activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; + addLiveTextDocument: (doc: Document) => void; } @observer export class MarqueeView extends React.Component { - private _reactionDisposer: Opt; - @observable _lastX: number = 0; @observable _lastY: number = 0; @observable _downX: number = 0; @observable _downY: number = 0; - - componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.container.MarqueeVisible, - (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY)) - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this.cleanupInteractions(); - } + @observable _used: boolean = false; + @observable _visible: boolean = false; + static DRAG_THRESHOLD = 4; @action - cleanupInteractions = () => { - document.removeEventListener("pointermove", this.onPointerMove, true) - document.removeEventListener("pointerup", this.onPointerUp, true); + 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 = (visible: boolean, downX: number, downY: number): void => { - if (visible) { - this._downX = this._lastX = downX; - this._downY = this._lastY = downY; + 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); @@ -67,11 +63,20 @@ export class MarqueeView extends React.Component 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(); + this.cleanupInteractions(true); + this._visible = false; if (!e.shiftKey) { SelectionManager.DeselectAll(); } @@ -83,6 +88,7 @@ export class MarqueeView extends React.Component 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; @@ -95,7 +101,10 @@ export class MarqueeView extends React.Component marqueeCommand = (e: KeyboardEvent) => { if (e.key == "Backspace" || e.key == "Delete") { this.marqueeSelect().map(d => this.props.removeDocument(d)); - this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting && ink.Data) { + this.marqueeInkDelete(ink.Data); + } this.cleanupInteractions(); } if (e.key == "c") { @@ -104,53 +113,61 @@ export class MarqueeView extends React.Component 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, 0); + d.SetNumber(KeyStore.Page, -1); d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); return d; }); - let liftedInk = this.marqueeInkSelect(true); - this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); - //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: liftedInk, - title: "a nested collection" - }); - this.props.addDocument(newCollection, false); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting && ink.Data) { + //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: this.marqueeInkSelect(ink.Data), + title: "a nested collection" + }); + this.props.addDocument(newCollection, false); + this.marqueeInkDelete(ink.Data); + } // }, 100); this.cleanupInteractions(); } } - marqueeInkSelect(select: boolean) { - let selRect = this.Bounds; - let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin. - let centerShiftY = 0 - (selRect.top + selRect.height / 2); - let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - if (ink && ink != FieldWaiting && ink.Data) { - let idata = new Map(); - ink.Data.forEach((value: StrokeData, key: string, map: any) => { - let inside = InkingCanvas.IntersectStrokeRect(value, selRect); - if (inside && select) { - 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 - }); - } else if (!inside && !select) { - idata.set(key, value); - } - }) - return idata; - } + @action + marqueeInkSelect(ink: Map) { + 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, ) { + // 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)); + + 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() { @@ -168,8 +185,16 @@ export class MarqueeView extends React.Component } render() { - let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); - let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - return (!this.props.container.MarqueeVisible ? (null) :
); + 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
+ + {this.props.children} + {!this._visible ? (null) : +
} + + +
; } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss index a797411f6..7d6d5aaab 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss @@ -5,6 +5,11 @@ transform-origin: left top; pointer-events: none; } +.previewCursorView { + position: absolute; + width:100%; + height:100%; +} //this is an animation for the blinking cursor! @keyframes blink { @@ -14,5 +19,5 @@ } #previewCursor { - animation: blink 1s infinite; + animation: blink 1s infinite; } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx index 95364f04b..339e056a8 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -1,7 +1,6 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; -import { Opt } from "../../../../fields/Field"; import { Documents } from "../../../documents/Documents"; import { Transform } from "../../../util/Transform"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; @@ -11,43 +10,50 @@ import React = require("react"); export interface PreviewCursorProps { getTransform: () => Transform; + getContainerTransform: () => Transform; container: CollectionFreeFormView; addLiveTextDocument: (doc: Document) => void; } @observer export class PreviewCursor extends React.Component { - private _reactionDisposer: Opt; - @observable _lastX: number = 0; @observable _lastY: number = 0; + @observable public _visible: boolean = false; + @observable public DownX: number = 0; + @observable public DownY: number = 0; + _showOnUp: boolean = false; + public _previewDivRef = React.createRef(); - componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.container.PreviewCursorVisible, - (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY)) - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this.cleanupInteractions(); + @action + cleanupInteractions = () => { + document.removeEventListener("pointerup", this.onPointerUp, true); } - @action - cleanupInteractions = () => { + onPointerDown = (e: React.PointerEvent) => { + this._visible = false; document.removeEventListener("keypress", this.onKeyPress, false); + this._showOnUp = true; + this._lastX = this.DownX = e.pageX; + this._lastY = this.DownY = e.pageY; + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("pointermove", this.onPointerMove, true); + } + @action + onPointerMove = (e: PointerEvent): void => { + if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) { + this._showOnUp = false; + } } @action - onCursorPlaced = (visible: boolean, downX: number, downY: number): void => { - if (visible) { + onPointerUp = (e: PointerEvent): void => { + if (this._showOnUp) { document.addEventListener("keypress", this.onKeyPress, false); - this._lastX = downX; - this._lastY = downY; - } else - this.cleanupInteractions(); + this._visible = true; + } + this.cleanupInteractions(); } @action @@ -61,16 +67,29 @@ export class PreviewCursor extends React.Component { let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); this.props.addLiveTextDocument(newBox); + document.removeEventListener("keypress", this.onKeyPress, false); + this._visible = false; e.stopPropagation(); } } + //when focus is lost, this will remove the preview cursor + @action + onBlur = (): void => { + this._visible = false; + document.removeEventListener("keypress", this.onKeyPress, false); + } render() { //get local position and place cursor there! - let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); + let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); + if (this._visible && this._previewDivRef.current) + this._previewDivRef.current!.focus(); return ( - !this.props.container.PreviewCursorVisible ? (null) : -
I
) - +
+ {this.props.children} + {!this._visible ? (null) : +
I
} +
+ ) } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 2ac86b53d28abb8be2f3abd501b801e543973e28 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 31 Mar 2019 22:45:43 -0400 Subject: restructured inkingCanvas and linksViews --- src/client/views/InkingCanvas.scss | 164 +++------------------ src/client/views/InkingCanvas.tsx | 121 +++++++-------- src/client/views/InkingControl.scss | 135 +++++++++++++++++ src/client/views/InkingControl.tsx | 3 +- .../CollectionFreeFormLinksView.scss | 3 + .../CollectionFreeFormLinksView.tsx | 9 +- .../collectionFreeForm/CollectionFreeFormView.scss | 10 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 6 +- src/client/views/nodes/PDFBox.tsx | 10 +- 10 files changed, 233 insertions(+), 238 deletions(-) create mode 100644 src/client/views/InkingControl.scss (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx') diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index e79b146b9..6d7821cd2 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -1,149 +1,25 @@ @import "global_variables"; -.inking-canvas { + +.inkingCanvas-paths, .inkingCanvas-paths-none { position: absolute; top: -50000px; - left: -50000px; // z-index: 99; //overlays ink on top of everything - svg { - position:absolute; - width: 100000px; - height: 100000px; - .highlight { - mix-blend-mode: multiply; - } - } + left: -50000px; + width: 100000px; + height: 100000px; + //z-index: 10000; // z-index: 99; //overlays ink on top of everything but that messes up blending and events don't propagate down to nested collections + .highlight { + mix-blend-mode: multiply; + } + .inkingCanvas-children { + transform: translate(50000px, 50000px); + pointer-events: none; + } + cursor:"crosshair"; + pointer-events: auto; + +} +.inkingCanvas-paths-none { + pointer-events: none; + cursor: "arrow"; } -.inking-control { - position: absolute; - left: 70px; - bottom: 70px; - margin: 0; - padding: 0; - display: flex; - label, - input, - option { - font-size: 12px; - } - input[type="range"] { - -webkit-appearance: none; - background-color: transparent; - vertical-align: middle; - margin-top: 8px; - &:focus { - outline: none; - } - &::-webkit-slider-runnable-track { - width: 100%; - height: 3px; - border-radius: 1.5px; - cursor: pointer; - background: $intermediate-color; - } - &::-webkit-slider-thumb { - height: 12px; - width: 12px; - border: 1px solid $intermediate-color; - border-radius: 6px; - background: $light-color; - cursor: pointer; - -webkit-appearance: none; - margin-top: -4px; - } - &::-moz-range-track { - width: 100%; - height: 3px; - border-radius: 1.5px; - cursor: pointer; - background: $light-color; - } - &::-moz-range-thumb { - height: 12px; - width: 12px; - border: 1px solid $intermediate-color; - border-radius: 6px; - background: $light-color; - cursor: pointer; - -webkit-appearance: none; - margin-top: -4px; - } - } - input[type="text"] { - border: none; - padding: 0 0px; - background: transparent; - color: $dark-color; - font-size: 12px; - margin-top: 4px; - } - .ink-panel { - margin: 6px 12px 6px 0; - height: 30px; - vertical-align: middle; - line-height: 36px; - padding: 0 10px; - color: $intermediate-color; - &:first { - margin-top: 0; - } - } - .ink-tools { - display: flex; - background-color: transparent; - border-radius: 0; - padding: 0; - button { - height: 36px; - padding: 0px; - padding-bottom: 3px; - margin-left: 10px; - background-color: transparent; - color: $intermediate-color; - } - button:hover { - transform: scale(1.15); - } - } - .ink-size { - display: flex; - justify-content: space-between; - input[type="text"] { - width: 42px; - } - >* { - margin-right: 6px; - &:last-child { - margin-right: 0; - } - } - } - .ink-color { - display: flex; - position: relative; - padding-right: 0; - label { - margin-right: 6px; - } - .ink-color-display { - border-radius: 11px; - width: 22px; - height: 22px; - margin-top: 6px; - cursor: pointer; - text-align: center; // span { - // color: $light-color; - // font-size: 8px; - // user-select: none; - // } - } - .ink-color-picker { - background-color: $light-color; - border-radius: 5px; - padding: 12px; - position: absolute; - bottom: 36px; - left: -3px; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - } - } -} \ No newline at end of file diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 8fb74ec48..a971264b9 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -57,51 +57,62 @@ export class InkingCanvas extends React.Component { } @action - handleMouseDown = (e: React.PointerEvent): void => { - if (e.button != 0 || + onPointerDown = (e: React.PointerEvent): void => { + this._isDrawing = false; + if (e.button != 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) { return; } - e.stopPropagation() - if (InkingControl.Instance.selectedTool === InkTool.Eraser) { - return - } - const point = this.relativeCoordinatesForEvent(e); - - // start the new line, saves a uuid to represent the field of the stroke - this._idGenerator = Utils.GenerateGuid(); - this.inkData.set(this._idGenerator, - { - pathData: [point], - color: InkingControl.Instance.selectedColor, - width: InkingControl.Instance.selectedWidth, - tool: InkingControl.Instance.selectedTool, - page: this.props.Document.GetNumber(KeyStore.CurPage, -1) - }); + document.addEventListener("pointermove", this.onPointerMove, true); + document.addEventListener("pointerup", this.onPointerUp, true); + e.stopPropagation(); + this._isDrawing = true; + if (InkingControl.Instance.selectedTool != InkTool.Eraser) { + const point = this.relativeCoordinatesForEvent(e.clientX, e.clientY); + + // start the new line, saves a uuid to represent the field of the stroke + this._idGenerator = Utils.GenerateGuid(); + this.inkData.set(this._idGenerator, + { + pathData: [point], + color: InkingControl.Instance.selectedColor, + width: InkingControl.Instance.selectedWidth, + tool: InkingControl.Instance.selectedTool, + page: this.props.Document.GetNumber(KeyStore.CurPage, -1) + }); + } } - @action - handleMouseMove = (e: React.PointerEvent): void => { - if (!this._isDrawing || - InkingControl.Instance.selectedTool === InkTool.None) { - return; + onPointerUp = (e: PointerEvent): void => { + if (this._isDrawing) { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this._isDrawing = false; + e.stopPropagation(); } - e.stopPropagation() - if (InkingControl.Instance.selectedTool === InkTool.Eraser) { - return - } - const point = this.relativeCoordinatesForEvent(e); + } - // add points to new line as it is being drawn - let data = this.inkData; - let strokeData = data.get(this._idGenerator); - if (strokeData) { - strokeData.pathData.push(point); - data.set(this._idGenerator, strokeData); + @action + onPointerMove = (e: PointerEvent): void => { + if (this._isDrawing) { + e.stopPropagation() + e.preventDefault(); + if (InkingControl.Instance.selectedTool === InkTool.Eraser) { + return + } + const point = this.relativeCoordinatesForEvent(e.clientX, e.clientY); + + // add points to new line as it is being drawn + let data = this.inkData; + let strokeData = data.get(this._idGenerator); + if (strokeData) { + strokeData.pathData.push(point); + data.set(this._idGenerator, strokeData); + } + + this.inkData = data; } - - this.inkData = data; } @action @@ -109,8 +120,8 @@ export class InkingCanvas extends React.Component { this._isDrawing = false; } - relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => { - let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY); + 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 }; @@ -124,32 +135,9 @@ export class InkingCanvas extends React.Component { } render() { - // styling for cursor - let canvasStyle = {}; - if (InkingControl.Instance.selectedTool === InkTool.None) { - canvasStyle = { pointerEvents: "none" }; - } else { - canvasStyle = { pointerEvents: "auto", cursor: "crosshair" }; - } - - // get data from server - // let inkField = this.props.Document.GetT(KeyStore.Ink, InkField); - // if (!inkField || inkField == FieldWaiting) { - // return (
- // - // - //
) - // } - - let lines = this.inkData; - // parse data from server - let paths: Array = [] let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) - Array.from(lines).map(item => { - let id = item[0]; - let strokeData = item[1]; + let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { if (strokeData.page == -1 || strokeData.page == curPage) paths.push( { width={strokeData.width} tool={strokeData.tool} deleteCallback={this.removeLine} />) - }) + return paths; + }, [] as JSX.Element[]); + let svgCanvasStyle = InkingControl.Instance.selectedTool == InkTool.None ? "-none" : ""; return ( -
- +
+ {paths} + {this.props.children}
) } diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss new file mode 100644 index 000000000..0d8fd8784 --- /dev/null +++ b/src/client/views/InkingControl.scss @@ -0,0 +1,135 @@ +@import "global_variables"; +.inking-control { + position: absolute; + left: 70px; + bottom: 70px; + margin: 0; + padding: 0; + display: flex; + label, + input, + option { + font-size: 12px; + } + input[type="range"] { + -webkit-appearance: none; + background-color: transparent; + vertical-align: middle; + margin-top: 8px; + &:focus { + outline: none; + } + &::-webkit-slider-runnable-track { + width: 100%; + height: 3px; + border-radius: 1.5px; + cursor: pointer; + background: $intermediate-color; + } + &::-webkit-slider-thumb { + height: 12px; + width: 12px; + border: 1px solid $intermediate-color; + border-radius: 6px; + background: $light-color; + cursor: pointer; + -webkit-appearance: none; + margin-top: -4px; + } + &::-moz-range-track { + width: 100%; + height: 3px; + border-radius: 1.5px; + cursor: pointer; + background: $light-color; + } + &::-moz-range-thumb { + height: 12px; + width: 12px; + border: 1px solid $intermediate-color; + border-radius: 6px; + background: $light-color; + cursor: pointer; + -webkit-appearance: none; + margin-top: -4px; + } + } + input[type="text"] { + border: none; + padding: 0 0px; + background: transparent; + color: $dark-color; + font-size: 12px; + margin-top: 4px; + } + .ink-panel { + margin: 6px 12px 6px 0; + height: 30px; + vertical-align: middle; + line-height: 36px; + padding: 0 10px; + color: $intermediate-color; + &:first { + margin-top: 0; + } + } + .ink-tools { + display: flex; + background-color: transparent; + border-radius: 0; + padding: 0; + button { + height: 36px; + padding: 0px; + padding-bottom: 3px; + margin-left: 10px; + background-color: transparent; + color: $intermediate-color; + } + button:hover { + transform: scale(1.15); + } + } + .ink-size { + display: flex; + justify-content: space-between; + input[type="text"] { + width: 42px; + } + >* { + margin-right: 6px; + &:last-child { + margin-right: 0; + } + } + } + .ink-color { + display: flex; + position: relative; + padding-right: 0; + label { + margin-right: 6px; + } + .ink-color-display { + border-radius: 11px; + width: 22px; + height: 22px; + margin-top: 6px; + cursor: pointer; + text-align: center; // span { + // color: $light-color; + // font-size: 8px; + // user-select: none; + // } + } + .ink-color-picker { + background-color: $light-color; + border-radius: 5px; + padding: 12px; + position: absolute; + bottom: 36px; + left: -3px; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + } + } +} \ No newline at end of file diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index eb2172d03..c1519dff8 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -2,10 +2,9 @@ import { observable, action, computed } from "mobx"; import { CirclePicker, ColorResult } from 'react-color' import React = require("react"); -import "./InkingCanvas.scss" import { InkTool } from "../../fields/InkField"; import { observer } from "mobx-react"; -import "./InkingCanvas.scss" +import "./InkingControl.scss" import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss index 9af0df7f5..4341c82f7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss @@ -4,4 +4,7 @@ width: 20000px; height: 20000px; pointer-events: none; + } + .collectionfreeformlinksview-container { + pointer-events: none; } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index ea392eaea..ef60aa672 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -94,9 +94,12 @@ export class CollectionFreeFormLinksView extends React.Component - {this.uniqueConnections.map(c => )} +
+ + {this.uniqueConnections.map(c => )} + {this.props.children} - ); +
+ ); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index c979b27a9..81d21d89a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -7,9 +7,6 @@ width: 100%; height: 100%; transform-origin: left top; - .inking-canvas { - transform-origin: 50000px 50000px; - } } .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { @@ -18,9 +15,6 @@ width: 100%; } - .inking-canvas { - transform-origin: 50000px 50000px; - } //nested freeform views // .collectionfreeformview-container { // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px), @@ -48,10 +42,6 @@ background: $light-color-secondary; } - .inking-canvas { - transform-origin: 50000px 50000px; - } - opacity: 0.99; border: 0px solid transparent; border-radius: $border-radius; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cdc5bdfb0..ae1c775e6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -314,16 +314,18 @@ export class CollectionFreeFormView extends CollectionViewBase {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} tabIndex={0} ref={this.createDropTarget}> + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}>
{this.backgroundView} - - {this.views} - + + + {this.views} + + {super.getCursors().map(entry => { if (entry.Data.length > 0) { let id = entry.Data[0][0]; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7f951864e..d52b662bd 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { computed, trace, reaction, runInAction, observable } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { KeyStore } from "../../../fields/KeyStore"; import { NumberField } from "../../../fields/NumberField"; @@ -6,8 +6,7 @@ import { Transform } from "../../util/Transform"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { Document } from "../../../fields/Document"; -import { DocumentManager } from "../../util/DocumentManager"; +import { thisExpression } from "babel-types"; @observer @@ -75,6 +74,7 @@ export class CollectionFreeFormDocumentView extends React.Component { @observable private _interactive: boolean = false; @observable private _loaded: boolean = false; - @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); } + @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 1); } @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); } componentDidMount() { @@ -435,8 +435,6 @@ export class PDFBox extends React.Component { @computed get pdfContent() { let page = this.curPage; - if (page == 0) - page = 1; const renderHeight = 2400; let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField); let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; @@ -463,10 +461,8 @@ export class PDFBox extends React.Component { return [ this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element), this._currAnno.map((element: any) => element), -
- {this.pdfContent} - {proxy} -
+ this.pdfContent, + proxy ]; } -- cgit v1.2.3-70-g09d2 From a687a9962c8952074177e52366b69dcce231a18e Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Apr 2019 13:46:32 -0400 Subject: fixed several things related to selections and creating text boxes. moved remote cursor stuff into its own file. --- src/client/util/SelectionManager.ts | 13 ++- src/client/views/InkingCanvas.tsx | 8 +- .../views/collections/CollectionViewBase.tsx | 8 -- .../CollectionFreeFormLinksView.tsx | 7 +- .../CollectionFreeFormRemoteCursors.tsx | 115 ++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 118 ++++----------------- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++--- .../collectionFreeForm/PreviewCursor.tsx | 67 ++++++++---- 8 files changed, 213 insertions(+), 147 deletions(-) create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1a711ae64..6d6ae26fc 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,5 +1,6 @@ import { observable, action } from "mobx"; import { DocumentView } from "../views/nodes/DocumentView"; +import { Document } from "../../fields/Document" export namespace SelectionManager { class Manager { @@ -29,8 +30,16 @@ export namespace SelectionManager { return manager.SelectedDocuments.indexOf(doc) !== -1; } - export function DeselectAll(): void { - manager.SelectedDocuments = [] + export function DeselectAll(except?: Document): void { + let found: DocumentView | undefined = undefined; + if (except) { + for (let i = 0; i < manager.SelectedDocuments.length; i++) { + let view = manager.SelectedDocuments[i]; + if (view.props.Document == except) + found = view; + } + } + manager.SelectedDocuments.length = 0; } export function SelectedDocuments(): Array { diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 15dfb255a..123ff679b 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -101,7 +101,6 @@ export class InkingCanvas extends React.Component { @computed get drawnPaths() { - trace(); // parse data from server let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { @@ -111,10 +110,10 @@ export class InkingCanvas extends React.Component { tool={strokeData.tool} deleteCallback={this.removeLine} />) return paths; }, [] as JSX.Element[]); - return [ + return [ {paths.filter(path => path.props.tool == InkTool.Highlighter)} , - + {paths.filter(path => path.props.tool != InkTool.Highlighter)} ]; } @@ -122,11 +121,10 @@ export class InkingCanvas extends React.Component { render() { let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect"; - trace(); return (
- {this.props.children} + {(this.props.children as any)() /* bcz: is there a better way to know that children is a function? */} {this.drawnPaths}
) diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 48adce4d8..987f3cb6c 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -76,14 +76,6 @@ export class CollectionViewBase extends React.Component } } - protected getCursors(): CursorEntry[] { - let doc = this.props.Document; - let id = CurrentUserUtils.id; - let cursors = doc.GetList(KeyStore.Cursors, []); - let notMe = cursors.filter(entry => entry.Data[0][0] !== id); - return id ? notMe : []; - } - @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index ef60aa672..eb20b3100 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, reaction, runInAction } from "mobx"; +import { computed, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -71,7 +71,7 @@ export class CollectionFreeFormLinksView extends React.Component { + let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { let srcViews = this.documentAnchors(connection.a); let targetViews = this.documentAnchors(connection.b); let possiblePairs: { a: Document, b: Document, }[] = []; @@ -90,13 +90,14 @@ export class CollectionFreeFormLinksView extends React.Component ); } render() { return (
- {this.uniqueConnections.map(c => )} + {this.uniqueConnections} {this.props.children}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx new file mode 100644 index 000000000..19382e66f --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -0,0 +1,115 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { FieldWaiting } from "../../../../fields/Field"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { TextField } from "../../../../fields/TextField"; +import { DragManager } from "../../../util/DragManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { InkingCanvas } from "../../InkingCanvas"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentContentsView } from "../../nodes/DocumentContentsView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; +import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; +import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase"; +import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import "./CollectionFreeFormView.scss"; +import { MarqueeView } from "./MarqueeView"; +import React = require("react"); +import v5 = require("uuid/v5"); +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; + +@observer +export class CollectionFreeFormRemoteCursors extends React.Component { + protected getCursors(): CursorEntry[] { + let doc = this.props.Document; + let id = CurrentUserUtils.id; + let cursors = doc.GetList(KeyStore.Cursors, []); + let notMe = cursors.filter(entry => entry.Data[0][0] !== id); + return id ? notMe : []; + } + + private crosshairs?: HTMLCanvasElement; + drawCrosshairs = (backgroundColor: string) => { + if (this.crosshairs) { + let c = this.crosshairs; + let ctx = c.getContext('2d'); + if (ctx) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, 20, 20); + + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; + + ctx.beginPath(); + + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); + + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); + + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); + + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); + + ctx.stroke(); + + // ctx.font = "10px Arial"; + // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); + } + } + } + @computed + get sharedCursors() { + return this.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( +
+ { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

{email[0].toUpperCase()}

+
+ ); + } + }) + } + + render() { + return this.sharedCursors; + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ae1c775e6..60fb95ff5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -18,6 +18,8 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); +import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; +import { PreviewCursor } from "./PreviewCursor"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -60,7 +62,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } - @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? + @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } @@ -97,13 +99,15 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerDown = (e: React.PointerEvent): void => { - if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { + if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointerup", this.onPointerUp); this._lastX = this.DownX = e.pageX; this._lastY = this.DownY = e.pageY; + if (this.props.isSelected()) + e.stopPropagation(); } } @@ -134,7 +138,6 @@ export class CollectionFreeFormView extends CollectionViewBase { onPointerWheel = (e: React.WheelEvent): void => { this.props.select(false); e.stopPropagation(); - e.preventDefault(); let coefficient = 1000; if (e.ctrlKey) { @@ -265,50 +268,13 @@ export class CollectionFreeFormView extends CollectionViewBase { getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; - - - private crosshairs?: HTMLCanvasElement; - drawCrosshairs = (backgroundColor: string) => { - if (this.crosshairs) { - let c = this.crosshairs; - let ctx = c.getContext('2d'); - if (ctx) { - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, 20, 20); - - ctx.fillStyle = "black"; - ctx.lineWidth = 0.5; - - ctx.beginPath(); - - ctx.moveTo(10, 0); - ctx.lineTo(10, 8); - - ctx.moveTo(10, 20); - ctx.lineTo(10, 12); - - ctx.moveTo(0, 10); - ctx.lineTo(8, 10); - - ctx.moveTo(20, 10); - ctx.lineTo(12, 10); - - ctx.stroke(); - - // ctx.font = "10px Arial"; - // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); - } - } - } + childViews = () => this.views; render() { let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); - // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; - // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; - // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY)); return (
-
- {this.backgroundView} - - - {this.views} - - - {super.getCursors().map(entry => { - if (entry.Data.length > 0) { - let id = entry.Data[0][0]; - let email = entry.Data[0][1]; - let point = entry.Data[1]; - this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") - return ( -
- { if (el) this.crosshairs = el }} - width={20} - height={20} - style={{ - position: 'absolute', - width: "20px", - height: "20px", - opacity: 0.5, - borderRadius: "50%", - border: "2px solid black" - }} - /> -

{email[0].toUpperCase()}

-
- ); - } - })} -
- {this.overlayView} + +
+ {this.backgroundView} + + + {this.childViews} + + + +
+ {this.overlayView} +
); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 2b0e7d228..20132a4b1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -21,7 +21,6 @@ interface MarqueeViewProps { activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; - addLiveTextDocument: (doc: Document) => void; } @observer @@ -77,10 +76,11 @@ export class MarqueeView extends React.Component onPointerUp = (e: PointerEvent): void => { this.cleanupInteractions(true); this._visible = false; + let mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(); + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } - this.props.selectDocuments(this.marqueeSelect()); + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -184,17 +184,17 @@ export class MarqueeView extends React.Component return selection; } - render() { + @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
- - {this.props.children} - {!this._visible ? (null) : -
} + return
+ } - + render() { + return
+ {this.props.children} + {!this._visible ? (null) : this.marqueeDiv}
; } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx index 877de8846..93c98f7b0 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -1,4 +1,4 @@ -import { action, observable, trace } from "mobx"; +import { action, observable, trace, computed, reaction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { Documents } from "../../../documents/Documents"; @@ -6,6 +6,7 @@ import { Transform } from "../../../util/Transform"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./PreviewCursor.scss"; import React = require("react"); +import { interfaceDeclaration } from "babel-types"; export interface PreviewCursorProps { @@ -23,27 +24,29 @@ export class PreviewCursor extends React.Component { @observable public DownX: number = 0; @observable public DownY: number = 0; _showOnUp: boolean = false; - public _previewDivRef = React.createRef(); @action cleanupInteractions = () => { document.removeEventListener("pointerup", this.onPointerUp, true); + document.removeEventListener("pointermove", this.onPointerMove, true); } @action onPointerDown = (e: React.PointerEvent) => { - this._visible = false; - document.removeEventListener("keypress", this.onKeyPress, false); - this._showOnUp = true; - this._lastX = this.DownX = e.pageX; - this._lastY = this.DownY = e.pageY; - document.addEventListener("pointerup", this.onPointerUp, true); - document.addEventListener("pointermove", this.onPointerMove, true); + if (e.button == 0 && this.props.container.props.active()) { + document.removeEventListener("keypress", this.onKeyPress, false); + this._showOnUp = true; + this.DownX = e.pageX; + this.DownY = e.pageY; + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("pointermove", this.onPointerMove, true); + } } @action onPointerMove = (e: PointerEvent): void => { if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) { this._showOnUp = false; + this._visible = false; } } @@ -51,6 +54,8 @@ export class PreviewCursor extends React.Component { onPointerUp = (e: PointerEvent): void => { if (this._showOnUp) { document.addEventListener("keypress", this.onKeyPress, false); + this._lastX = this.DownX; + this._lastY = this.DownY; this._visible = true; } this.cleanupInteractions(); @@ -72,25 +77,43 @@ export class PreviewCursor extends React.Component { e.stopPropagation(); } } - //when focus is lost, this will remove the preview cursor - @action - onBlur = (): void => { - this._visible = false; + + getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); + getVisible = () => this._visible; + setVisible = (v: boolean) => { + this._visible = v; document.removeEventListener("keypress", this.onKeyPress, false); } - render() { - //get local position and place cursor there! - let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); - if (this._visible && this._previewDivRef.current) - this._previewDivRef.current!.focus(); - trace(); return ( -
+
{this.props.children} - {!this._visible ? (null) : -
I
} +
) } +} + +export interface PromptProps { + getPoint: () => number[]; + getVisible: () => boolean; + setVisible: (v: boolean) => void; +} + +@observer +export class PreviewCursorPrompt extends React.Component { + private _promptRef = React.createRef(); + + //when focus is lost, this will remove the preview cursor + @action onBlur = (): void => this.props.setVisible(false); + + render() { + let p = this.props.getPoint(); + if (this.props.getVisible() && this._promptRef.current) + this._promptRef.current.focus(); + return
+ I +
; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 8448679e0a260ec881c54a10578bc464cd38f04e Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Apr 2019 17:05:42 -0400 Subject: added dragging multiple items. --- src/client/northstar/dash-nodes/HistogramBox.tsx | 4 +- src/client/util/DragManager.ts | 168 +++++++++++---------- src/client/views/DocumentDecorations.scss | 6 + src/client/views/DocumentDecorations.tsx | 109 +++++++++---- src/client/views/Main.tsx | 7 +- .../views/collections/CollectionDockingView.tsx | 11 +- src/client/views/collections/CollectionView.tsx | 2 +- .../views/collections/CollectionViewBase.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 ++- src/client/views/nodes/DocumentView.tsx | 15 +- 10 files changed, 213 insertions(+), 134 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx') diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index a968def96..49ebe5ebc 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -54,7 +54,7 @@ export class HistogramBox extends React.Component { @action dropX = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField); + let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField); if (h && h != FieldWaiting) { this.HistoOp.X = h.Data.X; } @@ -65,7 +65,7 @@ export class HistogramBox extends React.Component { @action dropY = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField); + let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField); if (h && h != FieldWaiting) { this.HistoOp.Y = h.Data.X; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 70b1c9829..9ffe964ef 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -14,9 +14,9 @@ export function setupDrag(_reference: React.RefObject, docFunc: document.removeEventListener("pointermove", onRowMove); document.removeEventListener('pointerup', onRowUp); - var dragData = new DragManager.DocumentDragData(docFunc()); + var dragData = new DragManager.DocumentDragData([docFunc()]); dragData.removeDocument = removeFunc; - DragManager.StartDocumentDrag(_reference.current!, dragData); + DragManager.StartDocumentDrag([_reference.current!], dragData); }); let onRowUp = action((e: PointerEvent): void => { document.removeEventListener("pointermove", onRowMove); @@ -27,7 +27,7 @@ export function setupDrag(_reference: React.RefObject, docFunc: if (e.button == 0) { e.stopPropagation(); if (e.shiftKey) { - CollectionDockingView.Instance.StartOtherDrag(docFunc(), e); + CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); } else { document.addEventListener("pointermove", onRowMove); document.addEventListener('pointerup', onRowUp); @@ -101,20 +101,21 @@ export namespace DragManager { } export class DocumentDragData { - constructor(dragDoc: Document) { - this.draggedDocument = dragDoc; - this.droppedDocument = dragDoc; + constructor(dragDoc: Document[]) { + this.draggedDocuments = dragDoc; + this.droppedDocuments = dragDoc; } - draggedDocument: Document; - droppedDocument: Document; + draggedDocuments: Document[]; + droppedDocuments: Document[]; xOffset?: number; yOffset?: number; aliasOnDrop?: boolean; removeDocument?: (collectionDrop: CollectionView) => void; [id: string]: any; } - export function StartDocumentDrag(ele: HTMLElement, dragData: DocumentDragData, options?: DragOptions) { - StartDrag(ele, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocument = dragData.aliasOnDrop ? dragData.draggedDocument.CreateAlias() : dragData.draggedDocument); + + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, options?: DragOptions) { + StartDrag(eles, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.draggedDocuments); } export class LinkDragData { @@ -125,49 +126,58 @@ export namespace DragManager { [id: string]: any; } export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) { - StartDrag(ele, dragData, options); + StartDrag([ele], dragData, options); } - function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { + function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { if (!dragDiv) { dragDiv = document.createElement("div"); dragDiv.className = "dragManager-dragDiv" DragManager.Root().appendChild(dragDiv); } - const w = ele.offsetWidth, h = ele.offsetHeight; - const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / w, scaleY = rect.height / h; - let x = rect.left, y = rect.top; - // const offsetX = e.x - rect.left, offsetY = e.y - rect.top; - - let dragElement = ele.cloneNode(true) as HTMLElement; - dragElement.style.opacity = "0.7"; - dragElement.style.position = "absolute"; - dragElement.style.bottom = ""; - dragElement.style.left = ""; - dragElement.style.transformOrigin = "0 0"; - dragElement.style.zIndex = "1000"; - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; - dragElement.style.width = `${rect.width / scaleX}px`; - dragElement.style.height = `${rect.height / scaleY}px`; - - // bcz: PDFs don't show up if you clone them because they contain a canvas. - // however, PDF's have a thumbnail field that contains an image of their canvas. - // So we replace the pdf's canvas with the image thumbnail - const doc: Document = dragData["draggedDocument"]; - if (doc) { - var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement; - let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField); - if (pdfBox && pdfBox.childElementCount && thumbnail) { - let img = new Image(); - img!.src = thumbnail.toString(); - img!.style.position = "absolute"; - img!.style.width = `${rect.width / scaleX}px`; - img!.style.height = `${rect.height / scaleY}px`; - pdfBox.replaceChild(img!, pdfBox.children[0]) + + let scaleXs: number[] = []; + let scaleYs: number[] = []; + let xs: number[] = []; + let ys: number[] = []; + + const docs: Document[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : []; + let dragElements = eles.map(ele => { + const w = ele.offsetWidth, h = ele.offsetHeight; + const rect = ele.getBoundingClientRect(); + const scaleX = rect.width / w, scaleY = rect.height / h; + let x = rect.left, y = rect.top; + xs.push(x); ys.push(y); + scaleXs.push(scaleX); scaleYs.push(scaleY); + let dragElement = ele.cloneNode(true) as HTMLElement; + dragElement.style.opacity = "0.7"; + dragElement.style.position = "absolute"; + dragElement.style.bottom = ""; + dragElement.style.left = ""; + dragElement.style.transformOrigin = "0 0"; + dragElement.style.zIndex = "1000"; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragElement.style.width = `${rect.width / scaleX}px`; + dragElement.style.height = `${rect.height / scaleY}px`; + + // bcz: PDFs don't show up if you clone them because they contain a canvas. + // however, PDF's have a thumbnail field that contains an image of their canvas. + // So we replace the pdf's canvas with the image thumbnail + if (docs.length) { + var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement; + let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); + if (pdfBox && pdfBox.childElementCount && thumbnail) { + let img = new Image(); + img!.src = thumbnail.toString(); + img!.style.position = "absolute"; + img!.style.width = `${rect.width / scaleX}px`; + img!.style.height = `${rect.height / scaleY}px`; + pdfBox.replaceChild(img!, pdfBox.children[0]) + } } - } - dragDiv.appendChild(dragElement); + dragDiv.appendChild(dragElement); + return dragElement; + }); let hideSource = false; if (options) { @@ -177,62 +187,64 @@ export namespace DragManager { hideSource = options.hideSource(); } } - const wasHidden = ele.hidden; - if (hideSource) { - ele.hidden = true; - } + eles.map(ele => ele.hidden = hideSource); + const moveHandler = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); - x += e.movementX; - y += e.movementY; if (dragData instanceof DocumentDragData) dragData.aliasOnDrop = e.ctrlKey || e.altKey; if (e.shiftKey) { abortDrag(); - CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); + CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); } - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragElements.map((dragElement, i) => dragElement.style.transform = `translate(${xs[i] += e.movementX}px, ${ys[i] += e.movementY}px) scale(${scaleXs[i]}, ${scaleYs[i]})`); }; const abortDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - dragDiv.removeChild(dragElement); - ele.hidden = false; + dragElements.map(dragElement => dragDiv.removeChild(dragElement)); + eles.map(ele => ele.hidden = false); } const upHandler = (e: PointerEvent) => { abortDrag(); - FinishDrag(ele, e, dragData, options, finishDrag); + FinishDrag(eles, e, dragData, options, finishDrag); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } - function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { - let parent = dragEle.parentElement; - if (parent) - parent.removeChild(dragEle); + function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { + let removed = dragEles.map(dragEle => { + let parent = dragEle.parentElement; + if (parent) + parent.removeChild(dragEle); + return [dragEle, parent]; + }); const target = document.elementFromPoint(e.x, e.y); - if (parent) - parent.appendChild(dragEle); - if (!target) { - return; - } - if (finishDrag) - finishDrag(dragData); - - target.dispatchEvent(new CustomEvent("dashOnDrop", { - bubbles: true, - detail: { - x: e.x, - y: e.y, - data: dragData + removed.map(r => { + let dragEle: HTMLElement = r[0]!; + let parent: HTMLElement | null = r[1]; + if (parent) + parent.appendChild(dragEle); + }); + if (target) { + if (finishDrag) + finishDrag(dragData); + + target.dispatchEvent(new CustomEvent("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + data: dragData + } + })); + + if (options) { + options.handlers.dragComplete({}); } - })); - - if (options) { - options.handlers.dragComplete({}); } DocumentDecorations.Instance.Hidden = false; } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 11595aa01..7a43f3087 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -32,6 +32,12 @@ } } +.documentDecorations-background { + background:lightblue; + position: absolute; + opacity: 0.1; +} + // position: absolute; // display: grid; // z-index: 1000; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 47098c3b5..faba3b6ea 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,14 +1,14 @@ -import { observable, computed, action } from "mobx"; -import React = require("react"); -import { SelectionManager } from "../util/SelectionManager"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; -import './DocumentDecorations.scss' -import { KeyStore } from '../../fields/KeyStore' +import { KeyStore } from '../../fields/KeyStore'; +import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; -import { props } from "bluebird"; import { DragManager } from "../util/DragManager"; +import { SelectionManager } from "../util/SelectionManager"; +import { CollectionView } from "./collections/CollectionView"; +import './DocumentDecorations.scss'; import { LinkMenu } from "./nodes/LinkMenu"; -import { ListField } from "../../fields/ListField"; +import React = require("react"); const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,6 +22,7 @@ export class DocumentDecorations extends React.Component { private _resizeBorderWidth = 16; private _linkButton = React.createRef(); @observable private _hidden = false; + @observable private _dragging = false; constructor(props: Readonly<{}>) { super(props) @@ -50,6 +51,50 @@ export class DocumentDecorations extends React.Component { public get Hidden() { return this._hidden; } public set Hidden(value: boolean) { this._hidden = value; } + _lastDrag: number[] = [0, 0]; + onBackgroundDown = (e: React.PointerEvent): void => { + document.removeEventListener("pointermove", this.onBackgroundMove); + document.addEventListener("pointermove", this.onBackgroundMove); + document.removeEventListener("pointerup", this.onBackgroundUp); + document.addEventListener("pointerup", this.onBackgroundUp); + this._lastDrag = [e.clientX, e.clientY] + e.stopPropagation(); + e.preventDefault(); + } + + @action + onBackgroundMove = (e: PointerEvent): void => { + let dragDocView = SelectionManager.SelectedDocuments()[0]; + const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); + dragData.aliasOnDrop = false; + dragData.xOffset = e.x - left; + dragData.yOffset = e.y - top; + dragData.removeDocument = (dropCollectionView: CollectionView) => + dragData.draggedDocuments.map(d => { + if (dragDocView.props.RemoveDocument && dragDocView.props.ContainingCollectionView !== dropCollectionView) { + dragDocView.props.RemoveDocument(d); + } + }); + this._dragging = true; + document.removeEventListener("pointermove", this.onBackgroundMove); + document.removeEventListener("pointerup", this.onBackgroundUp); + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => (docView as any)._mainCont!.current!), dragData, { + handlers: { + dragComplete: action(() => this._dragging = false), + }, + hideSource: true + }) + e.stopPropagation(); + } + + onBackgroundUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onBackgroundMove); + document.removeEventListener("pointerup", this.onBackgroundUp); + e.stopPropagation(); + e.preventDefault(); + } + onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { @@ -191,7 +236,6 @@ export class DocumentDecorations extends React.Component { // buttonOnPointerUp = (e: React.PointerEvent): void => { // e.stopPropagation(); // } - render() { var bounds = this.Bounds; if (this.Hidden) { @@ -218,25 +262,36 @@ export class DocumentDecorations extends React.Component { ); } return ( -
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
- -
{linkButton}
- -
+
+
1 ? 1000 : 0, + }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation() }} > +
+
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+ +
{linkButton}
+ +
+
) } } \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 75855c3d1..6f66f8f38 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -218,7 +218,8 @@ export class Main extends React.Component { focusDocument = (doc: Document) => { } noScaling = () => 1; - get content() { + @computed + get mainContent() { return !this.mainContainer ? (null) : + runInAction(() => { this.pwidth = r.entry.width; this.pheight = r.entry.height; })}> {({ measureRef }) =>
- {this.content} + {this.mainContent}
}
- {this.nodesMenu} {this.miscButtons} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f123149dd..c6cbc05e7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -47,9 +47,10 @@ export class CollectionDockingView extends React.Component { }, button: 0 }) + public StartOtherDrag(dragDocs: Document[], e: any) { + dragDocs.map(dragDoc => + this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. + onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 })); } @action @@ -199,7 +200,7 @@ export class CollectionDockingView extends React.Component) => { if (f instanceof Document) - DragManager.StartDocumentDrag(tab, new DragManager.DocumentDragData(f as Document), + DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f as Document]), { handlers: { dragComplete: action(() => { }), @@ -265,6 +266,7 @@ export class CollectionDockingView extends React.Component { } render() { + trace(); if (!this._document) return (null); var content = diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a1498e0ae..014aa1d8f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -113,7 +113,7 @@ export class CollectionView extends React.Component { if (index !== -1) { value.splice(index, 1) - SelectionManager.DeselectAll() + //SelectionManager.DeselectAll() ContextMenu.Instance.clearItems() return true; } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 987f3cb6c..316d20c9d 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -82,9 +82,9 @@ export class CollectionViewBase extends React.Component if (de.data instanceof DragManager.DocumentDragData) { if (de.data.aliasOnDrop) { [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => - de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); + de.data.draggedDocuments.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); } - let added = this.props.addDocument(de.data.droppedDocument, false); + let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true); if (added && de.data.removeDocument && !de.data.aliasOnDrop) { de.data.removeDocument(this.props.CollectionView); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 60fb95ff5..c5178f69d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -77,13 +77,20 @@ export class CollectionFreeFormView extends CollectionViewBase { let screenX = de.x - (de.data.xOffset as number || 0); let screenY = de.y - (de.data.yOffset as number || 0); const [x, y] = this.getTransform().transformPoint(screenX, screenY); - de.data.droppedDocument.SetNumber(KeyStore.X, x); - de.data.droppedDocument.SetNumber(KeyStore.Y, y); - if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) { - de.data.droppedDocument.SetNumber(KeyStore.Width, 300); - de.data.droppedDocument.SetNumber(KeyStore.Height, 300); - } - this.bringToFront(de.data.droppedDocument); + let dragDoc = de.data.draggedDocuments[0]; + let dragX = dragDoc.GetNumber(KeyStore.X, 0); + let dragY = dragDoc.GetNumber(KeyStore.Y, 0); + de.data.draggedDocuments.map(d => { + let docX = d.GetNumber(KeyStore.X, 0); + let docY = d.GetNumber(KeyStore.Y, 0); + d.SetNumber(KeyStore.X, x + (docX - dragX)); + d.SetNumber(KeyStore.Y, y + (docY - dragY)); + if (!d.GetNumber(KeyStore.Width, 0)) { + d.SetNumber(KeyStore.Width, 300); + d.SetNumber(KeyStore.Height, 300); + } + this.bringToFront(d); + }) } return true; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 71613ca4f..1195128dc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -106,7 +106,7 @@ export class DocumentView extends React.Component { if (this.props.isTopMost) { this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); } - else CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e); + else CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); e.stopPropagation(); } else { if (this.active && !e.isDefaultPrevented()) { @@ -125,9 +125,7 @@ export class DocumentView extends React.Component { if (this._mainCont.current) { this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } - runInAction(() => { - DocumentManager.Instance.DocumentViews.push(this); - }) + runInAction(() => DocumentManager.Instance.DocumentViews.push(this)) this._reactionDisposer = reaction( () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(), () => { @@ -149,10 +147,7 @@ export class DocumentView extends React.Component { if (this.dropDisposer) { this.dropDisposer(); } - runInAction(() => { - DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); - - }) + runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1)) if (this._reactionDisposer) { this._reactionDisposer(); } @@ -161,7 +156,7 @@ export class DocumentView extends React.Component { startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { if (this._mainCont.current) { const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - let dragData = new DragManager.DocumentDragData(this.props.Document); + let dragData = new DragManager.DocumentDragData([this.props.Document]); dragData.aliasOnDrop = dropAliasOfDraggedDoc; dragData.xOffset = x - left; dragData.yOffset = y - top; @@ -170,7 +165,7 @@ export class DocumentView extends React.Component { this.props.RemoveDocument(this.props.Document); } } - DragManager.StartDocumentDrag(this._mainCont.current, dragData, { + DragManager.StartDocumentDrag([this._mainCont.current], dragData, { handlers: { dragComplete: action(() => { }), }, -- cgit v1.2.3-70-g09d2