diff options
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 16 | ||||
-rw-r--r-- | src/client/util/LinkManager.ts | 38 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 2 | ||||
-rw-r--r-- | src/client/views/StyleProvider.tsx | 17 | ||||
-rw-r--r-- | src/client/views/collections/CollectionPileView.tsx | 15 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 5 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx | 20 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 2 | ||||
-rw-r--r-- | src/fields/Doc.ts | 33 |
12 files changed, 79 insertions, 79 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c2cdf7f72..f1db3e32c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1333,9 +1333,7 @@ export namespace DocUtils { newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; newCollection._width = newCollection._height = 110; - //newCollection.borderRounding = "40px"; newCollection._jitterRotation = 10; - newCollection._backgroundColor = "gray"; return newCollection; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index ab58f25e9..c4842e88a 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)); @@ -427,7 +427,7 @@ export namespace DragManager { SnappingManager.clearSnapLines(); batch.end(); }); - const moveHandler = (e: PointerEvent) => { + const moveHandler = async (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; @@ -438,7 +438,7 @@ export namespace DragManager { dragData.removeDocument?.(dragData.draggedDocuments[0]); } AbortDrag(); - finishDrag?.(new DragCompleteEvent(true, dragData)); + await finishDrag?.(new DragCompleteEvent(true, dragData)); DragManager.StartWindowDrag?.({ pageX: e.pageX, pageY: e.pageY, @@ -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 55f6b8e3c..32ddc140c 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -101,13 +101,13 @@ 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; return Colors.WHITE; case StyleProp.Hidden: return BoolCast(doc?._hidden); - case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"]); + case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"], doc?._viewType === CollectionViewType.Pile ? "50%" : ""); case StyleProp.TitleHeight: return 15; case StyleProp.BorderPath: return comicStyle() && props?.renderDepth ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 }; case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0; @@ -136,14 +136,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case DocumentType.VID: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; case DocumentType.COL: if (StrCast(Doc.LayoutField(doc)).includes("SliderBox")) break; - docColor = docColor ? Colors.DARK_GRAY : - doc?._isGroup ? "#00000004" : // very faint highlight to show bounds of group - (Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background + docColor = docColor || + (doc?._isGroup ? "#00000004" : // very faint highlight to show bounds of group + (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background isBackground() ? "cyan" : // ?? is there a good default for a background collection doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc StrCast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : - Doc.UserDoc().activeCollectionBackground)); + Doc.UserDoc().activeCollectionBackground))); break; //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; @@ -159,8 +159,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps switch (doc?.type) { case DocumentType.COL: return StrCast(doc?.boxShadow, - isBackground() || doc?._isGroup || docProps?.LayoutTemplateString ? undefined : // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide) - `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`); + doc?._viewType === CollectionViewType.Pile ? "4px 4px 10px 2px" : + isBackground() || doc?._isGroup || docProps?.LayoutTemplateString ? undefined : // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide) + `${darkScheme() ? Colors.DARK_GRAY : Colors.MEDIUM_GRAY} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`); case DocumentType.LABEL: if (doc?.annotationOn !== undefined) return "black 2px 2px 1px"; diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 6baf633dd..bc1407c53 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,12 +1,12 @@ -import { action, computed } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; import { NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, setupMoveUpEvents, returnTrue } from "../../../Utils"; +import { emptyFunction, returnTrue, setupMoveUpEvents } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; -import { UndoManager, undoBatch } from "../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPileView.scss"; import { CollectionSubView } from "./CollectionSubView"; @@ -15,6 +15,7 @@ import React = require("react"); @observer export class CollectionPileView extends CollectionSubView(doc => doc) { _originalChrome: any = ""; + _disposers: { [name: string]: IReactionDisposer } = {}; componentDidMount() { if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") { @@ -22,9 +23,14 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { } this._originalChrome = this.layoutDoc._chromeHidden; this.layoutDoc._chromeHidden = true; + + // pileups are designed to go away when they are empty. + this._disposers.selected = reaction(() => this.childDocs.length, + (num) => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)); } componentWillUnmount() { this.layoutDoc._chromeHidden = this._originalChrome; + Object.values(this._disposers).forEach(disposer => disposer?.()); } layoutEngine = () => StrCast(this.Document._pileLayoutEngine); @@ -107,9 +113,6 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { this._undoBatch?.end(); this._undoBatch = undefined; SnappingManager.SetIsDragging(false); - if (!this.childDocs.length) { - this.props.ContainingCollectionView?.removeDocument(this.props.Document); - } }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index ca45536f4..a5d27f038 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -453,8 +453,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: generatedDocuments.length > 1 ? generatedDocuments.map(d => { DocUtils.iconify(d); return d; }) : []; if (completed) completed(set); else { - if (isFreeformView) { - addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); + if (isFreeformView && generatedDocuments.length > 1) { + addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index afc1babeb..37444a9dc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -126,7 +126,8 @@ export function computerStarburstLayout( replica: "" }); }); - return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); + const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined }; + return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } @@ -399,7 +400,7 @@ function normalizeResults( ): ViewDefResult[] { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); const docEles = Array.from(docMap.entries()).map(ele => ele[1]); - const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0); + const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; 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 cead9e2e9..fd99abce5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -247,13 +247,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 @@ -264,12 +264,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; @@ -279,7 +279,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("=")) { @@ -287,18 +287,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/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d876ae818..cfd43bb62 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -340,7 +340,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @action finishMarquee = () => { this._marqueeing = undefined; - this.props.select(false) + this.props.select(false); } render() { 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 }; } |