aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorusodhi <61431818+usodhi@users.noreply.github.com>2021-03-08 12:56:58 -0500
committerusodhi <61431818+usodhi@users.noreply.github.com>2021-03-08 12:56:58 -0500
commite7dd6dbdf8d01cef390ff65c0948e5e70ac232cf (patch)
tree10551b265cb02acb3e8ee23b5f03eb22bf7f31d1 /src
parentd47c114193972dabf0d48e789bd58989cdeae605 (diff)
parent81cfcf5ab5caed1ab5cd052b3e8f00829631018f (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into filters
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts8
-rw-r--r--src/client/documents/Gitlike.ts100
-rw-r--r--src/client/util/CurrentUserUtils.ts10
-rw-r--r--src/client/util/GroupManager.tsx8
-rw-r--r--src/client/views/DocComponent.tsx1
-rw-r--r--src/client/views/DocumentDecorations.tsx13
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx17
-rw-r--r--src/client/views/collections/TreeView.tsx2
-rw-r--r--src/client/views/nodes/EquationBox.tsx2
-rw-r--r--src/fields/Doc.ts28
11 files changed, 154 insertions, 37 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ca5ee9cbd..0da80d2c4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -153,7 +153,7 @@ export class DocumentOptions {
"_carousel-caption-yMargin"?: number;
x?: number;
y?: number;
- z?: number;
+ z?: number; // whether document is in overlay (1) or not (0 or undefined)
author?: string;
layoutKey?: string;
type?: string;
@@ -260,9 +260,9 @@ export namespace Docs {
const _docOptions = new DocumentOptions();
export async function setupFieldInfos() {
- return await DocServer.GetRefField("FieldInfos7") as Doc ??
+ return await DocServer.GetRefField("FieldInfos8") as Doc ??
runInAction(() => {
- const infos = new Doc("FieldInfos7", true);
+ const infos = new Doc("FieldInfos8", true);
const keys = Object.keys(new DocumentOptions());
for (const key of keys) {
const options = (_docOptions as any)[key] as FInfo;
@@ -728,7 +728,7 @@ export namespace Docs {
}
export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) {
- return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { clipWidth: 50, _backgroundColor: "gray", targetDropAction: "alias", ...options });
+ return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", ...options });
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
diff --git a/src/client/documents/Gitlike.ts b/src/client/documents/Gitlike.ts
new file mode 100644
index 000000000..fddf317bc
--- /dev/null
+++ b/src/client/documents/Gitlike.ts
@@ -0,0 +1,100 @@
+import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { ObjectField } from "../../fields/ObjectField";
+import { Cast, DateCast } from "../../fields/Types";
+
+// synchs matching documents on the two branches that are being merged/pulled
+// currently this just synchs the main 'fieldKey' component of the data since
+// we don't have individual timestamps for all fields -- this is a problematic design issue.
+function GitlikeSynchDocs(bd: Doc, md: Doc) {
+ const fieldKey = Doc.LayoutFieldKey(md);
+ const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
+ const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
+ if (bdate === mdate || bdate > mdate) return;
+ const bdproto = bd && Doc.GetProto(bd);
+ if (bdproto && md) {
+ bdproto[fieldKey] = ObjectField.MakeCopy(md[fieldKey] as ObjectField);
+ bdproto[`${fieldKey}-lastModified`] = ObjectField.MakeCopy(md[`${fieldKey}-lastModified`] as ObjectField);
+ }
+}
+
+// pulls documents onto a branch from the branch's master
+// if a document exists on master but not on the branch, it is branched and added
+// NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
+async function GitlikePullFromMaster(branch: Doc, suffix = "") {
+ const masterMain = Cast(branch.branchOf, Doc, null);
+ // get the set of documents on both the branch and master
+ const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
+ const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+ // get the master documents that correspond to the branch documents
+ const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd);
+ const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
+ // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
+ const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+ const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+ oldDocsFromMaster?.forEach(md => {
+ const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
+ bd && GitlikeSynchDocs(bd, md);
+ });
+ // make branch clones of them, then add them to the branch
+ const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
+ newlyBranchedDocs.forEach(nd => {
+ Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
+ nd.context = branch;
+ });
+ // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted.
+ const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context);
+ // so then remove all the deleted main docs from this branch.
+ remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd));
+}
+
+// merges all branches from the master branch by first merging the top-level collection of documents,
+// and then merging all the annotations on those documents.
+// TODO: need to add an incrementing timestamp whenever anything merges. don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
+async function GitlikeMergeWithMaster(master: Doc, suffix = "") {
+ const branches = await DocListCastAsync(master.branches);
+ branches?.map(async branch => {
+ const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+ branchChildren && await Promise.all(branchChildren.map(async bd => {
+ // see if the branch's child exists on master.
+ const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true)).clone;
+ // if the branch's child didn't exist on master, we make a branch clone of the child to add to master.
+ // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
+ // on the branch child and master clone.
+ if (masterChild.branchOf) {
+ const branchDocProto = Doc.GetProto(bd);
+ const masterChildProto = Doc.GetProto(masterChild);
+ masterChildProto.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
+ masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
+ Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto); // the branch child should not have the master child in its branch list.
+ branchDocProto.branchOf = masterChild; // the branch child is now a branch of the master child
+ }
+ Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
+ masterChild.context = master;
+ GitlikeSynchDocs(Doc.GetProto(masterChild), bd);
+ }));
+ const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
+ masterChildren?.forEach(mc => { // see if any master children
+ if (!branchChildren?.find(bc => bc.branchOf === mc)) { // are not in the list of children for this branch.
+ Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it.
+ mc.context = undefined; // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp
+ }
+ });
+ });
+}
+
+// performs a "git"-like task: pull or merge
+// if pull, then target is a specific branch document that will be updated from its associated master
+// if merge, then target is the master doc that will merge in all branches associated with it.
+// TODO: parameterize 'merge' to specify which branch(es) should be merged.
+// extend 'merge' to allow a specific branch to be merge target (not just master);
+// make pull/merge be recursive (ie, this func currently just operates on the main doc and its children)
+export async function BranchTask(target: Doc, action: "pull" | "merge") {
+ const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster;
+ await func(target, "");
+ await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations"));
+}
+
+export async function BranchCreate(target: Doc) {
+ return (await Doc.MakeClone(target, false, true)).clone;
+} \ No newline at end of file
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 825d7b1d7..f47e198be 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -378,7 +378,7 @@ export class CurrentUserUtils {
((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptySlide === undefined) {
- const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, _backgroundColor: "transparent", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "transparent", system: true, cloneFieldFilter: new List<string>(["system"]) });
Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text');
FormattedTextBox.SelectOnLoad = textDoc[Id];
doc.emptySlide = textDoc;
@@ -549,7 +549,7 @@ export class CurrentUserUtils {
dontUndo: true,
title,
target,
- _backgroundColor: "black",
+ backgroundColor: "black",
dropAction: "alias",
removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
_width: 60,
@@ -564,7 +564,7 @@ export class CurrentUserUtils {
title: "menuItemPanel",
childDropAction: "alias",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
- _backgroundColor: "black", ignoreClick: true,
+ backgroundColor: "black", ignoreClick: true,
_gridGap: 0,
_yMargin: 0,
_yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, _chromeStatus: "disabled", system: true
@@ -621,7 +621,7 @@ export class CurrentUserUtils {
title: data.title,
_lockedPosition: true,
onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- _backgroundColor: data.backgroundColor, system: true
+ backgroundColor: data.backgroundColor, system: true
},
[this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
);
@@ -987,7 +987,7 @@ export class CurrentUserUtils {
static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
- reaction(() => DateCast((doc.globalGroupDatabase as Doc).lastModified),
+ reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]),
async () => {
const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index b24c8f681..62d656c5d 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -146,7 +146,7 @@ export class GroupManager extends React.Component<{}> {
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc);
- this.GroupManagerDoc.lastModified = new DateField;
+ this.GroupManagerDoc["data-lastModified"] = new DateField;
return true;
}
return false;
@@ -167,7 +167,7 @@ export class GroupManager extends React.Component<{}> {
const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
- this.GroupManagerDoc.lastModified = new DateField;
+ this.GroupManagerDoc["data-lastModified"] = new DateField;
if (group === this.currentGroup) {
this.currentGroup = undefined;
}
@@ -188,7 +188,7 @@ export class GroupManager extends React.Component<{}> {
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.shareWithAddedMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField);
+ this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField);
}
}
@@ -205,7 +205,7 @@ export class GroupManager extends React.Component<{}> {
const user = memberList.splice(index, 1)[0];
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.removeMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField);
+ this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField);
}
}
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 128ba858f..6480c6507 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -144,6 +144,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
toRemove.forEach(doc => {
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+ doc.context = undefined;
recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
});
this.props.select(false);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 44d4460fa..aeb2d582b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -10,7 +10,7 @@ import { InkField } from "../../fields/InkField";
import { ScriptField } from '../../fields/ScriptField';
import { Cast, NumCast } from "../../fields/Types";
import { GetEffectiveAcl } from '../../fields/util';
-import { setupMoveUpEvents, emptyFunction } from "../../Utils";
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
import { Docs, DocUtils } from "../documents/Documents";
import { DocumentType } from '../documents/DocumentTypes';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
@@ -129,7 +129,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
}
@undoBatch
@action
- onMaximizeClick = (e: React.MouseEvent): void => {
+ onMaximizeClick = (e: any): void => {
const selectedDocs = SelectionManager.Views();
if (selectedDocs.length) {
if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
@@ -152,12 +152,12 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
}
@undoBatch
- onIconifyClick = (e: React.MouseEvent): void => {
+ onIconifyClick = (): void => {
SelectionManager.Views().forEach(dv => dv?.iconify());
SelectionManager.DeselectAll();
}
- onSelectorClick = (e: React.MouseEvent) => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
+ onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
onRadiusDown = (e: React.PointerEvent): void => {
this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
@@ -413,9 +413,10 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
(collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
});
- const topBtn = (key: string, icon: string, click: (e: React.MouseEvent) => void, title: string) => (
+ const topBtn = (key: string, icon: string, click: (e: any) => void, title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
- <div className={`documentDecorations-${key}Button`} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onClick={click}>
+ <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, click, emptyFunction)} >
<FontAwesomeIcon icon={icon as any} />
</div>
</Tooltip>);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e5c835f39..59e1824c1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -329,7 +329,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const y = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y < y ? r.y : y, undefined as any)) || 0);
const r = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x + r.width > x ? r.x + r.width : x, undefined as any)) || 0;
const b = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y + r.height > y ? r.y + r.height : y, undefined as any)) || 0);
- const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: r - x, _height: b - y, x, y, annotationOn: srcWeb });
+ const anchor = Docs.Create.FreeformDocument([], { backgroundColor: "transparent", _width: r - x, _height: b - y, x, y, annotationOn: srcWeb });
anchor.context = srcWeb;
const key = Doc.LayoutFieldKey(srcWeb);
Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a217de193..857982782 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -3,15 +3,16 @@ import { observer } from "mobx-react";
import * as React from 'react';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast } from '../../../fields/Doc';
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { Cast, ScriptCast, StrCast, DateCast } from '../../../fields/Types';
import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { returnFalse } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
+import { BranchTask, BranchCreate } from '../../documents/Gitlike';
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -154,7 +155,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true,
- icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
+ icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#0000003d", color: "#ACCEF7",
_width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null)
});
Doc.SetInPlace(doc, "annotationOn", undefined, true);
@@ -298,6 +299,16 @@ export class CollectionView extends Touchable<CollectionViewProps> {
}
!Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+ optionItems.push({
+ description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.props.Document), "add:right"), icon: "project-diagram"
+ });
+ optionItems.push({
+ description: "Pull Master", event: () => BranchTask(this.props.Document, "pull"), icon: "project-diagram"
+ });
+ optionItems.push({
+ description: "Merge Branches", event: () => BranchTask(this.props.Document, "merge"), icon: "project-diagram"
+ });
+
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
if (!Doc.UserDoc().noviceMode && !this.props.Document.annotationOn) {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index e80cc970b..58bb5974f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -218,7 +218,7 @@ export class TreeView extends React.Component<TreeViewProps> {
layout: CollectionView.LayoutString("data"),
title: "-title-", "sidebarColor": "transparent", "sidebarViewType": CollectionViewType.Freeform,
_viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline",
- x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10
+ x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, backgroundColor: "transparent", _width: 1000, _height: 10
});
Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
Doc.GetProto(bullet).data = new List<Doc>([]);
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 9111cb418..dacafcdf4 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -65,7 +65,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps, EquationDo
const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
x: NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym](),
y: NumCast(this.layoutDoc.y),
- _width: 400, _height: 300, _backgroundColor: "white"
+ _width: 400, _height: 300, backgroundColor: "white"
});
this.props.addDocument?.(graph);
e.stopPropagation();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 05baacf80..c2f5efadc 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -504,13 +504,13 @@ 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): Promise<Doc> {
+ 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> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
- const copy = dontCreate ? doc : new Doc(undefined, true);
+ 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 = Cast(doc.cloneFieldFilter, listSpec("string"), exclusions);
+ const filter = [...exclusions, ...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);
@@ -520,12 +520,12 @@ export namespace Doc {
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)));
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, 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)); // 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, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields
} else {
- assignKey(ObjectField.MakeCopy(field));
+ !dontCreate && assignKey(ObjectField.MakeCopy(field));
if (field instanceof RichTextField) {
if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
rtfs.push({ copy, key, field });
@@ -535,7 +535,7 @@ export namespace Doc {
};
if (key === "proto") {
if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate));
+ assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate, asBranch));
}
} else {
if (field instanceof RefField) {
@@ -553,16 +553,20 @@ export namespace Doc {
}
}));
if (!dontCreate) {
- Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
- copy.cloneOf = doc;
+ Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
+ asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
+ if (!Doc.IsPrototype(copy)) {
+ console.log("ADDING: " + copy.title + " to " + doc.title + "'s branches");
+ Doc.AddDocToList(doc, "branches", Doc.GetProto(copy));
+ }
cloneMap.set(doc[Id], copy);
}
return copy;
}
- export async function MakeClone(doc: Doc, dontCreate: boolean = false) {
+ export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false) {
const cloneMap = new Map<string, Doc>();
const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"], dontCreate);
+ const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
@@ -587,7 +591,7 @@ export namespace Doc {
// a.click();
const { clone, map } = await Doc.MakeClone(doc, true);
function replacer(key: any, value: any) {
- if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
+ if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined;
else if (value instanceof Doc) {
if (key !== "field" && Number.isNaN(Number(key))) {
const __fields = value[FieldsSym]();