From b7353705ee06292e570c9847d72287190f3f42ed Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 29 Oct 2019 19:09:04 -0400 Subject: started implementing ink select with document decorations --- src/client/util/SelectionManager.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/client/util/SelectionManager.ts') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 2d717ca57..3ae43e029 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -11,6 +11,7 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; + @observable SelectedInk: Array> = []; @action @@ -41,6 +42,20 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; + manager.SelectedInk = []; + } + + @action + SelectInk(ink: Map, ctrlPressed: boolean): void { + if (manager.SelectedInk.indexOf(ink) === -1) { + if (!ctrlPressed) { + this.DeselectAll(); + } + + manager.SelectedInk.push(ink); + } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { + manager.SelectedInk = [ink]; + } } } @@ -53,6 +68,10 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } + export function SelectInk(ink: Map, ctrlPressed: boolean): void { + manager.SelectInk(ink, ctrlPressed); + } + export function IsSelected(doc: DocumentView): boolean { return manager.SelectedDocuments.indexOf(doc) !== -1; } @@ -75,4 +94,15 @@ export namespace SelectionManager { export function SelectedDocuments(): Array { return manager.SelectedDocuments.slice(); } + + export function SelectedInk(): Array> { + return manager.SelectedInk.slice(); + } + + export function AllSelected(): Array> { + let arr: Array> = []; + arr = SelectionManager.SelectedDocuments(); + arr.push(...SelectionManager.SelectedInk()); + return arr; + } } -- cgit v1.2.3-70-g09d2 From ca406ddce0a9b35ca9063f74979c952e080339f1 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 3 Nov 2019 16:59:13 -0500 Subject: some infrastructure stuff and bug fixes. not really any new features yet --- src/client/util/SelectionManager.ts | 13 +++++----- src/client/views/DocumentDecorations.tsx | 29 ++++++++++++++-------- src/client/views/InkingStroke.tsx | 6 +++++ .../views/collections/CollectionDockingView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 12 ++++++--- src/client/views/nodes/DocumentContentsView.tsx | 4 ++- 7 files changed, 46 insertions(+), 22 deletions(-) (limited to 'src/client/util/SelectionManager.ts') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 3ae43e029..2a57c67bd 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,6 +4,7 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; import { InkingControl } from "../views/InkingControl"; +import { InkDocAndStroke } from "../views/InkingStroke"; export namespace SelectionManager { @@ -11,7 +12,7 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; - @observable SelectedInk: Array> = []; + @observable SelectedInk: Array<{ Document: Doc, Ink: Map }> = []; @action @@ -46,7 +47,7 @@ export namespace SelectionManager { } @action - SelectInk(ink: Map, ctrlPressed: boolean): void { + SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { if (manager.SelectedInk.indexOf(ink) === -1) { if (!ctrlPressed) { this.DeselectAll(); @@ -68,7 +69,7 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - export function SelectInk(ink: Map, ctrlPressed: boolean): void { + export function SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { manager.SelectInk(ink, ctrlPressed); } @@ -95,12 +96,12 @@ export namespace SelectionManager { return manager.SelectedDocuments.slice(); } - export function SelectedInk(): Array> { + export function SelectedInk(): Array<{ Document: Doc, Ink: Map }> { return manager.SelectedInk.slice(); } - export function AllSelected(): Array> { - let arr: Array> = []; + export function AllSelected(): Array { + let arr: Array = []; arr = SelectionManager.SelectedDocuments(); arr.push(...SelectionManager.SelectedInk()); return arr; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cdb49350f..c3020eacb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -186,8 +186,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let top = bounds.y; let right = bounds.r; let bottom = bounds.b; - docViewOrInk.forEach((value: StrokeData, key: string) => { + let ink; + docViewOrInk.Ink.forEach((value: StrokeData, key: string) => { value.pathData.map(val => { + ink = docViewOrInk.Document.ink; left = Math.min(val.x, left); top = Math.min(val.y, top); right = Math.max(val.x, right); @@ -224,7 +226,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onBackgroundUp); document.removeEventListener("pointermove", this.onTitleMove); document.removeEventListener("pointerup", this.onTitleUp); - DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, { + DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, { handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, hideSource: true }); @@ -548,15 +550,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get selectionTitle(): string { - if (SelectionManager.SelectedDocuments().length === 1) { - let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey]; - if (typeof field === "string") { - return field; + if (SelectionManager.AllSelected().length === 1) { + let selected = SelectionManager.AllSelected()[0]; + if (selected instanceof DocumentView) { + let field = selected.props.Document[this._fieldKey]; + if (typeof field === "string") { + return field; + } + else if (typeof field === "number") { + return field.toString(); + } } - else if (typeof field === "number") { - return field.toString(); + else { + return "-ink strokes-"; } - } else if (SelectionManager.SelectedDocuments().length > 1) { + } else if (SelectionManager.AllSelected().length > 1) { return "-multiple-"; } return "-unset-"; @@ -581,7 +589,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } let minimizeIcon = (
- {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} + {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} + {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
); bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 332c22512..a097a7991 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -5,6 +5,7 @@ import React = require("react"); import { InkTool } from "../../new_fields/InkField"; import "./InkingStroke.scss"; import { AudioBox } from "./nodes/AudioBox"; +import { Doc } from "../../new_fields/Doc"; interface StrokeProps { @@ -20,6 +21,11 @@ interface StrokeProps { deleteCallback: (index: string) => void; } +export type InkDocAndStroke = { + Document: Doc; + Ink: Map; +}; + @observer export class InkingStroke extends React.Component { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 937a4949e..3ff99b9f4 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -617,7 +617,7 @@ export class DockedFrameRenderer extends React.Component { } } - get layoutDoc() { return this._document && Doc.Layout(this._document);} + get layoutDoc() { return this._document && Doc.Layout(this._document); } panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth; panelHeight = () => this._panelHeight; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5e2973368..9acffc952 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -107,7 +107,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { added && this.updateCluster(newBox); return added; } - private selectDocuments = (docs: Doc[], ink: Map[]) => { + private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); ink.forEach(i => SelectionManager.SelectInk(i, true)); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 414238ccd..138168fed 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,7 +28,7 @@ interface MarqueeViewProps { getTransform: () => Transform; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; - selectDocuments: (docs: Doc[], ink: Map[]) => void; + selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; @@ -198,8 +198,10 @@ export class MarqueeView extends React.Component 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; @@ -266,6 +268,10 @@ export class MarqueeView extends React.Component Date: Sun, 10 Nov 2019 16:27:56 -0500 Subject: inks are now dox --- src/client/documents/Documents.ts | 14 ++- src/client/util/SelectionManager.ts | 32 ------ src/client/views/DocumentDecorations.tsx | 80 ++++++--------- src/client/views/InkSelectDecorations.tsx | 16 +-- src/client/views/InkingCanvas.tsx | 12 +-- src/client/views/InkingStroke.tsx | 107 +++++++++------------ .../collectionFreeForm/CollectionFreeFormView.tsx | 64 +++++++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 92 +++++++++--------- src/client/views/nodes/DocumentView.tsx | 2 +- src/new_fields/InkField.ts | 20 ++-- src/new_fields/documentSchemas.ts | 1 + 11 files changed, 207 insertions(+), 233 deletions(-) (limited to 'src/client/util/SelectionManager.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2c6b40cb9..a074d267e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox"; import { QueryBox } from "../views/nodes/QueryBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { DocuLinkBox } from "../views/nodes/DocuLinkBox"; +import { InkingStroke } from "../views/InkingStroke"; +import { InkField } from "../../new_fields/InkField"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -107,6 +109,7 @@ export interface DocumentOptions { sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script dropConverter?: ScriptField; // script to run when documents are dropped on this Document. + strokeWidth?: number; // [key: string]: Opt; } @@ -209,6 +212,9 @@ export namespace Docs { [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: data } }], + [DocumentType.INK, { + layout: { view: InkingStroke, dataField: data } + }] ]); // All document prototypes are initialized with at least these values @@ -411,8 +417,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options); } - export function InkDocument(options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.INK), "", options); + export function InkDocument(color: string, tool: number, strokeWidth: number, points: { x: number, y: number }[], options: DocumentOptions = {}) { + let doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options); + doc.color = color; + doc.strokeWidth = strokeWidth; + doc.tool = tool; + return doc; } export function IconDocument(icon: string, options: DocumentOptions = {}) { diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 2a57c67bd..ca61f9014 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -3,8 +3,6 @@ import { Doc, Opt } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; -import { InkingControl } from "../views/InkingControl"; -import { InkDocAndStroke } from "../views/InkingStroke"; export namespace SelectionManager { @@ -12,7 +10,6 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; - @observable SelectedInk: Array<{ Document: Doc, Ink: Map }> = []; @action @@ -43,20 +40,6 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; - manager.SelectedInk = []; - } - - @action - SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { - if (manager.SelectedInk.indexOf(ink) === -1) { - if (!ctrlPressed) { - this.DeselectAll(); - } - - manager.SelectedInk.push(ink); - } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { - manager.SelectedInk = [ink]; - } } } @@ -69,10 +52,6 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - export function SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { - manager.SelectInk(ink, ctrlPressed); - } - export function IsSelected(doc: DocumentView): boolean { return manager.SelectedDocuments.indexOf(doc) !== -1; } @@ -95,15 +74,4 @@ export namespace SelectionManager { export function SelectedDocuments(): Array { return manager.SelectedDocuments.slice(); } - - export function SelectedInk(): Array<{ Document: Doc, Ink: Map }> { - return manager.SelectedInk.slice(); - } - - export function AllSelected(): Array { - let arr: Array = []; - arr = SelectionManager.SelectedDocuments(); - arr.push(...SelectionManager.SelectedInk()); - return arr; - } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e208e5f3b..10764a9ce 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,7 +24,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); -import { StrokeData } from '../../new_fields/InkField'; +import { PointData } from '../../new_fields/InkField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -162,44 +162,23 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get Bounds(): { x: number, y: number, b: number, r: number } { let x = this._forceUpdate; - this._lastBox = SelectionManager.AllSelected().reduce((bounds, docViewOrInk) => { - if (docViewOrInk instanceof DocumentView) { - if (docViewOrInk.props.renderDepth === 0 || - Doc.AreProtosEqual(docViewOrInk.props.Document, CurrentUserUtils.UserDocument)) { - return bounds; - } - let transform = (docViewOrInk.props.ScreenToLocalTransform().scale(docViewOrInk.props.ContentScaling())).inverse(); - if (transform.TranslateX === 0 && transform.TranslateY === 0) { - setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds - return this._lastBox; - } - - var [sptX, sptY] = transform.transformPoint(0, 0); - let [bptX, bptY] = transform.transformPoint(docViewOrInk.props.PanelWidth(), docViewOrInk.props.PanelHeight()); - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; + this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, docView) => { + if (docView.props.renderDepth === 0 || + Doc.AreProtosEqual(docView.props.Document, CurrentUserUtils.UserDocument)) { + return bounds; } - else { - let left = bounds.x; - let top = bounds.y; - let right = bounds.r; - let bottom = bounds.b; - let ink; - docViewOrInk.Ink.forEach((value: StrokeData, key: string) => { - value.pathData.map(val => { - ink = docViewOrInk.Document.ink; - left = Math.min(val.x, left); - top = Math.min(val.y, top); - right = Math.max(val.x, right); - bottom = Math.max(val.y, bottom); - }); - }); - return { - x: left, y: top, r: right, b: bottom - }; + let transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse(); + if (transform.TranslateX === 0 && transform.TranslateY === 0) { + setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds + return this._lastBox; } + + var [sptX, sptY] = transform.transformPoint(0, 0); + let [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight()); + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); return this._lastBox; } @@ -226,7 +205,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onBackgroundUp); document.removeEventListener("pointermove", this.onTitleMove); document.removeEventListener("pointerup", this.onTitleUp); - DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, { + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, { handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, hideSource: true }); @@ -550,21 +529,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get selectionTitle(): string { - if (SelectionManager.AllSelected().length === 1) { - let selected = SelectionManager.AllSelected()[0]; - if (selected instanceof DocumentView) { - let field = selected.props.Document[this._fieldKey]; - if (typeof field === "string") { - return field; - } - else if (typeof field === "number") { - return field.toString(); - } + if (SelectionManager.SelectedDocuments().length === 1) { + let selected = SelectionManager.SelectedDocuments()[0]; + let field = selected.props.Document[this._fieldKey]; + if (typeof field === "string") { + return field; } - else { - return "-ink strokes-"; + else if (typeof field === "number") { + return field.toString(); } - } else if (SelectionManager.AllSelected().length > 1) { + } else if (SelectionManager.SelectedDocuments().length > 1) { return "-multiple-"; } return "-unset-"; @@ -590,7 +564,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let minimizeIcon = (
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} - {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} + {(SelectionManager.SelectedDocuments().length === 1) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
); bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; @@ -611,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, pointerEvents: this.Interacting ? "none" : "all", - zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0, + zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
{ - value.pathData.map(val => { - left = Math.min(val.x, left); - top = Math.min(val.y, top); - right = Math.max(val.x, right); - bottom = Math.max(val.y, bottom); - }); + this._selectedInkNodes.forEach((value: PointData, key: string) => { + // value.pathData.map(val => { + // left = Math.min(val.x, left); + // top = Math.min(val.y, top); + // right = Math.max(val.x, right); + // bottom = Math.max(val.y, bottom); + // }); }); return { x: left, y: top, b: bottom, r: right }; } diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index a0ea37300..e5253c377 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -7,7 +7,7 @@ import { InkingControl } from "./InkingControl"; import { InkingStroke } from "./InkingStroke"; import React = require("react"); import { UndoManager } from "../util/UndoManager"; -import { StrokeData, InkField, InkTool } from "../../new_fields/InkField"; +import { PointData, InkField, InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { Cast, PromiseValue, NumCast } from "../../new_fields/Types"; import { Touchable } from "./Touchable"; @@ -26,15 +26,15 @@ export class InkingCanvas extends Touchable { maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome @observable inkMidX: number = 0; @observable inkMidY: number = 0; - private previousState?: Map; + private previousState?: Map; private _currentStrokeId: string = ""; - public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean { + public static IntersectStrokeRect(stroke: PointData, selRect: { left: number, top: number, width: number, height: number }): boolean { return stroke.pathData.reduce((inside: boolean, val) => inside || (selRect.left < val.x && selRect.left + selRect.width > val.x && selRect.top < val.y && selRect.top + selRect.height > val.y) , false); } - public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } { + public static StrokeRect(stroke: PointData): { left: number, top: number, right: number, bottom: number } { return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) => ({ left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y), @@ -58,12 +58,12 @@ export class InkingCanvas extends Touchable { } @computed - get inkData(): Map { + get inkData(): Map { let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField); return !map ? new Map : new Map(map.inkData); } - set inkData(value: Map) { + set inkData(value: Map) { this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 824f40b1f..411b0d3a0 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,79 +1,66 @@ import { observer } from "mobx-react"; -import { observable, trace, runInAction } from "mobx"; +import { observable, trace, runInAction, computed } from "mobx"; import { InkingControl } from "./InkingControl"; import React = require("react"); -import { InkTool } from "../../new_fields/InkField"; +import { InkTool, InkField, InkData } from "../../new_fields/InkField"; import "./InkingStroke.scss"; import { AudioBox } from "./nodes/AudioBox"; -import { Doc } from "../../new_fields/Doc"; -import { createSchema, makeInterface } from "../../new_fields/Schema"; +import { Doc, FieldResult } from "../../new_fields/Doc"; +import { createSchema, makeInterface, listSpec } from "../../new_fields/Schema"; import { documentSchema } from "../../new_fields/documentSchemas"; import { DocExtendableComponent } from "./DocComponent"; import { FieldViewProps, FieldView } from "./nodes/FieldView"; - - -interface StrokeProps { - offsetX: number; - offsetY: number; - id: string; - count: number; - line: Array<{ x: number, y: number }>; - color: string; - width: string; - tool: InkTool; - creationTime: number; - deleteCallback: (index: string) => void; -} +import { Transform } from "../util/Transform"; +import { Cast, FieldValue } from "../../new_fields/Types"; +import { List } from "../../new_fields/List"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); +export function CreatePolyline(points: { x: number, y: number }[], left: number, top: number, color?: string, width?: number) { + let pts = points.reduce((acc: string, pt: { x: number, y: number }) => acc + `${pt.x - left},${pt.y - top} `, ""); + return ( + + ); +} + @observer -export class InkingStroke extends DocExtendableComponent(InkDocument) { +export class InkingStroke extends DocExtendableComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } - @observable private _strokeTool: InkTool = this.props.tool; - @observable private _strokeColor: string = this.props.color; - @observable private _strokeWidth: string = this.props.width; - - deleteStroke = (e: React.PointerEvent): void => { - if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) { - this.props.deleteCallback(this.props.id); - e.stopPropagation(); - e.preventDefault(); - } - if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) { - AudioBox.SetScrubTime(this.props.creationTime); - e.stopPropagation(); - e.preventDefault(); - } - } - - parseData = (line: Array<{ x: number, y: number }>): string => { - return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L "); - } - - createStyle() { - switch (this._strokeTool) { - // add more tool styles here - default: - return { - fill: "none", - stroke: this._strokeColor, - strokeWidth: this._strokeWidth + "px", - }; - } - } + @computed get PanelWidth() { return this.props.PanelWidth(); } + @computed get PanelHeight() { return this.props.PanelHeight(); } render() { - let pathStyle = this.createStyle(); - let pathData = this.parseData(this.props.line); - let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes - let marker = this.props.tool === InkTool.Highlighter ? "-marker" : ""; - - let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser || - InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none"; - return (); + // let pathData = this.parseData(this.props.line); + let data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? []; + let xs = data.map(p => p.x); + let ys = data.map(p => p.y); + let left = Math.min(...xs); + let top = Math.min(...ys); + let right = Math.max(...xs); + let bottom = Math.max(...ys); + let points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth); + let width = right - left; + let height = bottom - top; + let scaleX = this.PanelWidth / width; + let scaleY = this.PanelHeight / height; + // let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes + return ( + + {points} + + ); } } \ 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 8294eaaec..21981c25e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkField, StrokeData, InkTool } from "../../../../new_fields/InkField"; +import { InkField, PointData, InkTool } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; @@ -40,6 +40,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import PDFMenu from "../../pdf/PDFMenu"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; import { InkingControl } from "../../InkingControl"; +import { InkingStroke, CreatePolyline } from "../../InkingStroke"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -108,10 +109,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { added && this.updateCluster(newBox); return added; } - private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => { + private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); - ink.forEach(i => SelectionManager.SelectInk(i, true)); } public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } @@ -284,14 +284,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { e.preventDefault(); if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - this._points.push({ x: e.pageX, y: e.pageY }); + let point = this.getTransform().transformPoint(e.pageX, e.pageY); + this._points.push({ x: point[0], y: point[1] }); } } } } + @action onPointerUp = (e: PointerEvent): void => { if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) return; + + if (this._points.length > 1) { + let B = this.svgBounds; + let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top })); + let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top }); + this.addDocument(inkDoc); + this._points = []; + } + document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.removeEventListener("touchmove", this.onTouch); @@ -324,11 +335,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, [[minx, maxx], [miny, maxy]]); let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField); if (ink && ink.inkData) { - ink.inkData.forEach((value: StrokeData, key: string) => { - let bounds = InkingCanvas.StrokeRect(value); - ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; - ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; - }); + // ink.inkData.forEach((value: PointData, key: string) => { + // let bounds = InkingCanvas.StrokeRect(value); + // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; + // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; + // }); } let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; @@ -363,8 +374,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.pan(e); } - if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - this._points.push({ x: e.clientX, y: e.clientY }); + else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { + let point = this.getTransform().transformPoint(e.clientX, e.clientY); + this._points.push({ x: point[0], y: point[1] }); } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); @@ -470,7 +482,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } else if (this.props.active()) { e.stopPropagation(); - this.zoom(e.clientX, e.clientY, e.deltaY) + this.zoom(e.clientX, e.clientY, e.deltaY); } } @@ -790,6 +802,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ...this.views, ]; } + + @computed get svgBounds() { + let xs = this._points.map(p => p.x); + let ys = this._points.map(p => p.y); + let right = Math.max(...xs); + let left = Math.min(...xs); + let bottom = Math.max(...ys); + let top = Math.min(...ys); + return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; + } + + @computed get currentStroke() { + if (this._points.length <= 1) { + return (null); + } + + let B = this.svgBounds; + + return ( + + {CreatePolyline(this._points, B.left, B.top)} + + ); + } + render() { // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) this.Document.fitX = this.contentBounds && this.contentBounds.x; @@ -808,9 +845,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> {!this.extensionDoc ? (null) : // - this.childViews + this.childViews() // } + {this.currentStroke} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 138168fed..b5f6f095e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,7 +1,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../../new_fields/Doc"; -import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { InkField, PointData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { listSpec } from "../../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; @@ -198,10 +198,10 @@ export class MarqueeView extends React.Component 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; @@ -217,7 +217,7 @@ export class MarqueeView extends React.Component { this.marqueeSelect(false).map(d => this.props.removeDocument(d)); if (this.ink) { - this.marqueeInkDelete(this.ink.inkData); + // this.marqueeInkDelete(this.ink.inkData); } SelectionManager.DeselectAll(); this.cleanupInteractions(false); @@ -336,8 +336,8 @@ export class MarqueeView extends React.Component) { - 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)) { - // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling()); - idata.set(key, - { - pathData: value.pathData.map(val => { - let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y); - return { x: tVal[0], y: tVal[1] }; - // return { x: val.x + centerShiftX, y: val.y + centerShiftY } - }), - color: value.color, - width: value.width, - tool: value.tool, - page: -1 - }); - } - }); - // InkSelectDecorations.Instance.SetSelected(idata); - 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: PointData, key: string, map: any) => { + // if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { + // // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling()); + // idata.set(key, + // { + // pathData: value.pathData.map(val => { + // let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y); + // return { x: tVal[0], y: tVal[1] }; + // // return { x: val.x + centerShiftX, y: val.y + centerShiftY } + // }), + // color: value.color, + // width: value.width, + // tool: value.tool, + // page: -1 + // }); + // } + // }); + // // InkSelectDecorations.Instance.SetSelected(idata); + // 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)); + // @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)); - if (ink) { - let idata = new Map(); - ink.forEach((value: StrokeData, key: string, map: any) => - !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); - this.ink = new InkField(idata); - } - } + // if (ink) { + // let idata = new Map(); + // ink.forEach((value: PointData, key: string, map: any) => + // !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); + // this.ink = new InkField(idata); + // } + // } marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 62529a5fb..5c89472ce 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -632,7 +632,7 @@ export class DocumentView extends DocComponent(Docu {searchHighlight}
} - + ; } render() { if (!this.props.Document) return (null); diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts index d94834e91..2d8bb582a 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -12,16 +12,12 @@ export enum InkTool { Scrubber } -export interface StrokeData { - pathData: Array<{ x: number, y: number }>; - color: string; - width: string; - tool: InkTool; - displayTimecode: number; - creationTime: number; +export interface PointData { + x: number; + y: number; } -export type InkData = Map; +export type InkData = Array; const pointSchema = createSimpleSchema({ x: true, y: true @@ -34,16 +30,16 @@ const strokeDataSchema = createSimpleSchema({ @Deserializable("ink") export class InkField extends ObjectField { - @serializable(map(object(strokeDataSchema))) + @serializable(list(object(strokeDataSchema))) readonly inkData: InkData; - constructor(data?: InkData) { + constructor(data: InkData) { super(); - this.inkData = data || new Map; + this.inkData = data; } [Copy]() { - return new InkField(DeepCopy(this.inkData)); + return new InkField(this.inkData); } [ToScriptString]() { diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e2730914f..0b28561bf 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -43,6 +43,7 @@ export const documentSchema = createSchema({ isAnimating: "boolean", // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents). animateToDimensions: listSpec("number"), // layout information about the target rectangle a document is animating towards scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc. + strokeWidth: "number" }); export const positionSchema = createSchema({ -- cgit v1.2.3-70-g09d2 From 63129c244fc2b9a5c60e6a94b864895641b86f57 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 20 Nov 2019 16:59:40 -0500 Subject: lots of changes to make rendering more efficient (fewer mobx invalidations). made selection and collecitonviews use an observableMap that works now. --- src/client/util/SelectionManager.ts | 41 ++-- src/client/views/DocComponent.tsx | 14 +- src/client/views/DocumentButtonBar.scss | 106 +++------ src/client/views/DocumentButtonBar.tsx | 264 ++++++++------------- src/client/views/Main.scss | 6 +- src/client/views/MainView.scss | 1 - src/client/views/TemplateMenu.scss | 50 ++++ src/client/views/TemplateMenu.tsx | 4 +- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionSchemaView.tsx | 14 +- .../views/collections/CollectionTreeView.tsx | 6 +- src/client/views/collections/CollectionView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 87 ++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- .../views/nodes/ContentFittingDocumentView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 27 ++- src/client/views/nodes/FieldView.tsx | 4 +- src/client/views/nodes/FormattedTextBox.tsx | 10 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 7 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 14 +- src/new_fields/Doc.ts | 16 +- 25 files changed, 326 insertions(+), 364 deletions(-) create mode 100644 src/client/views/TemplateMenu.scss (limited to 'src/client/util/SelectionManager.ts') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index ca61f9014..e01216e0f 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,45 +1,44 @@ -import { observable, action, runInAction, IReactionDisposer, reaction, autorun } from "mobx"; -import { Doc, Opt } from "../../new_fields/Doc"; +import { observable, action, runInAction, ObservableMap } from "mobx"; +import { Doc } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { NumCast, StrCast } from "../../new_fields/Types"; +import { computedFn } from "mobx-utils"; export namespace SelectionManager { class Manager { @observable IsDragging: boolean = false; - @observable SelectedDocuments: Array = []; + SelectedDocuments: ObservableMap = new ObservableMap(); @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it - if (manager.SelectedDocuments.indexOf(docView) === -1) { + if (!manager.SelectedDocuments.get(docView)) { if (!ctrlPressed) { this.DeselectAll(); } - manager.SelectedDocuments.push(docView); + manager.SelectedDocuments.set(docView, true); // console.log(manager.SelectedDocuments); docView.props.whenActiveChanged(true); - } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { - manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false)); - manager.SelectedDocuments = [docView]; + } else if (!ctrlPressed && Array.from(manager.SelectedDocuments.entries()).length > 1) { + Array.from(manager.SelectedDocuments.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false)); + manager.SelectedDocuments.clear(); + manager.SelectedDocuments.set(docView, true); } } @action DeselectDoc(docView: DocumentView): void { - let ind = manager.SelectedDocuments.indexOf(docView); - if (ind !== -1) { - manager.SelectedDocuments.splice(ind, 1); + if (manager.SelectedDocuments.get(docView)) { + manager.SelectedDocuments.delete(docView); docView.props.whenActiveChanged(false); } } @action DeselectAll(): void { - manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); - manager.SelectedDocuments = []; + Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false)); + manager.SelectedDocuments.clear(); } } @@ -52,14 +51,18 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - export function IsSelected(doc: DocumentView): boolean { - return manager.SelectedDocuments.indexOf(doc) !== -1; + export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean { + return outsideReaction ? + manager.SelectedDocuments.get(doc) ? true : false : + computedFn(function isSelected(doc: DocumentView) { + return manager.SelectedDocuments.get(doc) ? true : false; + })(doc); } export function DeselectAll(except?: Doc): void { let found: DocumentView | undefined = undefined; if (except) { - for (const view of manager.SelectedDocuments) { + for (const view of Array.from(manager.SelectedDocuments.keys())) { if (view.props.Document === except) found = view; } } @@ -72,6 +75,6 @@ export namespace SelectionManager { export function GetIsDragging() { return manager.IsDragging; } export function SelectedDocuments(): Array { - return manager.SelectedDocuments.slice(); + return Array.from(manager.SelectedDocuments.keys()); } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 286a77f81..1bd1006a8 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -27,7 +27,7 @@ interface DocExtendableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; - isSelected: () => boolean; + isSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; } export function DocExtendableComponent

(schemaCtor: (doc: Doc) => T) { @@ -37,7 +37,7 @@ export function DocExtendableComponent

(schemaCt @computed get layoutDoc() { return Doc.Layout(this.props.Document); } @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools + active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools } return Component; } @@ -49,7 +49,7 @@ interface DocAnnotatableProps { DataDoc?: Doc; fieldKey: string; whenActiveChanged: (isActive: boolean) => void; - isSelected: () => boolean; + isSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; } export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T) { @@ -82,10 +82,10 @@ export function DocAnnotatableComponent

(schema } whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); - active = () => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) && - (this.props.Document.forceActive || this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0) ? true : false) - annotationsActive = () => (InkingControl.Instance.selectedTool !== InkTool.None || - (this.props.Document.forceActive || this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0) ? true : false) + active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) && + (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || + (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) } return Component; } \ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index 8cd419bbe..db6bf2ba0 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -2,54 +2,23 @@ $linkGap : 3px; -.linkFlyout { +.documentButtonBar-linkFlyout { grid-column: 2/4; } -.linkButton-empty:hover { +.documentButtonBar-linkButton-empty:hover { background: $main-accent; transform: scale(1.05); cursor: pointer; } -.linkButton-nonempty:hover { +.documentButtonBar-linkButton-nonempty:hover { background: $main-accent; transform: scale(1.05); cursor: pointer; } - -.documentButtonBar { - margin-top: $linkGap; - grid-column: 1/4; - width: max-content; - height: auto; - display: flex; - flex-direction: row; -} - -.linkButtonWrapper { - pointer-events: auto; - padding-right: 5px; - width: 25px; -} - -.linkButton-linker { - height: 20px; - width: 20px; - text-align: center; - border-radius: 50%; - pointer-events: auto; - color: $dark-color; - border: $dark-color 1px solid; -} - -.linkButton-linker:hover { - cursor: pointer; - transform: scale(1.05); -} - -.linkButton-empty, -.linkButton-nonempty { +.documentButtonBar-linkButton-empty, +.documentButtonBar-linkButton-nonempty { height: 20px; width: 20px; border-radius: 50%; @@ -73,57 +42,38 @@ $linkGap : 3px; } } -.templating-menu { - position: absolute; - pointer-events: auto; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - transition: transform 0.2s; - text-align: center; +.documentButtonBar { + margin-top: $linkGap; + grid-column: 1/4; + width: max-content; + height: auto; display: flex; - justify-content: center; - align-items: center; + flex-direction: row; } -.templating-button, -.docDecs-tagButton { - width: 20px; +.documentButtonBar-button { + pointer-events: auto; + padding-right: 5px; + width: 25px; +} + +.documentButtonBar-linker { height: 20px; - border-radius: 50%; - opacity: 0.9; - font-size: 14; - background-color: $dark-color; - color: $light-color; + width: 20px; text-align: center; - cursor: pointer; - - &:hover { - background: $main-accent; - transform: scale(1.05); - } + border-radius: 50%; + pointer-events: auto; + color: $dark-color; + border: $dark-color 1px solid; + transition: 0.2s ease all; } -#template-list { - position: absolute; - top: 25px; - left: 0px; - width: max-content; - font-family: $sans-serif; - font-size: 12px; - background-color: $light-color-secondary; - padding: 2px 12px; - list-style: none; - - .templateToggle, .chromeToggle { - text-align: left; - } - - input { - margin-right: 10px; - } +.documentButtonBar-linker:hover { + cursor: pointer; + transform: scale(1.05); } + @-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } @keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } \ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c7ee413c9..1fefc70f1 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,7 +1,7 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, observable, runInAction } from "mobx"; +import { action, observable, runInAction, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../new_fields/Doc"; import { RichTextField } from '../../new_fields/RichTextField'; @@ -42,52 +42,53 @@ const fetch: IconProp = "sync-alt"; @observer export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> { private _linkButton = React.createRef(); - private _aliasButton = React.createRef(); - private _tooltipoff = React.createRef(); - private _textDoc?: Doc; + private _downX = 0; + private _downY = 0; + private _pullAnimating = false; + private _pushAnimating = false; + private _pullColorAnimating = false; + + @observable private pushIcon: IconProp = "arrow-alt-circle-up"; + @observable private pullIcon: IconProp = "arrow-alt-circle-down"; + @observable private pullColor: string = "white"; + @observable private isAnimatingFetch = false; + @observable private openHover = false; + public static Instance: DocumentButtonBar; + public static hasPushedHack = false; + public static hasPulledHack = false; constructor(props: { views: DocumentView[] }) { super(props); DocumentButtonBar.Instance = this; } - @observable public pushIcon: IconProp = "arrow-alt-circle-up"; - @observable public pullIcon: IconProp = "arrow-alt-circle-down"; - @observable public pullColor: string = "white"; - @observable public isAnimatingFetch = false; - @observable public openHover = false; - public pullColorAnimating = false; - - private pullAnimating = false; - private pushAnimating = false; - public startPullOutcome = action((success: boolean) => { - if (!this.pullAnimating) { - this.pullAnimating = true; + if (!this._pullAnimating) { + this._pullAnimating = true; this.pullIcon = success ? "check-circle" : "stop-circle"; setTimeout(() => runInAction(() => { this.pullIcon = "arrow-alt-circle-down"; - this.pullAnimating = false; + this._pullAnimating = false; }), 1000); } }); public startPushOutcome = action((success: boolean) => { - if (!this.pushAnimating) { - this.pushAnimating = true; + if (!this._pushAnimating) { + this._pushAnimating = true; this.pushIcon = success ? "check-circle" : "stop-circle"; setTimeout(() => runInAction(() => { this.pushIcon = "arrow-alt-circle-up"; - this.pushAnimating = false; + this._pushAnimating = false; }), 1000); } }); public setPullState = action((unchanged: boolean) => { this.isAnimatingFetch = false; - if (!this.pullColorAnimating) { - this.pullColorAnimating = true; + if (!this._pullColorAnimating) { + this._pullColorAnimating = true; this.pullColor = unchanged ? "lawngreen" : "red"; setTimeout(this.clearPullColor, 1000); } @@ -95,33 +96,17 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], private clearPullColor = action(() => { this.pullColor = "white"; - this.pullColorAnimating = false; + this._pullColorAnimating = false; }); - onLinkerButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - document.removeEventListener("pointermove", this.onLinkerButtonMoved); - document.addEventListener("pointermove", this.onLinkerButtonMoved); - document.removeEventListener("pointerup", this.onLinkerButtonUp); - document.addEventListener("pointerup", this.onLinkerButtonUp); - } - - - onLinkerButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onLinkerButtonMoved); - document.removeEventListener("pointerup", this.onLinkerButtonUp); - e.stopPropagation(); - } - @action - onLinkerButtonMoved = (e: PointerEvent): void => { - if (this._linkButton.current !== null) { - document.removeEventListener("pointermove", this.onLinkerButtonMoved); + onLinkButtonMoved = (e: PointerEvent): void => { + if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); let docView = this.props.views[0]; - let container = docView.props.ContainingCollectionDoc ? docView.props.ContainingCollectionDoc.proto : undefined; + let container = docView.props.ContainingCollectionDoc?.proto; let dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []); let linkDrag = UndoManager.StartBatch("Drag Link"); DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, { @@ -150,151 +135,106 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], onLinkButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - document.removeEventListener("pointermove", this.onLinkerButtonMoved); - document.addEventListener("pointermove", this.onLinkerButtonMoved); + this._downX = e.clientX; + this._downY = e.clientY; + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.addEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); document.addEventListener("pointerup", this.onLinkButtonUp); + e.stopPropagation(); } onLinkButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onLinkerButtonMoved); + document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); e.stopPropagation(); } - private get targetDoc() { - return this.props.views[0].props.Document; - } - - considerGoogleDocsPush = () => { - let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; - if (!canPush) return (null); - let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; - let icon: IconProp = published ? (this.pushIcon as any) : cloud; - return ( -

-
{ - DocumentButtonBar.hasPushedHack = false; - this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1; - }}> - -
-
- ); + @computed + get considerGoogleDocsPush() { + let targetDoc = this.props.views[0].props.Document; + let published = Doc.GetProto(targetDoc)[GoogleRef] !== undefined; + return
{ + DocumentButtonBar.hasPushedHack = false; + targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; + }}> + +
; } - considerGoogleDocsPull = () => { - let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; - let dataDoc = Doc.GetProto(this.targetDoc); - if (!canPull || !dataDoc[GoogleRef]) return (null); - let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; - icon = this.openHover ? "share" : icon; + @computed + get considerGoogleDocsPull() { + let targetDoc = this.props.views[0].props.Document; + let dataDoc = Doc.GetProto(targetDoc); let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; - let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`; - return ( -
-
e.altKey && runInAction(() => this.openHover = true)} - onPointerLeave={() => runInAction(() => this.openHover = false)} - onClick={e => { - if (e.altKey) { - e.preventDefault(); - window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); - } else { - this.clearPullColor(); - DocumentButtonBar.hasPulledHack = false; - this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; - dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); - } - }}> - -
-
- ); + return !dataDoc[GoogleRef] ? (null) :
e.altKey && runInAction(() => this.openHover = true)} + onPointerLeave={action(() => this.openHover = false)} + onClick={e => { + if (e.altKey) { + e.preventDefault(); + window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); + } else { + this.clearPullColor(); + DocumentButtonBar.hasPulledHack = false; + targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; + dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); + } + }}> + +
; } - public static hasPushedHack = false; - public static hasPulledHack = false; - - considerTooltip = () => { - let thisDoc = this.props.views[0].props.Document; - let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField; - if (!isTextDoc) return null; - this._textDoc = thisDoc; - return ( -
-
- {/* */} + @computed + get linkButton() { + let linkCount = LinkManager.Instance.getAllRelatedLinks(this.props.views[0].props.Document).length; + return
+ }> +
+ {linkCount ? linkCount : }
-
- - ); + +
; } - onTooltipOff = (e: React.PointerEvent): void => { - e.stopPropagation(); - if (this._textDoc) { - if (this._tooltipoff.current) { - if (this._tooltipoff.current.title === "Hide Tooltip") { - this._tooltipoff.current.title = "Show Tooltip"; - this._textDoc.tooltip = "hi"; - } - else { - this._tooltipoff.current.title = "Hide Tooltip"; - } - } - } + @computed + get contextButton() { + return { + where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : + this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : + this.props.views[0].props.addDocTab(doc, data, "onRight"); + return true; + }} />; } render() { - let linkButton = null; - if (this.props.views.length > 0) { - let selFirst = this.props.views[0]; - - let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length; - linkButton = }> -
- {linkCount ? linkCount : } -
-
; - } - let templates: Map = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean))); - return (
-
-
{linkButton}
+ let isText = this.props.views[0].props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field. need to add fieldKey to DocumentView + let considerPull = isText && this.considerGoogleDocsPull; + let considerPush = isText && this.considerGoogleDocsPush; + return
+
+ {this.linkButton}
-
+
- {this.considerGoogleDocsPush()} - {this.considerGoogleDocsPull()} - { - where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : this.props.views[0].props.addDocTab(doc, data, "onRight"); - return true; - }} /> - {/* {this.considerTooltip()} */} -
- ); +
+ {this.considerGoogleDocsPush} +
+
+ {this.considerGoogleDocsPull} +
+ {this.contextButton} +
; } } \ No newline at end of file diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 465527468..3b66160fb 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -5,7 +5,7 @@ html, body { width: 100%; height: 100%; - overflow: auto; + overflow: hidden; font-family: $sans-serif; margin: 0; position: absolute; @@ -65,10 +65,6 @@ button:hover { cursor: pointer; } -#root { - overflow: visible; -} - .svg-inline--fa { vertical-align: unset; } \ No newline at end of file diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index b3fff081e..0ee30f117 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -20,7 +20,6 @@ position: absolute; top: 0; left: 0; - overflow: auto; z-index: 1; } diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss new file mode 100644 index 000000000..186d3ab0d --- /dev/null +++ b/src/client/views/TemplateMenu.scss @@ -0,0 +1,50 @@ +@import "globalCssVariables"; +.templating-menu { + position: absolute; + pointer-events: auto; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.templating-button { + width: 20px; + height: 20px; + border-radius: 50%; + opacity: 0.9; + font-size: 14; + background-color: $dark-color; + color: $light-color; + text-align: center; + cursor: pointer; + + &:hover { + background: $main-accent; + transform: scale(1.05); + } +} + +.template-list { + position: absolute; + top: 25px; + left: 0px; + width: max-content; + font-family: $sans-serif; + font-size: 12px; + background-color: $light-color-secondary; + padding: 2px 12px; + list-style: none; + + .templateToggle, .chromeToggle { + text-align: left; + } + + input { + margin-right: 10px; + } +} \ No newline at end of file diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 1df5f49c4..c65b338b4 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -4,7 +4,7 @@ import { DocumentManager } from "../util/DocumentManager"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; -import './DocumentDecorations.scss'; +import './TemplateMenu.scss'; import { DocumentView } from "./nodes/DocumentView"; import { Template, Templates } from "./Templates"; import React = require("react"); @@ -175,7 +175,7 @@ export class TemplateMenu extends React.Component { return (
this.toggleTemplateActivity()}>+
-
    +
      {templateMenu} {}
    diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 3ff99b9f4..75d92105b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -448,7 +448,7 @@ export class CollectionDockingView extends React.Component [doc.title, Doc.IsBrushedDegree(doc)], () => { tab.titleElement[0].textContent = doc.title, { fireImmediately: true }; - tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegree(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegree(doc)]} 1px`; + tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegreeUnmemoized(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegreeUnmemoized(doc)]} 1px`; }); //TODO why can't this just be doc instead of the id? tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index ebd47fd19..65856cad3 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -112,7 +112,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (this.props.isSelected()) e.stopPropagation(); + if (this.props.isSelected(true)) e.stopPropagation(); else { this.props.select(false); } @@ -201,7 +201,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { render() { return
    -
    this.props.active() && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}> +
    this.props.active(true) && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}> {this.schemaTable}
    {this.dividerDragger} @@ -225,11 +225,11 @@ export interface SchemaTableProps { addDocument: (document: Doc) => boolean; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - active: () => boolean; + active: (outsideReaction: boolean) => boolean; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; - isSelected: () => boolean; + isSelected: (outsideReaction?: boolean) => boolean; isFocused: (document: Doc) => boolean; setFocused: (document: Doc) => void; setPreviewDoc: (document: Doc) => void; @@ -442,14 +442,14 @@ export class SchemaTable extends React.Component { onPointerDown = (e: React.PointerEvent): void => { this.props.setFocused(this.props.Document); - if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected()) { + if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected(true)) { e.stopPropagation(); } } @action onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this._headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected()) { + if (!this._cellIsEditing && !this._headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) { let direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : ""; this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); @@ -778,7 +778,7 @@ export class SchemaTable extends React.Component { } render() { - return
    this.props.active() && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > + return
    this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > {this.reactTable}
    this.createRow()}>+ new
    ; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0e3f0d1a9..8b993820b 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -49,7 +49,7 @@ export interface TreeViewProps { outerXf: () => { translateX: number, translateY: number }; treeViewId: string; parentKey: string; - active: () => boolean; + active: (outsideReaction?: boolean) => boolean; showHeaderFields: () => boolean; preventTreeViewOpen: boolean; renderedIds: string[]; @@ -130,7 +130,7 @@ class TreeView extends React.Component { onPointerDown = (e: React.PointerEvent) => e.stopPropagation(); onPointerEnter = (e: React.PointerEvent): void => { - this.props.active() && Doc.BrushDoc(this.dataDoc); + this.props.active(true) && Doc.BrushDoc(this.dataDoc); if (e.buttons === 1 && SelectionManager.GetIsDragging()) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); @@ -412,7 +412,7 @@ class TreeView extends React.Component { pinToPres: (document: Doc) => void, screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, - active: () => boolean, + active: (outsideReaction?: boolean) => boolean, panelWidth: () => number, renderDepth: number, showHeaderFields: () => boolean, diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 347fa7d0d..8387e95df 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -113,7 +113,7 @@ export class CollectionView extends Touchable { // bcz: Argh? What's the height of the collection chromes?? chromeHeight = () => (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0); - active = () => this.props.isSelected() || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; + active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2a63a3074..256ceb8f8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, trace } from "mobx"; +import { action, computed, observable, trace, ObservableMap, untracked, reaction, runInAction, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; @@ -39,6 +39,7 @@ import "./CollectionFreeFormView.scss"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { computedFn, keepAlive } from "mobx-utils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -68,11 +69,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _lastY: number = 0; private _clusterDistance: number = 75; private _hitCluster = false; + private _layoutComputeReaction: IReactionDisposer | undefined; + private _layoutPoolData = new ObservableMap(); + + public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive + @observable _layoutElements: ViewDefResult[] = []; @observable _clusterSets: (Doc[])[] = []; @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; } @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; } - @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); } + @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); } @computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; } private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } @@ -271,7 +277,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble) return; this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; - if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active()) { + if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -359,7 +365,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) { - if (this.props.active()) { + if (this.props.active(true)) { e.stopPropagation(); } return; @@ -493,7 +499,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } - else if (this.props.active()) { + else if (this.props.active(true)) { e.stopPropagation(); this.zoom(e.clientX, e.clientY, e.deltaY); } @@ -645,30 +651,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - lookupLayout = (doc: Doc, dataDoc?: Doc) => { - let data: any = undefined; - let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] }; - switch (this.Document.freeformLayoutEngine) { - case "pivot": computedElementData = this.doPivotLayout; break; - default: computedElementData = this.doFreeformLayout; break; - } - computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => { - if (key.layout === doc && key.data === dataDoc) { - data = value; - } - }); - return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition }; - } + childDataProvider = computedFn((doc: Doc) => this._layoutPoolData.get(doc[Id])); - @computed get doPivotLayout() { return computePivotLayout(this.props.Document, this.childDocs, this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX); } - @computed get doFreeformLayout() { - let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map(); let layoutDocs = this.childLayoutPairs.map(pair => pair.layout); const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log); let state = initResult && initResult.success ? initResult.result.scriptState : undefined; @@ -677,32 +667,41 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); state = pos.state === undefined ? state : pos.state; - layoutPoolData.set(pair, pos); + let data = this._layoutPoolData.get(pair.layout[Id]); + if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height || pos.transition !== data.transition) { + runInAction(() => this._layoutPoolData.set(pair.layout[Id], pos)); + } }); - return { map: layoutPoolData, elements: elements }; + return { elements: elements }; } - @computed get doLayoutComputation() { - let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] }; + let computedElementData: { elements: ViewDefResult[] }; switch (this.Document.freeformLayoutEngine) { case "pivot": computedElementData = this.doPivotLayout; break; default: computedElementData = this.doFreeformLayout; break; } this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => computedElementData.elements.push({ - ele: , - bounds: this.lookupLayout(pair.layout, pair.data) + bounds: this.childDataProvider(pair.layout) })); return computedElementData; } - @computed.struct get elements() { return this.doLayoutComputation.elements; } - @computed.struct get views() { return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } - @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } + componentDidMount() { + this._layoutComputeReaction = reaction(() => this.doLayoutComputation, + action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)), + { fireImmediately: true }); + } + componentWillUnmount() { + this._layoutComputeReaction && this._layoutComputeReaction(); + } + @computed.struct get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } + elementFunc = () => this._layoutElements; @action onCursorMove = (e: React.PointerEvent) => { @@ -846,15 +845,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { render() { trace(); // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) - this.Document.fitX = this.contentBounds && this.contentBounds.x; - this.Document.fitY = this.contentBounds && this.contentBounds.y; - this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x); - this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); + // this.Document.fitX = this.contentBounds && this.contentBounds.x; + // this.Document.fitY = this.contentBounds && this.contentBounds.y; + // this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x); + // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey. // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document return !this.extensionDoc ? (null) : -
    @@ -863,11 +862,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { {this.children} - {this.overlayViews} +
    ; } } +interface CollectionFreeFormOverlayViewProps { + elements: () => ViewDefResult[]; +} + +@observer +class CollectionFreeFormOverlayView extends React.Component{ + @computed.struct get overlayViews() { return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } + render() { + return this.overlayViews; + } +} + interface CollectionFreeFormViewPannableContentsProps { centeringShiftX: () => number; centeringShiftY: () => number; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1066f4f8d..5ed3fecb5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -191,7 +191,7 @@ export class MarqueeView extends React.Component { - if (!this.props.active()) this.props.selectDocuments([this.props.Document], []); + if (!this.props.active(true)) this.props.selectDocuments([this.props.Document], []); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index d0e1d1922..0badbd3fe 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -12,7 +12,7 @@ import React = require("react"); import { PositionDocument } from "../../../new_fields/documentSchemas"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; + dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; x?: number; y?: number; width?: number; @@ -24,6 +24,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @observer export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; + get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } @@ -32,7 +33,7 @@ export class CollectionFreeFormDocumentView extends DocComponent boolean; moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean; removeDocument: (document: Doc) => boolean; - active: () => boolean; + active: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 12ae5b6e5..b9b84d5ce 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -50,7 +50,7 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; @observer export class DocumentContentsView extends React.Component boolean, + isSelected: (outsideReaction: boolean) => boolean, select: (ctrl: boolean) => void, onClick?: ScriptField, layoutKey: string, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 411d6bdea..cf5a94f01 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -11,13 +11,12 @@ import { ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { ImageField } from '../../../new_fields/URLField'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils"; +import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; -import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; import { LinkManager } from '../../util/LinkManager'; @@ -65,7 +64,7 @@ export interface DocumentViewProps { PanelWidth: () => number; PanelHeight: () => number; focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void; - parentActive: () => boolean; + parentActive: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean; @@ -90,12 +89,13 @@ export class DocumentView extends DocComponent(Docu private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; + public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } - @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } + @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @computed get topMost() { return this.props.renderDepth === 0; } @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; } @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; } - @computed get onClickHandler() { trace(); console.log("this.props.doc = " + this.props.Document.title); return this.props.onClick ? this.props.onClick : this.Document.onClick; } + @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } @action componentDidMount() { @@ -215,7 +215,7 @@ export class DocumentView extends DocComponent(Docu if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCH))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -516,7 +516,7 @@ export class DocumentView extends DocComponent(Docu e.stopPropagation(); } ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - if (!SelectionManager.IsSelected(this)) { + if (!SelectionManager.IsSelected(this, true)) { SelectionManager.SelectDoc(this, false); } }); @@ -528,7 +528,7 @@ export class DocumentView extends DocComponent(Docu getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); - isSelected = () => SelectionManager.IsSelected(this); + isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; chromeHeight = () => { @@ -616,7 +616,7 @@ export class DocumentView extends DocComponent(Docu return <> {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
    - Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} /> + Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} />
    )} {!showTitle && !showCaption ? this.Document.searchFields ? @@ -638,9 +638,12 @@ export class DocumentView extends DocComponent(Docu } ; } + @computed get ignorePointerEvents() { + return this.Document.isBackground && !this.isSelected(); + } + render() { if (!this.props.Document) return (null); - trace(); const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; const colorSet = this.setsLayoutProp("backgroundColor"); @@ -662,10 +665,10 @@ export class DocumentView extends DocComponent(Docu let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear; return
    Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} style={{ transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), - pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", + pointerEvents: this.ignorePointerEvents ? "none" : "all", color: StrCast(this.Document.color), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 5108954bb..c93746773 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -31,7 +31,7 @@ export interface FieldViewProps { Document: Doc; DataDoc?: Doc; onClick?: ScriptField; - isSelected: () => boolean; + isSelected: (outsideReaction?: boolean) => boolean; select: (isCtrlPressed: boolean) => void; renderDepth: number; addDocument?: (document: Doc) => boolean; @@ -40,7 +40,7 @@ export interface FieldViewProps { removeDocument?: (document: Doc) => boolean; moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - active: () => boolean; + active: (outsideReaction?: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; focus: (doc: Doc) => void; PanelWidth: () => number; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 31919f192..5201f9bdc 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -839,7 +839,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (this.props.onClick && e.button === 0) { e.preventDefault(); } - if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -854,7 +854,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } (e.nativeEvent as any).formattedHandled = true; - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } } @@ -867,7 +867,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } onPointerWheel = (e: React.WheelEvent): void => { // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time - if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { e.stopPropagation(); } } @@ -878,7 +878,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & onClick = (e: React.MouseEvent): void => { if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } (e.nativeEvent as any).formattedHandled = true; - // if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { + // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) { // let href = (e.target as any).href; // let location: string; // if ((e.target as any).attributes.location) { @@ -918,7 +918,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) { clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); - if (this.props.isSelected() && offsetX < 40) { + if (this.props.isSelected(true) && offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: x, top: y }); if (pos && pos.pos > 0) { let node = this._editorView!.state.doc.nodeAt(pos.pos); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 35e9e4862..aa6e135fe 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -95,7 +95,7 @@ export class KeyValueBox extends React.Component { } onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons === 1 && this.props.isSelected()) { + if (e.buttons === 1 && this.props.isSelected(true)) { e.stopPropagation(); } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5cfd4b019..e7d8ac46c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -21,6 +21,7 @@ import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); import { documentSchema } from '../../../new_fields/documentSchemas'; +import { SelectionManager } from '../../util/SelectionManager'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -62,7 +63,7 @@ export class PDFBox extends DocAnnotatableComponent this._selectReactionDisposer = reaction(() => this.props.isSelected(), () => { document.removeEventListener("keydown", this.onKeyDown); - this.props.isSelected() && document.addEventListener("keydown", this.onKeyDown); + this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown); }, { fireImmediately: true }); } @@ -202,16 +203,16 @@ export class PDFBox extends DocAnnotatableComponent
    ; } - isChildActive = () => this._isChildActive; + isChildActive = (outsideReaction?: boolean) => this._isChildActive; @computed get renderPdfView() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); return
    this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0)); this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => { - let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting; + let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting; iframe.style.pointerEvents = interactive ? "all" : "none"; }, { fireImmediately: true }); }; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 77790a708..7d8f39e41 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -59,10 +59,10 @@ interface IViewerProps { startupLive: boolean; renderDepth: number; focus: (doc: Doc) => void; - isSelected: () => boolean; + isSelected: (outsideReaction?: boolean) => boolean; loaded: (nw: number, nh: number, np: number) => void; - active: () => boolean; - isChildActive: () => boolean; + active: (outsideReaction?: boolean) => boolean; + isChildActive: (outsideReaction?: boolean) => boolean; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; addDocument?: (doc: Doc) => boolean; @@ -166,7 +166,7 @@ export class PDFViewer extends DocAnnotatableComponent { - if (this.props.active() && e.clipboardData) { + if (this.props.active(true) && e.clipboardData) { let annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)"); // copied text markup color (blueish) if (annoDoc) { e.clipboardData.setData("text/plain", this._selectionText); @@ -397,7 +397,7 @@ export class PDFViewer extends DocAnnotatableComponent { let hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation + if (hit && hit.localName === "span" && this.props.isSelected(true)) { // drag selecting text stops propagation e.button === 0 && e.stopPropagation(); } // if alt+left click, drag and annotate @@ -405,11 +405,11 @@ export class PDFViewer extends DocAnnotatableComponent = new ObservableMap(); + BrushedDoc: ObservableMap = new ObservableMap(); } const brushManager = new DocBrush(); export class DocData { @observable _user_doc: Doc = undefined!; - @observable BrushedDoc: ObservableMap = new ObservableMap(); } // the document containing the view layout information - will be the Document itself unless the Document has @@ -654,11 +654,19 @@ export namespace Doc { export function UserDoc(): Doc { return manager._user_doc; } export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } export function IsBrushed(doc: Doc) { - return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)); + return computedFn(function IsBrushed(doc: Doc) { + return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)); + })(doc); } - export function IsBrushedDegree(doc: Doc) { + // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) + export function IsBrushedDegreeUnmemoized(doc: Doc) { return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0; } + export function IsBrushedDegree(doc: Doc) { + return computedFn(function IsBrushDegree(doc: Doc) { + return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0; + })(doc); + } export function BrushDoc(doc: Doc) { brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true); -- cgit v1.2.3-70-g09d2