From 43b09cb2aded66e67006d99e544d90637a62c048 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 21:54:10 -0400 Subject: improved styling and colors of color picker on richtexteditor --- src/new_fields/SchemaHeaderField.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/new_fields') diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 92d0aec9a..42a8485ac 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -41,6 +41,17 @@ export const PastelSchemaPalette = new Map([ export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; +export const DarkPastelSchemaPalette = new Map([ + ["pink2", "#c932b0"], + ["purple4", "#913ad6"], + ["bluegreen1", "#3978ed"], + ["bluegreen7", "#2adb3e"], + ["bluegreen5", "#21b0eb"], + ["yellow4", "#edcc0c"], + ["red2", "#eb3636"], + ["orange1", "#f2740f"], +]); + @scriptingGlobal @Deserializable("schemaheader") export class SchemaHeaderField extends ObjectField { -- cgit v1.2.3-70-g09d2 From c7c18eeea36b35ee9172a120352af84fe21f267b Mon Sep 17 00:00:00 2001 From: yipstanley 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/new_fields') 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 0b19ecd4e65c30154a744085eb80b3b375acfe3e Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 10 Nov 2019 17:01:45 -0500 Subject: some changes --- src/client/documents/Documents.ts | 1 + src/client/views/nodes/ButtonBox.scss | 13 ++++++++----- src/client/views/nodes/ButtonBox.tsx | 2 +- src/new_fields/documentSchemas.ts | 2 +- src/server/authentication/models/current_user_utils.ts | 8 ++++---- 5 files changed, 15 insertions(+), 11 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a074d267e..a1bdba9f1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -110,6 +110,7 @@ export interface DocumentOptions { 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; + color?: string; // [key: string]: Opt; } diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss index e8a3d1479..7c3825978 100644 --- a/src/client/views/nodes/ButtonBox.scss +++ b/src/client/views/nodes/ButtonBox.scss @@ -3,7 +3,7 @@ height: 100%; pointer-events: all; border-radius: inherit; - display:flex; + display: flex; flex-direction: column; } @@ -15,19 +15,22 @@ display: table; overflow: hidden; text-overflow: ellipsis; + letter-spacing: 2px; + text-transform: uppercase; } + .buttonBox-mainButtonCenter { height: 100%; - display:table-cell; + display: table-cell; vertical-align: middle; } .buttonBox-params { - display:flex; - flex-direction: row; + display: flex; + flex-direction: row; } .buttonBox-missingParam { - width:100%; + width: 100%; background: lightgray; } \ No newline at end of file diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index beb2b30fd..659ba154a 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -79,7 +79,7 @@ export class ButtonBox extends DocComponent(Butt return (
-
+
{(this.Document.text || this.Document.title)}
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 0b28561bf..5bac56a01 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -43,7 +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" + strokeWidth: "number", }); export const positionSchema = createSchema({ diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 56ea5bfe1..3f2960d16 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -83,7 +83,7 @@ export class CurrentUserUtils { }); return Docs.Create.ButtonDocument({ - width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Tools", targetContainer: sidebarContainer, + width: 35, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Tools", fontSize: 10, targetContainer: sidebarContainer, sourcePanel: Docs.Create.StackingDocument([dragCreators, color], { width: 500, height: 800, lockedPosition: true, chromeStatus: "disabled", title: "tools stack" }), @@ -108,19 +108,19 @@ export class CurrentUserUtils { }); return Docs.Create.ButtonDocument({ - width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library", + width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Library", fontSize: 10, sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], { title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true }), targetContainer: sidebarContainer, - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel") + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") }); } // setup the Search button which will display the search panel. static setupSearchPanel(sidebarContainer: Doc) { return Docs.Create.ButtonDocument({ - width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search", + width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Search", fontSize: 10, sourcePanel: Docs.Create.QueryDocument({ title: "search stack", ignoreClick: true }), -- cgit v1.2.3-70-g09d2 From e1930a62304db60d9eca542148995c5d03b79aea Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 11:44:52 -0500 Subject: fixed search on client/server to work with richTextFields. got rid of 'text' field on richtextfield extensions. --- .vscode/settings.json | 3 ++- src/client/views/DocComponent.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 ++++---- src/client/views/nodes/FormattedTextBox.tsx | 18 ++---------------- src/client/views/search/FilterBox.tsx | 2 +- src/new_fields/Doc.ts | 5 +++-- src/new_fields/RichTextField.ts | 10 +++++++--- src/new_fields/RichTextUtils.ts | 2 +- src/server/index.ts | 1 + 9 files changed, 22 insertions(+), 29 deletions(-) (limited to 'src/new_fields') diff --git a/.vscode/settings.json b/.vscode/settings.json index 5df697fee..c999af8b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "editor.detectIndentation": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, - "search.usePCRE2": true + "search.usePCRE2": true, + "typescript.tsdk": "node_modules\\typescript\\lib" } \ No newline at end of file diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index ae4b7cf3a..92c947fe6 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -34,7 +34,7 @@ export function DocExtendableComponent

(schemaCt //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { return schemaCtor(this.props.Document); } @computed get layoutDoc() { return Doc.Layout(this.props.Document); } - @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } + @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4fbb0516f..7e81cd673 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -138,7 +138,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 || !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"; @@ -352,9 +352,9 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action setCustomView = (custom: boolean): void => { - if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { - Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc); - } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized + 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); } } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 24d6f2509..65a51b357 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -85,7 +85,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F private _searchReactionDisposer?: Lambda; private _scrollToRegionReactionDisposer: Opt; private _reactionDisposer: Opt; - private _textReactionDisposer: Opt; private _heightReactionDisposer: Opt; private _rulesReactionDisposer: Opt; private _proxyReactionDisposer: Opt; @@ -191,9 +190,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F let tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); this._applyingChange = true; - this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); - this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); + this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n")); this._applyingChange = false; this.updateTitle(); this.tryUpdateHeight(); @@ -250,7 +248,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F // 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); + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); e.stopPropagation(); } // apply as template when dragging with Meta @@ -510,17 +508,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F () => this.tryUpdateHeight() ); - this._textReactionDisposer = reaction( - () => this.extensionDoc, - () => { - if (this.extensionDoc && (this.dataDoc.text || this.dataDoc.lastModified)) { - this.extensionDoc.text = this.dataDoc.text; - this.extensionDoc.lastModified = DateCast(this.dataDoc.lastModified)[Copy](); - this.dataDoc.text = undefined; - this.dataDoc.lastModified = undefined; - } - }, { fireImmediately: true }); - this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); @@ -834,7 +821,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); - this._textReactionDisposer && this._textReactionDisposer(); this._pushReactionDisposer && this._pushReactionDisposer(); this._pullReactionDisposer && this._pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index b841190d4..6e7e4ee1b 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -33,7 +33,7 @@ export enum Keys { export class FilterBox extends React.Component { static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB]; + public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB, DocumentType.TEMPLATE]; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 3bf1129b5..4531fd5e0 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -473,8 +473,9 @@ export namespace Doc { export function CreateDocumentExtensionForField(doc: Doc, fieldKey: string) { let docExtensionForField = new Doc(doc[Id] + fieldKey, true); - docExtensionForField.title = fieldKey + ".ext"; + docExtensionForField.title = fieldKey + ".ext"; // courtesy field--- shouldn't be needed except maybe for debugging docExtensionForField.extendsDoc = doc; // this is used by search to map field matches on the extension doc back to the document it extends. + docExtensionForField.extendsField = fieldKey; // this can be used by search to map matches on the extension doc back to the field that was extended. docExtensionForField.type = DocumentType.EXTENSION; let proto: Doc | undefined = doc; while (proto && !Doc.IsPrototype(proto) && proto.proto) { @@ -568,7 +569,7 @@ export namespace Doc { let layoutCustomLayout = Doc.MakeDelegate(templateDoc); titleTarget && (Doc.GetProto(target).title = titleTarget); - target.type = DocumentType.TEMPLATE; + Doc.GetProto(target).type = DocumentType.TEMPLATE; target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy](); Doc.GetProto(target)[targetKey] = layoutCustomLayout; diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index d2f76c969..fd5459876 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -10,17 +10,21 @@ export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; - constructor(data: string) { + @serializable(true) + readonly Text: string; + + constructor(data: string, text: string = "") { super(); this.Data = data; + this.Text = text; } [Copy]() { - return new RichTextField(this.Data); + return new RichTextField(this.Data, this.Text); } [ToScriptString]() { - return `new RichTextField("${this.Data}")`; + return `new RichTextField("${this.Data}", "${this.Text}")`; } } \ No newline at end of file diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 601939ed2..c2cca859c 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -52,7 +52,7 @@ export namespace RichTextUtils { }; export const Synthesize = (plainText: string, oldState?: RichTextField) => { - return new RichTextField(ToProsemirrorState(plainText, oldState)); + return new RichTextField(ToProsemirrorState(plainText, oldState), plainText); }; export const ToPlainText = (state: EditorState) => { diff --git a/src/server/index.ts b/src/server/index.ts index 1595781dc..ddd909479 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1163,6 +1163,7 @@ const suffixMap: { [type: string]: (string | [string, string | ((json: any) => a "pdf": ["_t", "url"], "audio": ["_t", "url"], "web": ["_t", "url"], + "RichTextField": ["_t", value => value.Text], "date": ["_d", value => new Date(value.date).toISOString()], "proxy": ["_i", "fieldId"], "list": ["_l", list => { -- cgit v1.2.3-70-g09d2 From e87b4b99323875afce2d9847f3bddd4196b85b81 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 14:48:57 -0500 Subject: added a lockedTransform field to lock pan/zoom. fixed text scrollintoview to scroll only when necessary. --- src/client/documents/Documents.ts | 3 ++- src/client/util/RichTextSchema.tsx | 1 + .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 7 +++++++ src/client/views/nodes/FormattedTextBox.tsx | 4 +++- src/new_fields/documentSchemas.ts | 3 ++- 6 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ba9f87025..1a9d67d83 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -76,7 +76,8 @@ export interface DocumentOptions { viewType?: number; backgroundColor?: string; ignoreClick?: boolean; - lockedPosition?: boolean; + lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged + lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed opacity?: number; defaultBackgroundColor?: string; dropAction?: dropActionType; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1004cb3d4..0d1ae3841 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -679,6 +679,7 @@ export class DashDocView { bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} + dontRegisterView={true} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} ContentScaling={this.contentScaling} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6e0f75bc1..0c5f4ec80 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -333,7 +333,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return; + if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -355,7 +355,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number, panType: string = "None") { - if (!this.Document.lockedPosition || this.Document.inOverlay) { + if (!this.Document.lockedTransform || this.Document.inOverlay) { this.Document.panTransformType = panType; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7e81cd673..98c610c68 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -372,6 +372,12 @@ export class DocumentView extends DocComponent(Docu this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; } + @undoBatch + @action + toggleLockTransform = (): void => { + this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true; + } + listen = async () => { Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, @@ -444,6 +450,7 @@ export class DocumentView extends DocComponent(Docu 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: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8b1e65663..015a21fd2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -774,7 +774,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; let r1 = refNode && refNode.getBoundingClientRect(); let r3 = self._ref.current!.getBoundingClientRect(); - r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + if (r1.top < r3.top || r1.top > r3.bottom) { + r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + } return true; }, dispatchTransaction: this.dispatchTransaction, diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e2730914f..fa47374f1 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -31,7 +31,8 @@ export const documentSchema = createSchema({ summarizedDocs: listSpec(Doc), // documents that are summarized by this document (and which will typically be opened by clicking this document) maximizedDocs: listSpec(Doc), // documents to maximize when clicking this document (generally this document will be an icon) 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 + lockedPosition: "boolean", // whether the document can be moved (dragged) + lockedTransform: "boolean", // whether the document can be panned/zoomed 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 -- cgit v1.2.3-70-g09d2 From d9eb5155734241e5c54353fe53168051fbd32c84 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sat, 16 Nov 2019 15:23:20 -0500 Subject: small change to document schema (font size is not a string_ --- src/new_fields/documentSchemas.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/new_fields') diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index fb650eefd..7592cdaa3 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -45,6 +45,7 @@ export const documentSchema = createSchema({ 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", + fontSize: "string" }); export const positionSchema = createSchema({ -- cgit v1.2.3-70-g09d2 From 6ecbfba52bad2dd64e07472b5f4ef06f95b5544b Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 18 Nov 2019 15:08:05 -0500 Subject: changing documentView to ignore transforms -- must be applied by view boxes --- src/client/documents/Documents.ts | 2 +- src/client/views/CollectionLinearView.scss | 6 +++-- src/client/views/CollectionLinearView.tsx | 29 +++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 9 +++---- .../collectionFreeForm/MarqueeView.scss | 1 + src/client/views/nodes/ColorBox.tsx | 4 ++- src/client/views/nodes/DocumentView.tsx | 31 ++++++++++++---------- src/client/views/nodes/FontIconBox.scss | 6 ++--- src/client/views/nodes/FontIconBox.tsx | 7 +++-- src/client/views/nodes/ImageBox.tsx | 7 +++-- src/client/views/nodes/PDFBox.tsx | 7 +---- src/client/views/nodes/VideoBox.tsx | 6 +++-- src/client/views/pdf/PDFViewer.scss | 1 + src/client/views/pdf/PDFViewer.tsx | 8 ++++-- src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 14 +++++----- 16 files changed, 74 insertions(+), 66 deletions(-) (limited to 'src/new_fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 268a14aca..3c88173cd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,7 +98,7 @@ export interface DocumentOptions { autoHeight?: boolean; removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document dbDoc?: Doc; - unchecked?: ScriptField; // returns whether a check box is unchecked + ischecked?: ScriptField; // returns whether a font icon box is checked activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script diff --git a/src/client/views/CollectionLinearView.scss b/src/client/views/CollectionLinearView.scss index 4423a7020..81210d7ae 100644 --- a/src/client/views/CollectionLinearView.scss +++ b/src/client/views/CollectionLinearView.scss @@ -17,6 +17,7 @@ height: 18px; margin-top:auto; margin-bottom:auto; + margin-right: 3px; cursor: pointer; transition: transform 0.2s; } @@ -51,8 +52,9 @@ .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable { position:relative; - margin-top: auto; - margin-bottom: auto; + margin:auto; + margin-left: 3px; + transform-origin: center 80%; } .collectionLinearView-docBtn-scalable:hover { transform: scale(1.15); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 7c6d33d36..f718735a8 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -22,18 +22,17 @@ const LinearDocument = makeInterface(documentSchema); export class CollectionLinearView extends CollectionSubView(LinearDocument) { @observable public addMenuToggle = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; - private _heightDisposer?: IReactionDisposer; - private _spacing = 20; + private _widthDisposer?: IReactionDisposer; componentWillUnmount() { this._dropDisposer && this._dropDisposer(); - this._heightDisposer && this._heightDisposer(); + this._widthDisposer && this._widthDisposer(); } 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(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0), - () => this.props.Document.width = 18 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), + this._widthDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0), + () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), { fireImmediately: true } ); } @@ -52,6 +51,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { let { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current); return new Transform(-translateX, -translateY, 1 / scale); } + render() { let guid = Utils.GenerateGuid(); return

@@ -60,19 +60,16 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} /> -
+
{this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => { let nested = pair.layout.viewType === CollectionViewType.Linear; let dref = React.createRef(); let nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension()); - let scalingContent = nested ? 1 : this.dimension() / (this._spacing + nativeWidth); - let scalingBox = nested ? 1 : this.dimension() / nativeWidth; - let deltaSize = nativeWidth * scalingBox - nativeWidth * scalingContent; + let deltaSize = nativeWidth * .15 / 2; return
scalingContent} // ugh - need to get rid of this inline function to avoid recomputing - PanelWidth={() => nested ? pair.layout[WidthSym]() : this.dimension()} - PanelHeight={() => nested ? pair.layout[HeightSym]() : this.dimension()} + ContentScaling={returnOne} + PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing + PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()} renderDepth={this.props.renderDepth + 1} focus={emptyFunction} backgroundColor={returnEmptyString} @@ -101,8 +98,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
; })} - {/*
  • */} -
    ; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 10aa93c36..a1559e049 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; @@ -852,6 +852,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } render() { + trace(); // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) this.Document.fitX = this.contentBounds && this.contentBounds.x; this.Document.fitY = this.contentBounds && this.contentBounds.y; @@ -867,11 +868,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> - {!this.extensionDoc ? (null) : - // - this.childViews() - // - } + {!this.extensionDoc ? (null) : this.childViews()} {this.currentStroke} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 04f6ec2ad..53b07318f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -8,6 +8,7 @@ } .marqueeView { overflow: hidden; + pointer-events: all; } .marqueeView:focus-within { diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index fda6d64f4..40674b034 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -38,7 +38,9 @@ export class ColorBox extends DocExtendableComponent e.button === 0 && !e.ctrlKey && e.stopPropagation()}> + onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} + style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} > +
    ; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fc2bc5169..d9e1f2c6b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -95,7 +95,7 @@ export class DocumentView extends DocComponent(Docu @computed get topMost() { return this.props.renderDepth === 0; } @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; } @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; } - @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } + @computed get onClickHandler() { trace(); console.log("this.props.doc = " + this.props.Document.title); return this.props.onClick ? this.props.onClick : this.Document.onClick; } @action componentDidMount() { @@ -537,8 +537,10 @@ export class DocumentView extends DocComponent(Docu return (showTitle ? 25 : 0) + 1; } + @computed get finalLayoutKey() { return this.props.layoutKey || "layout" } childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); @computed get contents() { + trace(); return ((Docu isSelected={this.isSelected} select={this.select} onClick={this.onClickHandler} - layoutKey={this.props.layoutKey || "layout"} + layoutKey={this.finalLayoutKey} DataDoc={this.props.DataDoc} />); } linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); @@ -582,16 +584,19 @@ export class DocumentView extends DocComponent(Docu } @computed get innards() { + trace(); const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle"); const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; const searchHighlight = (!this.Document.searchFields ? (null) : -
    +
    + {/* style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> */} {this.Document.searchFields}
    ); const captionView = (!showCaption ? (null) : -
    +
    + {/* style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}> */} (Docu
    ); const titleView = (!showTitle ? (null) :
    @@ -613,7 +618,7 @@ export class DocumentView extends DocComponent(Docu
    ); return <> {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) => -
    +
    Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} />
    )} {!showTitle && !showCaption ? @@ -639,7 +644,6 @@ export class DocumentView extends DocComponent(Docu render() { if (!this.props.Document) return (null); trace(); - const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined; const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; const colorSet = this.setsLayoutProp("backgroundColor"); @@ -648,17 +652,16 @@ export class DocumentView extends DocComponent(Docu this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) : ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); - const nativeWidth = this.layoutDoc.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.layoutDoc.ignoreAspect ? `${this.nativeWidth}px` : "100%"; - const nativeHeight = this.layoutDoc.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding; - const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + const localScale = fullDegree; - let animheight = animDims ? animDims[1] : nativeHeight; - let animwidth = animDims ? animDims[0] : nativeWidth; + const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined; + let animheight = animDims ? animDims[1] : "100%"; + let animwidth = animDims ? animDims[0] : "100%"; const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; - const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"]; + const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear; return
    (Docu background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor, width: animwidth, height: animheight, - transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`, + //transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`, opacity: this.Document.opacity }} > {this.innards} diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 75d093fcb..905601ce3 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -5,9 +5,9 @@ border-radius: inherit; background: black; border-radius: 100%; + transform-origin: top left; svg { - margin:18%; - width:65% !important; - height:65%; + width:95% !important; + height:95%; } } diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 83ecc4657..9a5de836f 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -39,8 +39,11 @@ export class FontIconBox extends DocComponent( let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); let referenceLayout = Doc.Layout(referenceDoc); return ; } } \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 212c99f9d..2a81c3577 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -268,6 +268,7 @@ export class ImageBox extends DocAnnotatableComponent); } + contentFunc = () => [this.content]; render() { - return (
    + return (
    - {() => [this.content]} + {this.contentFunc}
    ); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 8e0515f8a..ecbe2d309 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -205,14 +205,9 @@ export class PDFBox extends DocAnnotatableComponent } @computed get renderPdfView() { - trace(); const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); - return
    { + return
    { let hit = document.elementFromPoint(e.clientX, e.clientY); if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation e.button === 0 && e.stopPropagation(); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 53baea4ae..dd6e60c51 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -333,8 +333,10 @@ export class VideoBox extends DocAnnotatableComponent [this.youtubeVideoId ? this.youtubeContent : this.content]; render() { - return (
    + return (
    - {() => [this.youtubeVideoId ? this.youtubeContent : this.content]} + {this.contentFunc} {this.uIButtons}
    ); diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index f6fedf3da..8332501f4 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -6,6 +6,7 @@ position: absolute; overflow-y: auto; overflow-x: hidden; + transform-origin: top left; // .canvasWrapper { // transform: scale(0.75); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0cb671156..38e29b55d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -636,7 +636,7 @@ export class PDFViewer extends DocAnnotatableComponent
    @@ -660,7 +660,11 @@ export class PDFViewer extends DocAnnotatableComponent +
    {this.pdfViewerDiv} {this.annotationLayer} {this.standinViews} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 4531fd5e0..bae7f6a91 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -657,7 +657,7 @@ export namespace Doc { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)); } export function IsBrushedDegree(doc: Doc) { - return brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : brushManager.BrushedDoc.has(doc) ? 1 : 0; + return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0; } export function BrushDoc(doc: Doc) { brushManager.BrushedDoc.set(doc, true); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index a1f1294e6..5b9bba47d 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -46,7 +46,7 @@ export class CurrentUserUtils { let notes = CurrentUserUtils.setupNoteTypes(doc); doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); doc.activePen = doc; - let docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + let docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' }, { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] }, { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' }, @@ -55,16 +55,16 @@ export class CurrentUserUtils { { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, - { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, - { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, - { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, - { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc }, - { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this) && this.activePen.pen !== undefined`, backgroundColor: "white", activePen: doc }, + { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, + { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc }, + { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen, + ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, })); } -- cgit v1.2.3-70-g09d2 From 63129c244fc2b9a5c60e6a94b864895641b86f57 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 20 Nov 2019 16:59:40 -0500 Subject: lots of changes to make rendering more efficient (fewer mobx invalidations). made selection and collecitonviews use an observableMap that works now. --- src/client/util/SelectionManager.ts | 41 ++-- src/client/views/DocComponent.tsx | 14 +- src/client/views/DocumentButtonBar.scss | 106 +++------ src/client/views/DocumentButtonBar.tsx | 264 ++++++++------------- src/client/views/Main.scss | 6 +- src/client/views/MainView.scss | 1 - src/client/views/TemplateMenu.scss | 50 ++++ src/client/views/TemplateMenu.tsx | 4 +- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionSchemaView.tsx | 14 +- .../views/collections/CollectionTreeView.tsx | 6 +- src/client/views/collections/CollectionView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 87 ++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- .../views/nodes/ContentFittingDocumentView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 27 ++- src/client/views/nodes/FieldView.tsx | 4 +- src/client/views/nodes/FormattedTextBox.tsx | 10 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 7 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 14 +- src/new_fields/Doc.ts | 16 +- 25 files changed, 326 insertions(+), 364 deletions(-) create mode 100644 src/client/views/TemplateMenu.scss (limited to 'src/new_fields') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index ca61f9014..e01216e0f 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,45 +1,44 @@ -import { observable, action, runInAction, IReactionDisposer, reaction, autorun } from "mobx"; -import { Doc, Opt } from "../../new_fields/Doc"; +import { observable, action, runInAction, ObservableMap } from "mobx"; +import { Doc } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { NumCast, StrCast } from "../../new_fields/Types"; +import { computedFn } from "mobx-utils"; export namespace SelectionManager { class Manager { @observable IsDragging: boolean = false; - @observable SelectedDocuments: Array = []; + SelectedDocuments: ObservableMap = new ObservableMap(); @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it - if (manager.SelectedDocuments.indexOf(docView) === -1) { + if (!manager.SelectedDocuments.get(docView)) { if (!ctrlPressed) { this.DeselectAll(); } - manager.SelectedDocuments.push(docView); + manager.SelectedDocuments.set(docView, true); // console.log(manager.SelectedDocuments); docView.props.whenActiveChanged(true); - } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { - manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false)); - manager.SelectedDocuments = [docView]; + } else if (!ctrlPressed && Array.from(manager.SelectedDocuments.entries()).length > 1) { + Array.from(manager.SelectedDocuments.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false)); + manager.SelectedDocuments.clear(); + manager.SelectedDocuments.set(docView, true); } } @action DeselectDoc(docView: DocumentView): void { - let ind = manager.SelectedDocuments.indexOf(docView); - if (ind !== -1) { - manager.SelectedDocuments.splice(ind, 1); + if (manager.SelectedDocuments.get(docView)) { + manager.SelectedDocuments.delete(docView); docView.props.whenActiveChanged(false); } } @action DeselectAll(): void { - manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); - manager.SelectedDocuments = []; + Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false)); + manager.SelectedDocuments.clear(); } } @@ -52,14 +51,18 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - export function IsSelected(doc: DocumentView): boolean { - return manager.SelectedDocuments.indexOf(doc) !== -1; + export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean { + return outsideReaction ? + manager.SelectedDocuments.get(doc) ? true : false : + computedFn(function isSelected(doc: DocumentView) { + return manager.SelectedDocuments.get(doc) ? true : false; + })(doc); } export function DeselectAll(except?: Doc): void { let found: DocumentView | undefined = undefined; if (except) { - for (const view of manager.SelectedDocuments) { + for (const view of Array.from(manager.SelectedDocuments.keys())) { if (view.props.Document === except) found = view; } } @@ -72,6 +75,6 @@ export namespace SelectionManager { export function GetIsDragging() { return manager.IsDragging; } export function SelectedDocuments(): Array { - return manager.SelectedDocuments.slice(); + return Array.from(manager.SelectedDocuments.keys()); } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 286a77f81..1bd1006a8 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -27,7 +27,7 @@ interface DocExtendableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; - isSelected: () => boolean; + isSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; } export function DocExtendableComponent

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

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

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

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

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