From d818ef151ca65008e5c6bb5e92b709decb3026d8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 14 Apr 2025 18:35:49 -0400 Subject: fixed how templates are expanded to avoid template sub-component conflicts by changing how field keys are named. fixed various Cast functions to be more typesafe by including undefined as part of return type. overhaul of Doc.MakeClone, MakeCopy, FindRefernces - makeClone is no longer async. fixed inlined docs in text docs. --- src/client/documents/DocUtils.ts | 41 +- src/client/documents/DocumentTypes.ts | 2 +- src/client/documents/Documents.ts | 8 +- src/client/util/CurrentUserUtils.ts | 37 +- src/client/util/DictationManager.ts | 2 +- src/client/util/DocumentManager.ts | 2 +- src/client/util/DragManager.ts | 40 +- src/client/util/DropConverter.ts | 6 +- src/client/util/Import & Export/ImageUtils.ts | 2 +- src/client/util/LinkFollower.ts | 32 +- src/client/util/LinkManager.ts | 43 +- src/client/util/SearchUtil.ts | 6 +- src/client/util/SettingsManager.tsx | 2 +- src/client/views/DocComponent.tsx | 8 +- src/client/views/DocumentButtonBar.tsx | 8 +- src/client/views/FilterPanel.tsx | 4 +- src/client/views/GlobalKeyHandler.ts | 6 +- src/client/views/InkStrokeProperties.ts | 2 +- src/client/views/InkTranscription.tsx | 6 +- src/client/views/LightboxView.tsx | 4 +- src/client/views/Main.tsx | 4 +- src/client/views/MainView.tsx | 42 +- src/client/views/PinFuncs.ts | 8 +- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesView.tsx | 8 +- src/client/views/StyleProvider.tsx | 12 +- src/client/views/StyleProviderQuiz.tsx | 2 +- src/client/views/TagsView.tsx | 6 +- src/client/views/animationtimeline/Timeline.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 3 +- .../views/collections/CollectionDockingView.tsx | 16 +- .../views/collections/CollectionNoteTakingView.tsx | 11 +- .../views/collections/CollectionStackingView.tsx | 12 +- .../CollectionStackingViewFieldColumn.tsx | 4 +- src/client/views/collections/CollectionSubView.tsx | 37 +- src/client/views/collections/TabDocView.tsx | 14 +- src/client/views/collections/TreeView.tsx | 20 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 31 +- .../collectionFreeForm/FaceCollectionBox.tsx | 2 +- .../collectionFreeForm/ImageLabelBox.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 6 +- .../collectionLinear/CollectionLinearView.tsx | 4 +- .../collectionSchema/CollectionSchemaView.tsx | 4 +- .../collectionSchema/SchemaTableCell.tsx | 2 +- src/client/views/global/globalScripts.ts | 37 +- src/client/views/newlightbox/NewLightboxView.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 100 ++-- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/IconTagBox.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 24 +- src/client/views/nodes/KeyValueBox.tsx | 8 +- src/client/views/nodes/KeyValuePair.tsx | 4 +- src/client/views/nodes/PDFBox.tsx | 2 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 2 +- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/ScriptingBox.tsx | 13 +- src/client/views/nodes/WebBox.tsx | 18 +- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 55 +- .../views/nodes/formattedText/RichTextMenu.tsx | 6 +- .../views/nodes/formattedText/RichTextRules.ts | 19 +- src/client/views/nodes/trails/PresBox.tsx | 187 +++--- src/client/views/nodes/trails/PresElementBox.scss | 308 ---------- src/client/views/nodes/trails/PresElementBox.tsx | 628 --------------------- src/client/views/nodes/trails/PresSlideBox.scss | 308 ++++++++++ src/client/views/nodes/trails/PresSlideBox.tsx | 628 +++++++++++++++++++++ src/client/views/nodes/trails/index.ts | 2 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 2 +- src/client/views/search/FaceRecognitionHandler.tsx | 4 +- src/client/views/search/SearchBox.tsx | 2 +- src/client/views/smartdraw/StickerPalette.tsx | 13 +- src/debug/Repl.tsx | 6 +- src/fields/Doc.ts | 486 ++++++++-------- src/fields/List.ts | 6 +- src/fields/Proxy.ts | 64 +-- src/fields/Schema.ts | 21 +- src/fields/ScriptField.ts | 12 +- src/fields/Types.ts | 91 +-- src/fields/util.ts | 10 +- 83 files changed, 1799 insertions(+), 1806 deletions(-) delete mode 100644 src/client/views/nodes/trails/PresElementBox.scss delete mode 100644 src/client/views/nodes/trails/PresElementBox.tsx create mode 100644 src/client/views/nodes/trails/PresSlideBox.scss create mode 100644 src/client/views/nodes/trails/PresSlideBox.tsx (limited to 'src') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 5a8230847..7845c80aa 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -55,13 +55,13 @@ export namespace DocUtils { const matchLink = (val: string, anchor: Doc) => { const linkedToExp = (val ?? '').split('='); if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val; - return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; + return DocCast(anchor[linkedToExp[0]]) && Field.toScriptString(DocCast(anchor[linkedToExp[0]])!) === linkedToExp[1]; }; // prettier-ignore return (value === Doc.FilterNone && !allLinks.length) || (value === Doc.FilterAny && !!allLinks.length) || - (allLinks.some(link => matchLink(value as string, DocCast(link.link_anchor_1)) || - matchLink(value as string, DocCast(link.link_anchor_2)) )); + (allLinks.some(link => (DocCast(link.link_anchor_1) && matchLink(value as string, DocCast(link.link_anchor_1)!)) || + (DocCast(link.link_anchor_2) && matchLink(value as string, DocCast(link.link_anchor_2)!)) )); } if (typeof value === 'string') { value = value.replace(`,${ClientUtils.noRecursionHack}`, ''); @@ -266,7 +266,7 @@ export namespace DocUtils { Object.keys(funcs) .filter(key => !key.endsWith('-setter')) .forEach(key => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key])); const func = funcs[key]; if (ScriptCast(cfield)?.script.originalScript !== func) { const setFunc = Cast(funcs[key + '-setter'], 'string', null); @@ -375,12 +375,13 @@ export namespace DocUtils { const documentList: ContextMenuProps[] = foo .filter(btnDoc => !btnDoc.hidden) - .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) + .map(btnDoc => DocCast(btnDoc?.dragFactory)) .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title) + .map(doc => doc!) .map(dragDoc => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), event: undoable(() => { - const newDoc = (Doc.isTemplateDoc(dragDoc) ? DocUtils.delegateDragFactory : DocUtils.copyDragFactory)(dragDoc); + const newDoc = (dragDoc.isTemplateDoc ? DocUtils.delegateDragFactory : DocUtils.copyDragFactory)(dragDoc); if (newDoc) { newDoc._author = ClientUtils.CurrentUserEmail(); newDoc.x = x; @@ -437,6 +438,7 @@ export namespace DocUtils { .filter(btnDoc => !btnDoc.hidden) .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title) + .map(doc => doc!) .map(dragDoc => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), event: undoable(() => { @@ -494,11 +496,11 @@ export namespace DocUtils { .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc) .filter(d => d.isTemplateDoc); // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized - // first try to find a template that matches the specific document type (_). otherwise, fallback to a general match on + // first try to find a template that matches the specific document type (). otherwise, fallback to a general match on !docLayoutTemplate && allTemplates.forEach(tempDoc => { const templateType = StrCast(doc[templateName + '_fieldKey'] || doc.type); - StrCast(tempDoc.title) === templateName + '_' + templateType && (docLayoutTemplate = tempDoc); + StrCast(tempDoc.title) === templateName + (templateType[0].toUpperCase() + templateType.slice(1)) && (docLayoutTemplate = tempDoc); }); !docLayoutTemplate && allTemplates.forEach(tempDoc => { @@ -522,18 +524,13 @@ export namespace DocUtils { Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); } } else { - let fieldTemplate: Opt; - if (doc.data instanceof RichTextField || typeof doc.data === 'string') { - fieldTemplate = Docs.Create.TextDocument('', options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options); - } + const fieldTemplate = (() => { + if (doc.data instanceof RichTextField || typeof doc.data === 'string') return Docs.Create.TextDocument('', options); + if (doc.data instanceof PdfField) return Docs.Create.PdfDocument('http://www.msn.com', options); + if (doc.data instanceof VideoField) return Docs.Create.VideoDocument('http://www.cs.brown.edu', options); + if (doc.data instanceof AudioField) return Docs.Create.AudioDocument('http://www.cs.brown.edu', options); + if (doc.data instanceof ImageField) return Docs.Create.ImageDocument('http://www.cs.brown.edu', options); + })(); const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); @@ -904,8 +901,8 @@ export namespace DocUtils { return ndoc; } - export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') { - const { clone, map, linkMap } = await Doc.MakeClone(doc); + export function Zip(doc: Doc, zipFilename = 'dashExport.zip') { + const { clone, map, linkMap } = Doc.MakeClone(doc); const proms = new Set(); function replacer(key: string, value: { url: string; [key: string]: unknown }) { if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index dd0985182..5c6559836 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -36,7 +36,7 @@ export enum DocumentType { // special purpose wrappers that either take no data or are compositions of lower level types LINK = 'link', PRES = 'presentation', - PRESELEMENT = 'preselement', + PRESSLIDE = 'presslide', COMPARISON = 'comparison', PUSHPIN = 'pushpin', MAPROUTE = 'maproute', diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d93a432d4..bf9cc5bd4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -217,7 +217,7 @@ export class DocumentOptions { author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document', false); - rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)'); + rootDocument?: DOCt = new DocInfo('document that stores the data for compound template documents.'); color?: STRt = new StrInfo('foreground color data doc', false); hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false); backgroundColor?: STRt = new StrInfo('background color for data doc', false); @@ -1139,8 +1139,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) }); } - export function PresElementBoxDocument() { - return Prototypes.get(DocumentType.PRESELEMENT); + export function PresSlideDocument() { + return Prototypes.get(DocumentType.PRESSLIDE); } export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) { @@ -1148,7 +1148,7 @@ export namespace Docs { } export function AnnoPaletteDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyStickers]), { ...(options || {}) }); + return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([...(Doc.MyStickers ? [Doc.MyStickers] : [])]), { ...(options || {}) }); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 35dc5f1c7..1887c1716 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -148,12 +148,12 @@ export class CurrentUserUtils { { title: "Idea", backgroundColor: "pink", icon: "lightbulb" , _layout_showTitle: "title"}, { title: "Topic", backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}]; - + const metanote = DocCast(doc.emptyMetaNote); const reqdNoteList = [...reqdTempOpts.map(opts => { const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true}; const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined; return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts)); - }), DocCast(doc.emptyMetaNote), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))]; + }), ...(metanote ? [metanote]:[]), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))]; const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true }; return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); @@ -211,7 +211,7 @@ export class CurrentUserUtils { const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); const makeIconTemplate = (name: DocumentType | string | undefined, templateField: string, opts:DocumentOptions) => { - const title = "icon" + (name ? "_" + name : ""); + const title = "icon" + (name ? name[0].toUpperCase()+name.slice(1) : ""); const curIcon = DocCast(templateIconsDoc[title]); const creator = (() => { switch (opts.iconTemplate) { case DocumentType.IMG : return imageBox; @@ -424,7 +424,10 @@ pie title Minerals in my tap water const standardOps = (key:string) => ({ title : "Untitled "+ key, _layout_fitWidth: false, isSystem: true, "dragFactory_count": 0, cloneFieldFilter: new List(["isSystem"]) }); emptyThings.forEach( thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs)); - DocCast(Doc.UserDoc().emptyMetaNote).title = "MetaNote"; // hack: metanotes are used a template, so 'untitled metaNote' is an awkward name + const metanote = DocCast(Doc.UserDoc().emptyMetaNote); + if (metanote) { + metanote.title = "MetaNote"; // hack: metanotes are used a template, so 'untitled metaNote' is an awkward name + } return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, @@ -490,7 +493,7 @@ pie title Minerals in my tap water { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", }, { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", }, { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field - { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue: badgeValue}}, + { title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs??Doc.UserDoc(), icon: "users", funcs: {badgeValue: badgeValue}}, { title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), icon: "pres-trail", funcs: {target: getActiveDashTrails}}, { title: "Image Grouper", toolTip: "Image Grouper", target: this.setupImageGrouper(doc, "myImageGrouper"), icon: "folder-open", hidden: false }, { title: "Faces", toolTip: "Unique Faces", target: this.setupFaceCollection(doc, "myFaceCollection"), icon: "face-smile", hidden: false }, @@ -551,7 +554,6 @@ pie title Minerals in my tap water const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined); const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined; const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools); - // doc.myUserBtns = new PrefetchProxy(userBtns); const reqdToolOps:DocumentOptions = { title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0", layout_explainer: "This is a palette of documents that can be created.", _layout_dontCenter: "y", @@ -975,7 +977,10 @@ pie title Minerals in my tap water }; DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); - if (!Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards) Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards = new List(); + const sharedDocs = DocCast(doc.mySharedDocs); + if (sharedDocs) { + if (!Doc.GetProto(sharedDocs).data_dashboards) Doc.GetProto(sharedDocs).data_dashboards = new List(); + } } /// Import option on the left side button panel @@ -1000,9 +1005,9 @@ pie title Minerals in my tap water static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) { const doc = docIn; DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); - reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate), + reaction(() => DateCast(DocCast(doc.globalGroupDatabase)?.data_modificationDate), async () => { - const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); + const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase)?.data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || []; SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]); }, { fireImmediately: true }); @@ -1058,11 +1063,13 @@ pie title Minerals in my tap water SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API. This also triggers the LinkManager to be created - Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards); - Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs); - Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed); + if (Doc.MyFilesystem) { + Doc.MyDashboards && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards); + Doc.MyDashboards && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards); + Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed); + } - Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org"); + DocCast(Doc.UserDoc().emptyWebpage) && (Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)!).data = new WebField("https://www.wikipedia.org")); DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500); setInterval(UPDATE_SERVER_CACHE, 120000); @@ -1142,7 +1149,7 @@ pie title Minerals in my tap water const file = input.files?.[0]; if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') { const doc = await Doc.importDocument(file); - const list = Cast(Doc.MyImports.data, listSpec(Doc), null); + const list = Cast(Doc.MyImports?.data, listSpec(Doc), null); doc instanceof Doc && list?.splice(0, 0, doc); } else if (input.files && input.files.length !== 0) { const disposer = OverlayView.ShowSpinner(); @@ -1150,7 +1157,7 @@ pie title Minerals in my tap water if (results.length !== input.files?.length) { alert("Error uploading files - possibly due to unsupported file types"); } - const list = Cast(Doc.MyImports.data, listSpec(Doc), null); + const list = Cast(Doc.MyImports?.data, listSpec(Doc), null); list?.splice(0, 0, ...results); disposer(); } else { diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 2eef3da0e..dcef4a4fe 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -364,7 +364,7 @@ export namespace DictationManager { case 'nested collection':return Docs.Create.FreeformDocument([], {}); } // prettier-ignore })(); - created && Doc.AddDocToList(target.dataDoc, Doc.LayoutFieldKey(target.Document), created); + created && Doc.AddDocToList(target.dataDoc, Doc.LayoutDataKey(target.Document), created); } } }, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 5ce005811..ad57c2a62 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -183,7 +183,7 @@ export class DocumentManager { static _howl: Howl; static playAudioAnno(doc: Doc) { - const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement(); + const anno = Cast(doc[Doc.LayoutDataKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement(); if (anno) { this._howl?.stop(); if (anno instanceof AudioField) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index e2e4c0fe4..a66e6998b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -234,29 +234,27 @@ export namespace DragManager { dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc); return dropDoc; }; - const finishDrag = async (e: DragCompleteEvent) => { + const finishDrag = (e: DragCompleteEvent) => { const { docDragData } = e; setTimeout(() => dragData.dragEnding?.()); onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; - docDragData.droppedDocuments = ( - await Promise.all( - dragData.draggedDocuments.map(async d => - !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) - ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result as Doc) - : docDragData.dropAction === dropActionType.embed - ? Doc.BestEmbedding(d) - : docDragData.dropAction === dropActionType.add - ? d - : docDragData.dropAction === dropActionType.proto - ? d[DocData] - : docDragData.dropAction === dropActionType.copy - ? (await Doc.MakeClone(d)).clone - : d - ) + docDragData.droppedDocuments = dragData.draggedDocuments + .map(d => + !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) + ? addAudioTag(ScriptCast(d.onDragStart)!.script.run({ this: d }).result as Doc) + : docDragData.dropAction === dropActionType.embed + ? Doc.BestEmbedding(d) + : docDragData.dropAction === dropActionType.add + ? d + : docDragData.dropAction === dropActionType.proto + ? d[DocData] + : docDragData.dropAction === dropActionType.copy + ? Doc.MakeClone(d).clone + : d ) - ).filter(d => d); + .filter(d => d); ![dropActionType.same, dropActionType.proto].includes(StrCast(docDragData.dropAction) as dropActionType) && docDragData.droppedDocuments // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any)) @@ -364,7 +362,7 @@ export namespace DragManager { }; } - async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { + function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { const dropArgs = { cancelable: true, // allows preventDefault() to be called to cancel the drop bubbles: true, @@ -380,7 +378,7 @@ export namespace DragManager { }; target.dispatchEvent(new CustomEvent('dashPreDrop', dropArgs)); UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes) - await finishDrag?.(complete); + finishDrag?.(complete); UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called options?.dragComplete?.(complete); endDrag?.(); @@ -566,11 +564,11 @@ export namespace DragManager { const targClassName = e.target instanceof HTMLElement && typeof e.target.className === 'string' ? e.target.className : ''; if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(targClassName) && docDragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { - startWindowDragTimer = setTimeout(async () => { + startWindowDragTimer = setTimeout(() => { startWindowDragTimer = undefined; docDragData.dropAction = docDragData.userDropAction || dropActionType.same; AbortDrag(); - await finishDrag?.(new DragCompleteEvent(true, docDragData)); + finishDrag?.(new DragCompleteEvent(true, docDragData)); DragManager.StartWindowDrag?.(e, docDragData.droppedDocuments, aborted => { if (!aborted && (docDragData?.dropAction === dropActionType.move || docDragData?.dropAction === dropActionType.same)) { docDragData.removeDocument?.(docDragData?.draggedDocuments[0]); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 7d3f63448..b6b111930 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -3,7 +3,7 @@ import { DocData, DocLayout } from '../../fields/DocSymbols'; import { ObjectField } from '../../fields/ObjectField'; import { RichTextField } from '../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { StrCast } from '../../fields/Types'; +import { DocCast, StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { Docs, DocumentOptions } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; @@ -23,7 +23,7 @@ import { ScriptingGlobals } from './ScriptingGlobals'; */ function makeTemplate(doc: Doc, first: boolean = true): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; - if (layoutDoc.layout instanceof Doc) { + if (DocCast(layoutDoc.layout)) { return true; // its already a template } const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)?.[0]; @@ -68,7 +68,7 @@ export function MakeTemplate(doc: Doc) { * Makes a draggable button or image that will create a template doc Instance */ export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) { - const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; + const layoutDoc = doc; if (layoutDoc.type !== DocumentType.FONTICON) { !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); } diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts index 9c32ca25a..eae3460b3 100644 --- a/src/client/util/Import & Export/ImageUtils.ts +++ b/src/client/util/Import & Export/ImageUtils.ts @@ -15,7 +15,7 @@ export namespace ImageUtils { export const AssignImgInfo = (document: Doc, data?: Upload.InspectionResults) => { if (data) { data.nativeWidth && (document._height = (NumCast(document._width) * data.nativeHeight) / data.nativeWidth); - const field = '$' + Doc.LayoutFieldKey(document); + const field = '$' + Doc.LayoutDataKey(document); document[`${field}_nativeWidth`] = data.nativeWidth; document[`${field}_nativeHeight`] = data.nativeHeight; document[`${field}_path`] = data.source; diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 0e67dcfaa..6081c3fae 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -43,13 +43,13 @@ export class LinkFollower { }; public static traverseLink(link: Opt, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) { - const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)); - const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source); + const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)!); + const isAnchor = (source?: Doc, anchor?: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(DocCast(anchor?.annotationOn), source); const linkDocs = link ? [link] : Doc.Links(sourceDoc); - const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1 - const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2 - const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2))); - const backLinkWithoutTargetView = backLinks.find(l => !getView(DocCast(l.link_anchor_1))); + const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, DocCast(l.link_anchor_1))); // link docs where 'sourceDoc' is link_anchor_1 + const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, DocCast(l.link_anchor_2))); // link docs where 'sourceDoc' is link_anchor_2 + const fwdLinkWithoutTargetView = fwdLinks.find(l => !DocCast(l.link_anchor_2) || !getView(DocCast(l.link_anchor_2)!)); + const backLinkWithoutTargetView = backLinks.find(l => !DocCast(l.link_anchor_1) || !getView(DocCast(l.link_anchor_1)!)); const linkWithoutTargetDoc = traverseBacklink === undefined ? (fwdLinkWithoutTargetView ?? backLinkWithoutTargetView) : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks; const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1); @@ -60,17 +60,17 @@ export class LinkFollower { return false; } followLinks.forEach(async linkDoc => { - const target = ( + const target = DocCast( sourceDoc === linkDoc.link_anchor_1 ? linkDoc.link_anchor_2 : sourceDoc === linkDoc.link_anchor_2 ? linkDoc.link_anchor_1 - : Doc.AreProtosEqual(sourceDoc, linkDoc.link_anchor_1 as Doc) || Doc.AreProtosEqual((linkDoc.link_anchor_1 as Doc).annotationOn as Doc, sourceDoc) + : Doc.AreProtosEqual(sourceDoc, DocCast(linkDoc.link_anchor_1)) || Doc.AreProtosEqual(DocCast(DocCast(linkDoc.link_anchor_1)?.annotationOn), sourceDoc) ? linkDoc.link_anchor_2 : linkDoc.link_anchor_1 - ) as Doc; - const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc; + ); if (target) { + const srcAnchor = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc; const doFollow = (canToggle?: boolean) => { const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle); const options: FocusViewOptions = { @@ -102,14 +102,16 @@ export class LinkFollower { if (srcAnchor.followLinkLocation === OpenWhere.inParent) { const sourceDocParent = DocCast(sourceDoc.embedContainer); if (target.embedContainer instanceof Doc && target.embedContainer !== sourceDocParent) { - Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutFieldKey(target.embedContainer), target); + Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutDataKey(target.embedContainer), target); movedTarget = true; } - if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) { - Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target); - movedTarget = true; + if (sourceDocParent) { + if (!DocListCast(sourceDocParent[Doc.LayoutDataKey(sourceDocParent)]).includes(target)) { + Doc.AddDocToList(sourceDocParent, Doc.LayoutDataKey(sourceDocParent), target); + movedTarget = true; + } + Doc.SetContainer(target, sourceDocParent); } - Doc.SetContainer(target, sourceDocParent); } const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)]; if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 3f98ab3c4..344e2e4c0 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -136,7 +136,7 @@ export class LinkManager { true ); FieldLoader.ServerLoadStatus.message = 'links'; - this.addLinkDB(Doc.LinkDBDoc()); + Doc.LinkDBDoc() && this.addLinkDB(Doc.LinkDBDoc()!); } public createlink_relationshipLists = () => { @@ -148,16 +148,21 @@ export class LinkManager { public addLink(linkDoc: Doc, checkExists = false) { Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc); - if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) { - Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc); - // eslint-disable-next-line no-use-before-define - setTimeout(UPDATE_SERVER_CACHE, 100); + if (Doc.LinkDBDoc()) { + if (!checkExists || !DocListCast(Doc.LinkDBDoc()!.data).includes(linkDoc)) { + Doc.AddDocToList(Doc.LinkDBDoc()!, 'data', linkDoc); + // eslint-disable-next-line no-use-before-define + setTimeout(UPDATE_SERVER_CACHE, 100); + } } } public deleteLink(linkDoc: Doc) { - const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc); - linkDoc.$link_anchor_1 = linkDoc.$link_anchor_2 = undefined; - return ret; + if (Doc.LinkDBDoc()) { + const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc()!, 'data', linkDoc); + linkDoc.$link_anchor_1 = linkDoc.$link_anchor_2 = undefined; + return ret; + } + return false; } public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(LinkManager.Instance.deleteLink); @@ -178,8 +183,8 @@ export class LinkManager { } const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as '0' | '1' | '2')); - const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout) - const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']); + const anchorRoot = DocCast(anchor.rootDocument, anchor)!; // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout) + const annos = DocListCast(anchorRoot[Doc.LayoutDataKey(anchor) + '_annotations']); return Array.from( annos.reduce((set, anno) => { if (!processed.includes(anno)) { @@ -242,11 +247,13 @@ export function UPDATE_SERVER_CACHE() { .filter(doc => doc instanceof Doc) .map(doc => doc as Doc); const references = new Set(prototypes); + DocCast(Doc.UserDoc().myLinkDatabase) && references.add(DocCast(Doc.UserDoc().myLinkDatabase)!); // prevent crawling through link database here -- see below Doc.FindReferences(Doc.UserDoc(), references, undefined); - DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => { - if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) { - Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link); - Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link); + + DocListCast(DocCast(Doc.UserDoc().myLinkDatabase)?.data).forEach(link => { + if (DocCast(link.link_anchor_1) && !references.has(DocCast(link.link_anchor_1)!) && DocCast(link.link_anchor_2) && !references.has(DocCast(link.link_anchor_2)!)) { + DocCast(Doc.UserDoc().myLinkDatabase) && Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase)!, 'data', link); + Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link); } }); LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined)); @@ -257,10 +264,10 @@ export function UPDATE_SERVER_CACHE() { cacheDocumentIds = newCacheUpdate; // print out cached docs - //Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = '); - // const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc)); - // const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)')); - //Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str)); + Doc.MyDockedBtns?.linearView_IsOpen && console.log('Set cached docs = '); + const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc)); + const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)')); + Doc.MyDockedBtns?.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str)); rp.post(ClientUtils.prepend('/setCacheDocumentIds'), { body: { diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 733eae5f4..e4adcaa7e 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -9,7 +9,7 @@ export namespace SearchUtil { export type HighlightingResult = { [id: string]: { [key: string]: string[] } }; export function SearchCollection(collectionDoc: Opt, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) { - const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; + const blockedTypes = [DocumentType.PRESSLIDE, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; const blockedKeys = matchKeyNames ? [] : Object.entries(DocOptions) @@ -21,7 +21,7 @@ export namespace SearchUtil { const results = new ObservableMap(); if (collectionDoc) { - const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]); + const docs = DocListCast(collectionDoc[Doc.LayoutDataKey(collectionDoc)]); // eslint-disable-next-line @typescript-eslint/ban-types const docIDs: String[] = []; SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { @@ -77,7 +77,7 @@ export namespace SearchUtil { // eslint-disable-next-line no-loop-func docs.filter(d => d && !visited.includes(d)).forEach(d => { visited.push(d); - const fieldKey = Doc.LayoutFieldKey(d); + const fieldKey = Doc.LayoutDataKey(d); const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView'); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; data && newarray.push(...DocListCast(data)); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 88f1f3260..9e79fd870 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -578,7 +578,7 @@ export class SettingsManager extends React.Component {
{ClientUtils.CurrentUserEmail()}
-