diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/DictationManager.ts | 6 | ||||
| -rw-r--r-- | src/client/util/DocumentManager.ts | 153 | ||||
| -rw-r--r-- | src/client/util/DragManager.ts | 14 | ||||
| -rw-r--r-- | src/client/util/DropConverter.ts | 19 | ||||
| -rw-r--r-- | src/client/util/Import & Export/DirectoryImportBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/util/Import & Export/ImageUtils.ts | 2 | ||||
| -rw-r--r-- | src/client/util/InteractionUtils.tsx | 36 | ||||
| -rw-r--r-- | src/client/util/KeyCodes.ts | 136 | ||||
| -rw-r--r-- | src/client/util/LinkManager.ts | 4 | ||||
| -rw-r--r-- | src/client/util/RichTextRules.ts | 32 | ||||
| -rw-r--r-- | src/client/util/RichTextSchema.tsx | 18 | ||||
| -rw-r--r-- | src/client/util/Scripting.ts | 28 | ||||
| -rw-r--r-- | src/client/util/SearchUtil.ts | 4 | ||||
| -rw-r--r-- | src/client/util/SelectionManager.ts | 14 | ||||
| -rw-r--r-- | src/client/util/type_decls.d | 1 |
15 files changed, 313 insertions, 156 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 569c1ef6d..b3295ece0 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -10,7 +10,6 @@ import { CollectionViewType } from "../views/collections/CollectionView"; import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; -import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { Utils } from "../../Utils"; import { RichTextField } from "../../new_fields/RichTextField"; import { DictationOverlay } from "../views/DictationOverlay"; @@ -282,9 +281,8 @@ export namespace DictationManager { [DocumentType.COL, listSpec(Doc)], [DocumentType.AUDIO, AudioField], [DocumentType.IMG, ImageField], - [DocumentType.HIST, HistogramField], [DocumentType.IMPORT, listSpec(Doc)], - [DocumentType.TEXT, "string"] + [DocumentType.RTF, "string"] ]); const tryCast = (view: DocumentView, type: DocumentType) => { @@ -377,7 +375,7 @@ export namespace DictationManager { { expression: /view as (freeform|stacking|masonry|schema|tree)/g, action: (target: DocumentView, matches: RegExpExecArray) => { - const mode = CollectionViewType.valueOf(matches[1]); + const mode = matches[1]; mode && (target.props.Document._viewType = mode); }, restrictTo: [DocumentType.COL] diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index e0ffaf7e0..2d6078cf3 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,16 +1,16 @@ import { action, computed, observable } from 'mobx'; import { Doc, DocListCastAsync, DocListCast, Opt } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; -import { List } from '../../new_fields/List'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; -import { DocumentView } from '../views/nodes/DocumentView'; +import { DocumentView, DocFocusFunc } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { Scripting } from './Scripting'; import { SelectionManager } from './SelectionManager'; import { DocumentType } from '../documents/DocumentTypes'; +export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; export class DocumentManager { @@ -84,24 +84,20 @@ export class DocumentManager { return this.getDocumentViewById(toFind[Id], preferredCollection); } - public getFirstDocumentView(toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined { - const views = this.getDocumentViews(toFind); - return views?.find(view => view.props.Document !== originatingDoc); + public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => { + return this.getDocumentViews(toFind)?.find(view => view.props.Document !== originatingDoc); } public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; + const docViews = DocumentManager.Instance.DocumentViews; - // heurstic to return the "best" documents first: + // heuristic to return the "best" documents first: // choose an exact match over an alias match // choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth) - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); - DocumentManager.Instance.DocumentViews.map(view => - view.props.Document.presBox === undefined && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); + docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); return toReturn; } @@ -133,107 +129,110 @@ export class DocumentManager { return pairs; } - public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false, originatingDoc: Opt<Doc> = undefined): Promise<void> => { + static addRightSplit = (doc: Doc, finished?: () => void) => { + CollectionDockingView.AddRightSplit(doc); + finished?.(); + } + public jumpToDocument = async ( + targetDoc: Doc, + willZoom: boolean, + createViewFunc = DocumentManager.addRightSplit, + docContext?: Doc, + linkId?: string, + closeContextIfNotFound: boolean = false, + originatingDoc: Opt<Doc> = undefined, + finished?: () => void + ): Promise<void> => { + const getFirstDocView = DocumentManager.Instance.getFirstDocumentView; + const focusAndFinish = () => { finished?.(); return false; }; const highlight = () => { - const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - finalDocView && (finalDocView.Document.scrollToLinkID = linkId); - finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); + const finalDocView = getFirstDocView(targetDoc); + if (finalDocView) { + finalDocView.layoutDoc.scrollToLinkID = linkId; + Doc.linkFollowHighlight(finalDocView.props.Document); + } }; - const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc, originatingDoc); + const docView = getFirstDocView(targetDoc, originatingDoc); let annotatedDoc = await Cast(targetDoc.annotationOn, Doc); if (annotatedDoc) { - const first = DocumentManager.Instance.getFirstDocumentView(annotatedDoc); + const first = getFirstDocView(annotatedDoc); if (first) annotatedDoc = first.props.Document; } if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? - docView.props.focus(docView.props.Document, willZoom); + docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish); highlight(); } else { const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; - const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; - const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc); + const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; + const targetDocContext = annotatedDoc || contextDoc; if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default - (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); + createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); highlight(); - } else { - const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + } else { // otherwise try to get a view of the context of the target + const targetDocContextView = getFirstDocView(targetDocContext); targetDocContext.scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling - if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context + if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext.panTransformType = "Ease"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); // now find the target document within the context - if (targetDoc.displayTimecode) { // the target should show up once the video scrubs to the display timecode; + if (targetDoc.displayTimecode) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; targetDocContext.currentTimecode = targetDoc.displayTimecode; - } else { + finished?.(); + } else { // no timecode means we need to find the context view and focus on our target setTimeout(() => { - const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc); - if (retryDocView) { - retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context - } else { - if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); - targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); // otherwise create a new view of the target + const retryDocView = getFirstDocView(targetDoc); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { // we found the target in the context + retryDocView.props.focus(targetDoc, willZoom, undefined, focusAndFinish); // focus on the target in the context + } else { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. + if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document); + targetDoc.layout && createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target } highlight(); }, 0); } } else { // there's no context view so we need to create one first and try again - (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext); + createViewFunc(targetDocContext); // so first we create the target, but don't pass finished because we still need to create the target setTimeout(() => { - const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - const finalDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext); + const finalDocView = getFirstDocView(targetDoc); + const finalDocContextView = getFirstDocView(targetDocContext); setTimeout(() => // if not, wait a bit to see if the context can be loaded (e.g., a PDF). wait interval heurisitic tries to guess how we're animating based on what's just become visible - this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created. + this.jumpToDocument(targetDoc, willZoom, createViewFunc, undefined, linkId, true, undefined, finished), // pass true this time for closeContextIfNotFound + finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created. }, 0); } } } } - public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { + public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(doc.links); SelectionManager.DeselectAll(); - const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); - const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); - const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); - const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; - const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; - const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined; - const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined; - const linkFollowDocContexts = first.length ? [await first[0].context as Doc, await first[0].context as Doc] : second.length ? [await second[0].context as Doc, await second[0].context as Doc] : [undefined, undefined]; - const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2_timecode), NumCast(first[0].anchor1_timecode)] : second.length ? [NumCast(second[0].anchor1_timecode), NumCast(second[0].anchor2_timecode)] : [undefined, undefined]; - if (linkFollowDocs && linkDoc) { - const maxLocation = StrCast(linkDoc.maximizeLocation, "inTab"); - const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; - const target = linkFollowDocs[reverse ? 1 : 0]; - const annotatedDoc = await Cast(target.annotationOn, Doc); - if (annotatedDoc) { - annotatedDoc.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); // link docs where 'doc' is anchor2 + const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); + const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; + const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs); + const linkDoc = linkDocList.length && linkDocList[0]; + if (linkDoc) { + const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; + const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : + doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number"): + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number"):Cast(linkDoc.anchor1_timecode, "number"))); + if (target) { + const containerDoc = (await Cast(target.annotationOn, Doc)) || target; + containerDoc.currentTimecode = targetTimecode; + const targetContext = await target?.context as Doc; + const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc[Id], undefined, doc, finished); } else { - target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]); + finished?.(); } - DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id], undefined, doc); - } else if (link) { - DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined); - } - } - - @action - zoomIntoScale = (docDelegate: Doc, scale: number) => { - const docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate)); - docView?.props.zoomToScale(scale); - } - - getScaleOfDocView = (docDelegate: Doc) => { - const doc = Doc.GetProto(docDelegate); - - const docView = DocumentManager.Instance.getDocumentView(doc); - if (docView) { - return docView.props.getScale(); } else { - return 1; + finished?.(); } } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 1f2312032..3e9a5b63a 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -17,6 +17,7 @@ import { convertDropDataToButtons } from "./DropConverter"; import { AudioBox } from "../views/nodes/AudioBox"; import { DateField } from "../../new_fields/DateField"; import { DocumentView } from "../views/nodes/DocumentView"; +import { UndoManager } from "./UndoManager"; export type dropActionType = "place" | "alias" | "copy" | undefined; export function SetupDrag( @@ -199,6 +200,7 @@ export namespace DragManager { dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline")); return dropDoc; }; + const batch = UndoManager.StartBatch("dragging"); const finishDrag = (e: DragCompleteEvent) => { e.docDragData && (e.docDragData.droppedDocuments = dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : @@ -208,6 +210,7 @@ export namespace DragManager { e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) => (dragData?.removeDropProperties || []).concat(Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), [])).map(prop => drop[prop] = undefined) ); + batch.end(); }; dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, finishDrag); @@ -216,10 +219,10 @@ export namespace DragManager { // drag a button template and drop a new button export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { const finishDrag = (e: DragCompleteEvent) => { - const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title, isButton: true, onClick: ScriptField.MakeScript(script) }); - params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); + const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }); + params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields initialize?.(bd); - bd.buttonParams = new List<string>(params); + Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params); e.docDragData && (e.docDragData.droppedDocuments = [bd]); }; StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag); @@ -401,20 +404,21 @@ export namespace DragManager { function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) { const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => { - const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height }; + const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow }; dragEle.style.width = "0"; dragEle.style.height = "0"; + dragEle.style.overflow = "hidden"; return ret; }); const target = document.elementFromPoint(e.x, e.y); removed.map(r => { r.ele.style.width = r.w; r.ele.style.height = r.h; + r.ele.style.overflow = r.o; }); if (target) { const complete = new DragCompleteEvent(false, dragData); finishDrag?.(complete); - console.log(complete.aborted); target.dispatchEvent( new CustomEvent<DropEvent>("dashOnDrop", { bubbles: true, diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 69dc303cd..60a6bbb3c 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -35,8 +35,14 @@ export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string any = makeTemplate(d, false) || any; } }); - if (!docs.length && first) { - any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any; + if (first) { + if (docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template, but we still want its caption to be a textTemplate + if (doc.caption instanceof RichTextField && !doc.caption.Empty()) { + doc["caption-textTemplate"] = ComputedField.MakeFunction(`copyField(this.caption)`); + } + } else { + any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any; + } } if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) { if (!StrCast(layoutDoc.title).startsWith("-")) { @@ -52,13 +58,14 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant if (!doc.onDragStart && !doc.isButtonBar) { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; - if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) { + if (layoutDoc.type !== DocumentType.FONTICON) { !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); - } else { - (layoutDoc.layout instanceof Doc) && !data.userDropAction; } layoutDoc.isTemplateDoc = true; - dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: StrCast(layoutDoc.title), icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); + dbox = Docs.Create.FontIconDocument({ + _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, + backgroundColor: StrCast(doc.backgroundColor), title: StrCast(layoutDoc.title), icon: layoutDoc.isTemplateDoc ? "font" : "bolt" + }); dbox.dragFactory = layoutDoc; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 3d8bcbab7..438904688 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -126,7 +126,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name }); const { data, error } = exifData; if (document) { - Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data); + Doc.GetProto(document).exif = error || Docs.Get.FromJson({ data }); docs.push(document); } })); diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts index ab8c73d15..9fae5ff93 100644 --- a/src/client/util/Import & Export/ImageUtils.ts +++ b/src/client/util/Import & Export/ImageUtils.ts @@ -20,7 +20,7 @@ export namespace ImageUtils { nativeHeight, exifData: { error, data } } = await Networking.PostToServer("/inspectImage", { source }); - document.exif = error || Docs.Get.DocumentHierarchyFromJson(data); + document.exif = error || Docs.Get.FromJson({ data }); const proto = Doc.GetProto(document); proto["data-nativeWidth"] = nativeWidth; proto["data-nativeHeight"] = nativeHeight; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 2eec02a42..b1f136430 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -10,24 +10,9 @@ export namespace InteractionUtils { const REACT_POINTER_PEN_BUTTON = 0; const ERASER_BUTTON = 5; - export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) { - const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); - return ( - <polyline - points={pts} - style={{ - fill: "none", - stroke: color, - strokeWidth: width - }} - /> - ); - } - export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> { constructor( readonly fingers: number, - // readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[], readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], @@ -37,6 +22,11 @@ export namespace InteractionUtils { export interface MultiTouchEventDisposer { (): void; } + /** + * + * @param element - element to turn into a touch target + * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart) + */ export function MakeMultiTouchTarget( element: HTMLElement, startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void @@ -62,6 +52,11 @@ export namespace InteractionUtils { }; } + /** + * Turns an element onto a target for touch hold handling. + * @param element - element to add events to + * @param func - function to add to the event + */ export function MakeHoldTouchTarget( element: HTMLElement, func: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void @@ -92,7 +87,6 @@ export namespace InteractionUtils { return myTouches; } - // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) { const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); return ( @@ -107,6 +101,11 @@ export namespace InteractionUtils { ); } + /** + * Returns whether or not the pointer event passed in is of the type passed in + * @param e - pointer event. this event could be from a mouse, a pen, or a finger + * @param type - InteractionUtils.(PENTYPE | ERASERTYPE | MOUSETYPE | TOUCHTYPE) + */ export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { switch (type) { // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 @@ -119,6 +118,11 @@ export namespace InteractionUtils { } } + /** + * Returns euclidean distance between two points + * @param pt1 + * @param pt2 + */ export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); } diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts new file mode 100644 index 000000000..cacb72a57 --- /dev/null +++ b/src/client/util/KeyCodes.ts @@ -0,0 +1,136 @@ +/** + * Class contains the keycodes for keys on your keyboard. + * + * Useful for auto completion: + * + * ``` + * switch (event.key) + * { + * case KeyCode.UP: + * { + * // Up key pressed + * break; + * } + * case KeyCode.DOWN: + * { + * // Down key pressed + * break; + * } + * case KeyCode.LEFT: + * { + * // Left key pressed + * break; + * } + * case KeyCode.RIGHT: + * { + * // Right key pressed + * break; + * } + * default: + * { + * // ignore + * break; + * } + * } + * ``` + */ +export class KeyCodes { + public static TAB: number = 9; + public static CAPS_LOCK: number = 20; + public static SHIFT: number = 16; + public static CONTROL: number = 17; + public static SPACE: number = 32; + public static DOWN: number = 40; + public static UP: number = 38; + public static LEFT: number = 37; + public static RIGHT: number = 39; + public static ESCAPE: number = 27; + public static F1: number = 112; + public static F2: number = 113; + public static F3: number = 114; + public static F4: number = 115; + public static F5: number = 116; + public static F6: number = 117; + public static F7: number = 118; + public static F8: number = 119; + public static F9: number = 120; + public static F10: number = 121; + public static F11: number = 122; + public static F12: number = 123; + public static INSERT: number = 45; + public static HOME: number = 36; + public static PAGE_UP: number = 33; + public static PAGE_DOWN: number = 34; + public static DELETE: number = 46; + public static END: number = 35; + public static ENTER: number = 13; + public static BACKSPACE: number = 8; + public static NUMPAD_0: number = 96; + public static NUMPAD_1: number = 97; + public static NUMPAD_2: number = 98; + public static NUMPAD_3: number = 99; + public static NUMPAD_4: number = 100; + public static NUMPAD_5: number = 101; + public static NUMPAD_6: number = 102; + public static NUMPAD_7: number = 103; + public static NUMPAD_8: number = 104; + public static NUMPAD_9: number = 105; + public static NUMPAD_DIVIDE: number = 111; + public static NUMPAD_ADD: number = 107; + public static NUMPAD_ENTER: number = 13; + public static NUMPAD_DECIMAL: number = 110; + public static NUMPAD_SUBTRACT: number = 109; + public static NUMPAD_MULTIPLY: number = 106; + public static SEMICOLON: number = 186; + public static EQUAL: number = 187; + public static COMMA: number = 188; + public static MINUS: number = 189; + public static PERIOD: number = 190; + public static SLASH: number = 191; + public static BACKQUOTE: number = 192; + public static LEFTBRACKET: number = 219; + public static BACKSLASH: number = 220; + public static RIGHTBRACKET: number = 221; + public static QUOTE: number = 222; + public static ALT: number = 18; + public static COMMAND: number = 15; + public static NUMPAD: number = 21; + public static A: number = 65; + public static B: number = 66; + public static C: number = 67; + public static D: number = 68; + public static E: number = 69; + public static F: number = 70; + public static G: number = 71; + public static H: number = 72; + public static I: number = 73; + public static J: number = 74; + public static K: number = 75; + public static L: number = 76; + public static M: number = 77; + public static N: number = 78; + public static O: number = 79; + public static P: number = 80; + public static Q: number = 81; + public static R: number = 82; + public static S: number = 83; + public static T: number = 84; + public static U: number = 85; + public static V: number = 86; + public static W: number = 87; + public static X: number = 88; + public static Y: number = 89; + public static Z: number = 90; + public static NUM_0: number = 48; + public static NUM_1: number = 49; + public static NUM_2: number = 50; + public static NUM_3: number = 51; + public static NUM_4: number = 52; + public static NUM_5: number = 53; + public static NUM_6: number = 54; + public static NUM_7: number = 55; + public static NUM_8: number = 56; + public static NUM_9: number = 57; + public static SUBSTRACT: number = 189; + public static ADD: number = 187; +}
\ No newline at end of file diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 4457f41e2..e236c7f47 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -136,12 +136,12 @@ export class LinkManager { } } public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) { - linkDoc.linkRelationship = groupDoc.linkRelationship; + Doc.GetProto(linkDoc).linkRelationship = groupDoc.linkRelationship; } // removes group doc of given group type only from given anchor on given link public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) { - linkDoc.linkRelationship = "-ungrouped-"; + Doc.GetProto(linkDoc).linkRelationship = "-ungrouped-"; } // returns map of group type to anchor's links in that group type diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index b0a124cb8..3746199ba 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -62,6 +62,17 @@ export class RichTextRules { // ``` code block textblockTypeInputRule(/^```$/, schema.nodes.code_block), + // create an inline view of a tag stored under the '#' field + new InputRule( + new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), + (state, match, start, end) => { + const tag = match[1]; + if (!tag) return state.tr; + this.Document[DataSym]["#"] = tag; + const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), + // # heading textblockTypeInputRule( new RegExp(/^(#{1,6})\s$/), @@ -81,7 +92,7 @@ export class RichTextRules { // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc new InputRule( - new RegExp(/\[\[([a-zA-Z_#@\? \-0-9]*)(=[a-zA-Z_#@\? \-0-9]*)?(:[a-zA-Z_#@\? \-0-9]+)?\]\]$/), + new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? \-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; const docid = match[3]?.substring(1); @@ -99,21 +110,12 @@ export class RichTextRules { return state.tr; } if (value !== "" && value !== undefined) { - this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : value; + const num = value.match(/^[0-9.]/); + this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value); } const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); return state.tr.deleteRange(start, end).insert(start, fieldView); }), - // create an inline view of a tag stored under the '#' field - new InputRule( - new RegExp(/#([a-zA-Z_\-0-9]+)\s$/), - (state, match, start, end) => { - const tag = match[1]; - if (!tag) return state.tr; - this.Document[DataSym]["#"] = tag; - const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); - return state.tr.deleteRange(start, end).insert(start, fieldView); - }), // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document {{<layout>}} => show layout for this doc {{<layout> : Doc}} => show layout for another doc new InputRule( new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/), @@ -129,12 +131,12 @@ export class RichTextRules { } }); const node = (state.doc.resolve(start) as any).nodeAfter; - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "right", alias: Utils.GenerateGuid() }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() }); const sm = state.storedMarks || undefined; return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), new InputRule( - new RegExp(/##$/), + new RegExp(/>>$/), (state, match, start, end) => { const textDoc = this.Document[DataSym]; const numInlines = NumCast(textDoc.inlineTextCount); @@ -146,7 +148,7 @@ export class RichTextRules { textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] - textDocInline._textContext = ComputedField.MakeFunction(`copyField(this.${inlineFieldKey})`, { this: Doc.name }); + textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text textDoc[inlineFieldKey] = ""; // set a default value for the annotation const node = (state.doc.resolve(start) as any).nodeAfter; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 4a930177d..0599b3ebe 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -16,7 +16,7 @@ import { listSpec } from "../../new_fields/Schema"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; import { ComputedField } from "../../new_fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionView"; @@ -27,8 +27,12 @@ import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { Transform } from "./Transform"; import React = require("react"); -const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; +const + blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], + hrDOM: DOMOutputSpecArray = ["hr"], + preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], + brDOM: DOMOutputSpecArray = ["br"], + ulDOM: DOMOutputSpecArray = ["ul", 0]; // :: Object // [Specs](#model.NodeSpec) for the nodes defined in this schema. @@ -738,7 +742,7 @@ export class DashDocView { this._outer = document.createElement("span"); this._outer.style.position = "relative"; this._outer.style.textIndent = "0"; - this._outer.style.border = "1px solid " + StrCast(tbox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); this._outer.style.width = node.attrs.width; this._outer.style.height = node.attrs.height; this._outer.style.display = node.attrs.hidden ? "none" : "inline-block"; @@ -824,6 +828,8 @@ export class DashDocView { addDocTab={this._textBox.props.addDocTab} pinToPres={returnFalse} renderDepth={self._textBox.props.renderDepth + 1} + NativeHeight={returnZero} + NativeWidth={returnZero} PanelWidth={finalLayout[WidthSym]} PanelHeight={finalLayout[HeightSym]} focus={this.outerFocus} @@ -831,8 +837,6 @@ export class DashDocView { parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} - zoomToScale={emptyFunction} - getScale={returnOne} dontRegisterView={false} ContainingCollectionView={this._textBox.props.ContainingCollectionView} ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} @@ -959,8 +963,6 @@ export class DashFieldView { if (self._options?.length && !self._dashDoc[self._fieldKey]) { self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); } - // NOTE: if the field key starts with "@", then the actual field key is stored in the field 'fieldKey' (removing the @). - self._fieldKey = self._fieldKey.startsWith("@") ? StrCast(tbox.props.Document[StrCast(self._fieldKey).substring(1)]) : self._fieldKey; this._labelSpan.innerHTML = `${self._fieldKey}: `; const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index ce21b7fa7..12628273b 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -24,6 +24,8 @@ export interface ScriptError { export type ScriptResult = ScriptSucccess | ScriptError; +export type ScriptParam = { [name: string]: string }; + export interface CompiledScript { readonly compiled: true; readonly originalScript: string; @@ -37,6 +39,12 @@ export interface CompileError { } export type CompileResult = CompiledScript | CompileError; +export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is CompileError { + if ((toBeDetermined as CompileError).errors) { + return true; + } + return false; +} export namespace Scripting { export function addGlobal(global: { name: string }): void; @@ -89,9 +97,9 @@ const _scriptingGlobals: { [name: string]: any } = {}; let scriptingGlobals: { [name: string]: any } = _scriptingGlobals; function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { - const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error); - if ((options.typecheck !== false && errors) || !script) { - return { compiled: false, errors: diagnostics }; + const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); + if ((options.typecheck !== false && errors.length) || !script) { + return { compiled: false, errors }; } const paramNames = Object.keys(scriptingGlobals); @@ -195,14 +203,14 @@ export type Transformer = { getVars?: () => { capturedVariables: { [name: string]: Field } } }; export interface ScriptOptions { - requiredType?: string; - addReturn?: boolean; - params?: { [name: string]: string }; - capturedVariables?: { [name: string]: Field }; - typecheck?: boolean; - editable?: boolean; + requiredType?: string; // does function required a typed return value + addReturn?: boolean; // does the compiler automatically add a return statement + params?: { [name: string]: string }; // list of function parameters and their types + capturedVariables?: { [name: string]: Field }; // list of captured variables + typecheck?: boolean; // should the compiler perform typechecking + editable?: boolean; // can the script edit Docs traverser?: TraverserParam; - transformer?: Transformer; + transformer?: Transformer; // does the editor display a text label by each document that can be used as a captured document reference globals?: { [name: string]: any }; } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index b597f1e07..2026bf940 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -64,7 +64,7 @@ export namespace SearchUtil { const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc); for (let i = 0; i < textDocs.length; i++) { const testDoc = textDocs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && testDoc.type !== DocumentType.EXTENSION && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { theDocs.push(Doc.GetProto(testDoc)); theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase()))); } @@ -74,7 +74,7 @@ export namespace SearchUtil { const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); for (let i = 0; i < ids.length; i++) { const testDoc = docs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && testDoc.type !== DocumentType.EXTENSION && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { theDocs.push(testDoc); theLines.push([]); } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 7221fbbf9..6c386d684 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -33,7 +33,6 @@ export namespace SelectionManager { @action DeselectDoc(docView: DocumentView): void { if (manager.SelectedDocuments.get(docView)) { - docView.dontDecorateSelection = false; manager.SelectedDocuments.delete(docView); docView.props.whenActiveChanged(false); Doc.UserDoc().SelectedDocs = new List(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); @@ -41,10 +40,7 @@ export namespace SelectionManager { } @action DeselectAll(): void { - Array.from(manager.SelectedDocuments.keys()).map(dv => { - dv.dontDecorateSelection = false; - dv.props.whenActiveChanged(false); - }); + Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments.clear(); Doc.UserDoc().SelectedDocs = new List<Doc>([]); } @@ -59,11 +55,13 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - + // computed functions, such as used in IsSelected generate errors if they're called outside of a + // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature + // to avoid unnecessary mobx invalidations when running inside a reaction. export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean { return outsideReaction ? - manager.SelectedDocuments.get(doc) ? true : false : - computedFn(function isSelected(doc: DocumentView) { + manager.SelectedDocuments.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get() + computedFn(function isSelected(doc: DocumentView) { // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed return manager.SelectedDocuments.get(doc) ? true : false; })(doc); } diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 97f6b79fb..08aec3724 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -195,7 +195,6 @@ interface DocumentOptions { } declare const Docs: { ImageDocument(url: string, options?: DocumentOptions): Doc; VideoDocument(url: string, options?: DocumentOptions): Doc; - // HistogramDocument(url:string, options?:DocumentOptions); TextDocument(options?: DocumentOptions): Doc; PdfDocument(url: string, options?: DocumentOptions): Doc; WebDocument(url: string, options?: DocumentOptions): Doc; |
