diff options
Diffstat (limited to 'src')
25 files changed, 335 insertions, 607 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0da80d2c4..357b82cd6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -35,7 +35,6 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn import { AudioBox } from "../views/nodes/AudioBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; -import { DocHolderBox } from "../views/nodes/DocHolderBox"; import { DocFocusOptions } from "../views/nodes/DocumentView"; import { FilterBox } from "../views/nodes/FilterBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; @@ -59,6 +58,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { EquationBox } from "../views/nodes/EquationBox"; import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; +import { script } from "googleapis/build/src/apis/script"; const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); @@ -169,6 +169,7 @@ export class DocumentOptions { childLimitHeight?: number; // whether to limit the height of colleciton children. 0 - means height can be no bigger than width childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view) childLayoutString?: string; // template string for collection to use to render its children + childDontRegisterViews?: boolean; hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template @@ -191,7 +192,6 @@ export class DocumentOptions { presProgressivize?: boolean; borderRounding?: string; boxShadow?: string; - dontRegisterChildViews?: boolean; dontRegisterView?: boolean; lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox. "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form @@ -334,10 +334,6 @@ export namespace Docs { layout: { view: KeyValueBox, dataField: defaultDataKey }, options: { _height: 150 } }], - [DocumentType.DOCHOLDER, { - layout: { view: DocHolderBox, dataField: defaultDataKey }, - options: { _height: 250 } - }], [DocumentType.VID, { layout: { view: VideoBox, dataField: defaultDataKey }, options: { _currentTimecode: 0 }, @@ -768,7 +764,7 @@ export namespace Docs { export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) { const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { - dontRegisterChildViews: true, + childDontRegisterViews: true, isLinkButton: true, treeViewHideTitle: true, backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area treeViewExpandedView: "fields", removeDropProperties: new List(["_layerTags", "isLinkButton"]), ...options }, id); @@ -876,7 +872,7 @@ export namespace Docs { } export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, childDontRegisterViews: true, ...options, _viewType: CollectionViewType.Tree }, id); } export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) { @@ -1047,8 +1043,9 @@ export namespace DocUtils { const min = Number(docRangeFilters[i + 1]); const max = Number(docRangeFilters[i + 2]); const val = Cast(d[key], "number", null); - if (val === undefined || (val < min || val > max)) { - return false; + if (val < min || val > max) return false; + if (val === undefined) { + console.log("Should 'undefined' pass range filter or not?") } } return true; @@ -1135,7 +1132,7 @@ export namespace DocUtils { linkDoc.hidden = true; Doc.GetProto(linkDoc)["acl-Public"] = linkDoc["acl-Public"] = SharingPermissions.Add; linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null); - Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2?.title'); + Doc.GetProto(linkDoc).title = ComputedField.MakeFunction("generateLinkTitle(self)"); showPopup && makeLink(linkDoc, showPopup); return linkDoc; } @@ -1416,4 +1413,10 @@ export namespace DocUtils { } Scripting.addGlobal("Docs", Docs); -Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; });
\ No newline at end of file +Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); +Scripting.addGlobal(function generateLinkTitle(self: Doc) { + const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : "<?>"; + const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : "<?>"; + const relation = self.linkRelationship || "to"; + return `${anchor1title} (${relation}) ${anchor2title}`; +})
\ No newline at end of file diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f47e198be..743ce24a6 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -747,7 +747,7 @@ export class CurrentUserUtils { title: "My Dashboards", _height: 400, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })); const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); (doc.myDashboards as any as Doc).contextMenuScripts = new List<ScriptField>([newDashboard!]); @@ -763,7 +763,7 @@ export class CurrentUserUtils { title: "My Presentations", _height: 100, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })); const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); (doc.myPresentations as any as Doc).contextMenuScripts = new List<ScriptField>([newPresentations!]); @@ -783,7 +783,7 @@ export class CurrentUserUtils { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, isFolder: true, treeViewType: "fileSystem", - _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "proto", system: true + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true })); } return doc.myFilesystem as any as Doc; @@ -796,7 +796,7 @@ export class CurrentUserUtils { title: "Recently Closed", treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); @@ -809,9 +809,9 @@ export class CurrentUserUtils { if (doc.currentFilter === undefined) { doc.currentFilter = Docs.Create.FilterDocument({ title: "FilterDoc", _height: 20, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _yPadding: 10, _forceActive: true, childDropAction: "none", - treeViewTruncateTitleWidth: 90, treeViewPreventOpen: false, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true }); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); (doc.currentFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); @@ -826,7 +826,7 @@ export class CurrentUserUtils { doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })) as any as Doc; } } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a9cd7b4a9..5ebf29603 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -131,7 +131,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const title = (() => { switch (this.openHover) { default: - case UtilityButtonState.Default: return `${!dataDoc?.unchanged ? "Pull from" : "Fetch"} Google Docs`; + case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`; case UtilityButtonState.OpenRight: return "Open in Right Split"; case UtilityButtonState.OpenExternally: return "Open in new Browser Tab"; } @@ -167,7 +167,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV this.clearPullColor(); DocumentButtonBar.hasPulledHack = false; targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; - dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); + dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true); } }}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" @@ -175,7 +175,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV icon={(() => { switch (this.openHover) { default: - case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; case UtilityButtonState.OpenExternally: return "share"; } diff --git a/src/client/views/MainViewModal.scss b/src/client/views/MainViewModal.scss index 812fe540b..5f19590b4 100644 --- a/src/client/views/MainViewModal.scss +++ b/src/client/views/MainViewModal.scss @@ -1,26 +1,34 @@ -.dialogue-box { +.mainViewModal-cont { + pointer-events: all; position: absolute; - z-index: 1000; - text-align: center; - justify-content: center; - align-self: center; - align-content: center; - padding: 20px; - // background: gainsboro; - background: white; - border-radius: 10px; - border: 0.5px solid black; - box-shadow: #00000044 5px 5px 10px; - transform: translate(-50%, -50%); - top: 50%; - left: 50%; - transition: 0.5s all ease; -} - -.overlay { + z-index: 10000; width: 100%; height: 100%; - position: absolute; - z-index: 999; - transition: 0.5s all ease; + .dialogue-box { + position: absolute; + z-index: 1000; + text-align: center; + justify-content: center; + align-self: center; + align-content: center; + padding: 20px; + // background: gainsboro; + background: white; + border-radius: 10px; + border: 0.5px solid black; + box-shadow: #00000044 5px 5px 10px; + transform: translate(-50%, -50%); + top: 50%; + left: 50%; + transition: 0.5s all ease; + } + + .overlay { + width: 100%; + height: 100%; + position: absolute; + z-index: 999; + transition: 0.5s all ease; + } + }
\ No newline at end of file diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 34e0ff126..7f91c0079 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -21,19 +21,17 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> { const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1; const overlayOpacity = p.overlayDisplayedOpacity || 0.4; return !p.isDisplayed ? (null) : ( - <div style={{ + <div className="mainViewModal-cont" style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}> - <div - className={"dialogue-box"} - style={{ - borderColor: "black", - ...(p.dialogueBoxStyle || {}), - opacity: p.isDisplayed ? dialogueOpacity : 0 - }} - >{p.contents}</div> - <div - className={"overlay"} + <div className="dialogue-box" style={{ + borderColor: "black", + ...(p.dialogueBoxStyle || {}), + opacity: p.isDisplayed ? dialogueOpacity : 0 + }} > + {p.contents} + </div> + <div className="overlay" onClick={this.props?.closeOnExternalClick} style={{ backgroundColor: "gainsboro", @@ -44,6 +42,4 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> { </div> ); } - - }
\ No newline at end of file diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index f03e1dd9d..63439586c 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -99,43 +99,41 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { const scale = this.props.scaling?.() || 1; const anno = this.props.savedAnnotations.values()[0][0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; - const mainAnnoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); - if (anno.style.left) mainAnnoDoc.x = (parseInt(anno.style.left) - containerOffset[0]) / scale; - if (anno.style.top) mainAnnoDoc.y = (parseInt(anno.style.top) - containerOffset[1]) / scale + NumCast(this.props.scrollTop); - if (anno.style.height) mainAnnoDoc._height = parseInt(anno.style.height) / scale; - if (anno.style.width) mainAnnoDoc._width = parseInt(anno.style.width) / scale; - mainAnnoDoc.group = mainAnnoDoc; + const marqueeAnno = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); + marqueeAnno.x = (parseInt(anno.style.left || "0") - containerOffset[0]) / scale; + marqueeAnno.y = (parseInt(anno.style.top || "0") - containerOffset[1]) / scale + NumCast(this.props.scrollTop); + marqueeAnno._height = parseInt(anno.style.height || "0") / scale; + marqueeAnno._width = parseInt(anno.style.width || "0") / scale; anno.remove(); this.props.savedAnnotations.clear(); - return mainAnnoDoc; - } else { - const mainAnnoDoc = Docs.Create.FreeformDocument([], { type: DocumentType.PDFANNO, annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 }); - const mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); + return marqueeAnno; + } - let maxX = -Number.MAX_VALUE; - let minY = Number.MAX_VALUE; - const annoDocs: Doc[] = []; - this.props.savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { - const annoDoc = new Doc(); - if (anno.style.left) annoDoc.x = parseInt(anno.style.left); - if (anno.style.top) annoDoc.y = parseInt(anno.style.top); - if (anno.style.height) annoDoc._height = parseInt(anno.style.height); - if (anno.style.width) annoDoc._width = parseInt(anno.style.width); - annoDoc.group = mainAnnoDoc; - annoDoc.backgroundColor = color; - annoDocs.push(annoDoc); - anno.remove(); - (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); - (annoDoc.x !== undefined) && (maxX = Math.max(NumCast(annoDoc.x) + NumCast(annoDoc._width), maxX)); - })); + const textRegionAnno = Docs.Create.FreeformDocument([], { type: DocumentType.PDFANNO, annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 }); + let maxX = -Number.MAX_VALUE; + let minY = Number.MAX_VALUE; + const annoDocs: Doc[] = []; + this.props.savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => { + const textRegion = new Doc(); + textRegion.x = parseInt(anno.style.left ?? "0"); + textRegion.y = parseInt(anno.style.top ?? "0"); + textRegion._height = parseInt(anno.style.height ?? "0"); + textRegion._width = parseInt(anno.style.width ?? "0"); + textRegion.annoTextRegion = textRegionAnno; + textRegion.backgroundColor = color; + annoDocs.push(textRegion); + anno.remove(); + minY = Math.min(NumCast(textRegion.y), minY); + maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX); + })); - mainAnnoDocProto.y = Math.max(minY, 0); - mainAnnoDocProto.x = Math.max(maxX, 0); - // mainAnnoDocProto.text = this._selectionText; - mainAnnoDocProto.annotations = new List<Doc>(annoDocs); - this.props.savedAnnotations.clear(); - return mainAnnoDoc; - } + const textRegionAnnoProto = Doc.GetProto(textRegionAnno); + textRegionAnnoProto.y = Math.max(minY, 0); + textRegionAnnoProto.x = Math.max(maxX, 0); + // mainAnnoDocProto.text = this._selectionText; + textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs); + this.props.savedAnnotations.clear(); + return textRegionAnno; } @action highlight = (color: string, isLinkButton: boolean) => { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index e418a6f3c..bf72dbdba 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -131,7 +131,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const title = (() => { switch (this.openHover) { default: - case UtilityButtonState.Default: return `${!dataDoc?.unchanged ? "Pull from" : "Fetch"} Google Docs`; + case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`; case UtilityButtonState.OpenRight: return "Open in Right Split"; case UtilityButtonState.OpenExternally: return "Open in new Browser Tab"; } @@ -165,7 +165,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { this.clearPullColor(); PropertiesButtons.hasPulledHack = false; targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; - dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); + dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true); } }}> <FontAwesomeIcon className="documentdecorations-icon" size="lg" color="black" @@ -173,7 +173,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { icon={(() => { switch (this.openHover) { default: - case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; case UtilityButtonState.OpenExternally: return "share"; } diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 077e50dd2..654727a82 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -2,22 +2,22 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DataSym } from "../../../fields/Doc"; +import { DataSym, Doc, DocListCast } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; -import { StrCast, NumCast } from "../../../fields/Types"; -import { numberRange, setupMoveUpEvents, emptyFunction, returnEmptyString } from "../../../Utils"; +import { NumCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, numberRange, returnEmptyString, setupMoveUpEvents } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; +import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; import { CollectionStackingView } from "./CollectionStackingView"; import "./CollectionStackingView.scss"; -import { SnappingManager } from "../../util/SnappingManager"; -import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; -import { Id } from "../../../fields/FieldSymbols"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 95a95b10d..a8123b051 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -20,8 +20,9 @@ import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; +import { LightboxView } from "../LightboxView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { DocumentView, DocumentViewProps, DocFocusOptions, ViewAdjustment } from "../nodes/DocumentView"; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; import { FieldViewProps } from "../nodes/FieldView"; import { StyleProp } from "../StyleProvider"; import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; @@ -29,8 +30,6 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; -import { LightboxView } from "../LightboxView"; -import { DocumentType } from "../../documents/DocumentTypes"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -224,7 +223,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, NativeWidth={this.props.childIgnoreNativeSize ? returnZero : doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox NativeHeight={this.props.childIgnoreNativeSize ? returnZero : doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined} dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined} - dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)} + dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} rootSelected={this.rootSelected} dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} onClick={this.onChildClickHandler} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b14949d47..0a4422cbd 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -211,7 +211,7 @@ export class moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.styleProvider, returnTrue, this.props.ScreenToLocalTransform, this.outerXf, this.active, this.panelWidth, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), BoolCast(this.doc.treeViewPreventOpen), [], this.props.onCheckedClick, - this.onChildClick, this.props.treeViewSkipFields, true, this.whenActiveChanged, this.props.dontRegisterView || Cast(this.props.Document.dontRegisterChildViews, "boolean", null), this); + this.onChildClick, this.props.treeViewSkipFields, true, this.whenActiveChanged, this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null), this); } @computed get titleBar() { const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 58bb5974f..7188354bb 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -608,7 +608,7 @@ export class TreeView extends React.Component<TreeViewProps> { whenActiveChanged={this.props.whenActiveChanged} bringToFront={emptyFunction} cantBrush={this.props.treeView.props.cantBrush} - dontRegisterView={BoolCast(this.props.treeView.props.Document.dontRegisterChildViews)} + dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews)} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 660790b11..ec5cb8a61 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -950,7 +950,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P doc.hidden && Doc.UnHighlightDoc(doc); const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing; if (resetView) { - const restoreState = !LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document && savedState; + const restoreState = (!LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document) && savedState; if (typeof restoreState !== "boolean") { this.Document._panX = restoreState.panX; this.Document._panY = restoreState.panY; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 2a30e5fd0..e625b5b4b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -323,11 +323,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (Doc.GetSelectedTool() === InkTool.None) { if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; - if (!(e.nativeEvent as any).formattedHandled) { - if (!this.props.trySelectCluster(e.shiftKey)) { - this.setPreviewCursor(e.clientX, e.clientY, false); - } else e.stopPropagation(); - } + if (!this.props.trySelectCluster(e.shiftKey)) { + this.setPreviewCursor(e.clientX, e.clientY, false); + } else e.stopPropagation(); } } // let the DocumentView stopPropagation of this event when it selects this document diff --git a/src/client/views/nodes/DocHolderBox.scss b/src/client/views/nodes/DocHolderBox.scss deleted file mode 100644 index 6a9ef0b6f..000000000 --- a/src/client/views/nodes/DocHolderBox.scss +++ /dev/null @@ -1,15 +0,0 @@ -.documentBox-container { - width: 100%; - height: 100%; - pointer-events: all; - position: absolute; - .documentBox-lock { - margin: auto; - color: white; - position: absolute; - padding: 3px; - } - .contentFittingDocumentView { - position: absolute; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx deleted file mode 100644 index 765751a65..000000000 --- a/src/client/views/nodes/DocHolderBox.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, IReactionDisposer, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Field } from "../../../fields/Doc"; -import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; -import { listSpec, makeInterface } from "../../../fields/Schema"; -import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { TraceMobx } from "../../../fields/util"; -import { returnFalse } from "../../../Utils"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DragManager } from "../../util/DragManager"; -import { undoBatch } from "../../util/UndoManager"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; -import { StyleProp } from "../StyleProvider"; -import "./DocHolderBox.scss"; -import { DocumentView } from "./DocumentView"; -import { FieldView, FieldViewProps } from "./FieldView"; -import React = require("react"); - -type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>; -const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema); - -@observer -export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, DocHolderBoxSchema>(DocHolderBoxDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocHolderBox, fieldKey); } - _prevSelectionDisposer: IReactionDisposer | undefined; - _dropDisposer?: DragManager.DragDropDisposer; - _selections: Doc[] = []; - _contRef = React.createRef<HTMLDivElement>(); - _curSelection = -1; - componentDidMount() { - this._prevSelectionDisposer = reaction(() => this.dataDoc[this.fieldKey], (data) => { - if (data instanceof Doc && !this.isSelectionLocked()) { - this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1); - this._selections.push(data); - this._curSelection = this._selections.length - 1; - } - }); - } - componentWillUnmount() { - this._prevSelectionDisposer?.(); - } - specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" }); - funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" }); - funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" }); - funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "<KeyValueBox {...props} />", icon: "project-diagram" }); - - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - } - lockSelection = () => { - this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey]; - } - showSelection = () => { - this.dataDoc[this.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); - } - isSelectionLocked = () => { - const kvpstring = Field.toKeyValueString(this.dataDoc, this.fieldKey); - return !kvpstring || kvpstring.includes("DOC"); - } - toggleLockSelection = () => { - !this.isSelectionLocked() ? this.lockSelection() : this.showSelection(); - return true; - } - prevSelection = () => { - this.lockSelection(); - if (this._curSelection > 0) { - this.dataDoc[this.fieldKey] = this._selections[--this._curSelection]; - return true; - } - } - nextSelection = () => { - if (this._curSelection < this._selections.length - 1 && this._selections.length) { - this.dataDoc[this.fieldKey] = this._selections[++this._curSelection]; - return true; - } - } - onPointerDown = (e: React.PointerEvent) => { - if (this.active() && e.button === 0 && !e.ctrlKey) { - e.stopPropagation(); - } - } - onLockClick = (e: React.MouseEvent) => { - this.toggleLockSelection(); - (e.nativeEvent as any).formattedHandled = true; - e.stopPropagation(); - } - get xPad() { return NumCast(this.rootDoc._xPadding); } - get yPad() { return NumCast(this.rootDoc._yPadding); } - onClick = (e: React.MouseEvent) => { - let hitWidget: boolean | undefined = false; - if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); - else if (this._contRef.current!.getBoundingClientRect().bottom - this.yPad < e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); - else { - if (this._contRef.current!.getBoundingClientRect().left + this.xPad > e.clientX) hitWidget = this.prevSelection(); - if (this._contRef.current!.getBoundingClientRect().right - this.xPad < e.clientX) hitWidget = this.nextSelection(); - } - if (hitWidget) { - (e.nativeEvent as any).formattedHandled = true; - e.stopPropagation(); - } - } - pwidth = () => this.props.PanelWidth() - 2 * this.xPad; - pheight = () => this.props.PanelHeight() - 2 * this.yPad; - getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); - isActive = (outsideReaction: boolean) => this.active(outsideReaction) || this.props.renderDepth <= 1; - layoutTemplateDoc = () => Cast(this.layoutDoc.childLayoutTemplate, Doc, null); - get renderContents() { - const containedDoc = Cast(this.dataDoc[this.fieldKey], Doc, null); - const layoutTemplate = StrCast(this.layoutDoc.childLayoutString); - const contents = !(containedDoc instanceof Doc) || - Cast(containedDoc[Doc.LayoutFieldKey(containedDoc)], listSpec(Doc), null)?.includes(this.rootDoc) - ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ? - <DocumentView - Document={containedDoc} - DataDoc={undefined} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected - ContainingCollectionDoc={undefined} - styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} - docViewPath={this.props.docViewPath} - LayoutTemplateString={layoutTemplate} - LayoutTemplate={this.layoutTemplateDoc} - rootSelected={this.props.isSelected} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - ScreenToLocalTransform={this.getTransform} - renderDepth={containedDoc.type !== DocumentType.DOCHOLDER && !this.props.renderDepth ? 0 : this.props.renderDepth + 1} - PanelWidth={this.pwidth} - PanelHeight={this.pheight} - focus={this.props.focus} - parentActive={this.isActive} - dontRegisterView={true} - whenActiveChanged={this.props.whenActiveChanged} - bringToFront={returnFalse} /> : - <DocumentView - Document={containedDoc} - DataDoc={undefined} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected - ContainingCollectionDoc={undefined} - styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} - docViewPath={this.props.docViewPath} - ignoreAutoHeight={true} - LayoutTemplateString={layoutTemplate} - LayoutTemplate={this.layoutTemplateDoc} - rootSelected={this.props.isSelected} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - ScreenToLocalTransform={this.getTransform} - renderDepth={containedDoc.type !== DocumentType.DOCHOLDER && !this.props.renderDepth ? 0 : this.props.renderDepth + 1} - PanelWidth={this.pwidth} - PanelHeight={this.pheight} - focus={this.props.focus} - parentActive={this.isActive} - dontRegisterView={true} - whenActiveChanged={this.props.whenActiveChanged} - bringToFront={returnFalse} - />; - return contents; - } - render() { - const containedDoc = Cast(this.dataDoc[this.fieldKey], Doc, null); - TraceMobx(); - return !containedDoc ? (null) : <div className="documentBox-container" ref={this._contRef} - onContextMenu={this.specificContextMenu} - onPointerDown={this.onPointerDown} onClick={this.onClick} - style={{ - background: this.props.styleProvider?.(containedDoc, this.props, StyleProp.BackgroundColor), - border: `#00000021 solid ${this.xPad}px`, - borderTop: `#0000005e solid ${this.yPad}px`, - borderBottom: `#0000005e solid ${this.yPad}px`, - }}> - {this.renderContents} - <div className="documentBox-lock" onClick={this.onLockClick} ref={this.createDropTarget} - style={{ marginTop: - this.yPad, background: "black" }}> - <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" /> - </div> - </div >; - } - - @undoBatch - @action - drop = (e: Event, de: DragManager.DropEvent) => { - const docDragData = de.complete.docDragData; - if (docDragData?.draggedDocuments[0].type === DocumentType.FONTICON) { - const doc = Cast(docDragData.draggedDocuments[0].dragFactory, Doc, null); - this.layoutDoc.childLayoutTemplate = doc; - } - } - protected createDropTarget = (ele: HTMLDivElement) => { - this._dropDisposer?.(); - ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.rootDoc)); - } - -} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 32542d056..2f7923574 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -16,7 +16,6 @@ import { FunctionPlotBox } from "./FunctionPlotBox"; import { SliderBox } from "./SliderBox"; import { LinkBox } from "./LinkBox"; import { ScriptingBox } from "./ScriptingBox"; -import { DocHolderBox } from "./DocHolderBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; @@ -225,7 +224,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox, - ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, + ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox, ScreenshotBox, HTMLtag, ComparisonBox }} bindings={bindings} diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 085ffae26..d62061aea 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -280,7 +280,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp TraceMobx(); const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link"; - const buttonTitle = "Tap to view links"; + const buttonTitle = "Tap to view links; double tap to open link collection"; const title = this.props.InMenu ? menuTitle : buttonTitle; return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) : diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 83022a759..ebc65002f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -286,7 +286,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => { - if ((e as any).formattedHandled) { e.stopPropagation; return; } if (e.cancelBubble && this.active) { this.removeMoveListeners(); } @@ -475,7 +474,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } else clickFunc(); } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right"); - } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey && !(e.nativeEvent as any).formattedHandled) { + } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey); } else { if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -517,13 +516,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - - if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); } } } onPointerMove = (e: PointerEvent): void => { - if ((e as any).formattedHandled) { e.stopPropagation(); return; } if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return; if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index a4ae0b5a3..0f8dcad9d 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -1,8 +1,8 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; +import { computed, observable, action, trace, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../../fields/Doc"; +import { DataSym, Doc, DocListCast, Field, Opt, DocListCastAsync } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { List } from "../../../fields/List"; import { RichTextField } from "../../../fields/RichTextField"; @@ -19,8 +19,6 @@ import { SearchBox } from "../search/SearchBox"; import { FieldView, FieldViewProps } from './FieldView'; import './FilterBox.scss'; import { Scripting } from "../../util/Scripting"; -import { values } from "lodash"; -import { tokenToString } from "typescript"; import { SelectionManager } from "../../util/SelectionManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -59,10 +57,20 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc return FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document; } + @observable _loaded = false; + componentDidMount() { + reaction(() => DocListCastAsync(CollectionDockingView.Instance.props.Document.data), + async (activeTabsAsync) => { + const activeTabs = await activeTabsAsync; + activeTabs && (await SearchBox.foreachRecursiveDocAsync(activeTabs, emptyFunction)); + runInAction(() => this._loaded = true); + }, { fireImmediately: true }); + } @computed get allDocs() { + trace(); const allDocs = new Set<Doc>(); const targetDoc = FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document; - if (targetDoc) { + if (this._loaded && targetDoc) { const activeTabs = DocListCast(targetDoc.data); SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc)); setTimeout(() => targetDoc.allDocuments = new List<Doc>(Array.from(allDocs))); @@ -71,6 +79,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc } @computed get _allFacets() { + trace(); const noviceReqFields = ["author", "tags", "text", "type"]; const noviceLayoutFields: string[] = [];//["_curPage"]; const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; @@ -200,12 +209,12 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`; newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" }); } else if (facetHeader !== "tags" && nonNumbers / facetValues.strings.length < .1) { - newFacet = Docs.Create.SliderDocument({ title: facetHeader, _overflow: "visible", _height: 40, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", treeViewOpen: true }); + newFacet = Docs.Create.SliderDocument({ title: facetHeader, _overflow: "visible", _fitWidth: true, _height: 40, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", treeViewOpen: true }); const newFacetField = Doc.LayoutFieldKey(newFacet); const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05); - const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05); + const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * .1)); + const extendedMaxVal = maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * .05)); newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0]; newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1]; Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal; @@ -218,8 +227,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc newFacet.title = facetHeader; newFacet.treeViewOpen = true; newFacet.type = DocumentType.COL; - const capturedVariables = { layoutDoc: targetDoc, system: true, _stayInCollection: true, _hideContextMenu: true, dataDoc: (targetDoc.data as any)[0][DataSym] }; - newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables); + newFacet.target = targetDoc; + newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${facetHeader}")`); } newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet); } @@ -345,6 +354,26 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc value={null} closeMenuOnSelect={false} /> + {/* @computed get flyoutpanel() { + return <div className="filterBox-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> + {this._allFacets.map(facet => <label className="filterBox-flyout-facet" key={`${facet}`} onClick={e => this.facetClick(facet)}> + <input type="checkbox" onChange={emptyFunction} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} /> + <span className="checkmark" /> + {facet} + </label>)} + </div>; + } + render() { + const facetCollection = this.props.Document; + + return this.props.dontRegisterView ? (null) : <div className="filterBox-treeView" style={{ width: "100%" }}> + <div className="filterBox-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyoutpanel}> + <div className="filterBox-addFacetButton"> + <FontAwesomeIcon icon={"edit"} size={"lg"} /> + <span className="filterBox-span">Choose Facets</span> + </div> + </Flyout> */} </div> <div className="filterBox-tree" key="tree"> @@ -451,7 +480,7 @@ Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: } return undefined; }); -Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) { +Scripting.addGlobal(function readFacetData(targetDoc: Doc, facetHeader: string) { const allCollectionDocs = DocListCast(CollectionDockingView.Instance?.props.Document.allDocuments); const set = new Set<string>(); if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key))); @@ -464,7 +493,10 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) const doc = new Doc(); doc.system = true; doc.title = facetValue.toString(); - doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue }); + doc.target = targetDoc; + doc.facetHeader = facetHeader; + doc.facetValue = facetValue; + doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(self.target, self.facetHeader, self.facetValue)"); return doc; }); return new List<Doc>(facetValueDocSet); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0f669a544..ad11d55bf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -15,7 +15,7 @@ import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, Wid import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; -import { Id, Copy } from '../../../../fields/FieldSymbols'; +import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; @@ -90,11 +90,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static get DefaultLayout() { return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); } - public static CanAnnotate = true; public static Instance: FormattedTextBox; - public ProseRef?: HTMLDivElement; - public get EditorView() { return this._editorView; } - public get SidebarKey() { return this.fieldKey + "-sidebar"; } + public static LiveTextUndo: UndoManager.Batch | undefined; + static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _highlightStyleSheet: any = addStyleSheet(); + static _bulletStyleSheet: any = addStyleSheet(); + static _userStyleSheet: any = addStyleSheet(); + static _canAnnotate = true; + static _hadSelection: boolean = false; private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; @@ -111,6 +114,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _focusSpeed: Opt<number>; private _keymap: any = undefined; private _rules: RichTextRules | undefined; + private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle + private _forceDownNode: Node | undefined; + private _downEvent: any; + private _downX = 0; + private _downY = 0; + private _break = false; + public ProseRef?: HTMLDivElement; + public get EditorView() { return this._editorView; } + public get SidebarKey() { return this.fieldKey + "-sidebar"; } @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } @@ -121,10 +133,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } @computed get _recording() { return this.dataDoc?.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } + @computed get config() { + this._keymap = buildKeymap(schema, this.props); + this._rules = new RichTextRules(this.props.Document, this); + return { + schema, + plugins: [ + inputRules(this._rules.inpRules), + this.richTextMenuPlugin(), + history(), + keymap(this._keymap), + keymap(baseKeymap), + new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }), + new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) + ] + }; + } public static FocusedBox: FormattedTextBox | undefined; - public static SelectOnLoad = ""; public static PasteOnLoad: ClipboardEvent | undefined; + public static SelectOnLoad = ""; public static SelectOnLoadChar = ""; public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } public static GetHref(html: string): string { @@ -205,9 +233,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, this.getAnchor, targetCreator), e.pageX, e.pageY); }); - const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); - this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom)); + this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); } dispatchTransaction = (tx: Transaction) => { @@ -315,7 +342,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } // needs a better API for taking in a set of words with target documents instead of just one target - public hyperlinkTerms = (terms: string[], target: Doc) => { + hyperlinkTerms = (terms: string[], target: Doc) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); let tr = this._editorView.state.tr; @@ -335,7 +362,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr); } } - public highlightSearchTerms = (terms: string[], backward: boolean) => { + highlightSearchTerms = (terms: string[], backward: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); @@ -364,7 +391,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - public unhighlightSearchTerms = () => { + unhighlightSearchTerms = () => { if (window.screen.width < 600) null; else if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); @@ -407,18 +434,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // embed document when dragg marked as embed } else if (de.embedKey) { const target = dragData.droppedDocuments[0]; - // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - // if (link) { target._fitToBox = true; const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], + title: "dashDoc", + docid: target[Id], float: "right" }); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); e.stopPropagation(); - // } } // otherwise, fall through to outer collection to handle drop } } @@ -467,7 +492,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return ret; } - static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; updateHighlights = () => { clearStyleSheetRules(FormattedTextBox._userStyleSheet); if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) { @@ -551,9 +575,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, icon: "expand-arrows-alt" })); - const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ description: `${FormattedTextBox.CanAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox.CanAnnotate = !FormattedTextBox.CanAnnotate, icon: "expand-arrows-alt" }); + uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); @@ -612,10 +635,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (results && [DictationManager.Controls.Infringed].includes(results)) { DictationManager.Controls.stop(); } - //this._editorView!.focus(); }); } - stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; + stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort); setDictationContent = (value: string) => { if (this._editorView) { @@ -643,26 +665,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - @computed get config() { - this._keymap = buildKeymap(schema, this.props); - this._rules = new RichTextRules(this.props.Document, this); - return { - schema, - plugins: [ - inputRules(this._rules.inpRules), - this.richTextMenuPlugin(), - history(), - keymap(this._keymap), - keymap(baseKeymap), - new Plugin({ - props: { - attributes: { class: "ProseMirror-example-setup-style" } - } - }), new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) - ] - }; - } - makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) { const state = this._editorView?.state; if (state) { @@ -692,10 +694,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return anchorDoc ?? this.rootDoc; } - IsActive = () => { - return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; - } - scrollFocus = (doc: Doc, smooth: boolean) => { const anchorId = doc[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -753,7 +751,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. - this.props.contentsActive?.(this.IsActive); + this.props.contentsActive?.(this.active); this._cachedLinks = DocListCast(this.Document.links); this._disposers.autoHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), ({ width, autoHeight, scrollHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight) @@ -771,7 +769,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp instance => { if (instance) { this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); + this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true); } } ); @@ -801,8 +799,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp () => { if (!DocumentButtonBar.hasPulledHack) { DocumentButtonBar.hasPulledHack = true; - const unchanged = this.dataDoc.unchanged; - this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); + this.pullFromGoogleDoc(this.dataDoc.googleDocUnchanged ? this.checkState : this.updateState); } } ); @@ -877,19 +874,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); const pushSuccess = response !== undefined && !("errors" in response); - dataDoc.unchanged = pushSuccess; + dataDoc.googleDocUnchanged = pushSuccess; DocumentButtonBar.Instance.startPushOutcome(pushSuccess); } }; const undo = () => { - if (!exportState) { - return; - } - const content: GoogleApiClientUtils.Docs.Content = { - text: exportState.text, - requests: [] - }; - if (reference && content) { + if (exportState && reference) { + const content: GoogleApiClientUtils.Docs.Content = { + text: exportState.text, + requests: [] + }; GoogleApiClientUtils.Docs.write({ reference, content, mode }); } }; @@ -922,7 +916,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, 0); dataDoc.title = exportState.title; this.dataDoc["title-custom"] = true; - dataDoc.unchanged = true; + dataDoc.googleDocUnchanged = true; } else { delete dataDoc[GoogleRef]; } @@ -934,7 +928,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); const equalTitles = dataDoc.title === exportState.title; const unchanged = equalContent && equalTitles; - dataDoc.unchanged = unchanged; + dataDoc.googleDocUnchanged = unchanged; DocumentButtonBar.Instance.setPullState(unchanged); } } @@ -1094,7 +1088,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectAll(this._editorView!.state, this._editorView?.dispatch); this.startUndoTypingBatch(); } - } selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. @@ -1112,11 +1105,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); } - _downEvent: any; - _downX = 0; - _downY = 0; - _break = false; - _collapsed = false; onPointerDown = (e: React.PointerEvent): void => { if ((e.target as any).tagName === "AUDIOTAG") { e.preventDefault(); @@ -1164,9 +1152,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp document.removeEventListener("pointerup", this.onSelectEnd); document.removeEventListener("pointermove", this.onSelectMove); } - onPointerUp = (e: React.PointerEvent): void => { - if (!this._editorView?.state.selection.empty && FormattedTextBox.CanAnnotate) this.setupAnchorMenu(); + if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; if (!(e.nativeEvent as any).formattedHandled && this.active(true)) { @@ -1183,13 +1170,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); } } - @action onDoubleClick = (e: React.MouseEvent): void => { FormattedTextBoxComment.textBox = this; if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar - e.stopPropagation(); // if the text box is selected, then it consumes all down events + e.stopPropagation(); // if the text box is selected, then it consumes all click events } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1203,7 +1189,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); } } - @action onFocused = (e: React.FocusEvent): void => { FormattedTextBox.FocusedBox = this; @@ -1230,17 +1215,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } 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 a text note is selected and scrollable, stop event to prevent, say, outer collection from zooming. if ((this.props.rootSelected(true) || this.props.isSelected(true)) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { e.stopPropagation(); } } - - static _highlightStyleSheet: any = addStyleSheet(); - static _bulletStyleSheet: any = addStyleSheet(); - static _userStyleSheet: any = addStyleSheet(); - _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle - _forceDownNode: Node | undefined; onClick = (e: React.MouseEvent): void => { if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { this._forceDownNode = undefined; @@ -1276,7 +1255,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed; this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node; } - // 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, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) { this._forceUncollapse = false; @@ -1302,8 +1280,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (selectOrderedList || (!collapse && listNode.attrs.visibility)) { this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!))); } else if (!listNode.attrs.visibility || downNode === listNode) { - this._editorView.dispatch(this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility })); - this._editorView.dispatch(this._editorView.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, clickPos.pos))); + const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility }); + this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos))); } } addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); @@ -1321,30 +1299,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp view.root.removeEventListener("mouseup", originalUpHandler); view.mouseDown.up = (e: MouseEvent) => { !(e as any).formattedHandled && originalUpHandler(e); - // e.stopPropagation(); (e as any).formattedHandled = true; }; view.root.addEventListener("mouseup", view.mouseDown.up); } } - - public startUndoTypingBatch() { + startUndoTypingBatch() { !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); } - public endUndoTypingBatch() { const wasUndoing = this._undoTyping; this._undoTyping?.end(); this._undoTyping = undefined; return wasUndoing; } - public static LiveTextUndo: UndoManager.Batch | undefined; - public static HadSelection: boolean = false; onBlur = (e: any) => { if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } - FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; + FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ""; this.endUndoTypingBatch(); FormattedTextBox.LiveTextUndo?.end(); @@ -1363,7 +1336,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._lastText = curText; } } - onKeyDown = (e: React.KeyboardEvent) => { // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container) if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) { @@ -1385,36 +1357,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._rules!.EnteringStyle = false; } e.stopPropagation(); - if (e.key === "Escape") { - this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur?.(); - SelectionManager.DeselectAll(); - RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); - } else { - if (e.key === "Tab" || e.key === "Enter") { - if (e.key === "Enter") this.insertTime(); - e.preventDefault(); - } - if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { - const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); - } - this.startUndoTypingBatch(); + switch (e.key) { + case "Escape": + this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); + (document.activeElement as any).blur?.(); + SelectionManager.DeselectAll(); + RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); + return; + case "Enter": this.insertTime(); + case "Tab": e.preventDefault(); break; + default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; + case " ": + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})) + .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } + this.startUndoTypingBatch(); } - - ondrop = (eve: React.DragEvent) => { + ondrop = (e: React.DragEvent) => { this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); - eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. + e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. } - onScroll = (ev: React.UIEvent) => { + onScroll = (e: React.UIEvent) => { if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { this._ignoreScroll = true; this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop; this._ignoreScroll = false; } } - tryUpdateScrollHeight() { const proseHeight = this.ProseRef?.scrollHeight || 0; const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); @@ -1425,7 +1394,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... } } - fitToBox = () => this.props.Document._fitToBox; sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.SidebarKey); @@ -1442,7 +1410,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } @computed get sidebarHandle() { const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; - return <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} + return (!annotated && !this.active()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")) @@ -1489,7 +1457,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = (Doc.GetSelectedTool() === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); - if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(() => FormattedTextBoxComment.Hide()); + if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const margins = NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0); const selPad = Math.min(margins, 10); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 4e3721e2b..05d81f40f 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,15 +1,15 @@ import React = require("react"); -import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; -import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; -import "./Annotation.scss"; +import { FieldViewProps } from "../nodes/FieldView"; import { AnchorMenu } from "./AnchorMenu"; -import { FieldViewProps, FieldView } from "../nodes/FieldView"; +import "./Annotation.scss"; interface IAnnotationProps extends FieldViewProps { anno: Doc; @@ -21,73 +21,47 @@ interface IAnnotationProps extends FieldViewProps { export class Annotation extends React.Component<IAnnotationProps> { render() { - return DocListCast(this.props.anno.annotations).map(a => - <RegionAnnotation {...this.props} showInfo={this.props.showInfo} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />); + return DocListCast(this.props.anno.textInlineAnnotations).map(a => <RegionAnnotation {...this.props} document={a} key={a[Id]} />); } } interface IRegionAnnotationProps extends IAnnotationProps { - x: number; - y: number; - width: number; - height: number; document: Doc; } @observer class RegionAnnotation extends React.Component<IRegionAnnotationProps> { - private _reactionDisposer?: IReactionDisposer; - private _brushDisposer?: IReactionDisposer; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - @observable private _brushed: boolean = false; + @observable _brushed: boolean = false; + @computed get annoTextRegion() { return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document; } componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.document.delete, - (del) => del && this._mainCont.current && (this._mainCont.current.style.display = "none"), - { fireImmediately: true } - ); - - this._brushDisposer = reaction( - () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!), + this._disposers.brush = reaction( + () => this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion), brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0) ); } componentWillUnmount() { - this._brushDisposer?.(); - this._reactionDisposer?.(); + Object.values(this._disposers).forEach(disposer => disposer?.()); } @undoBatch deleteAnnotation = () => { - const annotation = DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]); - const group = FieldValue(Cast(this.props.document.group, Doc)); - if (group) { - if (annotation.indexOf(group) !== -1) { - const newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); - this.props.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>(newAnnotations); - } - - DocListCast(group.annotations).forEach(anno => anno.delete = true); - } + const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]); + this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); AnchorMenu.Instance.fadeOut(true); this.props.select(false); } @undoBatch - pinToPres = () => { - const group = FieldValue(Cast(this.props.document.group, Doc)); - group && this.props.pinToPres(group); - } + pinToPres = () => this.props.pinToPres(this.annoTextRegion); @undoBatch - makePushpin = action(() => { - const group = Cast(this.props.document.group, Doc, null); - group.isPushpin = !group.isPushpin; - }); + makePushpin = () => this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin; - isPushpin = () => BoolCast(Cast(this.props.document.group, Doc, null)?.isPushpin); + isPushpin = () => BoolCast(this.annoTextRegion.isPushpin); @action onPointerDown = (e: React.PointerEvent) => { @@ -102,32 +76,26 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); e.stopPropagation(); } - else if (e.button === 0 && this.props.document.group instanceof Doc) { + else if (e.button === 0) { e.stopPropagation(); - LinkManager.FollowLink(undefined, this.props.document.group, this.props, false); + LinkManager.FollowLink(undefined, this.annoTextRegion, this.props, false); } } - addTag = (key: string, value: string): boolean => { - const group = FieldValue(Cast(this.props.document.group, Doc)); - if (group) { - const valNum = parseInt(value); - group[key] = isNaN(valNum) ? value : valNum; - return true; - } - return false; + const valNum = parseInt(value); + this.annoTextRegion[key] = isNaN(valNum) ? value : valNum; + return true; } - @observable _showInfo = false; render() { - return (<div className="pdfAnnotation" onPointerEnter={action(() => this.props.showInfo(this.props.anno))} onPointerLeave={action(() => this.props.showInfo(undefined))} onPointerDown={this.onPointerDown} ref={this._mainCont} + return (<div className="pdfAnnotation" onPointerEnter={() => this.props.showInfo(this.props.anno)} onPointerLeave={() => this.props.showInfo(undefined)} onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ - top: this.props.y, - left: this.props.x, - width: this.props.width, - height: this.props.height, - opacity: !this._showInfo && this._brushed ? 0.5 : undefined, + left: NumCast(this.props.document.x), + top: NumCast(this.props.document.y), + width: NumCast(this.props.document._width), + height: NumCast(this.props.document._height), + opacity: this._brushed ? 0.5 : undefined, backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor), }} > </div>); diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index d17b2b5ef..3074eb4ec 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -34,7 +34,7 @@ border: unset; } .pdfViewerDash-text-selected { - position: relative; + // position: relative; // bcz: this breaks auto-scrolling using the inline search box z-index: -1; .textLayer{ pointer-events: all; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 720d2d92e..73aea9737 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -8,8 +8,7 @@ import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; import { createSchema, makeInterface } from "../../../fields/Schema"; -import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils"; @@ -23,12 +22,12 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; import { FieldViewProps } from "../nodes/FieldView"; -import { Annotation } from "./Annotation"; +import { LinkDocPreview } from "../nodes/LinkDocPreview"; import { AnchorMenu } from "./AnchorMenu"; +import { Annotation } from "./Annotation"; import "./PDFViewer.scss"; const pdfjs = require('pdfjs-dist/es5/build/pdf.js'); import React = require("react"); -import { LinkDocPreview } from "../nodes/LinkDocPreview"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); const _global = (window /* browser */ || global /* node */) as any; @@ -66,12 +65,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>(); @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript; - @observable private Index: number = -1; @observable private _marqueeing: number[] | undefined; @observable private _showWaiting = true; @observable private _showCover = false; @observable private _zoomed = 1; @observable private _overlayAnnoInfo: Opt<Doc>; + @observable private Index: number = -1; private _pdfViewer: any; private _styleRule: any; // stylesheet rule for making hyperlinks clickable @@ -89,12 +88,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu private _viewerIsSetup = false; private _ignoreScroll = false; private _initialScroll: Opt<number>; - private _smoothScrolling = true; + private _forcedScroll = true; @computed get allAnnotations() { return DocUtils.FilterDocs(DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined); } - @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); } + @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } componentDidMount = async () => { // change the address to be the file address of the PNG version of each page @@ -158,12 +157,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu copy = (e: ClipboardEvent) => { if (this.active() && e.clipboardData) { - //const annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)"); // copied text markup color (blueish) - //if (annoDoc) { e.clipboardData.setData("text/plain", this._selectionText); - // e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); - // e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); - //} e.preventDefault(); } } @@ -174,12 +168,13 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); await Promise.all(this._pageSizes.map<Pdfjs.PDFPromise<any>>((val, i) => this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => { + const page0or180 = page.rotate === 0 || page.rotate === 180; this._pageSizes.splice(i, 1, { - width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), - height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) + width: (page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1]), + height: (page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0]) }); - i === this.props.pdf.numPages - 1 && this.props.loaded?.((page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]), - (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]), i); + i === this.props.pdf.numPages - 1 && this.props.loaded?.((page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1]), + (page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0]), i); })))); this.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; } @@ -207,13 +202,13 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action setupPdfJsViewer = async () => { if (this._viewerIsSetup) return; - else this._viewerIsSetup = true; + this._viewerIsSetup = true; this._showWaiting = true; this.props.setPdfViewer(this); await this.initialLoad(); this._disposers.filterScript = reaction( - () => Cast(this.Document.filterScript, ScriptField), + () => ScriptCast(this.Document.filterScript), action(scriptField => { const oldScript = this._script.originalScript; this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; @@ -234,25 +229,23 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu document.removeEventListener("pagesinit", this.pagesinit); var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ""; this._disposers.scroll = reaction( - () => NumCast(this.Document._scrollTop), + () => Math.abs(NumCast(this.Document._scrollTop)), (pos) => { if (!this._ignoreScroll) { (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); - const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; + this._forcedScroll = true; if (duration) { - this._smoothScrolling = true; setTimeout(() => { - this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(pos || 0)); - setTimeout(() => this._smoothScrolling = false, duration); - }, delay); + this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos); + setTimeout(() => this._forcedScroll = false, duration); + }, this._mainCont.current ? 0 : 250); // wait for mainCont and try again to scroll } else { - this._smoothScrolling = true; - this._mainCont.current?.scrollTo({ top: Math.abs(pos || 0) }); - this._smoothScrolling = false; + this._mainCont.current?.scrollTo({ top: pos }); + this._forcedScroll = false; } } }, @@ -267,8 +260,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu createPdfViewer() { if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed + console.log("PDFViewer- I guess we got here"); if (this._retries < 5) { this._retries++; + console.log("PDFViewer- retry num:" + this._retries); setTimeout(() => this.createPdfViewer(), 1000); } return; @@ -277,9 +272,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu document.addEventListener("copy", this.copy); const eventBus = new PDFJSViewer.EventBus(true); eventBus._on("pagesinit", this.pagesinit); - eventBus._on("pagerendered", action(() => { - this._showWaiting = false; - })); + eventBus._on("pagerendered", action(() => this._showWaiting = false)); const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus }); const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus }); this._pdfViewer = new PDFJSViewer.PDFViewer({ @@ -306,7 +299,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu nextAnnotation = () => { this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1); this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]); - } @action @@ -325,8 +317,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } onScroll = (e: React.UIEvent<HTMLElement>) => { - if (this._mainCont.current && !this._smoothScrolling) { - this._ignoreScroll = true; + if (this._mainCont.current && !this._forcedScroll) { + this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation) if (!LinkDocPreview.LinkInfo) { this.layoutDoc._scrollTop = this._mainCont.current.scrollTop; } @@ -344,32 +336,24 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu return index; } - @action search = (searchString: string, fwd: boolean, clear: boolean = false) => { + const findOpts = { + caseSensitive: false, + findPrevious: !fwd, + highlightAll: true, + phraseSearch: true, + query: searchString + }; if (clear) { this._pdfViewer?.findController.executeCommand('reset', { query: "" }); } else if (!searchString) { fwd ? this.nextAnnotation() : this.prevAnnotation(); } else if (this._pdfViewer?.pageViewsReady) { - this._pdfViewer.findController.executeCommand('findagain', { - caseSensitive: false, - findPrevious: !fwd, - highlightAll: true, - phraseSearch: true, - query: searchString - }); + this._pdfViewer.findController.executeCommand('findagain', findOpts); } else if (this._mainCont.current) { - const executeFind = () => { - this._pdfViewer.findController.executeCommand('find', { - caseSensitive: false, - findPrevious: !fwd, - highlightAll: true, - phraseSearch: true, - query: searchString - }); - }; + const executeFind = () => this._pdfViewer.findController.executeCommand('find', findOpts); this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); } @@ -377,7 +361,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action onPointerDown = (e: React.PointerEvent): void => { - const hit = document.elementFromPoint(e.clientX, e.clientY); + // const hit = document.elementFromPoint(e.clientX, e.clientY); // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView, // but that's changed, so this shouldn't be needed. // if (hit && hit.localName === "span" && this.annotationsActive(true)) { // drag selecting text stops propagation @@ -415,11 +399,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this.props.select(false); } - @action - onSelectMove = (e: PointerEvent): void => { - // if (e.target && (e.target as any).parentElement === this._mainCont.current) - e.stopPropagation(); - } + onSelectMove = (e: PointerEvent) => e.stopPropagation(); @action onSelectEnd = (e: PointerEvent): void => { @@ -430,8 +410,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu const sel = window.getSelection(); if (sel?.type === "Range") { - const selRange = sel.getRangeAt(0); - this.createTextAnnotation(sel, selRange); + this.createTextAnnotation(sel, sel.getRangeAt(0)); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } } @@ -443,18 +422,16 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); - if (rect) { + if (rect && rect.width !== this._mainCont.current.clientWidth) { const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; - if (rect.width !== this._mainCont.current.clientWidth) { - const annoBox = document.createElement("div"); - annoBox.className = "marqueeAnnotator-annotationBox"; - // transforms the positions from screen onto the pdf div - annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString(); - annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString(); - annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString(); - annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString(); - this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top)); - } + const annoBox = document.createElement("div"); + annoBox.className = "marqueeAnnotator-annotationBox"; + // transforms the positions from screen onto the pdf div + annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString(); + annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString(); + annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString(); + this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top)); } } } @@ -510,10 +487,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } @computed get annotationLayer() { - TraceMobx(); return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}> - {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - <Annotation {...this.props} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />) + {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => + <Annotation {...this.props} fieldKey={this.fieldKey + "-annotations"} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />) } </div>; } @@ -573,7 +549,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu contentZoom = () => this._zoomed; render() { TraceMobx(); - return <div className={"pdfViewerDash" + (this.annotationsActive() ? "-interactive" : "")} ref={this._mainCont} + return <div className={`pdfViewerDash${this.annotationsActive() ? "-interactive" : ""}`} ref={this._mainCont} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ overflowX: this._zoomed !== 1 ? "scroll" : undefined, diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index c82d03fce..899fac66d 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt, DocListCastAsync } from '../../../fields/Doc'; import { documentSchema } from "../../../fields/documentSchemas"; import { Copy, Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -185,6 +185,21 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc return finalDocs; } + static async foreachRecursiveDocAsync(docs: Doc[], func: (doc: Doc) => void) { + let newarray: Doc[] = []; + while (docs.length > 0) { + newarray = []; + await Promise.all(docs.filter(d => d).map(async d => { + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); + const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + const docs = await DocListCastAsync(data); + docs && newarray.push(...docs); + func(d); + })); + docs = newarray; + } + } static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) { let newarray: Doc[] = []; while (docs.length > 0) { diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 4d8b6b55c..0bf942474 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -109,11 +109,10 @@ export const documentSchema = createSchema({ export const collectionSchema = createSchema({ - childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox childLayoutTemplate: Doc, // layout template to use to render children of a collecion childLayoutString: "string", //layout string to use to render children of a collection childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template) - dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links + childDontRegisterViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links onChildClick: ScriptField, // script to run for each child when its clicked onChildDoubleClick: ScriptField, // script to run for each child when its clicked onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view |