From 188e1e57860f58e9ebe3536a0e1f7cd84ea0db80 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 00:08:13 -0400 Subject: cleaned up link making. Documents don't add to the Undo stack when being created and Initializing is set. Links to text regions automatically update their link line endpoints even if autoMove isn't set. regularized initialization fields to avoid special cases about setting delegate keys without a leading "_" --- src/client/documents/Documents.ts | 227 ++++++++++----------- src/client/util/CurrentUserUtils.ts | 32 +-- src/client/util/LinkManager.ts | 1 - src/client/views/MarqueeAnnotator.tsx | 7 +- .../CollectionFreeFormLinkView.tsx | 13 +- src/client/views/nodes/DocumentView.tsx | 16 +- src/client/views/nodes/LinkAnchorBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 4 +- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/fields/Doc.ts | 8 +- src/fields/ScriptField.ts | 2 +- src/fields/util.ts | 6 +- 12 files changed, 156 insertions(+), 164 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8aa478cc1..24409933e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -58,7 +58,6 @@ 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"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; const path = require('path'); @@ -93,7 +92,7 @@ type PEVt = PEInfo | "none" | "all"; type DROPt = DAInfo | dropActionType; export class DocumentOptions { system?: BOOLt = new BoolInfo("is this a system created/owned doc"); - dropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto this doc "); + _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); color?: string; // foreground color data doc @@ -156,9 +155,11 @@ export class DocumentOptions { y?: number; z?: number; // whether document is in overlay (1) or not (0 or undefined) author?: string; - layoutKey?: string; + _layoutKey?: string; type?: string; title?: string; + "acl-Public"?: string; // public permissions + "_acl-Public"?: string; // public permissions version?: string; // version identifier for a document label?: string; hidden?: boolean; @@ -182,7 +183,7 @@ export class DocumentOptions { caption?: RichTextField; opacity?: number; defaultBackgroundColor?: string; - isLinkButton?: boolean; + _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked isFolder?: boolean; lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection @@ -206,9 +207,15 @@ export class DocumentOptions { dockingConfig?: string; annotationOn?: Doc; isPushpin?: boolean; - removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document + _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document iconShape?: string; // shapes of the fonticon border + layout_linkView?: Doc; // view template for a link document linkRelationship?: string; // type of relatinoship a link represents + linkDisplay?: boolean; // whether a link line should be dipslayed between the two link anchors + anchor1?: Doc; + anchor2?: Doc; + "anchor1-useLinkSmallAnchor"?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection) + "anchor2-useLinkSmallAnchor"?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection) ignoreClick?: boolean; onClick?: ScriptField; onDoubleClick?: ScriptField; @@ -304,31 +311,31 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, - options: { _height: 150, _xMargin: 10, _yMargin: 10 } + options: { _height: 150, _xMargin: 10, _yMargin: 10, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.SEARCH, { layout: { view: SearchBox, dataField: defaultDataKey }, - options: { _width: 400 } + options: { _width: 400, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.FILTER, { layout: { view: FilterBox, dataField: defaultDataKey }, - options: { _width: 400 } + options: { _width: 400, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.COLOR, { layout: { view: ColorBox, dataField: defaultDataKey }, - options: { _nativeWidth: 220, _nativeHeight: 300 } + options: { _nativeWidth: 220, _nativeHeight: 300, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.IMG, { layout: { view: ImageBox, dataField: defaultDataKey }, - options: {} + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.WEB, { layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true } + options: { _height: 300, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.COL, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { _panX: 0, _panY: 0, _viewScale: 1 } // , _width: 500, _height: 500 } + options: { _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.KVP, { layout: { view: KeyValueBox, dataField: defaultDataKey }, @@ -336,15 +343,15 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _currentTimecode: 0 }, + options: { _currentTimecode: 0, links: ComputedField.MakeFunction("links(self)") as any }, }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 35, backgroundColor: "lightGray" } + options: { _height: 35, backgroundColor: "lightGray", links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: defaultDataKey }, - options: { _curPage: 1, _fitWidth: true } + options: { _curPage: 1, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: defaultDataKey }, @@ -352,7 +359,11 @@ export namespace Docs { }], [DocumentType.LINK, { layout: { view: LinkBox, dataField: defaultDataKey }, - options: { _height: 150, description: "" } + options: { + _height: 150, description: "", links: ComputedField.MakeFunction("links(self)") as any, + linkBoxExcludedKeys: new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "_removeDropProperties", + "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]) + } }], [DocumentType.LINKDB, { data: new List(), @@ -365,53 +376,58 @@ export namespace Docs { options: { childDropAction: "alias", title: "Global Script Database" } }], [DocumentType.SCRIPTING, { - layout: { view: ScriptingBox, dataField: defaultDataKey } + layout: { view: ScriptingBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.YOUTUBE, { layout: { view: YoutubeBox, dataField: defaultDataKey } }], [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.FUNCPLOT, { layout: { view: FunctionPlotBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.BUTTON, { layout: { view: LabelBox, dataField: "onClick" }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.SLIDER, { layout: { view: SliderBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, - options: {} + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { _width: 40, _height: 40, borderRounding: "100%" }, + options: { _width: 40, _height: 40, borderRounding: "100%", links: ComputedField.MakeFunction("links(self)") as any }, }], - // [DocumentType.RECOMMENDATION, { - // layout: { view: RecommendationsBox, dataField: defaultDataKey }, - // options: { _width: 200, _height: 200 }, - // }], [DocumentType.WEBCAM, { - layout: { view: DashWebRTCVideo, dataField: defaultDataKey } + layout: { view: DashWebRTCVideo, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: defaultDataKey } }], [DocumentType.INK, { layout: { view: InkingStroke, dataField: defaultDataKey }, - options: { _fontFamily: "cursive", backgroundColor: "transparent" } + options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.SCREENSHOT, { layout: { view: ScreenshotBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.COMPARISON, { layout: { view: ComparisonBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.GROUPDB, { data: new List(), @@ -419,10 +435,12 @@ export namespace Docs { options: { childDropAction: "alias", title: "Global Group Database" } }], [DocumentType.GROUP, { - layout: { view: EmptyBox, dataField: defaultDataKey } + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.TEXTANCHOR, { - layout: { view: EmptyBox, dataField: defaultDataKey } + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { links: ComputedField.MakeFunction("links(self)") as any } }] ]); @@ -611,8 +629,6 @@ export namespace Docs { Scripting.addGlobal(Buxton); - const delegateKeys = ["x", "y", "system", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "removeDropProperties", "treeViewOpen"]; - /** * This function receives the relevant document prototype and uses * it to create a new of that base-level prototype, or the @@ -632,59 +648,33 @@ export namespace Docs { * main document. */ export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) { - const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys, "^_"); - - protoProps.system = delegateProps.system; - - if (!("author" in protoProps)) { - protoProps.author = Doc.CurrentUserEmail; - } - - if (!("creationDate" in protoProps)) { - protoProps.creationDate = new DateField; - protoProps[`${fieldKey}-lastModified`] = new DateField; - } - - protoProps.isPrototype = true; - - const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey, protoId); - const viewDoc = Doc.MakeDelegate(dataDoc, delegId); - + const viewKeys = ["x", "y", "system"]; + const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); + + dataProps.system = viewProps.system; + dataProps.isPrototype = true; + dataProps.author = Doc.CurrentUserEmail; + dataProps.creationDate = new DateField; + dataProps[`${fieldKey}-lastModified`] = new DateField; + dataProps["acl-Override"] = "None"; + dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; + dataProps[fieldKey] = data; // so that the list of annotations is already initialised, prevents issues in addonly. // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. + dataProps[fieldKey + "-annotations"] = new List(); + const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps as any, undefined, true); - dataDoc[fieldKey + "-annotations"] = new List(); - - proto.links = ComputedField.MakeFunction("links(self)"); - - viewDoc.author = Doc.CurrentUserEmail; - viewDoc.type !== DocumentType.LINK && viewDoc.type !== DocumentType.LABEL && DocUtils.MakeLinkToActiveAudio(viewDoc); - - viewDoc["acl-Public"] = dataDoc["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; - viewDoc["acl-Override"] = dataDoc["acl-Override"] = "None"; + const viewDoc = Doc.MakeDelegate(dataDoc, delegId); + viewProps.author = Doc.CurrentUserEmail; + viewProps.type !== DocumentType.LINK && viewDoc.type !== DocumentType.LABEL && DocUtils.MakeLinkToActiveAudio(viewDoc); + viewProps["acl-Override"] = "None"; + viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; + Doc.assign(viewDoc, viewProps, true, true); !Doc.IsSystem(dataDoc) && ![DocumentType.PDFANNO, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR, DocumentType.TEXTANCHOR].includes(proto.type as any) && - !dataDoc.isFolder && !protoProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc); - - return Doc.assign(viewDoc, delegateProps, true); - } + !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc); - /** - * This function receives the relevant top level document prototype - * and models a new instance by delegating from it. - * - * Note that it stores the data it recieves at the delegate's data key, - * and applies any document options to this new delegate / instance. - * @param proto the prototype from which to model this new delegate - * @param options initial values to apply to this new delegate - * @param value the data to store in this new delegate - */ - function MakeDataDelegate(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data", id: string | undefined = undefined) { - const deleg = Doc.MakeDelegate(proto, id); - if (value !== undefined) { - deleg[fieldKey] = value; - } - return Doc.assign(deleg, options as any); + return viewDoc; } export function ImageDocument(url: string, options: DocumentOptions = {}) { @@ -763,24 +753,15 @@ 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, { - 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 + const linkDoc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { + childDontRegisterViews: true, treeViewOpen: true, anchor1: source.doc, anchor2: target.doc, + _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); - const linkDocProto = Doc.GetProto(doc); - linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document. - linkDocProto.anchor1 = source.doc; - linkDocProto.anchor2 = target.doc; - - if (linkDocProto.linkBoxExcludedKeys === undefined) { - Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]); - Cast(linkDocProto.proto, Doc, null).layoutKey = undefined; - } - LinkManager.Instance.addLink(doc); + LinkManager.Instance.addLink(linkDoc); - return doc; + return linkDoc; } export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { @@ -1093,42 +1074,46 @@ export namespace DocUtils { if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return; if (target.doc === Doc.UserDoc()) return undefined; + const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { + if (showPopup) { + LinkManager.currentLink = linkDoc; - const makeLink = action((linkDoc: Doc, showPopup: number[]) => { - LinkManager.currentLink = linkDoc; + TaskCompletionBox.textDisplayed = "Link Created"; + TaskCompletionBox.popupX = showPopup[0]; + TaskCompletionBox.popupY = showPopup[1] - 33; + TaskCompletionBox.taskCompleted = true; - TaskCompletionBox.textDisplayed = "Link Created"; - TaskCompletionBox.popupX = showPopup[0]; - TaskCompletionBox.popupY = showPopup[1] - 33; - TaskCompletionBox.taskCompleted = true; + LinkDescriptionPopup.popupX = showPopup[0]; + LinkDescriptionPopup.popupY = showPopup[1]; + LinkDescriptionPopup.descriptionPopup = true; - LinkDescriptionPopup.popupX = showPopup[0]; - LinkDescriptionPopup.popupY = showPopup[1]; - LinkDescriptionPopup.descriptionPopup = true; + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.popupX + 200 > rect.width) { + LinkDescriptionPopup.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.popupY + 100 > rect.height) { + LinkDescriptionPopup.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.popupX + 200 > rect.width) { - LinkDescriptionPopup.popupX -= 190; - TaskCompletionBox.popupX -= 40; - } - if (LinkDescriptionPopup.popupY + 100 > rect.height) { - LinkDescriptionPopup.popupY -= 40; - TaskCompletionBox.popupY -= 40; + setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); } - - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); + return linkDoc; }); - const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id); - Doc.GetProto(linkDoc)["anchor1-useLinkSmallAnchor"] = source.doc.useLinkSmallAnchor; - Doc.GetProto(linkDoc)["anchor2-useLinkSmallAnchor"] = target.doc.useLinkSmallAnchor; - linkDoc.linkDisplay = true; - 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("generateLinkTitle(self)"); - showPopup && makeLink(linkDoc, showPopup); - return linkDoc; + return makeLink(Docs.Create.LinkDocument(source, target, { + title: ComputedField.MakeFunction("generateLinkTitle(self)") as any, + "anchor1-useLinkSmallAnchor": source.doc.useLinkSmallAnchor ? true : undefined, + "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined, + "acl-Public": SharingPermissions.Add, + "_acl-Public": SharingPermissions.Add, + layout_linkView: Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null), + linkDisplay: true, hidden: true, + linkRelationship, + _layoutKey: "layout_linkView", + description + }, id), showPopup); } export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { @@ -1357,7 +1342,7 @@ export namespace DocUtils { const pushpin = Docs.Create.FontIconDocument({ title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true, icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#ACCEF7", - _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null) + _width: 15, _height: 15, _xPadding: 0, _isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null) }); Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin); const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", ""); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e601d33f7..d7ab3564c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -483,12 +483,12 @@ export class CurrentUserUtils { title, toolTip, ignoreClick, - dropAction: "alias", + _dropAction: "alias", onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, onClick: click ? ScriptField.MakeScript(click) : undefined, backgroundColor, _hideContextMenu: true, - removeDropProperties: new List(["_stayInCollection"]), + _removeDropProperties: new List(["_stayInCollection"]), _stayInCollection: true, dragFactory, clickFactory, @@ -546,8 +546,8 @@ export class CurrentUserUtils { title, target, backgroundColor: "black", - dropAction: "alias", - removeDropProperties: new List(["dropAction", "_stayInCollection"]), + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), _width: 60, _height: 60, watchedDocuments, @@ -626,27 +626,27 @@ export class CurrentUserUtils { // sets up the main document for the mobile button static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { ...opts, - dropAction: undefined, removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, + _removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, borderRounding: "5px", boxShadow: "0 0", system: true }) as any as Doc // sets up the text container for the information contained within the mobile button static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { ...opts, - dropAction: undefined, removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, + _removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true }) as any as Doc // Sets up the title of the button static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { ...opts, - dropAction: undefined, title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", system: true + title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", system: true }) as any as Doc // Sets up the description of the button static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { ...opts, - dropAction: undefined, title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true + title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true }) as any as Doc @@ -660,11 +660,12 @@ export class CurrentUserUtils { ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, + _dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, clipboard: data.clipboard, onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, - backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, system: true + backgroundColor: data.backgroundColor, + _removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, system: true })); } @@ -721,7 +722,7 @@ export class CurrentUserUtils { // setup a color picker if (doc.myColorPicker === undefined) { const color = Docs.Create.ColorDocument({ - title: "color picker", _width: 300, dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true + title: "color picker", _width: 300, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true }); doc.myColorPicker = new PrefetchProxy(color); } @@ -856,16 +857,16 @@ export class CurrentUserUtils { })) as any as Doc static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, dropAction: "alias", removeDropProperties: new List(["dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true + ...opts, _dropAction: "alias", _removeDropProperties: new List(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true })) as any as Doc /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc) { if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), dontUndo: true, _stayInCollection: true, dropAction: "alias", _hideContextMenu: true, removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "click to undo", title: "undo", icon: "undo-alt", system: true }); + doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "click to undo", title: "undo", icon: "undo-alt", system: true }); } if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), dontUndo: true, _stayInCollection: true, dropAction: "alias", _hideContextMenu: true, removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "click to redo", title: "redo", icon: "redo-alt", system: true }); + doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), dontUndo: true, _stayInCollection: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "click to redo", title: "redo", icon: "redo-alt", system: true }); } if (doc.dockedBtns === undefined) { doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); @@ -906,9 +907,8 @@ export class CurrentUserUtils { if (!sharedDocs) { sharedDocs = Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", ignoreClick: true, _lockedPosition: true, + _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add }, sharingDocumentId + "outer", sharingDocumentId); - (sharedDocs as Doc)["acl-Public"] = Doc.GetProto(sharedDocs as Doc)["acl-Public"] = SharingPermissions.Add; } if (sharedDocs instanceof Doc) { sharedDocs.userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 0512864df..dde68401e 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,4 +1,3 @@ -import { runInAction } from "mobx"; import { computedFn } from "mobx-utils"; import { Doc, DocListCast, Opt } from "../../fields/Doc"; import { BoolCast, Cast, StrCast } from "../../fields/Types"; diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 1e97f9b41..2e50c9b6d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -98,13 +98,13 @@ export class MarqueeAnnotator extends React.Component { @undoBatch @action - makeAnnotationDocument = (color: string): Opt => { + makeAnnotationDocument = (color: string, isLinkButton?: boolean): Opt => { if (this.props.savedAnnotations.size === 0) return undefined; if ((Array.from(this.props.savedAnnotations.values())[0][0] as any).marqueeing) { const scale = this.props.scaling?.() || 1; const anno = Array.from(this.props.savedAnnotations.values())[0][0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; - const marqueeAnno = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); + const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, 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; @@ -144,9 +144,8 @@ export class MarqueeAnnotator extends React.Component { highlight = (color: string, isLinkButton: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); - const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color); + const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton); annotationDoc && this.props.addDocument(annotationDoc); - annotationDoc && (annotationDoc.isLinkButton = isLinkButton); return annotationDoc as Doc ?? undefined; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 51cb9387a..a8f5e6dd2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -40,7 +40,6 @@ export class CollectionFreeFormLinkView extends React.Component this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. - if (!linkDoc.linkAutoMove) return; const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; const adiv = acont.length ? acont[0] : A.ContentDiv; @@ -60,8 +59,10 @@ export class CollectionFreeFormLinkView extends React.Component, zoom: boolean, setPushpin: boolean): void => { this.Document.ignoreClick = false; - this.Document.isLinkButton = !this.Document.isLinkButton; - setPushpin && (this.Document.isPushpin = this.Document.isLinkButton); - if (this.Document.isLinkButton && !this.onClickHandler) { + this.Document._isLinkButton = !this.Document._isLinkButton; + setPushpin && (this.Document.isPushpin = this.Document._isLinkButton); + if (this.Document._isLinkButton && !this.onClickHandler) { this.Document.followLinkZoom = zoom; this.Document.followLinkLocation = location; } else { @@ -570,13 +570,13 @@ export class DocumentViewInternal extends DocComponent { this.Document.ignoreClick = false; - this.Document.isLinkButton = true; + this.Document._isLinkButton = true; this.Document.isPushpin = true; } @undoBatch @action followLinkOnClick = (location: Opt, zoom: boolean,): void => { this.Document.ignoreClick = false; - this.Document.isLinkButton = true; + this.Document._isLinkButton = true; this.Document.isPushpin = false; this.Document.followLinkZoom = zoom; this.Document.followLinkLocation = location; @@ -584,14 +584,14 @@ export class DocumentViewInternal extends DocComponent { this.Document.ignoreClick = false; - this.Document.isLinkButton = false; + this.Document._isLinkButton = false; this.Document.isPushpin = false; this.Document.onClick = this.layoutDoc.onClick = undefined; } @undoBatch noOnClick = (): void => { this.Document.ignoreClick = false; - this.Document.isLinkButton = false; + this.Document._isLinkButton = false; } @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document); @@ -630,7 +630,7 @@ export class DocumentViewInternal extends DocComponent { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); - alias.isLinkButton = undefined; + alias._isLinkButton = undefined; alias._layerTags = undefined; alias.layoutKey = "layout"; this.props.addDocTab(alias, "add:right"); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 575fbcf2e..0bded738a 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -152,8 +152,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff); @@ -371,7 +373,8 @@ export namespace Doc { * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key * to a potentially undefined field, where each entry in this mapping is optional. */ - export function assign(doc: Doc, fields: Partial>>, skipUndefineds: boolean = false) { + export function assign(doc: Doc, fields: Partial>>, skipUndefineds: boolean = false, isInitializing = false) { + isInitializing && (doc[Initializing] = true); for (const key in fields) { if (fields.hasOwnProperty(key)) { const value = fields[key]; @@ -380,6 +383,7 @@ export namespace Doc { } } } + isInitializing && (doc[Initializing] = false); return doc; } @@ -779,10 +783,12 @@ export namespace Doc { export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt { if (doc) { const delegate = new Doc(id, true); + delegate[Initializing] = true; delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate); title && (delegate.title = title); + delegate[Initializing] = false; return delegate; } return undefined; diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 9345ecde5..bd93bf5fb 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -150,7 +150,7 @@ export class ScriptField extends ObjectField { } public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) { const compiled = CompileScript(script, { - params: { this: Doc.name, self: Doc.name, _last_: "any", ...params }, + params: { this: Doc?.name || "Doc", self: Doc?.name || "Doc", _last_: "any", ...params }, typecheck: false, editable: true, addReturn: addReturn, diff --git a/src/fields/util.ts b/src/fields/util.ts index 6038a0534..36604c790 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; @@ -96,7 +96,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } - (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({ + !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({ redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue }); @@ -162,7 +162,7 @@ export function GetEffectiveAcl(target: any, user?: string): symbol { } function getPropAcl(target: any, prop: string | symbol | number) { - if (prop === UpdatingFromServer || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent + if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); } -- cgit v1.2.3-70-g09d2 From 26fc8ca2af1697069eb9ba6128abd013550daf2e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 01:15:11 -0400 Subject: minor tweaks from last --- src/client/documents/Documents.ts | 85 +++++++--------------- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- src/fields/util.ts | 8 +- 3 files changed, 36 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 24409933e..332f7304b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,7 @@ import { action, runInAction } from "mobx"; import { basename, extname } from "path"; import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { HtmlField } from "../../fields/HtmlField"; import { InkField } from "../../fields/InkField"; @@ -193,6 +193,8 @@ export class DocumentOptions { presProgressivize?: boolean; borderRounding?: string; boxShadow?: string; + data?: any; + baseProto?: boolean; // is this a base prototoype 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 @@ -210,6 +212,7 @@ export class DocumentOptions { _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document iconShape?: string; // shapes of the fonticon border layout_linkView?: Doc; // view template for a link document + layout_keyValue?: string; // view tempalte for key value docs linkRelationship?: string; // type of relatinoship a link represents linkDisplay?: boolean; // whether a link line should be dipslayed between the two link anchors anchor1?: Doc; @@ -444,8 +447,6 @@ export namespace Docs { }] ]); - // All document prototypes are initialized with at least these values - const defaultOptions: DocumentOptions = { x: 0, y: 0, _width: 300 }; // bcz: do we really want to set anything here? could also try to set in render() methods for types that need a default const suffix = "Proto"; /** @@ -486,30 +487,22 @@ export namespace Docs { * @param type */ const PrototypeMap: PrototypeMap = new Map(); - export function get(type: DocumentType): Doc { - return PrototypeMap.get(type)!; - } + export function get(type: DocumentType): Doc { return PrototypeMap.get(type)!; } /** * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. */ - export function MainLinkDocument() { - return Prototypes.get(DocumentType.LINKDB); - } + export function MainLinkDocument() { return Prototypes.get(DocumentType.LINKDB); } /** * A collection of all scripts in the database */ - export function MainScriptDocument() { - return Prototypes.get(DocumentType.SCRIPTDB); - } + export function MainScriptDocument() { return Prototypes.get(DocumentType.SCRIPTDB); } /** - * A collection of all groups in the database + * A collection of all user acl groups in the database */ - export function MainGroupDocument() { - return Prototypes.get(DocumentType.GROUPDB); - } + export function MainGroupDocument() { return Prototypes.get(DocumentType.GROUPDB); } /** * This is a convenience method that is used to initialize @@ -535,14 +528,12 @@ export namespace Docs { const title = prototypeId.toUpperCase().replace(upper, `_${upper}`); // synthesize the default options, the type and title from computed values and // whatever options pertain to this specific prototype - const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; - options.layout = layout.view?.LayoutString(layout.dataField); - const doc = Doc.assign(new Doc(prototypeId, true), { system: true, layoutKey: "layout", ...options } as any); - doc.data = template.data; - doc.layout_keyValue = KeyValueBox.LayoutString(""); - return doc; + const options: DocumentOptions = { + system: true, _layoutKey: "layout", title, type, baseProto: true, x: 0, y: 0, _width: 300, ...(template.options || {}), + layout: layout.view?.LayoutString(layout.dataField), data: template.data, layout_keyValue: KeyValueBox.LayoutString("") + }; + return Doc.assign(new Doc(prototypeId, true), options as any, undefined, true); } - } /** @@ -648,7 +639,7 @@ export namespace Docs { * main document. */ export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) { - const viewKeys = ["x", "y", "system"]; + const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); dataProps.system = viewProps.system; @@ -664,12 +655,11 @@ export namespace Docs { dataProps[fieldKey + "-annotations"] = new List(); const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps as any, undefined, true); - const viewDoc = Doc.MakeDelegate(dataDoc, delegId); viewProps.author = Doc.CurrentUserEmail; - viewProps.type !== DocumentType.LINK && viewDoc.type !== DocumentType.LABEL && DocUtils.MakeLinkToActiveAudio(viewDoc); viewProps["acl-Override"] = "None"; viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; - Doc.assign(viewDoc, viewProps, true, true); + const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true); + viewProps.type !== DocumentType.LINK && viewDoc.type !== DocumentType.LABEL && DocUtils.MakeLinkToActiveAudio(viewDoc); !Doc.IsSystem(dataDoc) && ![DocumentType.PDFANNO, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR, DocumentType.TEXTANCHOR].includes(proto.type as any) && !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc); @@ -679,22 +669,16 @@ export namespace Docs { export function ImageDocument(url: string, options: DocumentOptions = {}) { const imgField = new ImageField(new URL(url)); - const inst = InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: path.basename(url), ...options }); - let target = imgField.url.href; - if (new RegExp(window.location.origin).test(target)) { - const extension = path.extname(target); - target = `${target.substring(0, target.length - extension.length)}_o${extension}`; - } - return inst; + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: path.basename(url), ...options }); } + export function PresDocument(initial: List = new List(), options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options); } export function ScriptingDocument(script: Opt, options: DocumentOptions = {}, fieldKey?: string) { - const res = InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script, options); - fieldKey && res.proto instanceof Doc && (res.proto.layout = ScriptingBox.LayoutString(fieldKey)); - return res; + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script, + { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } export function VideoDocument(url: string, options: DocumentOptions = {}) { @@ -718,9 +702,8 @@ export namespace Docs { } export function AudioDocument(url: string, options: DocumentOptions = {}) { - const instance = InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), { useLinkSmallAnchor: true, ...options }); // hideLinkButton: false, useLinkSmallAnchor: false, - Doc.GetProto(instance).backgroundColor = ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'"); - return instance; + return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), + { useLinkSmallAnchor: true, ...options, backgroundColor: ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'") as any }); // hideLinkButton: false, useLinkSmallAnchor: false, } export function SearchDocument(options: DocumentOptions = {}) { @@ -766,6 +749,7 @@ export namespace Docs { export function InkDocument(color: string, tool: string, strokeWidth: string, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { const I = new Doc(); + I[Initializing] = true; I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString("data"); I.color = color; @@ -788,20 +772,16 @@ export namespace Docs { I.data = new InkField(points); I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; I["acl-Override"] = "None"; + I[Initializing] = false; return I; } export function PdfDocument(url: string, options: DocumentOptions = {}) { - const pdfProto = Prototypes.get(DocumentType.PDF); - pdfProto._fitWidth = true; // backward compatibility -- can be removed after db is reset - return InstanceFromProto(pdfProto, new PdfField(new URL(url)), { ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), { ...options }); } export function WebDocument(url: string, options: DocumentOptions = {}) { - const webProto = Prototypes.get(DocumentType.WEB); - webProto.scrollHeight = 100000; // backward compatibility -- can be removed after db is reset - webProto._fitWidth = true; // backward compatibility -- can be removed after db is reset - return InstanceFromProto(webProto, url ? new WebField(new URL(url)) : undefined, { _chromeStatus: url ? undefined : "enabled", _lockedTransform: true, ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, { _chromeStatus: url ? undefined : "enabled", _lockedTransform: true, ...options }); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { @@ -861,7 +841,6 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Multirow }); } - export function MasonryDocument(documents: Array, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Masonry }); } @@ -879,11 +858,6 @@ export namespace Docs { } export function ButtonDocument(options?: DocumentOptions) { - // const btn = InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" }); - // btn.layoutKey = "layout_onClick"; - // btn.height = 250; - // btn.width = 200; - // btn.layout_onClick = ScriptingBox.LayoutString("onClick"); return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" }); } @@ -891,7 +865,6 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SLIDER), undefined, { ...(options || {}) }); } - export function FontIconDocument(options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { hideLinkButton: true, ...(options || {}) }); } @@ -904,11 +877,9 @@ export namespace Docs { } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", _fitWidth: true, system: true }); const all = TreeDocument([], { title: "Off-Screen Tabs", freezeChildren: "add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true }); - Doc.GetProto(inst).data = new List([tabs, all]); - return inst; + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d64db1ee1..d44867541 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -667,7 +667,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { - const anchor = anchorDoc ?? Docs.Create.TextanchorDocument(); + const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) }); const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]); if (anchor !== anchorDoc) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -681,7 +681,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; - Doc.GetProto(anchor).title = this._editorView?.state.doc.textBetween(sel.from, sel.to); return anchor; } return anchorDoc ?? this.rootDoc; diff --git a/src/fields/util.ts b/src/fields/util.ts index 36604c790..86f68ea6e 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -98,7 +98,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({ redo: () => receiver[prop] = value, - undo: () => receiver[prop] = curValue + undo: () => { + // console.log("Undo: " + prop + " = " + curValue); // bcz: uncomment to log undo + receiver[prop] = curValue + } }); return true; } @@ -380,6 +383,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any lastValue = ObjectField.MakeCopy(receiver[prop]); }, undo: action(() => { + // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo diff.items.forEach((item: any) => { const ind = receiver[prop].indexOf(item.value ? item.value() : item); ind !== -1 && receiver[prop].splice(ind, 1); @@ -397,6 +401,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any lastValue = ObjectField.MakeCopy(receiver[prop]); }), undo: () => { + // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo diff.items.forEach((item: any) => { const ind = (prevValue as List).indexOf(item.value ? item.value() : item); ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); @@ -410,6 +415,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any lastValue = ObjectField.MakeCopy(receiver[prop]); }, undo: () => { + // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo receiver[prop] = ObjectField.MakeCopy(prevValue as List); lastValue = ObjectField.MakeCopy(receiver[prop]); } -- cgit v1.2.3-70-g09d2 From ed83093961b7e19df4675c9e2cf0a78eae333614 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 11:31:20 -0400 Subject: made linkanchorbuttons not show up next to pdf annos. gave pdfannos a baseProto. made textboxes dynamically remove hashtags when deleted from text. --- src/client/documents/Documents.ts | 7 +++++++ src/client/views/DocumentButtonBar.tsx | 22 +++++++++++++++++----- src/client/views/MarqueeAnnotator.tsx | 2 +- src/client/views/StyleProvider.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 ++++++++++++ 6 files changed, 40 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 332f7304b..a2850fd98 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -420,6 +420,10 @@ export namespace Docs { [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: defaultDataKey } }], + [DocumentType.PDFANNO, { + layout: { view: CollectionView, dataField: defaultDataKey }, + options: { hideLinkButton: true } + }], [DocumentType.INK, { layout: { view: InkingStroke, dataField: defaultDataKey }, options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any } @@ -801,6 +805,9 @@ export namespace Docs { documents.map(d => d.context = inst); return inst; } + export function PdfAnnoDocument(documents: Array, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.PDFANNO), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id); + } export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 4a1ec4d6c..07b419e5f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -184,6 +184,18 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ; } @computed + get followLinkButton() { + const targetDoc = this.view0?.props.Document; + return !targetDoc ? (null) : {"follow primary link on click"}}> +
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}> + +
+
; + } + @computed get pinButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) : (DocumentV const presPinWithViewIcon = ; const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) :
{"Pin with current view"}
}> -
this.pinWithView(targetDoc)}> +
this.pinWithView(targetDoc)}> {presPinWithViewIcon}
; @@ -244,8 +254,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) :
{"Open Sharing Manager"}
}>
SharingManager.Instance.open(this.view0, targetDoc)}> - +
; } @@ -357,6 +366,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
{this.contextButton}
*/} + {!SelectionManager.Views()?.some(v => v.allLinks.length) ? (null) :
+ {this.followLinkButton} +
}
{this.pinButton}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 2e50c9b6d..046f327fc 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -114,7 +114,7 @@ export class MarqueeAnnotator extends React.Component { return marqueeAnno; } - const textRegionAnno = Docs.Create.FreeformDocument([], { type: DocumentType.PDFANNO, annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 }); + const textRegionAnno = Docs.Create.PdfAnnoDocument([], { 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[] = []; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index d9ab0d397..5cbbcce79 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -74,7 +74,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt - {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors} + {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} {this.hideLinkButton ? (null) : } @@ -808,7 +808,7 @@ export class DocumentViewInternal extends DocComponent { + if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) { + accumTags.push(node.attrs.fieldKey) + } + }); + const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#")); + const added = accumTags.filter(tag => !curTags.includes(tag)); + const removed = curTags.filter(tag => !accumTags.includes(tag)); + removed.forEach(r => this.dataDoc[r] = undefined); + added.forEach(a => this.dataDoc[a] = a); + let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { this._applyingChange = this.fieldKey; -- cgit v1.2.3-70-g09d2 From f692e63595bf7492178cc55de41808595bbe73e2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 11:36:35 -0400 Subject: from last --- src/client/documents/Documents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a2850fd98..44747ebaf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -422,7 +422,7 @@ export namespace Docs { }], [DocumentType.PDFANNO, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { hideLinkButton: true } + options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true } }], [DocumentType.INK, { layout: { view: InkingStroke, dataField: defaultDataKey }, -- cgit v1.2.3-70-g09d2 From ee53c138015fcf232e424b61a4a9e5521e49ada9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 14:36:33 -0400 Subject: extended Doc.setFilter to allow replacing/appending a filter. fixed anchor unhighlighting in WebBox. reorged some properties on baseProtos (usLInkSmallAnchor etc) --- src/client/documents/Documents.ts | 6 ++--- src/client/util/DocumentManager.ts | 5 +++- src/client/views/DocComponent.tsx | 6 +++-- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/StyleProvider.tsx | 2 +- .../views/collections/CollectionTimeView.tsx | 2 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 ++------ src/client/views/nodes/FunctionPlotBox.tsx | 6 +---- src/client/views/nodes/PDFBox.tsx | 4 +-- src/client/views/nodes/WebBox.tsx | 30 ++++++++++++---------- src/client/views/pdf/PDFViewer.tsx | 4 +-- src/fields/Doc.ts | 7 +++-- 12 files changed, 38 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 44747ebaf..89071f75b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -411,7 +411,7 @@ export namespace Docs { }], [DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { _width: 40, _height: 40, borderRounding: "100%", links: ComputedField.MakeFunction("links(self)") as any }, + options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: ComputedField.MakeFunction("links(self)") as any }, }], [DocumentType.WEBCAM, { layout: { view: DashWebRTCVideo, dataField: defaultDataKey }, @@ -447,7 +447,7 @@ export namespace Docs { }], [DocumentType.TEXTANCHOR, { layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: ComputedField.MakeFunction("links(self)") as any, useSmallLinkButton: true, hideLinkButton: true } }] ]); @@ -873,7 +873,7 @@ export namespace Docs { } export function FontIconDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { hideLinkButton: true, ...(options || {}) }); + return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) }); } export function FilterDocument(options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.FILTER), undefined, { ...(options || {}) }); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 8cb80ecf9..34ff03335 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -229,7 +229,10 @@ export class DocumentManager { } Scripting.addGlobal(function DocFocusOrOpen(doc: any) { const dv = DocumentManager.Instance.getDocumentView(doc); - if (dv && dv?.props.Document === doc) dv.props.focus(doc, { willZoom: true }); + if (dv && dv.props.Document === doc) { + dv.props.focus(doc, { willZoom: true }); + Doc.linkFollowHighlight(dv?.props.Document, false); + } else { const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 876fbac54..86396dc4d 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -203,8 +203,10 @@ export function ViewBoxAnnotatableComponent

this.props.whenActiveChanged(this._isChildActive = isActive)); active = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool === InkTool.None && - (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0 || BoolCast((this.layoutDoc as any).forceActive)) ? true : false) - annotationsActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || (this.props.layerProvider?.(this.props.Document) === false && this.props.active()) || + (this.props.rootSelected(outsideReaction) || + this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + annotationsActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || + (this.props.layerProvider?.(this.props.Document) === false && this.props.active()) || (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) } return Component; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 07b419e5f..e248ef39a 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -214,7 +214,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV if (targetDoc) { TabDocView.PinDoc(targetDoc); const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const scrollable: boolean = (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking); + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); if (scrollable) { const scroll = targetDoc._scrollTop; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5cbbcce79..1ee99817c 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -74,7 +74,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc) { getAnchor = () => { const anchor = Docs.Create.TextanchorDocument({ title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, - useLinkSmallAnchor: true, - hideLinkButton: true, annotationOn: this.rootDoc }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 57dba0f75..c82baf691 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -921,7 +921,7 @@ export class CollectionFreeFormView extends CollectionSubView { - const anchor = Docs.Create.TextanchorDocument({ - title: StrCast(this.layoutDoc._viewType), - useLinkSmallAnchor: true, - hideLinkButton: true, - annotationOn: this.rootDoc - }); + const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); const proto = Doc.GetProto(anchor); proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index e8bec9676..4be7d1c37 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -37,11 +37,7 @@ export class FunctionPlotBox extends ViewBoxBaseComponent this.createGraph()); } getAnchor = () => { - const anchor = Docs.Create.TextanchorDocument({ - useLinkSmallAnchor: true, - hideLinkButton: true, - annotationOn: this.rootDoc - }); + const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc }); anchor.xRange = new List(Array.from(this._plot.options.xAxis.domain)); anchor.yRange = new List(Array.from(this._plot.options.yAxis.domain)); return anchor; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 0dbe0c917..d9c0fab02 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -101,8 +101,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent { const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), - useLinkSmallAnchor: true, - hideLinkButton: true, annotationOn: this.rootDoc, y: NumCast(this.layoutDoc._scrollTop), }); @@ -144,7 +142,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); return

Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey())}> + onClick={e => Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> {tag}
; } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ed412ad99..78bd6cbf6 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -205,8 +205,6 @@ export class WebBox extends ViewBoxAnnotatableComponent { const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), - useLinkSmallAnchor: true, - hideLinkButton: true, annotationOn: this.rootDoc, y: NumCast(this.layoutDoc._scrollTop), }); @@ -455,7 +453,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey())}> + onClick={e => Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> {tag}
; } @@ -541,7 +539,7 @@ export class WebBox extends ViewBoxAnnotatableComponent +
@@ -563,22 +561,26 @@ export class WebBox extends ViewBoxAnnotatableComponent {this.content} + CollectionView={undefined} + ScreenToLocalTransform={this.scrollXf} + renderDepth={this.props.renderDepth + 1} + scaling={returnOne} + //pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} + childPointerEvents={true} /> {this.annotationLayer}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 68b1452f8..0a46b2120 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -532,13 +532,13 @@ export class PDFViewer extends ViewBoxAnnotatableComponent + renderDepth={this.props.renderDepth + 1} + childPointerEvents={true} />
; } @computed get pdfViewerDiv() { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5bc770c15..953d96ffa 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -13,7 +13,6 @@ import { CollectionDockingView } from "../client/views/collections/CollectionDoc import { intersectRect, Utils } from "../Utils"; import { DateField } from "./DateField"; import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; -import { InkTool } from "./InkField"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; @@ -25,7 +24,6 @@ import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField"; import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; import JSZip = require("jszip"); -import { prefix } from "@fortawesome/free-regular-svg-icons"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -1069,9 +1067,9 @@ export namespace Doc { // filters document in a container collection: // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined - export function setDocFilter(target: Opt, key: string, value: any, modifiers: "remove" | "match" | "check" | "x", toggle?: boolean, fieldSuffix?: string) { + export function setDocFilter(target: Opt, key: string, value: any, modifiers: "remove" | "match" | "check" | "x", toggle?: boolean, fieldPrefix?: string, append: boolean = true) { const container = target ?? CollectionDockingView.Instance.props.Document; - const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters"; + const filterField = "_" + (fieldPrefix ? fieldPrefix + "-" : "") + "docFilters"; const docFilters = Cast(container[filterField], listSpec("string"), []); runInAction(() => { for (let i = 0; i < docFilters.length; i++) { @@ -1089,6 +1087,7 @@ export namespace Doc { if (!docFilters.length && modifiers === "match" && value === undefined) { container[filterField] = undefined; } else if (modifiers !== "remove") { + !append && (docFilters.length = 0); docFilters.push(key + ":" + value + ":" + modifiers); container[filterField] = new List(docFilters); } -- cgit v1.2.3-70-g09d2 From 5845674720497f87c5ccada7a188817f3c912c6e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 15:42:13 -0400 Subject: preparing to break sidebar out into its own component --- src/client/documents/Documents.ts | 2 +- src/client/views/nodes/PDFBox.tsx | 52 ++++---- src/client/views/nodes/WebBox.tsx | 258 ++++++++++++++++++-------------------- 3 files changed, 152 insertions(+), 160 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 89071f75b..86ca50316 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -642,7 +642,7 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) { + function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) { const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index d9c0fab02..37341253d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -38,7 +38,7 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); export class PDFBox extends ViewBoxAnnotatableComponent(PdfDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } private _searchString: string = ""; - private _initialScale: number = 0; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. + private _initialScrollTarget: Opt; private _displayPdfLive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); @@ -50,7 +50,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent; + componentWillUnmount() { this._selectReactionDisposer?.(); } + componentDidMount() { + this.props.setContentView?.(this); + this._selectReactionDisposer = reaction(() => this.props.isSelected(), + () => { + document.removeEventListener("keydown", this.onKeyDown); + this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown); + }, { fireImmediately: true }); + } + scrollFocus = (doc: Doc, smooth: boolean) => { if (DocListCast(this.rootDoc[this.sidebarKey()]).includes(doc)) { if (this.layoutDoc["_" + this.sidebarKey() + "-docFilters"]) { @@ -95,7 +103,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { @@ -107,15 +115,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.props.isSelected(), - () => { - document.removeEventListener("keydown", this.onKeyDown); - this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown); - }, { fireImmediately: true }); - } @action loaded = (nw: number, nh: number, np: number) => { @@ -125,6 +124,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.fieldKey + "-sidebar"; sidebarFiltersHeight = () => 50; sidebarTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.dataDoc), 0).scale(this.props.scaling?.() || 1); @@ -134,19 +134,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey()); sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey()); sidebarDocFilters = () => [...StrListCast(this.layoutDoc._docFilters), ...StrListCast(this.layoutDoc[this.sidebarKey() + "-docFilters"])]; - @computed get allTags() { + @computed get sidebarAllTags() { const keys = new Set(); DocListCast(this.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); return Array.from(keys.keys()).filter(key => key[0]).filter(key => !key.startsWith("_") && (key[0] === "#" || key[0] === key[0].toUpperCase())).sort(); } - renderTag = (tag: string) => { - const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); - return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> - {tag} -
; - } @computed get sidebarOverlay() { + const renderTag = (tag: string) => { + const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); + return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> + {tag} +
; + } return !this.layoutDoc._showSidebar ? (null) :
- {this.allTags.map(tag => this.renderTag(tag))} + {this.sidebarAllTags.map(renderTag)}
; } @@ -217,9 +217,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent { this._pdfViewer = pdfViewer; - if (this.initialScrollTarget) { - this.scrollFocus(this.initialScrollTarget, false); - this.initialScrollTarget = undefined; + if (this._initialScrollTarget) { + this.scrollFocus(this._initialScrollTarget, false); + this._initialScrollTarget = undefined; } } searchStringChanged = (e: React.ChangeEvent) => this._searchString = e.currentTarget.value; @@ -303,7 +303,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent target[tag] = tag); + this.sidebarAllTags.map(tag => target[tag] = tag); DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation"); this.sidebarAddDocument(target); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 78bd6cbf6..df50f7b90 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -44,15 +44,15 @@ const WebDocument = makeInterface(documentSchema); @observer export class WebBox extends ViewBoxAnnotatableComponent(WebDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } + private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _mainCont: React.RefObject = React.createRef(); private _outerRef: React.RefObject = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _annotationLayer: React.RefObject = React.createRef(); private _keyInput = React.createRef(); - @observable _scrollTimer: any; - @observable private _overlayAnnoInfo: Opt; private _initialScroll: Opt; - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); + @observable private _scrollTimer: any; + @observable private _overlayAnnoInfo: Opt; @observable private _marqueeing: number[] | undefined; @observable private _url: string = "hello"; @observable private _isAnnotating = false; @@ -60,11 +60,13 @@ export class WebBox extends ViewBoxAnnotatableComponent(); @observable private _scrollHeight = 1500; @computed get scrollHeight() { return this._scrollHeight; } + @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } + @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; } constructor(props: any) { super(props); - if (this.dataDoc[this.fieldKey] instanceof WebField) { + if (this.webField) { Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850); Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850); } @@ -74,6 +76,58 @@ export class WebBox extends ViewBoxAnnotatableComponent this._url = this.webField?.toString() || ""); + + this._disposers.selection = reaction(() => this.props.isSelected(), + selected => !selected && setTimeout(() => { + Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); + + if (this.webField?.href.indexOf("youtube") !== -1) { + const youtubeaspect = 400 / 315; + const nativeWidth = Doc.NativeWidth(this.layoutDoc); + const nativeHeight = Doc.NativeHeight(this.layoutDoc); + if (this.webField) { + if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) { + if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600); + Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect); + this.layoutDoc._height = this.layoutDoc[WidthSym]() / youtubeaspect; + } + } // else it's an HTMLfield + } else if (this.webField && !this.dataDoc.text) { + const result = await WebRequest.get(Utils.CorsProxy(this.webField.href)); + if (result) { + this.dataDoc.text = htmlToText.fromString(result.content); + } + } + + var quickScroll = true; + this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop), + (scrollTop) => { + if (quickScroll) { + this._initialScroll = scrollTop; + } + else { + const viewTrans = StrCast(this.Document._viewTransition); + 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.goTo(scrollTop, duration); + } + }, + { fireImmediately: true } + ); + quickScroll = false; + } + componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); + this._iframe?.removeEventListener('wheel', this.iframeWheel, true); + } + @action createTextAnnotation = (sel: Selection, selRange: Range) => { if (this._mainCont.current) { @@ -102,6 +156,32 @@ export class WebBox extends ViewBoxAnnotatableComponent this.urlEditor; // controls to be added to the top bar when a document of this type is selected + + scrollFocus = (doc: Doc, smooth: boolean) => { + if (doc !== this.rootDoc && this._outerRef.current) { + const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); + if (scrollTo !== undefined) { + const focusSpeed = smooth ? 500 : 0; + this._initialScroll !== undefined && (this._initialScroll = scrollTo); + this.goTo(scrollTo, focusSpeed); + return focusSpeed; + } + } + this._initialScroll = NumCast(doc.y); + return 0; + } + + getAnchor = () => { + const anchor = Docs.Create.TextanchorDocument({ + title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), + annotationOn: this.rootDoc, + y: NumCast(this.layoutDoc._scrollTop), + }); + this.addDocument(anchor); + return anchor; + } + @action iframeUp = (e: PointerEvent) => { if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) { @@ -151,8 +231,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { + if (!this._scrollTimer) { + this._scrollTimer = setTimeout(action(() => this._scrollTimer = undefined), 250); // this turns events off on the iframe which allows scrolling to change direction smoothly + } + } + @action setDashScrollTop = (scrollTop: number, timeout: number = 250) => { const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight()); @@ -177,90 +264,6 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (!this._scrollTimer) { - this._scrollTimer = setTimeout(action(() => this._scrollTimer = undefined), 250); // this turns events off on the iframe which allows scrolling to change direction smoothly - } - } - onWheel = (e: any) => { - e.stopPropagation(); - e.preventDefault(); - } - onScroll = (e: any) => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0); - scrollFocus = (doc: Doc, smooth: boolean) => { - if (doc !== this.rootDoc && this._outerRef.current) { - const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); - if (scrollTo !== undefined) { - const focusSpeed = smooth ? 500 : 0; - this._initialScroll !== undefined && (this._initialScroll = scrollTo); - this.goTo(scrollTo, focusSpeed); - return focusSpeed; - } - } - this._initialScroll = NumCast(doc.y); - return 0; - } - - getAnchor = () => { - const anchor = Docs.Create.TextanchorDocument({ - title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), - annotationOn: this.rootDoc, - y: NumCast(this.layoutDoc._scrollTop), - }); - this.addDocument(anchor); - return anchor; - } - - async 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. - - const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField); - runInAction(() => this._url = urlField?.url.toString() || ""); - - this._disposers.selection = reaction(() => this.props.isSelected(), - selected => !selected && setTimeout(() => { - Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - })); - - const field = Cast(this.rootDoc[this.props.fieldKey], WebField); - if (field?.url.href.indexOf("youtube") !== -1) { - const youtubeaspect = 400 / 315; - const nativeWidth = Doc.NativeWidth(this.layoutDoc); - const nativeHeight = Doc.NativeHeight(this.layoutDoc); - if (field) { - if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) { - if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600); - Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect); - this.layoutDoc._height = this.layoutDoc[WidthSym]() / youtubeaspect; - } - } // else it's an HTMLfield - } else if (field?.url && !this.dataDoc.text) { - const result = await WebRequest.get(Utils.CorsProxy(field.url.href)); - if (result) { - this.dataDoc.text = htmlToText.fromString(result.content); - } - } - - var quickScroll = true; - this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop), - (scrollTop) => { - if (quickScroll) { - this._initialScroll = scrollTop; - } - else { - const viewTrans = StrCast(this.Document._viewTransition); - 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.goTo(scrollTop, duration); - } - }, - { fireImmediately: true } - ); - quickScroll = false; - } goTo = (scrollTop: number, duration: number) => { if (this._outerRef.current) { @@ -275,11 +278,6 @@ export class WebBox extends ViewBoxAnnotatableComponent disposer?.()); - this._iframe?.removeEventListener('wheel', this.iframeWheel, true); - } - @action forward = () => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null); @@ -312,12 +310,13 @@ export class WebBox extends ViewBoxAnnotatableComponent { + submitURL = (newUrl?: string) => { + if (!newUrl) return; if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl; try { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null); - const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString(); + const url = this.webField?.toString(); if (url) { if (history === undefined) { this.dataDoc[this.fieldKey + "-history"] = new List([url]); @@ -336,7 +335,6 @@ export class WebBox extends ViewBoxAnnotatableComponent this.urlEditor; onWebUrlDrop = (e: React.DragEvent) => { const { dataTransfer } = e; const html = dataTransfer.getData("text/html"); @@ -371,12 +369,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}> GO - - + + ); @@ -391,6 +385,19 @@ export class WebBox extends ViewBoxAnnotatableComponent { + if (!e.altKey && e.button === 0 && this.active(true)) { + this._marqueeing = [e.clientX, e.clientY]; + this.props.select(false); + } + } + @action finishMarquee = (x?: number, y?: number) => { + this._marqueeing = undefined; + this._isAnnotating = false; + x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false); + } + @computed get urlContent() { const field = this.dataDoc[this.props.fieldKey]; @@ -424,7 +431,7 @@ export class WebBox extends ViewBoxAnnotatableComponent target[tag] = tag); + this.sidebarAllTags.map(tag => target[tag] = tag); DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation"); this.sidebarAddDocument(target); } @@ -445,19 +452,19 @@ export class WebBox extends ViewBoxAnnotatableComponent boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey()); sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey()); sidebarDocFilters = () => [...StrListCast(this.layoutDoc._docFilters), ...StrListCast(this.layoutDoc[this.sidebarKey() + "-docFilters"])]; - @computed get allTags() { + @computed get sidebarAllTags() { const keys = new Set(); DocListCast(this.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); return Array.from(keys.keys()).filter(key => key[0]).filter(key => !key.startsWith("_") && (key[0] === "#" || key[0] === key[0].toUpperCase())).sort(); } - renderTag = (tag: string) => { - const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); - return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> - {tag} -
; - } @computed get sidebarOverlay() { + const renderTag = (tag: string) => { + const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); + return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> + {tag} +
; + } return !this.layoutDoc._showSidebar ? (null) :
- {this.allTags.map(tag => this.renderTag(tag))} + {this.sidebarAllTags.map(renderTag)}
; } - @computed - get content() { + @computed get content() { return
{this.urlContent}
; } - showInfo = action((anno: Opt) => this._overlayAnnoInfo = anno); - @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } @computed get annotationLayer() { TraceMobx(); return
@@ -518,20 +522,8 @@ export class WebBox extends ViewBoxAnnotatableComponent; } - @action - onMarqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.active(true)) { - this._marqueeing = [e.clientX, e.clientY]; - this.props.select(false); - } - } + showInfo = action((anno: Opt) => this._overlayAnnoInfo = anno); setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; - @action finishMarquee = (x?: number, y?: number) => { - this._marqueeing = undefined; - this._isAnnotating = false; - x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false); - } - panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); @@ -551,8 +543,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { e.stopPropagation(); e.preventDefault(); }} // block wheel events from propagating since they're handled by the iframe + onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} onPointerDown={this.onMarqueeDown} >
Date: Thu, 18 Mar 2021 16:27:17 -0400 Subject: broke sidebarAnnos into its own component --- src/client/views/SidebarAnnos.scss | 20 +++++++ src/client/views/SidebarAnnos.tsx | 104 +++++++++++++++++++++++++++++++++++++ src/client/views/nodes/PDFBox.scss | 21 +------- src/client/views/nodes/PDFBox.tsx | 101 ++++++++--------------------------- src/client/views/nodes/WebBox.scss | 20 ------- src/client/views/nodes/WebBox.tsx | 80 ++++++---------------------- 6 files changed, 163 insertions(+), 183 deletions(-) create mode 100644 src/client/views/SidebarAnnos.scss create mode 100644 src/client/views/SidebarAnnos.tsx (limited to 'src') diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss new file mode 100644 index 000000000..9eea83d59 --- /dev/null +++ b/src/client/views/SidebarAnnos.scss @@ -0,0 +1,20 @@ +.sidebarAnnos-tagList { + display: flex; + flex-direction: row; + overflow: auto; + flex-flow: row; + flex-wrap: wrap; + .sidebarAnnos-filterTag, .sidebarAnnos-filterTag-active { + font-weight: bold; + padding-left: 6; + padding-right: 6; + box-shadow: black 1px 1px 4px; + border-radius: 5; + margin: 2; + height: 20; + background-color: lightgrey; + } + .sidebarAnnos-filterTag-active { + background-color: white; + } +} \ No newline at end of file diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx new file mode 100644 index 000000000..026942999 --- /dev/null +++ b/src/client/views/SidebarAnnos.tsx @@ -0,0 +1,104 @@ +import { computed } from 'mobx'; +import { observer } from "mobx-react"; +import { Doc, DocListCast, StrListCast } from "../../fields/Doc"; +import { List } from '../../fields/List'; +import { NumCast } from '../../fields/Types'; +import { emptyFunction, OmitKeys, returnOne, returnTrue, returnZero } from '../../Utils'; +import { Transform } from '../util/Transform'; +import { CollectionStackingView } from './collections/CollectionStackingView'; +import { CollectionViewType } from './collections/CollectionView'; +import { FieldViewProps } from './nodes/FieldView'; +import { SearchBox } from './search/SearchBox'; +import "./SidebarAnnos.scss"; +import { StyleProp } from './StyleProvider'; +import React = require("react"); + +interface extraProps { + fieldKey: string; + layoutDoc: Doc; + rootDoc: Doc; + dataDoc: Doc; + annotationsActive: (outsideReaction: boolean) => boolean; + whenActiveChanged: (isActive: boolean) => void; + ScreenToLocalTransform: () => Transform; + addDocument: (doc: (Doc | Doc[]), suffix: string) => boolean; + removeDocument: (doc: (Doc | Doc[]), suffix: string) => boolean; + moveDocument: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string) => boolean; +} +@observer +export class SidebarAnnos extends React.Component { + @computed get allHashtags() { + const keys = new Set(); + DocListCast(this.props.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); + return Array.from(keys.keys()).filter(key => key[0]).filter(key => !key.startsWith("_") && (key[0] === "#" || key[0] === key[0].toUpperCase())).sort(); + } + get filtersKey() { return "_" + this.sidebarKey() + "-docFilters"; } + + makeDocUnfiltered = (doc: Doc) => { + if (DocListCast(this.props.rootDoc[this.sidebarKey()]).includes(doc)) { + if (this.props.layoutDoc[this.filtersKey]) { + this.props.layoutDoc[this.filtersKey] = new List(); + return true; + } + } + return false; + } + sidebarKey = () => this.props.fieldKey + "-sidebar"; + filtersHeight = () => 50; + screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1); + panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth); + panelHeight = () => this.props.PanelHeight() - this.filtersHeight() - 20; + addDocument = (doc: Doc | Doc[]) => this.props.addDocument(doc, this.sidebarKey()); + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey()); + removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey()); + docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])]; + + render() { + const renderTag = (tag: string) => { + const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`); + return
Doc.setDocFilter(this.props.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> + {tag} +
; + } + return !this.props.layoutDoc._showSidebar ? (null) : +
+
+ +
+
+ {this.allHashtags.map(renderTag)} +
+
; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 564873cf5..d1dca5c68 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -189,26 +189,7 @@ } } - .pdfBox-tagList { - display: flex; - flex-direction: row; - overflow: auto; - flex-flow: row; - flex-wrap: wrap; - .pdfBox-filterTag, .pdfBox-filterTag-active { - font-weight: bold; - padding-left: 6; - padding-right: 6; - box-shadow: black 1px 1px 4px; - border-radius: 5; - margin: 2; - height: 20; - background-color: lightgrey; - } - .pdfBox-filterTag-active { - background-color: white; - } - } + .pdfBox-title-outer { width: 100%; height: 100%; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 37341253d..81ef2472a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -6,30 +6,27 @@ import "pdfjs-dist/web/pdf_viewer.css"; import { Doc, DocListCast, Opt, StrListCast, WidthSym } from "../../../fields/Doc"; import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, OmitKeys, returnOne, returnTrue, returnZero, Utils } from '../../../Utils'; +import { Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch } from '../../util/UndoManager'; import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { CollectionStackingView } from '../collections/CollectionStackingView'; import { CollectionViewType } from '../collections/CollectionView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { PDFViewer } from "../pdf/PDFViewer"; -import { SearchBox } from '../search/SearchBox'; -import { StyleProp } from '../StyleProvider'; +import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { DocFocusOptions } from './DocumentView'; -import { List } from '../../../fields/List'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -43,6 +40,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent(); private _selectReactionDisposer: IReactionDisposer | undefined; + private _sidebarRef = React.createRef(); @observable private _searching: boolean = false; @observable private _pdf: Opt; @@ -97,12 +95,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - if (DocListCast(this.rootDoc[this.sidebarKey()]).includes(doc)) { - if (this.layoutDoc["_" + this.sidebarKey() + "-docFilters"]) { - this.layoutDoc["_" + this.sidebarKey() + "-docFilters"] = new List(); - return 1; - } - } + if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; this._initialScrollTarget = doc; return this._pdfViewer?.scrollFocus(doc, smooth); } @@ -125,69 +118,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.fieldKey + "-sidebar"; - sidebarFiltersHeight = () => 50; - sidebarTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.dataDoc), 0).scale(this.props.scaling?.() || 1); - sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth); - sidebarHeight = () => this.props.PanelHeight() - this.sidebarFiltersHeight() - 20; - sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.sidebarKey()); - sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey()); - sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey()); - sidebarDocFilters = () => [...StrListCast(this.layoutDoc._docFilters), ...StrListCast(this.layoutDoc[this.sidebarKey() + "-docFilters"])]; - @computed get sidebarAllTags() { - const keys = new Set(); - DocListCast(this.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys.keys()).filter(key => key[0]).filter(key => !key.startsWith("_") && (key[0] === "#" || key[0] === key[0].toUpperCase())).sort(); - } - @computed get sidebarOverlay() { - const renderTag = (tag: string) => { - const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); - return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> - {tag} -
; - } - return !this.layoutDoc._showSidebar ? (null) : -
-
- -
-
- {this.sidebarAllTags.map(renderTag)} -
-
; - } - public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); }; public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }; public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }; @@ -281,6 +211,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent
); } + sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth); + specificContextMenu = (e: React.MouseEvent): void => { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); @@ -294,7 +226,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - this.Document._showSidebar = true; + this.props.Document._showSidebar = true; const startup = StrListCast(this.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" "); const target = Docs.Create.TextDocument(startup, { title: "anno", @@ -303,9 +235,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent target[tag] = tag); + //this.sidebarAllTags.map(tag => target[tag] = tag); DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation"); - this.sidebarAddDocument(target); + this._sidebarRef.current?.addDocument(target); } @computed get renderTitleBox() { @@ -316,6 +248,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent
; } + @computed get renderPdfView() { TraceMobx(); const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); @@ -335,7 +268,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent - {this.sidebarOverlay} + {this.settingsPanel()} ; } diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index cdff4aa74..ca82c049c 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -18,26 +18,6 @@ border-radius: 3px; pointer-events: all; } - .webBox-tagList { - display: flex; - flex-direction: row; - overflow: auto; - flex-flow: row; - flex-wrap: wrap; - .webBox-filterTag, .webBox-filterTag-active { - font-weight: bold; - padding-left: 6; - padding-right: 6; - box-shadow: black 1px 1px 4px; - border-radius: 5; - margin: 2; - height: 20; - background-color: lightgrey; - } - .webBox-filterTag-active { - background-color: white; - } - } .pdfViewerDash-dragAnnotationBox { position: absolute; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index df50f7b90..2a0fc47b4 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -36,6 +36,7 @@ import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { LinkDocPreview } from "./LinkDocPreview"; import "./WebBox.scss"; import React = require("react"); +import { SidebarAnnos } from "../SidebarAnnos"; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -51,6 +52,7 @@ export class WebBox extends ViewBoxAnnotatableComponent = React.createRef(); private _keyInput = React.createRef(); private _initialScroll: Opt; + private _sidebarRef = React.createRef(); @observable private _scrollTimer: any; @observable private _overlayAnnoInfo: Opt; @observable private _marqueeing: number[] | undefined; @@ -159,6 +161,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.urlEditor; // controls to be added to the top bar when a document of this type is selected scrollFocus = (doc: Doc, smooth: boolean) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; if (doc !== this.rootDoc && this._outerRef.current) { const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); if (scrollTo !== undefined) { @@ -431,9 +434,9 @@ export class WebBox extends ViewBoxAnnotatableComponent target[tag] = tag); + //this.sidebarAllTags.map(tag => target[tag] = tag); DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation"); - this.sidebarAddDocument(target); + this._sidebarRef.current?.addDocument(target); } toggleSidebar = () => { if (this.layoutDoc.nativeWidth === this.layoutDoc[this.fieldKey + "-nativeWidth"]) { @@ -443,68 +446,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.fieldKey + "-sidebar"; - sidebarFiltersHeight = () => 50; - sidebarTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.dataDoc), 0).scale(this.props.scaling?.() || 1); sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth); - sidebarHeight = () => this.props.PanelHeight() - this.sidebarFiltersHeight() - 20; - sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.sidebarKey()); - sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey()); - sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey()); - sidebarDocFilters = () => [...StrListCast(this.layoutDoc._docFilters), ...StrListCast(this.layoutDoc[this.sidebarKey() + "-docFilters"])]; - @computed get sidebarAllTags() { - const keys = new Set(); - DocListCast(this.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys.keys()).filter(key => key[0]).filter(key => !key.startsWith("_") && (key[0] === "#" || key[0] === key[0].toUpperCase())).sort(); - } - @computed get sidebarOverlay() { - const renderTag = (tag: string) => { - const active = StrListCast(this.rootDoc[this.sidebarKey() + "-docFilters"]).includes(`${tag}:${tag}:check`); - return
Doc.setDocFilter(this.rootDoc, tag, tag, "check", true, this.sidebarKey(), e.shiftKey)}> - {tag} -
; - } - return !this.layoutDoc._showSidebar ? (null) : -
-
- -
-
- {this.sidebarAllTags.map(renderTag)} -
-
; - } @computed get content() { return
e.stopPropagation()} onClick={e => this.toggleSidebar()} > - {this.sidebarOverlay} +
); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 95048247c59ae04b7c35db70e9888f7bc851a3dc Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Mar 2021 23:12:42 -0400 Subject: more cleanup of webbox/pdfbox/sidebarAnnos --- src/client/views/SidebarAnnos.tsx | 23 +++++- src/client/views/nodes/PDFBox.scss | 11 ++- src/client/views/nodes/PDFBox.tsx | 153 +++++++++++++++---------------------- src/client/views/nodes/WebBox.tsx | 67 +++++----------- src/client/views/pdf/PDFViewer.tsx | 2 +- 5 files changed, 111 insertions(+), 145 deletions(-) (limited to 'src') diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 026942999..bbe874eac 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -1,13 +1,16 @@ import { computed } from 'mobx'; import { observer } from "mobx-react"; import { Doc, DocListCast, StrListCast } from "../../fields/Doc"; +import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; -import { NumCast } from '../../fields/Types'; +import { NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, OmitKeys, returnOne, returnTrue, returnZero } from '../../Utils'; +import { Docs, DocUtils } from '../documents/Documents'; import { Transform } from '../util/Transform'; import { CollectionStackingView } from './collections/CollectionStackingView'; import { CollectionViewType } from './collections/CollectionView'; import { FieldViewProps } from './nodes/FieldView'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { SearchBox } from './search/SearchBox'; import "./SidebarAnnos.scss"; import { StyleProp } from './StyleProvider'; @@ -27,6 +30,7 @@ interface extraProps { } @observer export class SidebarAnnos extends React.Component { + _stackRef = React.createRef(); @computed get allHashtags() { const keys = new Set(); DocListCast(this.props.rootDoc[this.sidebarKey()]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); @@ -34,6 +38,21 @@ export class SidebarAnnos extends React.Component { } get filtersKey() { return "_" + this.sidebarKey() + "-docFilters"; } + anchorMenuClick = (anchor: Doc) => { + this.props.layoutDoc._showSidebar = true; + const startup = StrListCast(this.props.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" "); + const target = Docs.Create.TextDocument(startup, { + title: "anno", + annotationOn: this.props.rootDoc, _width: 200, _height: 50, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), + _fontFamily: StrCast(Doc.UserDoc().fontFamily) + }); + FormattedTextBox.SelectOnLoad = target[Id]; + FormattedTextBox.DontSelectInitialText = true; + this.allHashtags.map(tag => target[tag] = tag); + DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation"); + this.addDocument(target); + this._stackRef.current?.focusDocument(target); + } makeDocUnfiltered = (doc: Doc) => { if (DocListCast(this.props.rootDoc[this.sidebarKey()]).includes(doc)) { if (this.props.layoutDoc[this.filtersKey]) { @@ -69,7 +88,7 @@ export class SidebarAnnos extends React.Component { height: "100%" }}>
- ; - private _displayPdfLive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); private _selectReactionDisposer: IReactionDisposer | undefined; @@ -46,21 +41,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent; @observable private _pageControls = false; + @computed get pdfUrl() { return Cast(this.dataDoc[this.props.fieldKey], PdfField); } + constructor(props: any) { super(props); const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927; const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200; !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); - const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - if (pdfUrl) { - if (PDFBox.pdfcache.get(pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(pdfUrl.url.href)); - else if (PDFBox.pdfpromise.get(pdfUrl.url.href)) PDFBox.pdfpromise.get(pdfUrl.url.href)?.then(action(pdf => this._pdf = pdf)); + if (this.pdfUrl) { + if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)); + else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action(pdf => this._pdf = pdf)); } const backup = "oldPath"; - const { Document } = this.props; - const pdf = Cast(this.dataDoc[this.props.fieldKey], PdfField); - const href = pdf?.url?.href; + const href = this.pdfUrl?.url.href; if (href) { const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g; const matches = pathCorrectionTest.exec(href); @@ -72,7 +66,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { this._pdfViewer?.search(string, fwd); }; - public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }; - public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }; + public search = (string: string, fwd: boolean) => this._pdfViewer?.search(string, fwd); + public prevAnnotation = () => this._pdfViewer?.prevAnnotation(); + public nextAnnotation = () => this._pdfViewer?.nextAnnotation(); public backPage = () => { this.Document._curPage = (this.Document._curPage || 1) - 1; return true; }; public forwardPage = () => { this.Document._curPage = (this.Document._curPage || 1) + 1; return true; }; - public gotoPage = (p: number) => { this.Document._curPage = p; }; + public gotoPage = (p: number) => this.Document._curPage = p; @undoBatch onKeyDown = action((e: KeyboardEvent) => { let processed = false; - if (e.key === "f" && e.ctrlKey) { - this._searching = true; - setTimeout(() => this._searchRef.current && this._searchRef.current.focus(), 100); - processed = true; - } - if (e.key === "PageDown") processed = this.forwardPage(); - if (e.key === "PageUp") processed = this.backPage(); - if (e.target instanceof HTMLInputElement || this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { - if (e.key === "ArrowDown" || e.key === "ArrowRight") processed = this.forwardPage(); - if (e.key === "ArrowUp" || e.key === "ArrowLeft") processed = this.backPage(); + switch (e.key) { + case "f": if (e.ctrlKey) { + setTimeout(() => this._searchRef.current?.focus(), 100); + this._searching = processed = true; + } + break; + case "PageDown": processed = this.forwardPage(); break; + case "PageUp": processed = this.backPage(); break; } if (processed) { e.stopImmediatePropagation(); @@ -154,92 +146,72 @@ export class PDFBox extends ViewBoxAnnotatableComponent) => this._searchString = e.currentTarget.value; toggleSidebar = () => { - if (this.layoutDoc.nativeWidth === this.layoutDoc[this.fieldKey + "-nativeWidth"]) { - this.layoutDoc.nativeWidth = 250 + NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - } else { - this.layoutDoc.nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - } - this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"])); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); + const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]); + this.layoutDoc.nativeWidth = nativeWidth + (this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0); + this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (nativeWidth / nativeHeight); } settingsPanel() { const pageBtns = <> - - ; const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; const curPage = this.Document._curPage || 1; return !this.active() ? (null) : - (
e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true} +
[KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}> -
e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> +
e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> - +
-
- 99 ? 4 : 3}ch`, pointerEvents: "all" }} onChange={e => this.Document._curPage = Number(e.currentTarget.value)} - style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }} onClick={action(() => this._pageControls = !this._pageControls)} /> {this._pageControls ? pageBtns : (null)}
- -
); +
; } sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth); - specificContextMenu = (e: React.MouseEvent): void => { - const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); const funcs: ContextMenuProps[] = []; - pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" }); + funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(this.pdfUrl.url.pathname), icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Annotation View ", event: () => this.Document._showSidebar = !this.Document._showSidebar, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } - anchorMenuClick = (anchor: Doc) => { - this.props.Document._showSidebar = true; - const startup = StrListCast(this.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" "); - const target = Docs.Create.TextDocument(startup, { - title: "anno", - annotationOn: this.rootDoc, _width: 200, _height: 50, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), - _fontFamily: StrCast(Doc.UserDoc().fontFamily) - }); - FormattedTextBox.SelectOnLoad = target[Id]; - FormattedTextBox.DontSelectInitialText = true; - //this.sidebarAllTags.map(tag => target[tag] = tag); - DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup", "annotation"); - this._sidebarRef.current?.addDocument(target); - } - @computed get renderTitleBox() { const classname = "pdfBox" + (this.active() ? "-interactive" : ""); return
@@ -251,15 +223,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}> + style={{ + height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? + NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined + }}>
>(); render() { TraceMobx(); - if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0)) { - this._displayPdfLive = true; - } - if (this._displayPdfLive) { - if (this._pdf) return this.renderPdfView; + if (this._pdf) return this.renderPdfView; - const href = Cast(this.dataDoc[this.props.fieldKey], PdfField, null)?.url.href; - if (href) { - if (PDFBox.pdfcache.get(href)) setTimeout(action(() => this._pdf = PDFBox.pdfcache.get(href))); - else { - if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); - PDFBox.pdfpromise.get(href)?.then(action(pdf => PDFBox.pdfcache.set(href, this._pdf = pdf))); - } + const href = this.pdfUrl?.url.href; + if (href) { + if (PDFBox.pdfcache.get(href)) setTimeout(action(() => this._pdf = PDFBox.pdfcache.get(href))); + else { + if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); + PDFBox.pdfpromise.get(href)?.then(action(pdf => PDFBox.pdfcache.set(href, this._pdf = pdf))); } } return this.renderTitleBox; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2a0fc47b4..adfe5d694 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -8,19 +8,17 @@ import { Id } from "../../../fields/FieldSymbols"; import { HtmlField } from "../../../fields/HtmlField"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; -import { listSpec, makeInterface } from "../../../fields/Schema"; +import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { SnappingManager } from "../../util/SnappingManager"; import { undoBatch } from "../../util/UndoManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { CollectionStackingView } from "../collections/CollectionStackingView"; -import { CollectionViewType } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; @@ -29,14 +27,11 @@ import { LightboxView } from "../LightboxView"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; import { AnchorMenu } from "../pdf/AnchorMenu"; import { Annotation } from "../pdf/Annotation"; -import { SearchBox } from "../search/SearchBox"; -import { StyleProp } from "../StyleProvider"; +import { SidebarAnnos } from "../SidebarAnnos"; import { FieldView, FieldViewProps } from './FieldView'; -import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { LinkDocPreview } from "./LinkDocPreview"; import "./WebBox.scss"; import React = require("react"); -import { SidebarAnnos } from "../SidebarAnnos"; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -110,9 +105,7 @@ export class WebBox extends ViewBoxAnnotatableComponent NumCast(this.layoutDoc._scrollTop), (scrollTop) => { - if (quickScroll) { - this._initialScroll = scrollTop; - } + if (quickScroll) this._initialScroll = scrollTop; else { const viewTrans = StrCast(this.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); @@ -151,11 +144,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.urlEditor; // controls to be added to the top bar when a document of this type is selected @@ -283,8 +273,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null); - const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null); + const future = StrListCast(this.dataDoc[this.fieldKey + "-future"]); + const history = StrListCast(this.dataDoc[this.fieldKey + "-history"]); if (future.length) { history.push(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!)); @@ -296,8 +286,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null); - const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null); + const future = StrListCast(this.dataDoc[this.fieldKey + "-future"]); + const history = StrListCast(this.dataDoc[this.fieldKey + "-history"]); if (history.length) { if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List([this._url]); else future.push(this._url); @@ -317,8 +307,8 @@ export class WebBox extends ViewBoxAnnotatableComponent; } else if (field instanceof WebField) { const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href; - // view =