From d58195a0470b3882d6b43b18f1f4ab7a373a671f Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sat, 12 Oct 2019 17:02:31 -0400 Subject: marquee now relies on pdf-style menu. pdf-style menu is now componentized so that other things can use it --- .../collections/collectionFreeForm/MarqueeView.tsx | 206 +++++++++++++-------- 1 file changed, 129 insertions(+), 77 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 82193aefa..ad4da7733 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,6 +19,7 @@ import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); +import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -195,6 +196,14 @@ export class MarqueeView extends React.Component } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } + if (!this._commandExecuted) { + MarqueeOptionsMenu.Instance.createCollection = this.collection; + MarqueeOptionsMenu.Instance.delete = this.delete; + MarqueeOptionsMenu.Instance.summarize = this.summary; + MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; + MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; + MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); + } this.cleanupInteractions(true); if (e.altKey) { @@ -255,6 +264,117 @@ export class MarqueeView extends React.Component Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink = value; } + @action + showMarquee = () => { + this._visible = true; + } + + @action + hideMarquee = () => { + this._visible = false; + } + + @action + delete = () => { + this.marqueeSelect(false).map(d => this.props.removeDocument(d)); + if (this.ink) { + this.marqueeInkDelete(this.ink.inkData); + } + SelectionManager.DeselectAll(); + this.cleanupInteractions(false); + MarqueeOptionsMenu.Instance.fadeOut(true); + } + + getCollection = (selected: Doc[]) => { + let bounds = this.Bounds; + let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; + let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); + let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); + let usedPaletted = new Map(); + [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { + let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); + if (palette.indexOf(bg) !== -1) { + palette.splice(palette.indexOf(bg), 1); + if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); + else usedPaletted.set(bg, 1); + } + }); + usedPaletted.delete("#f1efeb"); + usedPaletted.delete("white"); + usedPaletted.delete("rgba(255,255,255,1)"); + let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); + let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; + let inkData = this.ink ? this.ink.inkData : undefined; + let newCollection = Docs.Create.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panX: 0, + panY: 0, + backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, + defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, + width: bounds.width, + height: bounds.height, + title: "a nested collection", + }); + let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); + dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; + this.marqueeInkDelete(inkData); + return newCollection; + } + + @action + collection = (e: KeyboardEvent | React.PointerEvent | undefined) => { + let bounds = this.Bounds; + let selected = this.marqueeSelect(false); + if (e instanceof KeyboardEvent ? e.key === "c" : true) { + selected.map(d => { + this.props.removeDocument(d); + d.x = NumCast(d.x) - bounds.left - bounds.width / 2; + d.y = NumCast(d.y) - bounds.top - bounds.height / 2; + d.page = -1; + return d; + }); + } + let newCollection = this.getCollection(selected); + this.props.addDocument(newCollection, false); + this.props.selectDocuments([newCollection]); + MarqueeOptionsMenu.Instance.fadeOut(true); + } + + @action + summary = (e: KeyboardEvent | React.PointerEvent | undefined) => { + let bounds = this.Bounds; + let selected = this.marqueeSelect(false); + let newCollection = this.getCollection(selected); + + selected.map(d => { + this.props.removeDocument(d); + d.x = NumCast(d.x) - bounds.left - bounds.width / 2; + d.y = NumCast(d.y) - bounds.top - bounds.height / 2; + d.page = -1; + return d; + }); + newCollection.chromeStatus = "disabled"; + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + Doc.GetProto(summary).summarizedDocs = new List([newCollection]); + newCollection.x = bounds.left + bounds.width; + Doc.GetProto(newCollection).summaryDoc = summary; + Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); + if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); + container.viewType = CollectionViewType.Stacking; + container.autoHeight = true; + Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" + this.props.addLiveTextDocument(container); + } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them + Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" + this.props.addLiveTextDocument(summary); + } + MarqueeOptionsMenu.Instance.fadeOut(true); + } + @undoBatch @action marqueeCommand = async (e: KeyboardEvent) => { @@ -265,12 +385,7 @@ export class MarqueeView extends React.Component this._commandExecuted = true; e.stopPropagation(); (e as any).propagationIsStopped = true; - this.marqueeSelect(false).map(d => this.props.removeDocument(d)); - if (this.ink) { - this.marqueeInkDelete(this.ink.inkData); - } - SelectionManager.DeselectAll(); - this.cleanupInteractions(false); + this.delete(); e.stopPropagation(); } if (e.key === "c" || e.key === "s" || e.key === "S") { @@ -278,80 +393,12 @@ export class MarqueeView extends React.Component e.stopPropagation(); e.preventDefault(); (e as any).propagationIsStopped = true; - let bounds = this.Bounds; - let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; - return d; - }); + this.collection(e); } - let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", - "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); - let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); - let usedPaletted = new Map(); - [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); - if (palette.indexOf(bg) !== -1) { - palette.splice(palette.indexOf(bg), 1); - if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); - else usedPaletted.set(bg, 1); - } - }); - usedPaletted.delete("#f1efeb"); - usedPaletted.delete("white"); - usedPaletted.delete("rgba(255,255,255,1)"); - let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); - let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; - let inkData = this.ink ? this.ink.inkData : undefined; - let newCollection = Docs.Create.FreeformDocument(selected, { - x: bounds.left, - y: bounds.top, - panX: 0, - panY: 0, - backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, - defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, - width: bounds.width, - height: bounds.height, - title: "a nested collection", - }); - let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); - dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; - this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; - return d; - }); - newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - Doc.GetProto(summary).summarizedDocs = new List([newCollection]); - newCollection.x = bounds.left + bounds.width; - Doc.GetProto(newCollection).summaryDoc = summary; - Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); - if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. - let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); - container.viewType = CollectionViewType.Stacking; - container.autoHeight = true; - Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" - this.props.addLiveTextDocument(container); - } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them - Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" - this.props.addLiveTextDocument(summary); - } - } - else { - this.props.addDocument(newCollection, false); - this.props.selectDocuments([newCollection]); + this.summary(e); } this.cleanupInteractions(false); } @@ -435,8 +482,13 @@ export class MarqueeView extends React.Component @computed get marqueeDiv() { let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + /** + * @RE - The commented out span below + * This contains the "C for collection, ..." text on marquees. + * Commented out by syip2 when the marquee menu was added. + */ return
- + {/* */}
; } -- cgit v1.2.3-70-g09d2 From ab959c44ea6393783fcfef82a241a7bc3e9124ed Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sun, 13 Oct 2019 14:29:59 -0400 Subject: improved marquee menu interactions, fixed issue with panning on cffv --- .../collectionFreeForm/CollectionFreeFormView.scss | 3 +++ .../collectionFreeForm/MarqueeOptionsMenu.tsx | 12 +++--------- .../collections/collectionFreeForm/MarqueeView.tsx | 18 +++++++++++++++--- 3 files changed, 21 insertions(+), 12 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index a20d6df3a..9b608a576 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -26,6 +26,9 @@ } .collectionfreeformview-container { + // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions. + touch-action: none; + .collectionfreeformview>.jsx-parser { position: inherit; height: 100%; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 7c5b87283..91fcad4be 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -25,25 +25,19 @@ export default class MarqueeOptionsMenu extends AntimodeMenu { , , , ] diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ad4da7733..4a7fa629c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -51,13 +51,15 @@ export class MarqueeView extends React.Component } @action - cleanupInteractions = (all: boolean = false) => { + cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => { if (all) { document.removeEventListener("pointerup", this.onPointerUp, true); document.removeEventListener("pointermove", this.onPointerMove, true); } document.removeEventListener("keydown", this.marqueeCommand, true); - this._visible = false; + if (hideMarquee) { + this._visible = false; + } } @undoBatch @@ -204,7 +206,14 @@ export class MarqueeView extends React.Component MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); } - this.cleanupInteractions(true); + this.cleanupInteractions(true, this._commandExecuted); + + let hideMarquee = () => { + this.hideMarquee(); + MarqueeOptionsMenu.Instance.fadeOut(true); + document.removeEventListener("pointerdown", hideMarquee); + } + document.addEventListener("pointerdown", hideMarquee) if (e.altKey) { e.preventDefault(); @@ -283,6 +292,7 @@ export class MarqueeView extends React.Component SelectionManager.DeselectAll(); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); } getCollection = (selected: Doc[]) => { @@ -321,6 +331,7 @@ export class MarqueeView extends React.Component let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); + this.hideMarquee(); return newCollection; } @@ -341,6 +352,7 @@ export class MarqueeView extends React.Component this.props.addDocument(newCollection, false); this.props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); } @action -- cgit v1.2.3-70-g09d2 From 462cd06a1bff6e510132a4434a72fd7b4245cfdf Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 15 Oct 2019 17:29:57 -0400 Subject: good enough for stanley to start texting i think --- src/client/views/DocComponent.tsx | 7 +-- src/client/views/Main.scss | 2 +- src/client/views/MainView.scss | 11 ---- .../views/collections/CollectionDockingView.scss | 69 ++++++++++++++++++++-- .../views/collections/CollectionDockingView.tsx | 20 ++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- 6 files changed, 79 insertions(+), 34 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 9d7c06750..94717352e 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -7,18 +7,13 @@ import { listSpec } from '../../new_fields/Schema'; import { InkingControl } from './InkingControl'; import { InkTool } from '../../new_fields/InkField'; -<<<<<<< HEAD -export function DocComponent

(schemaCtor: (doc: Doc) => T) { - class Component extends Touchable

{ -======= /// DocComponents returns a generic base class for React views of document fields that are not interactive interface DocComponentProps { Document: Doc; } export function DocComponent

(schemaCtor: (doc: Doc) => T) { - class Component extends React.Component

{ ->>>>>>> 33811c112c7e479813908ba10f72813954a3e289 + class Component extends Touchable

{ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 0335e12a5..4a174f8c7 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -36,7 +36,7 @@ p { ::-webkit-scrollbar { -webkit-appearance: none; height: 8px; - width: 8px; + width: 20px; } ::-webkit-scrollbar-thumb { diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index a6b28f488..25af67161 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -34,15 +34,9 @@ display: flex; flex-direction: column; position: absolute; -<<<<<<< HEAD width: 100%; height: 100%; - border: black 1px solid; -======= - width:100%; - height:100%; ->>>>>>> 33811c112c7e479813908ba10f72813954a3e289 .documentView-node-topmost { background: lightgrey; } @@ -73,13 +67,8 @@ .mainView-expandFlyoutButton { position: absolute; -<<<<<<< HEAD top: 100px; right: 30px; -======= - top: 5px; - right: 5px; ->>>>>>> 33811c112c7e479813908ba10f72813954a3e289 cursor: pointer; } diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 6f5abd05b..b4ab8d550 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,12 +1,13 @@ @import "../../views/globalCssVariables.scss"; -.lm_active .messageCounter{ - color:white; +.lm_active .messageCounter { + color: white; background: #999999; } + .messageCounter { - width:18px; - height:20px; + width: 18px; + height: 20px; text-align: center; border-radius: 20px; margin-left: 5px; @@ -18,22 +19,29 @@ .collectiondockingview-container { width: 100%; - height:100%; + height: 100%; border-style: solid; border-width: $COLLECTION_BORDER_WIDTH; position: absolute; top: 0; left: 0; overflow: hidden; + + .collectionDockingView-dragAsDocument { + touch-action: none; + } + .lm_controls>li { opacity: 0.6; transform: scale(1.2); } + .lm_maximised .lm_controls .lm_maximise { opacity: 1; transform: scale(0.8); background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important; } + .flexlayout__layout { left: 0; top: 0; @@ -42,17 +50,21 @@ position: absolute; overflow: hidden; } + .flexlayout__splitter { background-color: black; } + .flexlayout__splitter:hover { background-color: #333; } + .flexlayout__splitter_drag { border-radius: 5px; background-color: #444; z-index: 1000; } + .flexlayout__outline_rect { position: absolute; cursor: move; @@ -62,6 +74,7 @@ z-index: 1000; box-sizing: border-box; } + .flexlayout__outline_rect_edge { cursor: move; border: 2px solid #b7d1b5; @@ -70,12 +83,14 @@ z-index: 1000; box-sizing: border-box; } + .flexlayout__edge_rect { position: absolute; z-index: 1000; box-shadow: inset 0 0 5px rgba(0, 0, 0, .2); background-color: lightgray; } + .flexlayout__drag_rect { position: absolute; cursor: move; @@ -94,11 +109,13 @@ padding: 10px; word-wrap: break-word; } + .flexlayout__tabset { overflow: hidden; background-color: #222; box-sizing: border-box; } + .flexlayout__tab { overflow: auto; position: absolute; @@ -106,6 +123,7 @@ background-color: #222; color: black; } + .flexlayout__tab_button { cursor: pointer; padding: 2px 8px 3px 8px; @@ -117,28 +135,35 @@ vertical-align: top; box-sizing: border-box; } + .flexlayout__tab_button--selected { color: #ddd; background-color: #222; } + .flexlayout__tab_button--unselected { color: gray; } + .flexlayout__tab_button_leading { display: inline-block; } + .flexlayout__tab_button_content { display: inline-block; } + .flexlayout__tab_button_textbox { float: left; border: none; color: lightgreen; background-color: #222; } + .flexlayout__tab_button_textbox:focus { outline: none; } + .flexlayout__tab_button_trailing { display: inline-block; margin-left: 5px; @@ -146,10 +171,12 @@ width: 8px; height: 8px; } + .flexlayout__tab_button:hover .flexlayout__tab_button_trailing, .flexlayout__tab_button--selected .flexlayout__tab_button_trailing { background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; } + .flexlayout__tab_button_overflow { float: left; width: 20px; @@ -162,6 +189,7 @@ font-family: Arial, sans-serif; background: transparent url("../../../../node_modules/flexlayout-react/images/more.png") no-repeat left; } + .flexlayout__tabset_header { position: absolute; left: 0; @@ -172,6 +200,7 @@ /*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/ box-sizing: border-box; } + .flexlayout__tab_header_inner { position: absolute; left: 0; @@ -179,6 +208,7 @@ bottom: 0; width: 10000px; } + .flexlayout__tab_header_outer { background-color: black; position: absolute; @@ -188,12 +218,15 @@ /*height: 100px;*/ overflow: hidden; } + .flexlayout__tabset-selected { background-image: linear-gradient(#111, #444); } + .flexlayout__tabset-maximized { background-image: linear-gradient(#666, #333); } + .flexlayout__tab_toolbar { position: absolute; display: flex; @@ -203,6 +236,7 @@ bottom: 0; right: 0; } + .flexlayout__tab_toolbar_button-min { width: 20px; height: 20px; @@ -210,6 +244,7 @@ outline-width: 0; background: transparent url("../../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center; } + .flexlayout__tab_toolbar_button-max { width: 20px; height: 20px; @@ -217,14 +252,18 @@ outline-width: 0; background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center; } + .flexlayout__popup_menu {} + .flexlayout__popup_menu_item { padding: 2px 10px 2px 10px; color: #ddd; } + .flexlayout__popup_menu_item:hover { background-color: #444444; } + .flexlayout__popup_menu_container { box-shadow: inset 0 0 5px rgba(0, 0, 0, .15); border: 1px solid #555; @@ -233,33 +272,39 @@ position: absolute; z-index: 1000; } + .flexlayout__border_top { background-color: black; border-bottom: 1px solid #ddd; box-sizing: border-box; overflow: hidden; } + .flexlayout__border_bottom { background-color: black; border-top: 1px solid #333; box-sizing: border-box; overflow: hidden; } + .flexlayout__border_left { background-color: black; border-right: 1px solid #333; box-sizing: border-box; overflow: hidden; } + .flexlayout__border_right { background-color: black; border-left: 1px solid #333; box-sizing: border-box; overflow: hidden; } + .flexlayout__border_inner_bottom { display: flex; } + .flexlayout__border_inner_left { position: absolute; white-space: nowrap; @@ -267,6 +312,7 @@ transform-origin: top right; transform: rotate(-90deg); } + .flexlayout__border_inner_right { position: absolute; white-space: nowrap; @@ -274,6 +320,7 @@ transform-origin: top left; transform: rotate(90deg); } + .flexlayout__border_button { background-color: #222; color: white; @@ -285,29 +332,36 @@ vertical-align: top; box-sizing: border-box; } + .flexlayout__border_button--selected { color: #ddd; background-color: #222; } + .flexlayout__border_button--unselected { color: gray; } + .flexlayout__border_button_leading { float: left; display: inline; } + .flexlayout__border_button_content { display: inline-block; } + .flexlayout__border_button_textbox { float: left; border: none; color: green; background-color: #ddd; } + .flexlayout__border_button_textbox:focus { outline: none; } + .flexlayout__border_button_trailing { display: inline-block; margin-left: 5px; @@ -315,10 +369,12 @@ width: 8px; height: 8px; } + .flexlayout__border_button:hover .flexlayout__border_button_trailing, .flexlayout__border_button--selected .flexlayout__border_button_trailing { background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; } + .flexlayout__border_toolbar_left { position: absolute; display: flex; @@ -328,6 +384,7 @@ left: 0; right: 0; } + .flexlayout__border_toolbar_right { position: absolute; display: flex; @@ -337,6 +394,7 @@ left: 0; right: 0; } + .flexlayout__border_toolbar_top { position: absolute; display: flex; @@ -346,6 +404,7 @@ bottom: 0; right: 0; } + .flexlayout__border_toolbar_bottom { position: absolute; display: flex; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1f78c8c97..5e00fda36 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -426,15 +426,17 @@ export class CollectionDockingView extends React.Component { - e.preventDefault(); - e.stopPropagation(); - DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { - handlers: { dragComplete: emptyFunction }, - hideSource: false - }); - }}>, dragSpan); + ReactDOM.render( { + e.preventDefault(); + e.stopPropagation(); + DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { + handlers: { dragComplete: emptyFunction }, + hideSource: false + }); + }}>, dragSpan); ReactDOM.render(, gearSpan); // ReactDOM.render( { // where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e9f4c40a6..07db6354f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -198,7 +198,7 @@ export class MarqueeView extends React.Component } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } - if (!this._commandExecuted) { + if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; @@ -213,7 +213,7 @@ export class MarqueeView extends React.Component MarqueeOptionsMenu.Instance.fadeOut(true); document.removeEventListener("pointerdown", hideMarquee); } - document.addEventListener("pointerdown", hideMarquee) + document.addEventListener("pointerdown", hideMarquee); if (e.altKey) { e.preventDefault(); -- cgit v1.2.3-70-g09d2 From 8a3cfa8d6e72c9bfea4b38760e0b138b6525574c Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 16:42:37 -0400 Subject: a bunch of fixes to layouts to support templates in paticular. --- src/client/util/DocumentManager.ts | 2 + src/client/util/DragManager.ts | 4 +- src/client/views/CollectionLinearView.tsx | 7 +- src/client/views/DocumentButtonBar.tsx | 3 +- src/client/views/DocumentDecorations.tsx | 47 +++++----- src/client/views/InkingControl.tsx | 9 +- .../views/collections/CollectionStackingView.tsx | 11 ++- .../views/collections/CollectionTreeView.scss | 1 + .../views/collections/CollectionTreeView.tsx | 17 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 63 ++++++------- .../collections/collectionFreeForm/MarqueeView.tsx | 31 ++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/FontIconBox.tsx | 5 +- src/client/views/nodes/FormattedTextBox.tsx | 101 +++++++++++---------- src/new_fields/Doc.ts | 11 ++- 16 files changed, 178 insertions(+), 144 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 00de39671..e23ac55c2 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -72,6 +72,8 @@ export class DocumentManager { toReturn = view; } }); + } else { + break; } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 12e0b11ba..06dab024e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -211,7 +211,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; - forceUserDropAction: dropActionType; + embedDoc?: boolean; moveDocument?: MoveFunction; isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; @@ -414,7 +414,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = dragData.forceUserDropAction ? dragData.forceUserDropAction : e.ctrlKey || e.altKey ? "alias" : undefined; + dragData.userDropAction = e.ctrlKey ? "alias" : undefined; } if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index af3b194ea..44d9b042e 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -15,6 +15,7 @@ import { CollectionViewType } from './collections/CollectionBaseView'; import { CollectionSubView } from './collections/CollectionSubView'; import { documentSchema, DocumentView } from './nodes/DocumentView'; import { translate } from 'googleapis/build/src/apis/translate'; +import { DocumentType } from '../documents/DocumentTypes'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -50,8 +51,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" }); - dbox.dragFactory = doc; + let template = doc.layout instanceof Doc && doc.layout.isTemplate ? doc.layout : doc; + template.isTemplate = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; + dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplate ? "font" : "bolt" }); + dbox.dragFactory = template; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); } else if (doc.viewType === CollectionViewType.Linear) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a00a4298f..959b120ed 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -165,7 +165,8 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); - dragData.forceUserDropAction = "alias"; + dragData.embedDoc = true; + dragData.dropAction = "alias"; DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, { offsetX: dragData.offset[0], offsetY: dragData.offset[1], diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 755739a11..2f40ea746 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -471,47 +471,48 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); + let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document); let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; - let width = (doc.width || 0); - let height = (doc.height || (nheight / nwidth * width)); + let width = (layoutDoc.width || 0); + let height = (layoutDoc.height || (nheight / nwidth * width)); let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - doc.x = (doc.x || 0) + dX * (actualdW - width); - doc.y = (doc.y || 0) + dY * (actualdH - height); + layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width); + layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... - let fixedAspect = e.ctrlKey || (!doc.ignoreAspect && nwidth && nheight); - if (fixedAspect && e.ctrlKey && doc.ignoreAspect) { - doc.ignoreAspect = false; - proto.nativeWidth = nwidth = doc.width || 0; - proto.nativeHeight = nheight = doc.height || 0; + let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); + if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { + layoutDoc.ignoreAspect = false; + proto.nativeWidth = nwidth = layoutDoc.width || 0; + proto.nativeHeight = nheight = layoutDoc.height || 0; } if (fixedAspect && (!nwidth || !nheight)) { - proto.nativeWidth = nwidth = doc.width || 0; - proto.nativeHeight = nheight = doc.height || 0; + proto.nativeWidth = nwidth = layoutDoc.width || 0; + proto.nativeHeight = nheight = layoutDoc.height || 0; } - if (nwidth > 0 && nheight > 0 && !BoolCast(doc.ignoreAspect)) { + if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) { if (Math.abs(dW) > Math.abs(dH)) { if (!fixedAspect) { - Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true); + Doc.SetInPlace(doc, "nativeWidth", actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0), true); } - doc.width = actualdW; - if (fixedAspect && !doc.fitWidth) doc.height = nheight / nwidth * doc.width; - else doc.height = actualdH; + layoutDoc.width = actualdW; + if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width; + else layoutDoc.height = actualdH; } else { if (!fixedAspect) { - Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true); + Doc.SetInPlace(doc, "nativeHeight", actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0), true); } - doc.height = actualdH; - if (fixedAspect && !doc.fitWidth) doc.width = nwidth / nheight * doc.height; - else doc.width = actualdW; + layoutDoc.height = actualdH; + if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height; + else layoutDoc.width = actualdW; } } else { - dW && (doc.width = actualdW); - dH && (doc.height = actualdH); - dH && element.props.Document.autoHeight && Doc.SetInPlace(element.props.Document, "autoHeight", false, true); + dW && (layoutDoc.width = actualdW); + dH && (layoutDoc.height = actualdH); + dH && layoutDoc.autoHeight && Doc.SetInPlace(layoutDoc, "autoHeight", false, true); } } }); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 51fc7ca8f..46c6fae1c 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -41,7 +41,9 @@ export class InkingControl { if (InkingControl.Instance.selectedTool === InkTool.None) { let selected = SelectionManager.SelectedDocuments(); let oldColors = selected.map(view => { - let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); + let targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory : + view.props.Document.layout instanceof Doc ? view.props.Document.layout : + view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let sel = window.getSelection(); if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) { targetDoc.color = this._selectedColor; @@ -79,7 +81,10 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - !ruleProvider && (targetDoc.backgroundColor = matchedColor); + if (!ruleProvider) { + if (targetDoc) + targetDoc.backgroundColor = matchedColor; + } return { target: targetDoc, diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 90fc9c3e0..cde1a5036 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -110,9 +110,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } componentDidMount() { - // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { - if (BoolCast(this.props.Document.autoHeight)) { + if (this.props.Document.autoHeight) { let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); if (this.isStackingView) { return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght, @@ -188,15 +187,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } getDocHeight(d?: Doc) { if (!d) return 0; + let layoutDoc = d.layout instanceof Doc ? d.layout : d; let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); - if (!d.ignoreAspect && !d.fitWidth && nw && nh) { + if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) { let aspect = nw && nh ? nh / nw : 1; - if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid); + if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid); return wid * aspect; } - return d.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : Math.min(wid * NumCast(d.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : d[HeightSym](); + return layoutDoc.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : + Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym](); } columnDividerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index ca0c321b7..bff8ce5c1 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -16,6 +16,7 @@ background: $light-color-secondary; font-size: 13px; overflow: auto; + cursor: default; ul { list-style: none; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index abaa9662c..403da0e54 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -251,19 +251,21 @@ class TreeView extends React.Component { } docWidth = () => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); - return NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; + let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); + return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } docHeight = () => { let bounds = this.boundsOfCollectionDocument; return Math.min(this.MAX_EMBED_HEIGHT, (() => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); + let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); - return this.props.document.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : - Math.min(this.docWidth() * NumCast(this.props.document.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth, + return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth, NumCast(this.props.containingCollection.height)))) : - NumCast(this.props.document.height) ? NumCast(this.props.document.height) : 50; + NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50; })()); } @@ -461,10 +463,11 @@ class TreeView extends React.Component { let rowWidth = () => panelWidth() - 20; return docs.map((child, i) => { - let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); + const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); if (!pair.layout || pair.data instanceof Promise) { return (null); } + const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; let indent = i === 0 ? undefined : () => { if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { @@ -482,7 +485,7 @@ class TreeView extends React.Component { }; let rowHeight = () => { let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0); - return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym](); + return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); }; return { - d.x = x + NumCast(d.x) - dropX; - d.y = y + NumCast(d.y) - dropY; - if (!NumCast(d.width)) { - d.width = 300; + let layoutDoc = d.layout instanceof Doc ? d.layout : d; + layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; + layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + if (!NumCast(layoutDoc.width)) { + layoutDoc.width = 300; } if (!NumCast(d.height)) { let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); - d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; + layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); })); @@ -156,14 +157,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; + let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc; let x = xp - de.data.offset[0]; let y = yp - de.data.offset[1]; - let dropX = NumCast(de.data.dropDocument.x); - let dropY = NumCast(de.data.dropDocument.y); - dragDoc.x = x + NumCast(dragDoc.x) - dropX; - dragDoc.y = y + NumCast(dragDoc.y) - dropY; - de.data.targetContext = this.props.Document; - dragDoc.targetContext = this.props.Document; + let dropX = NumCast(layoutDoc.x); + let dropY = NumCast(layoutDoc.y); + layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; + layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); } } @@ -173,11 +174,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - let cx = NumCast(cd.x) - this._clusterDistance; - let cy = NumCast(cd.y) - this._clusterDistance; - let cw = NumCast(cd.width) + 2 * this._clusterDistance; - let ch = NumCast(cd.height) + 2 * this._clusterDistance; - return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? + let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd; + let cx = NumCast(layoutDoc.x) - this._clusterDistance; + let cy = NumCast(layoutDoc.y) - this._clusterDistance; + let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance; + let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance; + return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? NumCast(cd.cluster) : cluster; }, -1); } @@ -185,14 +187,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); if (cluster !== -1) { let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); - - // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view - let prevSelected = SelectionManager.SelectedDocuments(); - this.selectDocuments(eles); - let clusterDocs = SelectionManager.SelectedDocuments(); - SelectionManager.DeselectAll(); - prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - + let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); let de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); @@ -223,8 +218,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; this._clusterSets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + let memberLayout = member.layout instanceof Doc ? member.layout : member; + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) { doc.cluster = i; } })); @@ -296,10 +293,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childLayoutPairs.map(pair => pair.layout); + let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { - PDFMenu.Instance.fadeOut(true); let minx = docs.length ? NumCast(docs[0].x) : 0; let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; let miny = docs.length ? NumCast(docs[0].y) : 0; @@ -549,7 +545,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; 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 }); + let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; + const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state }); state = pos.state === undefined ? state : pos.state; layoutPoolData.set(pair, pos); }); @@ -592,7 +589,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let i = 0; const width = Math.max(...docs.map(doc => NumCast(doc.width))); const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs) { + for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) { doc.x = x; doc.y = y; x += width + 20; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ecdd02b0f..1362736cf 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -281,7 +281,7 @@ export class MarqueeView extends React.Component let bounds = this.Bounds; let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => { + selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -325,7 +325,7 @@ export class MarqueeView extends React.Component this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => { + selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -394,20 +394,22 @@ export class MarqueeView extends React.Component let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } }); if (!selection.length && selectBackgrounds) { this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } @@ -420,10 +422,11 @@ export class MarqueeView extends React.Component let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { selection.push(doc); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 93f6dc468..5e8ac3ecd 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -36,8 +36,8 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); if (maxLocation === "inPlace") { expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc)); - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2); DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); } else { expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); @@ -392,7 +392,7 @@ export class DocumentView extends DocComponent(Docu if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) { let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(portalID).then(existingPortal => { - let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID }); + let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID }); DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link"); this.Document.isButton = true; }); @@ -491,7 +491,7 @@ export class DocumentView extends DocComponent(Docu layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }); } layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.Document.autoHeight = !this.Document.autoHeight, icon: "plus" }); + layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 848afccf3..c5bf28d5b 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -8,6 +8,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { StrCast } from '../../../new_fields/Types'; import { Utils } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; +import { Doc } from '../../../new_fields/Doc'; const FontIconSchema = createSchema({ icon: "string" }); @@ -35,8 +36,10 @@ export class FontIconBox extends DocComponent( this._backgroundReaction && this._backgroundReaction(); } render() { + let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); + let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc; return ; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ae67e94cc..f157a953e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -127,7 +127,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let view = this._editorView!; if (view.state.selection.from === view.state.selection.to) return false; if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.props.Document.color = color; + this.layoutDoc.color = color; } let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); @@ -143,7 +143,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } + + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document + @computed get layoutDoc(): Doc { + return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + } linkOnDeselect: Map = new Map(); @@ -249,39 +255,41 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction) { - let target = de.data.droppedDocuments[0]; - const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - target.fitToBox = true; - let node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], - float: "right" - }); - let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node)); - this.tryUpdateHeight(); - e.stopPropagation(); - } else if (de.data instanceof DragManager.DocumentDragData) { + if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; - if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) { - if (de.mods === "AltKey") { - if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); - e.stopPropagation(); - } - } else if (de.mods === "CtrlKey") { - draggedDoc.isTemplate = true; - if (typeof (draggedDoc.layout) === "string") { - let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); - layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); - this.props.Document.layout = layoutDelegateToOverrideFieldKey; - } else { - this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; - } + // replace text contents whend dragging with Alt + if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") { + if (draggedDoc.data instanceof RichTextField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); + e.stopPropagation(); + } + // apply as template when dragging with Meta + } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") { + draggedDoc.isTemplate = true; + let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + if (typeof (draggedDoc.layout) === "string") { + newLayout = Doc.MakeDelegate(draggedDoc); + newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); } + this.props.Document.layout = newLayout; e.stopPropagation(); - } + // embed document when dragging with a userDropAction or an embedDoc flag set + } else if (de.data.userDropAction || de.data.embedDoc) { + let target = de.data.droppedDocuments[0]; + const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + if (link) { + target.fitToBox = true; + let node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "right" + }); + let view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + this.tryUpdateHeight(); + e.stopPropagation(); + } + } // otherwise, fall through to outer collection to handle drop } } @@ -486,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ); this._heightReactionDisposer = reaction( - () => [this.props.Document[WidthSym](), this.props.Document.autoHeight], + () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight], () => this.tryUpdateHeight() ); @@ -505,7 +513,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); this._searchReactionDisposer = reaction(() => { - return StrCast(this.props.Document.search_string); + return StrCast(this.layoutDoc.search_string); }, searchString => { if (searchString) { this.highlightSearchTerms([searchString]); @@ -518,7 +526,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._rulesReactionDisposer = reaction(() => { let ruleProvider = this.props.ruleProvider; - let heading = NumCast(this.props.Document.heading); + let heading = NumCast(this.layoutDoc.heading); if (ruleProvider instanceof Doc) { return { align: StrCast(ruleProvider["ruleAlign_" + heading], ""), @@ -530,7 +538,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, action((rules: any) => { this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13); + this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13); rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { @@ -547,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }), { fireImmediately: true } ); this._scrollToRegionReactionDisposer = reaction( - () => StrCast(this.props.Document.scrollToLinkID), + () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { let findLinkFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -585,7 +593,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); setTimeout(() => this.unhighlightSearchTerms(), 2000); } - this.props.Document.scrollToLinkID = undefined; + this.layoutDoc.scrollToLinkID = undefined; } }, @@ -885,7 +893,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } } else { - let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) }); + let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); this.props.addDocument && this.props.addDocument(webDoc); } e.stopPropagation(); @@ -983,19 +991,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.Document.isAnimating && this.props.Document.autoHeight && scrollHeight !== 0 && + if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); - let dh = NumCast(this.props.Document.height, 0); - this.props.Document.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + let dh = NumCast(this.layoutDoc.height, 0); + this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; } } render() { let style = "hidden"; - let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground + let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); if (this.props.isSelected()) { @@ -1004,8 +1012,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return (

(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); -- cgit v1.2.3-70-g09d2 From 3d910bae8771e67cabc1500c49b77d425fdf62e9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 19 Oct 2019 13:49:20 -0400 Subject: cleaned up a bunch layout vs doc issues related to templates --- src/client/views/DocumentDecorations.tsx | 6 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/TemplateMenu.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 8 +-- .../views/collections/CollectionTreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 66 +++++++++++----------- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++++---- .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 ++--- src/client/views/nodes/DocumentContentsView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 11 ++-- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/new_fields/Doc.ts | 24 +++++--- 14 files changed, 86 insertions(+), 95 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8409a34da..07af4799b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -470,7 +470,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); - let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document); + let layoutDoc = PositionDocument(Doc.Layout(element.props.Document)); let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; let width = (layoutDoc.width || 0); @@ -478,8 +478,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width); - layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height); + doc.x = (doc.x || 0) + dX * (actualdW - width); + doc.y = (doc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplateField ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index c3a617ffe..105adc03d 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -81,7 +81,7 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - (!ruleProvider && targetDoc) && (view.props.Document.backgroundColor = matchedColor); + (!ruleProvider && targetDoc) && (Doc.Layout(view.props.Document).backgroundColor = matchedColor); return { target: targetDoc, diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index da776f887..d76b033f0 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -117,13 +117,13 @@ export class TemplateMenu extends React.Component { @action toggleChrome = (): void => { this.props.docs.map(dv => { - let layout = dv.Document.layout instanceof Doc ? dv.Document.layout : dv.Document; + let layout = Doc.Layout(dv.Document); layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled"); }); } render() { - let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout : this.props.docs[0].Document; + let layout = Doc.Layout(this.props.docs[0].Document); let templateMenu: Array = []; this.props.templates.forEach((checked, template) => templateMenu.push()); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index f9f040c6b..1a578f4fc 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -74,11 +74,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { this._heightMap.set(key, sectionHeight); } - get layoutDoc() { - // if this document's layout field contains a document (ie, a rendering template), then we will use that - // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. - return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; - } + get layoutDoc() { return Doc.Layout(this.props.Document); } get Sections() { if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map(); @@ -189,7 +185,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } getDocHeight(d?: Doc) { if (!d) return 0; - let layoutDoc = d.layout instanceof Doc ? d.layout : d; + let layoutDoc = Doc.Layout(d); let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 5d88e6290..2fbe8527e 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -251,7 +251,7 @@ class TreeView extends React.Component { } docWidth = () => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + let layoutDoc = Doc.Layout(this.props.document); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } @@ -259,7 +259,7 @@ class TreeView extends React.Component { let bounds = this.boundsOfCollectionDocument; return Math.min(this.MAX_EMBED_HEIGHT, (() => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + let layoutDoc = Doc.Layout(this.props.document); if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : @@ -467,7 +467,6 @@ class TreeView extends React.Component { if (!pair.layout || pair.data instanceof Promise) { return (null); } - const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; let indent = i === 0 ? undefined : () => { if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { @@ -483,6 +482,7 @@ class TreeView extends React.Component { let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => { return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); }; + const childLayout = Doc.Layout(pair.layout); let rowHeight = () => { let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0); return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 932b6722b..eff73b14e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -130,22 +130,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (super.drop(e, de)) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { - let firstLayoutDoc = de.data.droppedDocuments[0].layout instanceof Doc ? de.data.droppedDocuments[0].layout : de.data.droppedDocuments[0]; - let z = NumCast(firstLayoutDoc.z); + let firstDoc = de.data.droppedDocuments[0]; + let z = NumCast(firstDoc.z); let x = (z ? xpo : xp) - de.data.offset[0]; let y = (z ? ypo : yp) - de.data.offset[1]; - let dropX = NumCast(firstLayoutDoc.x); - let dropY = NumCast(firstLayoutDoc.y); + let dropX = NumCast(firstDoc.x); + let dropY = NumCast(firstDoc.y); de.data.droppedDocuments.forEach(action((d: Doc) => { - let layoutDoc = d.layout instanceof Doc ? d.layout : d; - layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; - layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + let layoutDoc = Doc.Layout(d); + d.x = x + NumCast(d.x) - dropX; + d.y = y + NumCast(d.y) - dropY; if (!NumCast(layoutDoc.width)) { layoutDoc.width = 300; } - if (!NumCast(d.height)) { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); + if (!NumCast(layoutDoc.height)) { + let nw = NumCast(layoutDoc.nativeWidth); + let nh = NumCast(layoutDoc.nativeHeight); layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); @@ -157,13 +157,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc; let x = xp - de.data.offset[0]; let y = yp - de.data.offset[1]; - let dropX = NumCast(layoutDoc.x); - let dropY = NumCast(layoutDoc.y); - layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; - layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + let dropX = NumCast(dragDoc.x); + let dropY = NumCast(dragDoc.y); + dragDoc.x = x + NumCast(dragDoc.x) - dropX; + dragDoc.y = y + NumCast(dragDoc.y) - dropY; de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); } @@ -174,9 +173,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd; - let cx = NumCast(layoutDoc.x) - this._clusterDistance; - let cy = NumCast(layoutDoc.y) - this._clusterDistance; + let layoutDoc = Doc.Layout(cd); + let cx = NumCast(cd.x) - this._clusterDistance; + let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance; let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance; return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? @@ -218,10 +217,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; this._clusterSets.map((set, i) => set.map(member => { - let memberLayout = member.layout instanceof Doc ? member.layout : member; - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { doc.cluster = i; } })); @@ -293,18 +290,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout); + let docs = this.childLayoutPairs.map(pair => pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { let minx = docs.length ? NumCast(docs[0].x) : 0; - let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + let maxx = docs.length ? NumCast(Doc.Layout(docs[0]).width) + minx : minx; let miny = docs.length ? NumCast(docs[0].y) : 0; - let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + let maxy = docs.length ? NumCast(Doc.Layout(docs[0]).height) + miny : miny; let ranges = docs.filter(doc => doc).reduce((range, doc) => { + let layoutDoc = Doc.Layout(doc); let x = NumCast(doc.x); - let xe = x + NumCast(doc.width); + let xe = x + NumCast(layoutDoc.width); let y = NumCast(doc.y); - let ye = y + NumCast(doc.height); + let ye = y + NumCast(layoutDoc.height); return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; }, [[minx, maxx], [miny, maxy]]); @@ -408,9 +406,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.props.Document.scrollY = NumCast(doc.y) - offset; } } else { - let layoutdoc = doc.layout instanceof Doc ? doc.layout : doc; - const newPanX = NumCast(layoutdoc.x) + NumCast(layoutdoc.width) / 2; - const newPanY = NumCast(layoutdoc.y) + NumCast(layoutdoc.height) / 2; + let layoutdoc = Doc.Layout(doc); + const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2; const newState = HistoryUtil.getState(); newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); @@ -471,10 +469,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { const script = this.Document.arrangeScript; const result = script && script.script.run(params, console.log); + const layoutDoc = Doc.Layout(params.doc); if (result && result.success) { return { ...result, transition: "transform 1s" }; } - return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; + return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") }; } viewDefsToJSX = (views: any[]) => { @@ -530,8 +529,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { - let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; - const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state }); + 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); }); @@ -574,7 +572,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let i = 0; const width = Math.max(...docs.map(doc => NumCast(doc.width))); const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) { + for (const doc of docs) { doc.x = x; doc.y = y; x += width + 20; @@ -592,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading. this.Document.isRuleProvider && this.childLayoutPairs.map(pair => // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template) - DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => { + DocListCast(Doc.Layout(pair.layout).data).map(heading => { let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout; if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1362736cf..637168f1b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -281,7 +281,7 @@ export class MarqueeView extends React.Component let bounds = this.Bounds; let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { + selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -296,7 +296,7 @@ export class MarqueeView extends React.Component let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map(); [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); + let bg = StrCast(Doc.Layout(child).backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); @@ -325,7 +325,7 @@ export class MarqueeView extends React.Component this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { + selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -394,9 +394,9 @@ export class MarqueeView extends React.Component let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; - var x = NumCast(layoutDoc.x); - var y = NumCast(layoutDoc.y); + let layoutDoc = Doc.Layout(doc); + var x = NumCast(doc.x); + var y = NumCast(doc.y); var w = NumCast(layoutDoc.width); var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { @@ -405,9 +405,9 @@ export class MarqueeView extends React.Component }); if (!selection.length && selectBackgrounds) { this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; - var x = NumCast(layoutDoc.x); - var y = NumCast(layoutDoc.y); + let layoutDoc = Doc.Layout(doc); + var x = NumCast(doc.x); + var y = NumCast(doc.y); var w = NumCast(layoutDoc.width); var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { @@ -422,9 +422,9 @@ export class MarqueeView extends React.Component let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; - var x = NumCast(layoutDoc.x); - var y = NumCast(layoutDoc.y); + let layoutDoc = Doc.Layout(doc); + var x = NumCast(doc.x); + var y = NumCast(doc.y); var w = NumCast(layoutDoc.width); var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 8153923de..2243a44d5 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -34,13 +34,13 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; 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 : NumCast(this.layoutDoc.x); } - 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 : NumCast(this.layoutDoc.y); } + 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); } get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); } get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); } @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; } - @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } - @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } + @computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); } + @computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); } @computed get renderScriptDim() { if (this.Document.renderScript) { @@ -92,11 +92,7 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; - get layoutDoc() { - // if this document's layout field contains a document (ie, a rendering template), then we will use that - // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. - return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; - } + get layoutDoc() { return Doc.Layout(this.props.Document); } @observable _animPos: number[] | undefined = undefined; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d375405b9..bb9315ca3 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -77,11 +77,7 @@ export class DocumentContentsView extends React.Component(Docu public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost() { return this.props.renderDepth === 0; } - @computed get nativeWidth() { return this.Document.nativeWidth || 0; } - @computed get nativeHeight() { return this.Document.nativeHeight || 0; } + @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; } + @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; } @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } @action @@ -287,8 +287,9 @@ export class DocumentView extends DocComponent(Docu @undoBatch deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } - @undoBatch - static makeNativeViewClicked = async (doc: Doc): Promise => swapViews(doc, "layoutNative", "layoutCustom") + static makeNativeViewClicked = async (doc: Doc): Promise => { + undoBatch(() => swapViews(doc, "layoutNative", "layoutCustom"))(); + } static makeCustomViewClicked = async (doc: Doc, dataDoc: Opt) => { const batch = UndoManager.StartBatch("CustomViewClicked"); @@ -580,7 +581,7 @@ export class DocumentView extends DocComponent(Docu // the document containing the view layout information - will be the Document itself unless the Document has // a layout field. In that case, all layout information comes from there unless overriden by Document get layoutDoc(): Document { - return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document); + return Document(Doc.Layout(this.props.Document)); } // does Document set a layout prop diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index c5bf28d5b..fd6a475fb 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -37,7 +37,7 @@ export class FontIconBox extends DocComponent( } render() { let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); - let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc; + let referenceLayout = Doc.Layout(referenceDoc); return +
- - } - - - - - - - + } + + + + + + +
{this.renderEmbeddedInline()} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index c6f654c33..08cb66d5f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -645,7 +645,8 @@ export namespace Doc { @observable BrushedDoc: ObservableMap = new ObservableMap(); } - // returns the active layout document for 'doc'. + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; } export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } const manager = new DocData(); diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts new file mode 100644 index 000000000..8c3b62067 --- /dev/null +++ b/src/new_fields/documentSchemas.ts @@ -0,0 +1,51 @@ +import { makeInterface, createSchema, listSpec } from "./Schema"; +import { ScriptField } from "./ScriptField"; +import { Doc } from "./Doc"; + +export const documentSchema = createSchema({ + // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out + layoutKey: "string", // holds the field key for the field that actually holds the current lyoat + layoutCustom: Doc, // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey') + title: "string", // document title (can be on either data document or layout) + nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set + nativeHeight: "number", // " + width: "number", // width of document in its container's coordinate system + height: "number", // " + color: "string", // foreground color of document + backgroundColor: "string", // background color of document + opacity: "number", // opacity of document + //links: listSpec(Doc), // computed (readonly) list of links associated with this document + dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") + removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped + onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. + ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document + autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents + isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed + isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) + type: "string", // enumerated type of document + maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) + lockedPosition: "boolean", // whether the document can be spatially manipulated + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently + borderRounding: "string", // border radius rounding of document + searchFields: "string", // the search fields to display when this document matches a search in its metadata + heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) + showCaption: "string", // whether editable caption text is overlayed at the bottom of the document + showTitle: "string", // whether an editable title banner is displayed at tht top of the document + isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) +}); + +export const positionSchema = createSchema({ + zIndex: "number", + x: "number", + y: "number", + z: "number", +}); + +export type Document = makeInterface<[typeof documentSchema]>; +export const Document = makeInterface(documentSchema); + +export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +export const PositionDocument = makeInterface(documentSchema, positionSchema); -- cgit v1.2.3-70-g09d2 From 3b16a81e21942684d63f175b60629f85070715e1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 21 Oct 2019 23:55:19 -0400 Subject: got rid of CollectionBaseView --- src/client/documents/Documents.ts | 2 +- src/client/util/DictationManager.ts | 2 +- src/client/util/DropConverter.ts | 2 +- src/client/views/CollectionLinearView.tsx | 2 +- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionBaseView.scss | 26 --- .../views/collections/CollectionBaseView.tsx | 180 -------------------- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/CollectionView.scss | 26 +++ src/client/views/collections/CollectionView.tsx | 183 ++++++++++++++++----- .../views/collections/CollectionViewChromes.tsx | 2 +- .../views/collections/ParentDocumentSelector.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/ButtonBox.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 2 +- .../views/presentationview/PresElementBox.tsx | 4 +- src/client/views/search/SearchItem.tsx | 2 +- 20 files changed, 186 insertions(+), 266 deletions(-) delete mode 100644 src/client/views/collections/CollectionBaseView.scss delete mode 100644 src/client/views/collections/CollectionBaseView.tsx create mode 100644 src/client/views/collections/CollectionView.scss (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a400e68a3..b1406d5e1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -2,7 +2,7 @@ import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; import { HistogramOperation } from "../northstar/operations/HistogramOperation"; import { CollectionView } from "../views/collections/CollectionView"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { AudioBox } from "../views/nodes/AudioBox"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index ae991635f..6bbd3d0ed 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -6,7 +6,7 @@ import { DocumentType } from "../documents/DocumentTypes"; import { Doc, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs } from "../documents/Documents"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index eea3da1bc..6b53333d7 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,5 +1,5 @@ import { DragManager } from "./DragManager"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; import { ObjectField } from "../../new_fields/ObjectField"; diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 1f28ef35d..31a518a6c 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -8,7 +8,7 @@ import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from ' import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import "./CollectionLinearView.scss"; -import { CollectionViewType } from './collections/CollectionBaseView'; +import { CollectionViewType } from './collections/CollectionView'; import { CollectionSubView } from './collections/CollectionSubView'; import { DocumentView } from './nodes/DocumentView'; import { documentSchema } from '../../new_fields/documentSchemas'; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 55a61f098..26e70c5c7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -24,7 +24,7 @@ import { HistoryUtil } from '../util/History'; import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { CollectionLinearView } from './CollectionLinearView'; -import { CollectionBaseView, CollectionViewType } from './collections/CollectionBaseView'; +import { CollectionViewType, CollectionView } from './collections/CollectionView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; @@ -228,7 +228,7 @@ export class MainView extends React.Component { if (!state.nro) { DocServer.Control.makeReadOnly(); } - CollectionBaseView.SetSafeMode(true); + CollectionView.SetSafeMode(true); } else if (state.nro || state.nro === null || state.readonly === false) { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss deleted file mode 100644 index aff965469..000000000 --- a/src/client/views/collections/CollectionBaseView.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "../globalCssVariables"; - -#collectionBaseView { - border-width: 0; - border-color: $light-color-secondary; - border-style: solid; - border-radius: 0 0 $border-radius $border-radius; - box-sizing: border-box; - border-radius: inherit; - width: 100%; - height: 100%; - overflow: auto; -} - -#google-tags { - transition: all 0.5s ease 0s; - width: 30px; - height: 30px; - position: absolute; - bottom: 15px; - left: 15px; - border: 2px solid black; - border-radius: 50%; - padding: 3px; - background: white; -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx deleted file mode 100644 index 9f3d33e6f..000000000 --- a/src/client/views/collections/CollectionBaseView.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast } from '../../../new_fields/Doc'; -import { listSpec } from '../../../new_fields/Schema'; -import { BoolCast, Cast, FieldValue, PromiseValue, StrCast } from '../../../new_fields/Types'; -import { ImageField } from '../../../new_fields/URLField'; -import { DocumentManager } from '../../util/DocumentManager'; -import { SelectionManager } from '../../util/SelectionManager'; -import { ContextMenu } from '../ContextMenu'; -import { FieldViewProps } from '../nodes/FieldView'; -import './CollectionBaseView.scss'; - -export enum CollectionViewType { - Invalid, - Freeform, - Schema, - Docking, - Tree, - Stacking, - Masonry, - Pivot, - Linear, -} - -export namespace CollectionViewType { - - const stringMapping = new Map([ - ["invalid", CollectionViewType.Invalid], - ["freeform", CollectionViewType.Freeform], - ["schema", CollectionViewType.Schema], - ["docking", CollectionViewType.Docking], - ["tree", CollectionViewType.Tree], - ["stacking", CollectionViewType.Stacking], - ["masonry", CollectionViewType.Masonry], - ["pivot", CollectionViewType.Pivot], - ["linear", CollectionViewType.Linear] - ]); - - export const valueOf = (value: string) => { - return stringMapping.get(value.toLowerCase()); - }; - -} - -export interface CollectionRenderProps { - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; - active: () => boolean; - whenActiveChanged: (isActive: boolean) => void; -} - -export interface CollectionViewProps extends FieldViewProps { - onContextMenu?: (e: React.MouseEvent) => void; - children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null | (JSX.Element | null)[]; - className?: string; - contentRef?: React.Ref; -} - -@observer -export class CollectionBaseView extends React.Component { - @observable private static _safeMode = false; - static InSafeMode() { return this._safeMode; } - static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } - get collectionViewType(): CollectionViewType | undefined { - let Document = this.props.Document; - let viewField = Cast(Document.viewType, "number"); - if (CollectionBaseView._safeMode) { - if (viewField === CollectionViewType.Freeform) { - return CollectionViewType.Tree; - } - if (viewField === CollectionViewType.Invalid) { - return CollectionViewType.Freeform; - } - } - if (viewField !== undefined) { - return viewField; - } else { - return CollectionViewType.Invalid; - } - } - - active = (): boolean => { - var isSelected = this.props.isSelected(); - return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; - } - - //TODO should this be observable? - private _isChildActive = false; - whenActiveChanged = (isActive: boolean) => { - this._isChildActive = isActive; - this.props.whenActiveChanged(isActive); - } - - - @action.bound - addDocument(doc: Doc): boolean { - let targetDataDoc = Doc.GetProto(this.props.Document); - let targetField = this.props.fieldKey; - Doc.AddDocToList(targetDataDoc, targetField, doc); - let extension = Doc.fieldExtensionDoc(targetDataDoc, targetField); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field - extension && (extension.lastModified = new DateField(new Date(Date.now()))); - Doc.GetProto(doc).lastOpened = new DateField; - return true; - } - - @action.bound - removeDocument(doc: Doc): boolean { - let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); - docView && SelectionManager.DeselectDoc(docView); - //TODO This won't create the field if it doesn't already exist - let targetDataDoc = this.props.Document; - let targetField = this.props.fieldKey; - let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); - index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - - if (index !== -1) { - value.splice(index, 1); - - // SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems(); - return true; - } - return false; - } - - // this is called with the document that was dragged and the collection to move it into. - // if the target collection is the same as this collection, then the move will be allowed. - // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. - @action.bound - moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { - return true; - } - return this.removeDocument(doc) ? addDocument(doc) : false; - } - - showIsTagged = () => { - const children = DocListCast(this.props.Document.data); - const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); - const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); - if (allTagged) { - return ( - - ); - } - return (null); - } - - render() { - const props: CollectionRenderProps = { - addDocument: this.addDocument, - removeDocument: this.removeDocument, - moveDocument: this.moveDocument, - active: this.active, - whenActiveChanged: this.whenActiveChanged, - }; - const viewtype = this.collectionViewType; - return ( -
- {this.showIsTagged()} - {viewtype !== undefined ? this.props.children(viewtype, props) : (null)} -
- ); - } - -} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2cad41acb..47c355fc8 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -23,7 +23,7 @@ import { EditableView } from "../EditableView"; import { MainView } from '../MainView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { Templates } from '../Templates'; -import { CollectionViewType } from './CollectionBaseView'; +import { CollectionViewType } from './CollectionView'; import { CollectionSchemaPreview } from './CollectionSchemaView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss new file mode 100644 index 000000000..e4187e4d6 --- /dev/null +++ b/src/client/views/collections/CollectionView.scss @@ -0,0 +1,26 @@ +@import "../globalCssVariables"; + +.collectionView { + border-width: 0; + border-color: $light-color-secondary; + border-style: solid; + border-radius: 0 0 $border-radius $border-radius; + box-sizing: border-box; + border-radius: inherit; + width: 100%; + height: 100%; + overflow: auto; +} + +#google-tags { + transition: all 0.5s ease 0s; + width: 30px; + height: 30px; + position: absolute; + bottom: 15px; + left: 15px; + border: 2px solid black; + border-radius: 50%; + padding: 3px; + background: white; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f8eb28ade..8d5694bf0 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -5,12 +5,9 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo import { observer } from "mobx-react"; import * as React from 'react'; import { Id } from '../../../new_fields/FieldSymbols'; -import { StrCast, Cast } from '../../../new_fields/Types'; +import { StrCast, BoolCast, Cast } from '../../../new_fields/Types'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from '../ContextMenuItem'; -import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView'; import { CollectionDockingView } from "./CollectionDockingView"; import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; @@ -26,18 +23,75 @@ import { DocListCast } from '../../../new_fields/Doc'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app export const COLLECTION_BORDER_WIDTH = 2; - +import { DateField } from '../../../new_fields/DateField'; +import { Doc, } from '../../../new_fields/Doc'; +import { listSpec } from '../../../new_fields/Schema'; +import { DocumentManager } from '../../util/DocumentManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import './CollectionView.scss'; +import { FieldViewProps, FieldView } from '../nodes/FieldView'; library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); +export enum CollectionViewType { + Invalid, + Freeform, + Schema, + Docking, + Tree, + Stacking, + Masonry, + Pivot, + Linear, +} + +export namespace CollectionViewType { + const stringMapping = new Map([ + ["invalid", CollectionViewType.Invalid], + ["freeform", CollectionViewType.Freeform], + ["schema", CollectionViewType.Schema], + ["docking", CollectionViewType.Docking], + ["tree", CollectionViewType.Tree], + ["stacking", CollectionViewType.Stacking], + ["masonry", CollectionViewType.Masonry], + ["pivot", CollectionViewType.Pivot], + ["linear", CollectionViewType.Linear] + ]); + + export const valueOf = (value: string) => stringMapping.get(value.toLowerCase()); +} + +export interface CollectionRenderProps { + addDocument: (document: Doc) => boolean; + removeDocument: (document: Doc) => boolean; + moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + active: () => boolean; + whenActiveChanged: (isActive: boolean) => void; +} + @observer export class CollectionView extends React.Component { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _reactionDisposer: IReactionDisposer | undefined; + private _isChildActive = false; //TODO should this be observable? @observable private _isLightboxOpen = false; @observable private _curLightboxImg = 0; @observable private _collapsed = true; + @observable private static _safeMode = false; + public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } + + get collectionViewType(): CollectionViewType | undefined { + let viewField = Cast(this.props.Document.viewType, "number"); + if (CollectionView._safeMode) { + if (viewField === CollectionViewType.Freeform) { + return CollectionViewType.Tree; + } + if (viewField === CollectionViewType.Invalid) { + return CollectionViewType.Freeform; + } + } + return viewField === undefined ? CollectionViewType.Invalid : viewField; + } componentDidMount = () => { this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus), @@ -51,32 +105,73 @@ export class CollectionView extends React.Component { }); } - componentWillUnmount = () => { - this._reactionDisposer && this._reactionDisposer(); + componentWillUnmount = () => this._reactionDisposer && this._reactionDisposer(); + + // 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; + + whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; + + @action.bound + addDocument(doc: Doc): boolean { + let targetDataDoc = Doc.GetProto(this.props.Document); + Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + let extension = Doc.fieldExtensionDoc(targetDataDoc, this.props.fieldKey); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field + extension && (extension.lastModified = new DateField(new Date(Date.now()))); + Doc.GetProto(doc).lastOpened = new DateField; + return true; + } + + @action.bound + removeDocument(doc: Doc): boolean { + let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); + docView && SelectionManager.DeselectDoc(docView); + let value = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + + ContextMenu.Instance.clearItems(); + if (index !== -1) { + value.splice(index, 1); + return true; + } + return false; + } + + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. + @action.bound + moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { + return true; + } + return this.removeDocument(doc) ? addDocument(doc) : false; } - // bcz: Argh? What's the height of the collection chomes?? - chromeHeight = () => { - return (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0); + showIsTagged = () => { + const children = DocListCast(this.props.Document[this.props.fieldKey]); + const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); + const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); + return !allTagged ? (null) : ; } private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; + let props = { ...this.props, ...renderProps, chromeCollapsed: this._collapsed, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" }; switch (type) { - case CollectionViewType.Schema: return (); - // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - case CollectionViewType.Docking: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } - case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } - case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } - case CollectionViewType.Linear: { return (); } + case CollectionViewType.Schema: return (); + case CollectionViewType.Docking: return (); + case CollectionViewType.Tree: return (); + case CollectionViewType.Linear: { return (); } + case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } + case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } case CollectionViewType.Freeform: - default: - this.props.Document.freeformLayoutEngine = undefined; - return (); + default: { this.props.Document.freeformLayoutEngine = undefined; return (); } } - return (null); } @action @@ -87,22 +182,18 @@ export class CollectionView extends React.Component { private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - if (this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking) { - return [(null), this.SubViewHelper(type, renderProps)]; - } - return [ - , - this.SubViewHelper(type, renderProps) - ]; + let chrome = this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) : + ; + return [chrome, this.SubViewHelper(type, renderProps)]; } onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); - let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; + let subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" }); - if (CollectionBaseView.InSafeMode()) { + if (CollectionView._safeMode) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); @@ -126,7 +217,7 @@ export class CollectionView extends React.Component { !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); - let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; + let layoutItems = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); ContextMenu.Instance.addItem({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); @@ -142,15 +233,23 @@ export class CollectionView extends React.Component { onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); } - render() { - return (<> - - {this.SubView} - - + const props: CollectionRenderProps = { + addDocument: this.addDocument, + removeDocument: this.removeDocument, + moveDocument: this.moveDocument, + active: this.active, + whenActiveChanged: this.whenActiveChanged, + }; + return (
+ {this.showIsTagged()} + {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => Cast(d.data, ImageField) ? Cast(d.data, ImageField)!.url.href : ""))} - - ); +
); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index dd5e630e4..cfc6c2a3f 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -14,7 +14,7 @@ import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; import { DocLike } from "../MetadataEntryMenu"; -import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionViewType } from "./CollectionView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import * as Autosuggest from 'react-autosuggest'; diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 7f2913214..8b6fa330c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -7,7 +7,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; import { NumCast } from "../../../new_fields/Types"; -import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionViewType } from "./CollectionView"; import { DocumentButtonBar } from "../DocumentButtonBar"; import { DocumentManager } from "../../util/DocumentManager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0419bc3fa..d7350fe1a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -35,7 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { documentSchema, positionSchema } from "../../DocComponent"; +import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e0bf4a2f0..4f86eeb2b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -15,7 +15,7 @@ import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { CollectionViewType } from "../CollectionBaseView"; +import { CollectionViewType } from "../CollectionView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index ef194624a..efe2c7f2a 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -5,7 +5,7 @@ import { FieldViewProps, FieldView } from "../nodes/FieldView"; import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { undoBatch } from "../../util/UndoManager"; import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { SelectionManager } from "../../util/SelectionManager"; import { DocumentManager } from "../../util/DocumentManager"; diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 1531d825b..beb2b30fd 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -66,7 +66,8 @@ export class ButtonBox extends DocComponent(Butt @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData && e.target) { - this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments); + this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments.map((d, i) => + d.onDragStart ? de.data.draggedDocuments[i] : d)); e.stopPropagation(); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f741dae9a..05080b824 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -26,7 +26,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionViewType } from '../collections/CollectionBaseView'; +import { CollectionViewType } from '../collections/CollectionView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 7ec5d0471..cbb83b511 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -10,7 +10,7 @@ import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 5f758f496..7a1b4f9fb 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -12,7 +12,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnFalse } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; -import { CollectionViewType } from '../collections/CollectionBaseView'; +import { CollectionViewType } from '../collections/CollectionView'; import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; import { DocComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; @@ -51,7 +51,7 @@ export class PresElementBox extends DocComponent(P @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); } @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; } - @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc } + @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc; } @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); } /** diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 60307065a..f1d825aa0 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -15,7 +15,7 @@ import { LinkManager } from "../../util/LinkManager"; import { SearchUtil } from "../../util/SearchUtil"; import { Transform } from "../../util/Transform"; import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ContextMenu } from "../ContextMenu"; import { DocumentView } from "../nodes/DocumentView"; -- cgit v1.2.3-70-g09d2 From fd3d2b4ea1c8950c9ac9a15b053b285c8bdec96b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 22 Oct 2019 01:16:54 -0400 Subject: simplified marquee view & fixed scroll issue. --- .../views/collections/collectionFreeForm/MarqueeView.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 4f86eeb2b..44b6fe030 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -436,21 +436,17 @@ export class MarqueeView extends React.Component + return
; } render() { - let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; - return
e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}> -
e.currentTarget.scrollLeft = 0} > - {this._visible ? this.marqueeDiv : null} -
- {this.props.children} -
-
+ return
e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children}
; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 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 +++++++++++ src/client/views/DocumentDecorations.tsx | 57 ++++++++++++++------- src/client/views/InkSelectDecorations.scss | 5 ++ src/client/views/InkSelectDecorations.tsx | 59 ++++++++++++++++++++++ src/client/views/MainView.tsx | 2 + src/client/views/Touchable.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../collections/collectionFreeForm/MarqueeView.tsx | 18 +++++-- 8 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 src/client/views/InkSelectDecorations.scss create mode 100644 src/client/views/InkSelectDecorations.tsx (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') 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; + } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3d73f048d..90d6e1e8d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -25,6 +25,8 @@ import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); import { TooltipTextMenu } from '../util/TooltipTextMenu'; +import { InkingCanvas } from './InkingCanvas'; +import { StrokeData } from '../../new_fields/InkField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -166,23 +168,42 @@ 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.SelectedDocuments().reduce((bounds, documentView) => { - if (documentView.props.renderDepth === 0 || - Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) { - return bounds; + 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) + }; } - let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.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; + else { + let left = bounds.x; + let top = bounds.y; + let right = bounds.r; + let bottom = bounds.b; + docViewOrInk.forEach((value: StrokeData, 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, r: right, b: bottom + }; } - - var [sptX, sptY] = transform.transformPoint(0, 0); - let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.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; } @@ -559,7 +580,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } render() { var bounds = this.Bounds; - let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; + let seldoc = SelectionManager.AllSelected().length ? SelectionManager.AllSelected()[0] : undefined; if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } @@ -586,7 +607,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.SelectedDocuments().length > 1 ? 900 : 0, + zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
e.preventDefault()}>
e.preventDefault()}>
- + {(SelectionManager.SelectedDocuments.length && SelectionManager.SelectedDocuments()[0]) ? : (null)}
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss new file mode 100644 index 000000000..daff58fd6 --- /dev/null +++ b/src/client/views/InkSelectDecorations.scss @@ -0,0 +1,5 @@ +.inkSelectDecorations { + position: absolute; + border: black 1px solid; + z-index: 9001; +} \ No newline at end of file diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx new file mode 100644 index 000000000..a8eef3305 --- /dev/null +++ b/src/client/views/InkSelectDecorations.tsx @@ -0,0 +1,59 @@ +import React = require("react"); +import { Touchable } from "./Touchable"; +import { StrokeData } from "../../new_fields/InkField"; +import { observer } from "mobx-react"; +import { computed, observable, action, runInAction } from "mobx"; +import "./InkSelectDecorations.scss" + +@observer +export default class InkSelectDecorations extends Touchable { + static Instance: InkSelectDecorations; + + @observable private _selectedInkNodes: Map = new Map(); + + constructor(props: Readonly<{}>) { + super(props); + + InkSelectDecorations.Instance = this; + } + + @action + public SetSelected = (inkNodes: Map, keepOld: boolean = false) => { + if (!keepOld) { + this._selectedInkNodes = new Map(); + } + inkNodes.forEach((value: any, key: any) => { + runInAction(() => this._selectedInkNodes.set(key, value)); + }); + } + + @computed + get Bounds(): { x: number, y: number, b: number, r: number } { + let left = Number.MAX_VALUE; + let top = Number.MAX_VALUE; + let right = -Number.MAX_VALUE; + let bottom = -Number.MAX_VALUE; + this._selectedInkNodes.forEach((value: StrokeData, 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 }; + } + + render() { + let bounds = this.Bounds; + return ( +
+ +
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0ede0b770..41de32f1f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -37,6 +37,7 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; +import InkSelectDecorations from './InkSelectDecorations'; @observer export class MainView extends React.Component { @@ -512,6 +513,7 @@ export class MainView extends React.Component { + {this.mainContent} diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 4955129ba..26779b168 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { action } from 'mobx'; import { InteractionUtils } from '../util/InteractionUtils'; -export abstract class Touchable extends React.Component { +export abstract class Touchable extends React.Component { protected _touchDrag: boolean = false; protected prevPoints: Map = new Map(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 123941b03..c24e52aba 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -103,9 +103,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { added && this.updateCluster(newBox); return added; } - private selectDocuments = (docs: Doc[]) => { + private selectDocuments = (docs: 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)); } 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); } @@ -190,7 +191,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view let prevSelected = SelectionManager.SelectedDocuments(); - this.selectDocuments(eles); + this.selectDocuments(eles, []); let clusterDocs = SelectionManager.SelectedDocuments(); SelectionManager.DeselectAll(); prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 07db6354f..cd9ac7ecc 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -20,6 +20,7 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; +import InkSelectDecorations from "../../InkSelectDecorations"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -27,7 +28,7 @@ interface MarqueeViewProps { container: CollectionFreeFormView; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; - selectDocuments: (docs: Doc[]) => void; + selectDocuments: (docs: Doc[], ink: Map[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; @@ -190,13 +191,14 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document], []); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document], + this.ink ? [this.marqueeInkSelect(this.ink.inkData)] : []); } if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; @@ -350,7 +352,7 @@ export class MarqueeView extends React.Component } let newCollection = this.getCollection(selected); this.props.addDocument(newCollection); - this.props.selectDocuments([newCollection]); + this.props.selectDocuments([newCollection], []); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } @@ -422,9 +424,14 @@ export class MarqueeView extends React.Component 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 => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })), + 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, @@ -432,6 +439,7 @@ export class MarqueeView extends React.Component }); } }); + // InkSelectDecorations.Instance.SetSelected(idata); return idata; } -- 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/views/collections/collectionFreeForm/MarqueeView.tsx') 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/views/collections/collectionFreeForm/MarqueeView.tsx') 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 e86c44757770c097392fb17726041a4410c16e74 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sat, 16 Nov 2019 16:22:42 -0500 Subject: fixed some bugs with touch, text document are a little buggy? --- src/client/views/InkingCanvas.scss | 51 ----- src/client/views/InkingCanvas.tsx | 217 --------------------- .../views/collections/CollectionStaffView.tsx | 4 - .../collectionFreeForm/CollectionFreeFormView.tsx | 9 +- .../collections/collectionFreeForm/MarqueeView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 7 +- 6 files changed, 9 insertions(+), 280 deletions(-) delete mode 100644 src/client/views/InkingCanvas.scss delete mode 100644 src/client/views/InkingCanvas.tsx (limited to 'src/client/views/collections/collectionFreeForm/MarqueeView.tsx') diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss deleted file mode 100644 index 8f32652ed..000000000 --- a/src/client/views/InkingCanvas.scss +++ /dev/null @@ -1,51 +0,0 @@ -@import "globalCssVariables"; - -.inkingCanvas { - // opacity: 0.99; - touch-action: none; - - .jsx-parser { - position: absolute; - width: 100%; - height: 100%; - background: inherit; - //z-index: -1; // allows annotations to appear on videos when screen is full-size & ... - } -} - -.inkingCanvas-paths-ink, -.inkingCanvas-paths-markers, -.inkingCanvas-noSelect, -.inkingCanvas-canSelect { - position: absolute; - top: 0; - left: 0; - width: 8192px; - height: 8192px; - cursor: "crosshair"; - pointer-events: all; -} - -.inkingCanvas-canSelect, -.inkingCanvas-noSelect { - top: -50000px; - left: -50000px; - width: 100000px; - height: 100000px; -} - -.inkingCanvas-noSelect { - pointer-events: none; - cursor: "crosshair"; -} - -.inkingCanvas-paths-ink, -.inkingCanvas-paths-markers { - pointer-events: none; - z-index: 10000; // overlays ink on top of everything - cursor: "arrow"; -} - -.inkingCanvas-paths-markers { - mix-blend-mode: multiply; -} \ No newline at end of file diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx deleted file mode 100644 index e5253c377..000000000 --- a/src/client/views/InkingCanvas.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { action, computed, trace, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Utils } from "../../Utils"; -import { Transform } from "../util/Transform"; -import "./InkingCanvas.scss"; -import { InkingControl } from "./InkingControl"; -import { InkingStroke } from "./InkingStroke"; -import React = require("react"); -import { UndoManager } from "../util/UndoManager"; -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"; -import { InteractionUtils } from "../util/InteractionUtils"; - -interface InkCanvasProps { - getScreenTransform: () => Transform; - AnnotationDocument: Doc; - Document: Doc; - inkFieldKey: string; - children: () => JSX.Element[]; -} - -@observer -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 _currentStrokeId: string = ""; - 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: 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), - right: Math.max(bounds.right, val.x), bottom: Math.max(bounds.bottom, val.y) - }) - , { left: Number.MAX_VALUE, top: Number.MAX_VALUE, right: -Number.MAX_VALUE, bottom: -Number.MAX_VALUE }); - } - - componentDidMount() { - PromiseValue(Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField)).then(ink => runInAction(() => { - if (ink) { - let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) => - strokeData.pathData.reduce(([mix, max, miy, may], p) => - [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)], - [mix, max, miy, may]), - [Number.MAX_VALUE, Number.MIN_VALUE, Number.MAX_VALUE, Number.MIN_VALUE]); - this.inkMidX = (bounds[0] + bounds[1]) / 2; - this.inkMidY = (bounds[2] + bounds[3]) / 2; - } - })); - } - - @computed - 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) { - this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value); - } - - @action - onPointerDown = (e: React.PointerEvent): void => { - if (e.button !== 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) { - return; - } - - document.addEventListener("pointermove", this.onPointerMove, true); - document.addEventListener("pointerup", this.onPointerUp, true); - e.stopPropagation(); - e.preventDefault(); - - this.previousState = new Map(this.inkData); - - if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - // start the new line, saves a uuid to represent the field of the stroke - this._currentStrokeId = Utils.GenerateGuid(); - const data = this.inkData; - data.set(this._currentStrokeId, { - pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)], - color: InkingControl.Instance.selectedColor, - width: InkingControl.Instance.selectedWidth, - tool: InkingControl.Instance.selectedTool, - displayTimecode: NumCast(this.props.Document.currentTimecode, -1), - creationTime: new Date().getTime() - }); - this.inkData = data; - } - } - - @action - handle1PointerMove = (e: TouchEvent) => { - e.stopPropagation(); - e.preventDefault(); - let pointer = e.targetTouches.item(0); - if (pointer) { - this.handleMove(pointer.clientX, pointer.clientY); - } - } - - handle2PointersMove = () => { } - - @action - onPointerUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onPointerMove, true); - document.removeEventListener("pointerup", this.onPointerUp, true); - let coord = this.relativeCoordinatesForEvent(e.clientX, e.clientY); - if (Math.abs(coord.x - this.inkMidX) > 500 || Math.abs(coord.y - this.inkMidY) > 500) { - this.inkMidX = coord.x; - this.inkMidY = coord.y; - } - e.stopPropagation(); - e.preventDefault(); - - const batch = UndoManager.StartBatch("One ink stroke"); - const oldState = this.previousState || new Map; - this.previousState = undefined; - const newState = new Map(this.inkData); - UndoManager.AddEvent({ - undo: () => this.inkData = oldState, - redo: () => this.inkData = newState - }); - batch.end(); - } - - handleMove = (x: number, y: number) => { - if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - let data = this.inkData; // add points to new line as it is being drawn - let strokeData = data.get(this._currentStrokeId); - if (strokeData) { - strokeData.pathData.push(this.relativeCoordinatesForEvent(x, y)); - data.set(this._currentStrokeId, strokeData); - } - this.inkData = data; - } - } - - @action - onPointerMove = (e: PointerEvent): void => { - if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) { - return; - } - e.stopPropagation(); - e.preventDefault(); - this.handleMove(e.clientX, e.clientY); - } - - relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => { - let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey); - return { x, y }; - } - - @action - removeLine = (id: string): void => { - if (!this.previousState) { - this.previousState = new Map(this.inkData); - document.addEventListener("pointermove", this.onPointerMove, true); - document.addEventListener("pointerup", this.onPointerUp, true); - } - let data = this.inkData; - data.delete(id); - this.inkData = data; - } - - // @computed - // get drawnPaths() { - // let curTimecode = NumCast(this.props.Document.currentTimecode, -1); - // let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { - // if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) { - // paths.push(); - // } - // return paths; - // }, [] as JSX.Element[]); - // let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter); - // let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter); - // return [!penPaths.length ? (null) : - // - // {penPaths} - // , - // !markerPaths.length ? (null) : - // - // {markerPaths} - // ]; - // } - - render() { - let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect"; - let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser || - InkingControl.Instance.selectedTool === InkTool.Scrubber ? "pointer" : "default") : undefined; - return ( -
-
- {this.props.children()} - {/* {this.drawnPaths} */} -
- ); - } -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx index 5b1224b96..eea05ea61 100644 --- a/src/client/views/collections/CollectionStaffView.tsx +++ b/src/client/views/collections/CollectionStaffView.tsx @@ -1,5 +1,4 @@ import { CollectionSubView } from "./CollectionSubView"; -import { InkingCanvas } from "../InkingCanvas"; import { Transform } from "../../util/Transform"; import React = require("react") import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx"; @@ -54,9 +53,6 @@ export class CollectionStaffView extends CollectionSubView(doc => doc) { render() { return (
- - {() => []} - {this.staves} {this.addStaffButton}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 61d097c62..c7806a097 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -24,7 +24,6 @@ import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"; import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; -import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; @@ -298,8 +297,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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); + UndoManager.RunInBatch(() => { + 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); + }, "addink"); this._points = []; } @@ -385,7 +386,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { handle1PointerMove = (e: TouchEvent) => { // panning a workspace - if (!e.cancelBubble && this.props.active()) { + if (!e.cancelBubble && this.props.active() && !SelectionManager.GetIsDragging()) { let pt = e.targetTouches.item(0); if (pt) { this.pan(pt); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b5f6f095e..1066f4f8d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -13,7 +13,6 @@ import { Docs } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; -import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; import { CollectionViewType } from "../CollectionView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5b3eea280..6ffa62fe5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,6 +41,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { InteractionUtils } from '../../util/InteractionUtils'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -138,7 +139,7 @@ export class DocumentView extends DocComponent(Docu (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { e.stopPropagation(); let preventDefault = true; - if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click + if (this._doubleTap && this.props.renderDepth && !this.onClickHandler ?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click let fullScreenAlias = Doc.MakeAlias(this.props.Document); if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) { fullScreenAlias.layoutKey = "layoutCustom"; @@ -216,7 +217,7 @@ export class DocumentView extends DocComponent(Docu } else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || 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) { + if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCH))) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); @@ -354,7 +355,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action setCustomView = (custom: boolean): void => { - if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { + if (this.props.ContainingCollectionView ?.props.DataDoc || this.props.ContainingCollectionView ?.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); } else { custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); -- cgit v1.2.3-70-g09d2