aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/DragManager.ts12
-rw-r--r--src/client/util/LinkManager.ts38
-rw-r--r--src/client/util/Scripting.ts2
-rw-r--r--src/client/views/StyleProvider.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx20
-rw-r--r--src/fields/Doc.ts33
8 files changed, 55 insertions, 58 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ab58f25e9..dd50727dd 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -210,16 +210,16 @@ export namespace DragManager {
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
return dropDoc;
};
- const finishDrag = (e: DragCompleteEvent) => {
+ const finishDrag = async (e: DragCompleteEvent) => {
const docDragData = e.docDragData;
dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
docDragData.droppedDocuments =
- dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
+ await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
docDragData.dropAction === "alias" ? Doc.MakeAlias(d) :
docDragData.dropAction === "proto" ? Doc.GetProto(d) :
- docDragData.dropAction === "copy" ? Doc.MakeClone(d) : d);
+ docDragData.dropAction === "copy" ? (await Doc.MakeClone(d)).clone : d));
!["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []);
const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
@@ -509,7 +509,7 @@ export namespace DragManager {
`translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
);
};
- const upHandler = (e: PointerEvent) => {
+ const upHandler = async (e: PointerEvent) => {
dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options);
endDrag();
};
@@ -517,7 +517,7 @@ export namespace DragManager {
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions) {
+ async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions) {
const dropArgs = {
bubbles: true,
detail: {
@@ -531,7 +531,7 @@ export namespace DragManager {
}
};
target.dispatchEvent(new CustomEvent<DropEvent>("dashPreDrop", dropArgs));
- finishDrag?.(complete);
+ await finishDrag?.(complete);
target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", dropArgs));
options?.dragComplete?.(complete);
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3c3d5c3b8..08f4ac9b7 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,15 +1,14 @@
+import { observable, observe, action } from "mobx";
import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, Opt, DirectLinksSym, Field } from "../../fields/Doc";
-import { BoolCast, Cast, StrCast, PromiseValue } from "../../fields/Types";
+import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { ProxyField } from "../../fields/Proxy";
+import { BoolCast, Cast, PromiseValue, StrCast } from "../../fields/Types";
import { LightboxView } from "../views/LightboxView";
import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
import { DocumentManager } from "./DocumentManager";
import { SharingManager } from "./SharingManager";
import { UndoManager } from "./UndoManager";
-import { observe, observable, reaction } from "mobx";
-import { listSpec } from "../../fields/Schema";
-import { List } from "../../fields/List";
-import { ProxyField } from "../../fields/Proxy";
type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
/*
@@ -34,7 +33,7 @@ export class LinkManager {
LinkManager._instance = this;
setTimeout(() => {
LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
- const addLinkToDoc = (link: Doc): any => {
+ const addLinkToDoc = action((link: Doc): any => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => addLinkToDoc(link)));
@@ -43,8 +42,8 @@ export class LinkManager {
Doc.GetProto(a2)[DirectLinksSym].add(link);
Doc.GetProto(link)[DirectLinksSym].add(link);
}
- };
- const remLinkFromDoc = (link: Doc): any => {
+ });
+ const remLinkFromDoc = action((link: Doc): any => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => remLinkFromDoc(link)));
@@ -53,7 +52,7 @@ export class LinkManager {
Doc.GetProto(a2)[DirectLinksSym].delete(link);
Doc.GetProto(link)[DirectLinksSym].delete(link);
}
- };
+ });
const watchUserLinks = (userLinks: List<Doc>) => {
const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
observe(userLinks, change => {
@@ -75,8 +74,10 @@ export class LinkManager {
});
}
- public addLink(linkDoc: Doc) {
- return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+ public addLink(linkDoc: Doc, checkExists = false) {
+ if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
+ Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+ }
}
public deleteLink(linkDoc: Doc) { return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); }
public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
@@ -85,19 +86,6 @@ export class LinkManager {
public getAllDirectLinks(anchor: Doc): Doc[] {
return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
} // finds all links that contain the given anchor
- public getAllLinks(): Doc[] { return []; }//this.allLinks(); }
-
- // allLinks = computedFn(function allLinks(this: any): Doc[] {
- // const linkData = Doc.LinkDBDoc().data;
- // const lset = new Set<Doc>(DocListCast(linkData));
- // SharingManager.Instance.users.forEach(user => DocListCast(user.linkDatabase?.data).forEach(doc => lset.add(doc)));
- // LinkManager.Instance.allLinks().filter(link => {
- // const a1 = Cast(link?.anchor1, Doc, null);
- // const a2 = Cast(link?.anchor2, Doc, null);
- // return link && ((a1?.author !== undefined && a2?.author !== undefined) || link.author === Doc.CurrentUserEmail) && (Doc.AreProtosEqual(anchor, a1) || Doc.AreProtosEqual(anchor, a2) || Doc.AreProtosEqual(link, anchor));
- // });
- // return Array.from(lset);
- // }, true);
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
const lfield = Doc.LayoutFieldKey(anchor);
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index c3c3083be..f981f84cd 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -181,7 +181,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
if (batch) {
batch.end();
}
- onError?.(error);
+ onError?.(script + " " + error);
return { success: false, error, result: errorVal };
}
};
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 6b94539c9..32ddc140c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -101,7 +101,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const backColor = backgroundCol();
if (!backColor) return undefined;
const nonAlphaColor = backColor.startsWith("#") ? (backColor as string).substring(0, 7) :
- backColor.startsWith("rgba") ? backColor.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : backColor
+ backColor.startsWith("rgba") ? backColor.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : backColor;
const col = Color(nonAlphaColor).rgb();
const colsum = (col.red() + col.green() + col.blue());
if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index f39443ae2..a5d27f038 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -454,7 +454,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (completed) completed(set);
else {
if (isFreeformView && generatedDocuments.length > 1) {
- addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!));
} else {
generatedDocuments.forEach(addDocument);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b1f2750c3..1f4fcb2a5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -368,8 +368,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
SelectionManager.DeselectAll();
selected.forEach(d => this.props.removeDocument?.(d));
const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
- this.props.addDocument?.(newCollection!);
- this.props.selectDocuments([newCollection!]);
+ this.props.addDocument?.(newCollection);
+ this.props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index 90f64f163..0c434eae5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -246,13 +246,13 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
} else {
// check if the input is a number
let inputIsNum = true;
- for (let s of value) {
- if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) {
+ for (const s of value) {
+ if (isNaN(parseInt(s)) && !(s === ".") && !(s === ",")) {
inputIsNum = false;
}
}
// check if the input is a boolean
- let inputIsBool: boolean = value == "false" || value == "true";
+ const inputIsBool: boolean = value === "false" || value === "true";
// what to do in the case
if (!inputIsNum && !inputIsBool && !value.startsWith("=")) {
// if it's not a number, it's a string, and should be processed as such
@@ -263,12 +263,12 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const vsqLength = valueSansQuotes.length;
// get rid of outer quotes
valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0,
- valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength);
+ valueSansQuotes.charAt(vsqLength - 1) === "\"" ? vsqLength - 1 : vsqLength);
}
let inputAsString = '"';
// escape any quotes in the string
for (const i of valueSansQuotes) {
- if (i == '"') {
+ if (i === '"') {
inputAsString += '\\"';
} else {
inputAsString += i;
@@ -278,7 +278,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
inputAsString += '"';
//two options here: we can strip off outer quotes or we can figure out what's going on with the script
const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length
+ const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
// handle numbers and expressions
} else if (inputIsNum || value.startsWith("=")) {
@@ -286,18 +286,18 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const inputscript = value.substring(value.startsWith("=") ? 1 : 0);
// if commas are not stripped, the parser only considers the numbers after the last comma
let inputSansCommas = "";
- for (let s of inputscript) {
- if (!(s == ",")) {
+ for (const s of inputscript) {
+ if (!(s === ",")) {
inputSansCommas += s;
}
}
const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = value.length !== value.length || value.length - 2 !== value.length
+ const changeMade = value.length !== value.length || value.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
// handle booleans
} else if (inputIsBool) {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = value.length !== value.length || value.length - 2 !== value.length
+ const changeMade = value.length !== value.length || value.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
}
}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 464a8ad05..7993af149 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -516,30 +516,30 @@ export namespace Doc {
return alias;
}
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
- if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
- const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
- await Promise.all([...Object.keys(doc), "links"].map(async key => {
+ const fieldExclusions = (doc.type === DocumentType.TEXTANCHOR) ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
+ const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
+ await Promise.all(Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
const assignKey = (val: any) => !dontCreate && (copy[key] = val);
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === "links" && Doc.IsPrototype(doc) ? doc[key] : ProxyField.WithoutProxy(() => doc[key]);
+ const field = ProxyField.WithoutProxy(() => doc[key]);
const copyObjectField = async (field: ObjectField) => {
const list = Cast(doc[key], listSpec(Doc));
const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, rtfs, exclusions, dontCreate, asBranch)));
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
!dontCreate && assignKey(new List<Doc>(clones));
} else if (doc[key] instanceof Doc) {
- assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields
+ assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields
} else {
!dontCreate && assignKey(ObjectField.MakeCopy(field));
if (field instanceof RichTextField) {
- if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
+ if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
rtfs.push({ copy, key, field });
}
}
@@ -547,14 +547,17 @@ export namespace Doc {
};
if (key === "proto") {
if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate, asBranch));
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+ }
+ } else if (key === "anchor1" || key === "anchor2") {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
}
} else {
if (field instanceof RefField) {
assignKey(field);
} else if (cfield instanceof ComputedField) {
!dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
- (key === "links" && field instanceof ObjectField) && await copyObjectField(field);
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
@@ -564,6 +567,10 @@ export namespace Doc {
}
}
}));
+ for (const link of Array.from(doc[DirectLinksSym])) {
+ const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
+ linkMap.set(link, linkClone);
+ }
if (!dontCreate) {
Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
@@ -576,8 +583,10 @@ export namespace Doc {
}
export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false) {
const cloneMap = new Map<string, Doc>();
+ const linkMap = new Map<Doc, Doc>();
const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
@@ -589,7 +598,7 @@ export namespace Doc {
};
const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
const re = new RegExp(regex, "g");
- copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
return { clone: copy, map: cloneMap };
}