From 21eb25198c27d65e398d11b018a8dc792297e35a Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Jun 2022 09:33:01 -0400 Subject: updated fontIconBadge to use 'viewed' list to decrement value when items are viewed. cleaned up how sharedDoc is setup in currentUserUtils to allow updates to not require rebuilding DB --- src/client/documents/Documents.ts | 10 ++- src/client/util/CurrentUserUtils.ts | 75 +++++++++++++--------- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/button/FontIconBadge.tsx | 32 ++++----- src/client/views/nodes/button/FontIconBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/fields/Doc.ts | 1 + src/fields/Types.ts | 4 ++ 8 files changed, 77 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b290b2d58..5f009573e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,6 +98,7 @@ export class DocumentOptions { allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); 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 ??? "); + userColor?: string; // color associated with a Dash user (seen in header fields of shared documents) color?: string; // foreground color data doc backgroundColor?: STRt = new StrInfo("background color for data doc"); _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); @@ -190,6 +191,10 @@ export class DocumentOptions { childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children + childContextMenuFilters?: List; + childContextMenuScripts?: List; + childContextMenuLabels?: List; + childContextMenuIcons?: List; hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; @@ -198,7 +203,6 @@ export class DocumentOptions { hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; - watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) @@ -250,7 +254,8 @@ export class DocumentOptions { numBtnType?: string; numBtnMax?: number; numBtnMin?: number; - switchToggle?: boolean; + switchToggle?: boolean; + badgeValue?: ScriptField; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded @@ -294,6 +299,7 @@ export class DocumentOptions { treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically + treeViewChildDoubleClick?: ScriptField; // // Action Button buttonMenu?: boolean; // whether a action button should be displayed diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 60bfc165b..b4e18a8bb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -7,7 +7,7 @@ import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; @@ -354,6 +354,7 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc) { + const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -361,7 +362,7 @@ export class CurrentUserUtils { { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue}, { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, ]; @@ -369,8 +370,9 @@ export class CurrentUserUtils { static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { - await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) => + await this.setupLinkDocs(doc, linkDatabaseId); + await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => Docs.Create.FontIconDocument({ icon, btnType: ButtonType.MenuButton, @@ -387,7 +389,7 @@ export class CurrentUserUtils { _removeDropProperties: new List(["dropAction", "_stayInCollection"]), _width: 60, _height: 60, - watchedDocuments, + badgeValue, onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) }) ); @@ -900,10 +902,7 @@ export class CurrentUserUtils { } // Sharing sidebar is where shared documents are contained - static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.myPublishedDocs === undefined) { - doc.myPublishedDocs = new List(); - } + static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { @@ -915,28 +914,41 @@ export class CurrentUserUtils { } doc.myLinkDatabase = new PrefetchProxy(linkDocs); } - // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document - if (doc.mySharedDocs === undefined) { - let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); - if (!sharedDocs) { - sharedDocs = Docs.Create.TreeDocument([], { - title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - _chromeHidden: true, boxShadow: "0 0", - dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" - }, sharingDocumentId + "outer", sharingDocumentId); - (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; - } - if (sharedDocs instanceof Doc) { - Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; - const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); - const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); - sharedDocs.childContextMenuFilters = new List([dashboardFilter!,]); - sharedDocs.childContextMenuScripts = new List([addToDashboards!,]); - sharedDocs.childContextMenuLabels = new List(["Add to Dashboards",]); - sharedDocs.childContextMenuIcons = new List(["user-plus",]); - } - doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc); + } + // A user's sharing document is where all documents that are shared to that user are placed. + // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field + // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents + static async setupSharedDocs(doc: Doc, sharingDocumentId: string) { + const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); + const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); + const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) + + const sharedDocOpts:DocumentOptions = { + title: "My Shared Docs", + userColor: "rgb(202, 202, 202)", + childContextMenuFilters: new List([dashboardFilter!,]), + childContextMenuScripts: new List([addToDashboards!,]), + childContextMenuLabels: new List(["Add to Dashboards",]), + childContextMenuIcons: new List(["user-plus",]), + treeViewChildDoubleClick: dblClkScript, + }; + const sharedRequiredDocOpts:DocumentOptions = { + "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar + _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, + explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + }; + + const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); + if (!(sharedDocs instanceof Doc)) { + doc.mySharedDocs = new PrefetchProxy( + Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); + } else { + Object.entries(sharedRequiredDocOpts).forEach(pair => { + const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc); + targetDoc[pair[0]] = pair[1]; + }); } } @@ -1044,6 +1056,7 @@ export class CurrentUserUtils { doc.savedFilters = new List(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; + doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8824750a3..59dc5671b 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -613,7 +613,7 @@ export class TreeView extends React.Component { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } - onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index cf86b5e07..df17d603f 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -6,31 +6,31 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils import { DragManager } from "../../../util/DragManager"; import "./FontIconBadge.scss"; -interface FontIconBadgeProps { - collection: Doc | undefined; +interface FontIconBadgeProps { + value: string | undefined; } @observer export class FontIconBadge extends React.Component { _notifsRef = React.createRef(); - onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const dragData = new DragManager.DocumentDragData([this.props.collection!]); - DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - return true; - }, - returnFalse, emptyFunction, false); - } + // onPointerDown = (e: React.PointerEvent) => { + // setupMoveUpEvents(this, e, + // (e: PointerEvent) => { + // const dragData = new DragManager.DocumentDragData([this.props.collection!]); + // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + // return true; + // }, + // returnFalse, emptyFunction, false); + // } render() { - if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read + if (this.props.value === undefined) return (null); return
-
0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} +
+ {this.props.value}
; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index a1b9023f3..97e6eddfe 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
{this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
); break; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f29b879b3..16a523b40 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -382,11 +382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - // creates links between terms in a document and documents which have a matching Id + // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6abc27b23..7b72787d1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 7e2aa5681..bf40a0d7b 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -76,6 +76,10 @@ export function Cast(field: FieldResult, ctor: T, defaultVal return defaultVal === null ? undefined : defaultVal; } +export function DocCast(field: FieldResult, defaultVal?: Doc) { + return Cast(field, Doc, null) ?? defaultVal; +} + export function NumCast(field: FieldResult, defaultVal: number | null = 0) { return Cast(field, "number", defaultVal); } -- cgit v1.2.3-70-g09d2