aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DictationManager.ts6
-rw-r--r--src/client/util/DocumentManager.ts153
-rw-r--r--src/client/util/DragManager.ts14
-rw-r--r--src/client/util/DropConverter.ts19
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx2
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts2
-rw-r--r--src/client/util/InteractionUtils.tsx36
-rw-r--r--src/client/util/KeyCodes.ts136
-rw-r--r--src/client/util/LinkManager.ts4
-rw-r--r--src/client/util/RichTextRules.ts32
-rw-r--r--src/client/util/RichTextSchema.tsx18
-rw-r--r--src/client/util/Scripting.ts28
-rw-r--r--src/client/util/SearchUtil.ts4
-rw-r--r--src/client/util/SelectionManager.ts14
-rw-r--r--src/client/util/type_decls.d1
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;