aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
authorNaafiyan Ahmed <naafiyan@gmail.com>2022-07-12 12:08:49 -0400
committerNaafiyan Ahmed <naafiyan@gmail.com>2022-07-12 12:08:49 -0400
commit9ad978eac113cf3559d885c62a9368a68f6333ec (patch)
tree8daa3a5663379b29d8121aaa39e80cfd5e7fd9ed /src/client/util
parent31041d7a5b2c3699518ebb33ccab016af0acd579 (diff)
parent5628b585fa6356d66cf2e7454be20e3b847ad22e (diff)
merged master
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CaptureManager.tsx157
-rw-r--r--src/client/util/CurrentUserUtils.ts395
-rw-r--r--src/client/util/DocumentManager.ts186
-rw-r--r--src/client/util/GroupManager.tsx253
-rw-r--r--src/client/util/History.ts45
-rw-r--r--src/client/util/LinkFollower.ts112
-rw-r--r--src/client/util/LinkManager.ts259
-rw-r--r--src/client/util/RecordingApi.ts269
-rw-r--r--src/client/util/ScrollBox.tsx23
-rw-r--r--src/client/util/SelectionManager.ts35
-rw-r--r--src/client/util/SettingsManager.tsx471
-rw-r--r--src/client/util/SharingManager.tsx575
-rw-r--r--src/client/util/SnappingManager.ts49
13 files changed, 1483 insertions, 1346 deletions
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index c247afa26..0b5957fac 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,17 +1,17 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { convertToObject } from "typescript";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { BoolCast, StrCast, Cast } from "../../fields/Types";
-import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils";
-import { LightboxView } from "../views/LightboxView";
-import { MainViewModal } from "../views/MainViewModal";
-import "./CaptureManager.scss";
-import { SelectionManager } from "./SelectionManager";
-import { undoBatch } from "./UndoManager";
-const higflyout = require("@hig/flyout");
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { convertToObject } from 'typescript';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { BoolCast, StrCast, Cast } from '../../fields/Types';
+import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
+import { LightboxView } from '../views/LightboxView';
+import { MainViewModal } from '../views/MainViewModal';
+import './CaptureManager.scss';
+import { SelectionManager } from './SelectionManager';
+import { undoBatch } from './UndoManager';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -22,32 +22,31 @@ export class CaptureManager extends React.Component<{}> {
@observable _document: any;
@observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
-
constructor(props: {}) {
super(props);
CaptureManager.Instance = this;
}
- public close = action(() => this.isOpen = false);
+ public close = action(() => (this.isOpen = false));
public open = action((doc: Doc) => {
this.isOpen = true;
this._document = doc;
});
-
@computed get visibilityContent() {
-
- return <div className="capture-block">
- <div className="capture-block-title">Visibility</div>
- <div className="capture-block-radio">
- <div className="radio-container">
- <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private
- </div>
- <div className="radio-container">
- <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public
+ return (
+ <div className="capture-block">
+ <div className="capture-block-title">Visibility</div>
+ <div className="capture-block-radio">
+ <div className="radio-container">
+ <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private
+ </div>
+ <div className="radio-container">
+ <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public
+ </div>
</div>
</div>
- </div>;
+ );
}
@computed get linksContent() {
@@ -66,75 +65,79 @@ export class CaptureManager extends React.Component<{}> {
order.push(
<div className="list-item">
<div className="number">{i}</div>
- {(l.anchor1 as Doc).title}
+ {StrCast((l.anchor1 as Doc).title)}
</div>
);
}
});
}
- return <div className="capture-block">
- <div className="capture-block-title">Links</div>
- <div className="capture-block-list">
- {order}
+ return (
+ <div className="capture-block">
+ <div className="capture-block-title">Links</div>
+ <div className="capture-block-list">{order}</div>
</div>
- </div>;
+ );
}
@computed get closeButtons() {
- return <div className="capture-block">
- <div className="buttons">
- <div className="save" onClick={() => {
- LightboxView.SetLightboxDoc(this._document);
- this.close();
- }}>
- Save
- </div>
- <div className="cancel" onClick={() => {
- const selected = SelectionManager.Views().slice();
- SelectionManager.DeselectAll();
- selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
- this.close();
- }}>
- Cancel
+ return (
+ <div className="capture-block">
+ <div className="buttons">
+ <div
+ className="save"
+ onClick={() => {
+ LightboxView.SetLightboxDoc(this._document);
+ this.close();
+ }}>
+ Save
+ </div>
+ <div
+ className="cancel"
+ onClick={() => {
+ const selected = SelectionManager.Views().slice();
+ SelectionManager.DeselectAll();
+ selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
+ this.close();
+ }}>
+ Cancel
+ </div>
</div>
</div>
- </div>;
+ );
}
-
-
private get captureInterface() {
- return <div className="capture-interface">
- <div className="capture-t1">
- <div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}>
+ return (
+ <div className="capture-interface">
+ <div className="capture-t1">
+ <div className="recordButtonOutline" style={{}}>
+ <div className="recordButtonInner" style={{}}></div>
</div>
+ Conversation Capture
</div>
- Conversation Capture
- </div>
- <div className="capture-t2">
-
- </div>
- {this.visibilityContent}
- {this.linksContent}
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color="black" size={"lg"} />
+ <div className="capture-t2"></div>
+ {this.visibilityContent}
+ {this.linksContent}
+ <div className="close-button" onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ </div>
+ {this.closeButtons}
</div>
- {this.closeButtons}
- </div>;
-
+ );
}
render() {
- return <MainViewModal
- contents={this.captureInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: "500px", height: "350px", border: "none", background: "whitesmoke" }}
- overlayStyle={{ background: "black" }}
- overlayDisplayedOpacity={0.6}
- />;
+ return (
+ <MainViewModal
+ contents={this.captureInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ closeOnExternalClick={this.close}
+ dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
+ overlayStyle={{ background: 'black' }}
+ overlayDisplayedOpacity={0.6}
+ />
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index dca77250c..6c80cf0f4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,37 +1,28 @@
-import { computed, observable, reaction } from "mobx";
+import { reaction } from "mobx";
import * as rp from 'request-promise';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
-import { InkTool } from "../../fields/InkField";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
-import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types";
+import { ScriptField } from "../../fields/ScriptField";
+import { Cast, DateCast, DocCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
-import { SharingPermissions } from "../../fields/util";
+import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
-import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
import { TreeViewType } from "../views/collections/CollectionTreeView";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
-import { TreeView } from "../views/collections/TreeView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
import { OverlayView } from "../views/OverlayView";
import { DragManager } from "./DragManager";
import { MakeTemplate } from "./DropConverter";
-import { HistoryUtil } from "./History";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
-import { SelectionManager } from "./SelectionManager";
import { ColorScheme } from "./SettingsManager";
-import { SharingManager } from "./SharingManager";
-import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
interface Button {
@@ -57,59 +48,6 @@ interface Button {
export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
- private static curr_id: string;
- //TODO tfs: these should be temporary...
- private static mainDocId: string | undefined;
-
- public static get id() { return this.curr_id; }
- public static get MainDocId() { return this.mainDocId; }
- public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
- @computed public static get UserDocument() { return Doc.UserDoc(); }
-
- @observable public static GuestTarget: Doc | undefined;
- @observable public static GuestDashboard: Doc | undefined;
- @observable public static GuestMobile: Doc | undefined;
- @observable public static propertiesWidth: number = 0;
- @observable public static headerBarHeight: number = 0;
- @observable public static searchPanelWidth: number = 0;
-
- static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) {
- scripts && Object.keys(scripts).map(key => {
- if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) {
- doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any", documentView:Doc.name}, {"_readOnly_": true});
- }
- });
- funcs && Object.keys(funcs).map(key => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) {
- doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true});
- }
- });
- return doc;
- }
- static AssignOpts(doc:Doc|undefined, reqdOpts:DocumentOptions, items?:Doc[]) {
- if (doc) {
- const compareValues = (val1:any, val2:any) => {
- if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) {
- return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v));
- }
- return val1 === val2;
- }
- Object.entries(reqdOpts).forEach(pair => {
- const targetDoc = pair[0].startsWith("_") ? doc : Doc.GetProto(doc as Doc);
- if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/,"")) ||
- !compareValues(pair[1], targetDoc[pair[0]])) {
- targetDoc[pair[0]] = pair[1];
- }
- });
- items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), "data", item));
- items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), "data", item));
- }
- return doc;
- }
- static AssignDocField(doc:Doc, field:string, creator:(reqdOpts:DocumentOptions, items?:Doc[]) => Doc, reqdOpts:DocumentOptions, items?: Doc[], scripts?:{[key:string]:string}, funcs?:{[key:string]:string}) {
- return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
- }
// initializes experimental advanced template views - slideView, headerView
static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) {
@@ -139,12 +77,12 @@ export class CurrentUserUtils {
const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' };
const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
if (templateBtn) {
- this.AssignOpts(templateBtn,btnOpts);
- this.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions);
+ DocUtils.AssignOpts(templateBtn,btnOpts);
+ DocUtils.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions);
}
return templateBtn;
};
- return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
+ return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
});
const reqdOpts:DocumentOptions = {
@@ -154,7 +92,7 @@ export class CurrentUserUtils {
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
const reqdFuncs = { hidden: "IsNoviceMode()" };
- return this.AssignScripts(this.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
}
/// Initializes templates that can be applied to notes
@@ -167,16 +105,16 @@ export class CurrentUserUtils {
const reqdNoteList = reqdTempOpts.map(opts => {
const reqdOpts = {...opts, title: "text", system: true};
const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined;
- return this.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
+ return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
});
const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true };
- return this.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
+ return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
}
/// Initializes collection of templates for notes and click functions
static setupDocTemplates(doc: Doc, field="myTemplates") {
- this.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"});
+ DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"});
const templates = [
DocCast(doc.presElement),
CurrentUserUtils.setupNoteTemplates(doc),
@@ -184,13 +122,13 @@ export class CurrentUserUtils {
];
const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, };
const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
- return this.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts);
+ return DocUtils.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts);
}
// setup templates for different document types when they are iconified from Document Decorations
static setupDefaultIconTemplates(doc: Doc, field="template-icons") {
const reqdOpts = { title: "icon templates", _height: 75, system: true };
- const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
+ const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
const iconFieldName = "icon" + (type ? "_" + type : "");
@@ -201,8 +139,8 @@ export class CurrentUserUtils {
case DocumentType.FONTICON: creator = fontBox; break;
}
const allopts = {system: true, ...opts};
- return this.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
- this.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
+ return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
+ DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
{onClick:"deiconifyView(documentView)"});
};
const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
@@ -224,7 +162,7 @@ export class CurrentUserUtils {
makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }),
//makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
].filter(d => d).map(d => d!);
- this.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
+ DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
}
/// initalizes the set of "empty<DocType>" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation
@@ -297,7 +235,7 @@ export class CurrentUserUtils {
}, funcs: {title: 'self.text?.Text'}},
];
- emptyThings.forEach(thing => this.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs));
+ emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs));
return [
{ toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, },
@@ -326,7 +264,7 @@ export class CurrentUserUtils {
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
_removeDropProperties: new List<string>(["_stayInCollection"]),
};
- return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
});
const reqdOpts:DocumentOptions = {
@@ -335,7 +273,7 @@ export class CurrentUserUtils {
childDocumentsActive: true
};
const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
- return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
}
/// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents
@@ -348,7 +286,7 @@ export class CurrentUserUtils {
{ title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} },
{ title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", },
{ title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", },
- { title: "Shared Docs", target: this.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}},
+ { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}},
{ title: "Trails", target: this.setupTrails(doc, "myTrails"), icon: "pres-trail", },
{ title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}}));
@@ -356,7 +294,7 @@ export class CurrentUserUtils {
/// the empty panel that is filled with whichever left menu button's panel has been selected
static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") {
- this.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true});
+ DocUtils.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true});
}
/// Initializes the left sidebar menu buttons and the panels they open up
@@ -370,20 +308,20 @@ export class CurrentUserUtils {
_width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias",
_removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
};
- return this.AssignScripts(this.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
});
const reqdStackOpts:DocumentOptions ={
title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
_chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
};
- return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
// Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") {
const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,};
- this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
+ DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
}
// Sets up mobile buttons for inside mobile menu
@@ -436,42 +374,6 @@ export class CurrentUserUtils {
}) as any as Doc
- static setupThumbButtons(doc: Doc) {
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
- { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" },
- { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" },
- { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange" },
- { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" },
- { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" },
- ];
- return docProtoData.map(data => Docs.Create.FontIconDocument({
- _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon,
- _dropAction: data.pointerDown ? "copy" : undefined,
- ignoreClick: data.ignoreClick,
- onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
- clipboard: data.clipboard,
- onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined,
- onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
- backgroundColor: data.backgroundColor,
- _removeDropProperties: new List<string>(["dropAction"]),
- dragFactory: data.dragFactory,
- system: true
- }));
- }
-
- static setupThumbDoc(userDoc: Doc) {
- if (!userDoc.thumbDoc) {
- const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
- _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons",
- _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true
- });
- thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
- _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true
- });
- userDoc.thumbDoc = thumbDoc;
- }
- return Cast(userDoc.thumbDoc, Doc);
- }
static setupMobileInkingDoc(userDoc: Doc) {
return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true });
@@ -492,7 +394,7 @@ export class CurrentUserUtils {
/// Search option on the left side button panel
static setupSearcher(doc: Doc, field:string) {
- return this.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias",
_lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, });
}
@@ -506,7 +408,7 @@ export class CurrentUserUtils {
title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0",
_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
};
- return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
}
/// initializes the left sidebar dashboard pane
@@ -517,7 +419,7 @@ export class CurrentUserUtils {
const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true,
title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true };
const reqdBtnScript = {onClick: newDashboard,}
- const newDashboardButton = this.AssignScripts(this.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
+ const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
const reqdOpts:DocumentOptions = {
title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true,
@@ -530,7 +432,7 @@ export class CurrentUserUtils {
childContextMenuIcons: new List<string>(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
};
- myDashboards = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
+ myDashboards = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
const contextMenuScripts = [newDashboard];
const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
@@ -553,7 +455,7 @@ export class CurrentUserUtils {
const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true,
title: "New trail", toolTip: "Create new trail", btnType: ButtonType.ClickButton, buttonText: "New trail", icon: "plus", system: true };
const reqdBtnScript = {onClick: `createNewPresentation()`};
- const newTrailButton = this.AssignScripts(this.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
+ const newTrailButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
const reqdOpts:DocumentOptions = {
title: "My Trails", _showTitle: "title", _height: 100,
@@ -564,7 +466,7 @@ export class CurrentUserUtils {
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true,
explainer: "All of the trails that you have created will appear here."
};
- myTrails = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
+ myTrails = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
const contextMenuScripts = [reqdBtnScript.onClick];
if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) {
myTrails.contextMenuScripts = new List<ScriptField>(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
@@ -575,15 +477,15 @@ export class CurrentUserUtils {
/// initializes the left sidebar File system pane
static setupFilesystem(doc: Doc, field:string) {
var myFilesystem = DocCast(doc[field]);
- const myFileOrphans = this.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
+ const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
- const newFolder = `makeTopLevelFolder()`;
+ const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
_forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30,
title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true
};
const newFolderScript = { onClick: newFolder};
- const newFolderButton = this.AssignScripts(this.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
+ const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true,
title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true,
@@ -593,7 +495,7 @@ export class CurrentUserUtils {
childContextMenuIcons: new List<string>(["plus"]),
explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
};
- myFilesystem = this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]);
+ myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]);
const childContextMenuScripts = [newFolder];
if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
myFilesystem.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
@@ -610,13 +512,13 @@ export class CurrentUserUtils {
contextMenuIcons:new List<string>(["trash"]),
explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
};
- const recentlyClosed = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
+ const recentlyClosed = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
const clearAll = (target:string) => `getProto(${target}).data = new List([])`;
const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true,
title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true,
toolTip: "Empty recently closed",};
- const clearDocsButton = this.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
+ const clearDocsButton = DocUtils.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton;
@@ -626,19 +528,6 @@ export class CurrentUserUtils {
return recentlyClosed;
}
- /// creates a new, empty filter doc
- static createFilterDoc() {
- const clearAll = `getProto(self).data = new List([])`;
- const reqdOpts:DocumentOptions = {
- _lockedPosition: true, _autoHeight: true, _fitWidth: true, _height: 150, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true,
- title: "Unnamed Filter", filterBoolean: "AND", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true,
- childDropAction: "none", treeViewHideTitle: true, treeViewTruncateTitleWidth: 150,
- childContextMenuLabels: new List<string>(["Clear All"]),
- childContextMenuScripts: new List<ScriptField>([ScriptField.MakeFunction(clearAll)!]),
- };
- return Docs.Create.FilterDocument(reqdOpts);
- }
-
/// initializes the left sidebar panel view of the UserDoc
static setupUserDocView(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
@@ -646,8 +535,8 @@ export class CurrentUserUtils {
boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true,
treeViewHideTitle: true, treeViewTruncateTitleWidth: 150
};
- if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
- return this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
+ if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
}
static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, {
@@ -666,7 +555,7 @@ export class CurrentUserUtils {
static setupDockedButtons(doc: Doc, field="myDockedBtns") {
const dockedBtns = DocCast(doc[field]);
const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}) =>
- this.AssignScripts(this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
CurrentUserUtils.createToolButton(opts), scripts);
const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
@@ -680,7 +569,7 @@ export class CurrentUserUtils {
};
reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
- return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
+ return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
static textTools():Button[] {
@@ -740,16 +629,16 @@ export class CurrentUserUtils {
CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid]),
- title: "Perspective", toolTip: "View", width: 100,btnType: ButtonType.DropdownList,ignoreClick: true, scripts: { script: 'setView(value, _readOnly_)'}},
- { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}},
- { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}},
- { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setBackgroundColor(value, _readOnly_)'},funcs: {hidden: '!selectedDocumentType()'}}, // Only when a document is selected
- { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}, funcs: {hidden: '!selectedDocumentType()'}},
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, scripts: { onClick: 'toggleOverlay(_readOnly_)'}, funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'}}, // Only when floating document is selected in freeform
- { title: "Text", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available
- { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available
- { title: "Web", icon: "web", subMenu: CurrentUserUtils.webTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.WEB}")`, hidden: `!selectedDocumentType("${DocumentType.WEB}")`} }, // Only when Web is selected
- { title: "Schema", icon: "schema", subMenu: CurrentUserUtils.schemaTools(), funcs: {linearViewIsExpanded: `selectedDocumentType(undefined, "${CollectionViewType.Schema}")`, hidden: `!selectedDocumentType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
+ { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}},
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform", true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Text", icon: "text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.RTF}")`} }, // Always available
+ { title: "Ink", icon: "ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), funcs: {hidden: 'false', inearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.INK}")`} }, // Always available
+ { title: "Web", icon: "web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), funcs: {hidden: `!SelectionManager_selectedDocType("${DocumentType.WEB}")`, linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.WEB}")`, } }, // Only when Web is selected
+ { title: "Schema", icon: "schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), funcs: {hidden: `!SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`, linearViewIsExpanded: `SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected
];
}
@@ -768,13 +657,13 @@ export class CurrentUserUtils {
...params.funcs,
backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally
}
- return this.AssignScripts(this.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
}
/// Initializes all the default buttons for the top bar context menu
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
- const ctxtMenuBtnsDoc = this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
+ const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => {
const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
if (!params.subMenu) {
@@ -786,20 +675,20 @@ export class CurrentUserUtils {
const items = params.subMenu?.map(sub =>
this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title))
);
- return this.AssignScripts(
- this.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), undefined, params.funcs);
+ return DocUtils.AssignScripts(
+ DocUtils.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), undefined, params.funcs);
}
});
- return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
+ return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
}
/// collection of documents rendered in the overlay layer above all tabs and other UI
static setupOverlays(doc: Doc, field = "myOverlayDocs") {
- return this.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true });
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true });
}
static setupPublished(doc:Doc, field = "myPublishedDocs") {
- return this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true });
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true });
}
/// The database of all links on all documents
@@ -838,7 +727,7 @@ export class CurrentUserUtils {
explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
};
- this.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
+ DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
}
/// Import option on the left side button panel
@@ -849,12 +738,12 @@ export class CurrentUserUtils {
childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go."
};
- const myImports = this.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
+ const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
_width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
buttonText: "Import", icon: "upload", system: true };
- this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
+ DocUtils.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
return myImports;
}
@@ -905,17 +794,16 @@ export class CurrentUserUtils {
return doc.clickFuncs as Doc;
}
-
/// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be
/// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field
/// whether to revert to "default" values, or to leave them as the user/system last set them.
static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
- this.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
+ DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]),
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
+ SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
doc.system ?? (doc.system = true);
doc.title ?? (doc.title = Doc.CurrentUserEmail);
@@ -949,8 +837,8 @@ export class CurrentUserUtils {
this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
- this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
- this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents
+ DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents
if (doc.activeDashboard instanceof Doc) {
// undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
@@ -958,12 +846,12 @@ export class CurrentUserUtils {
}
new LinkManager();
- DocServer.UPDATE_SERVER_CACHE();
+ setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
return doc;
}
static setupFieldInfos(doc:Doc, field="fieldInfos") {
const fieldInfoOpts = { title: "Field Infos", system: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object
- const infos = this.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts);
+ const infos = DocUtils.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts);
const entries = Object.entries(new DocumentOptions());
entries.forEach(pair => {
if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) {
@@ -975,7 +863,7 @@ export class CurrentUserUtils {
case Doc.name: opts.fieldValues = new List<Doc>(options.values as any); break;
default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
}
- this.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+ DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
}
});
}
@@ -996,7 +884,6 @@ export class CurrentUserUtils {
}
public static async loadUserDocument(id: string) {
- this.curr_id = id;
await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
if (userDocumentId !== "guest") {
@@ -1013,46 +900,6 @@ export class CurrentUserUtils {
});
}
- public static _urlState: HistoryUtil.DocUrl;
-
- /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there).
- /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url)
- public static openDashboard = (doc: Doc|undefined, fromHistory = false) => {
- if (!doc) return false;
- CurrentUserUtils.MainDocId = doc[Id];
- Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc);
-
- // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
- Doc.UserDoc() ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc);
-
- const state = CurrentUserUtils._urlState;
- if (state.sharing === true && !Doc.UserDoc()) {
- DocServer.Control.makeReadOnly();
- } else {
- fromHistory || HistoryUtil.pushState({
- type: "doc",
- docId: doc[Id],
- readonly: state.readonly,
- nro: state.nro,
- sharing: false,
- });
- if (state.readonly === true || state.readonly === null) {
- DocServer.Control.makeReadOnly();
- } else if (state.safe) {
- if (!state.nro) {
- DocServer.Control.makeReadOnly();
- }
- CollectionView.SetSafeMode(true);
- } else if (state.nro || state.nro === null || state.readonly === false) {
- } else if (doc.readOnly) {
- DocServer.Control.makeReadOnly();
- } else {
- DocServer.Control.makeEditable();
- }
- }
-
- return true;
- }
public static importDocument = () => {
const input = document.createElement("input");
@@ -1069,7 +916,7 @@ export class CurrentUserUtils {
// setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
// docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
// }
- const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null);
+ const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
doc instanceof Doc && list?.splice(0, 0, doc);
} else if (input.files && input.files.length !== 0) {
const disposer = OverlayView.ShowSpinner();
@@ -1077,7 +924,7 @@ export class CurrentUserUtils {
if (results.length !== input.files?.length) {
alert("Error uploading files - possibly due to unsupported file types");
}
- const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null);
+ const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
list?.splice(0, 0, ...results);
disposer();
} else {
@@ -1086,110 +933,12 @@ export class CurrentUserUtils {
};
input.click();
}
-
- public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(CurrentUserUtils.ActiveDashboard); }
-
- public static closeActiveDashboard = () => { CurrentUserUtils.ActiveDashboard = undefined; }
-
- public static removeDashboard = async (dashboard:Doc) => {
- const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data);
- if (dashboards?.length) {
- if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard));
- Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard);
- if (!dashboards.length) CurrentUserUtils.ActivePage = "home";
- }
- }
- public static createNewDashboard = (id?: string, name?: string) => {
- const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true);
- const dashboards = CurrentUserUtils.MyDashboards;
- const dashboardCount = DocListCast(dashboards.data).length + 1;
- const freeformOptions: DocumentOptions = {
- x: 0,
- y: 400,
- _width: 1500,
- _height: 1000,
- _fitWidth: true,
- _backgroundGridShow: true,
- title: `Untitled Tab 1`,
- };
- const title = name ? name : `Dashboard ${dashboardCount}`
- const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, "row");
- freeformDoc.context = dashboardDoc;
-
- // switching the tabs from the datadoc to the regular doc
- const dashboardTabs = DocListCast(dashboardDoc[DataSym].data);
- dashboardDoc.data = new List<Doc>(dashboardTabs);
- dashboardDoc["pane-count"] = 1;
-
- CurrentUserUtils.ActivePresentation = presentation;
-
- Doc.AddDocToList(dashboards, "data", dashboardDoc);
- // open this new dashboard
- CurrentUserUtils.ActiveDashboard = dashboardDoc;
- CurrentUserUtils.ActivePage = "dashboard";
- }
-
- public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
- const tbox = Docs.Create.TextDocument("", {
- _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor,
- _width: width || 200, _height: 35, x: x, y: y, _fitWidth: true, _autoHeight: true, title
- });
- const template = Doc.UserDoc().defaultTextLayout;
- if (template instanceof Doc) {
- tbox._width = NumCast(template._width);
- tbox.layoutKey = "layout_" + StrCast(template.title);
- Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
- }
- return tbox;
- }
-
- public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); }
- public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); }
- public static get MySearcher() { return DocCast(Doc.UserDoc().mySearcher); }
- public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); }
- public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); }
- public static get MyTools() { return DocCast(Doc.UserDoc().myTools); }
- public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); }
- public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); }
- public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); }
- public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); }
- public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); }
- public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); }
- public static get MyTrails() { return DocCast(Doc.UserDoc().myTrails); }
- public static get MyImports() { return DocCast(Doc.UserDoc().myImports); }
- public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); }
- public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); }
- public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); }
- public static get MyPublishedDocs() { return DocCast(Doc.UserDoc().myPublishedDocs); }
- public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); }
- public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; }
- public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); }
- public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; }
- public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); }
- public static set ActivePage(val) { Doc.UserDoc().activePage = val; }
- public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; }
- public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; }
}
-ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs");
+ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
-ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(); }, "creates a snapshot copy of a dashboard");
-ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(); }, "creates a new dashboard when called");
ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called");
ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called");
ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard");
-ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards");
-ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { CurrentUserUtils.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards");
-ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
- let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
- return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
-});
-ScriptingGlobals.add(function makeTopLevelFolder() {
- TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
- const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true };
- return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
-}); \ No newline at end of file
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 0435cd535..d3ac2f03f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -4,44 +4,43 @@ import { Id } from '../../fields/FieldSymbols';
import { Cast } from '../../fields/Types';
import { returnFalse } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
-import { CollectionView } from '../views/collections/CollectionView';
import { LightboxView } from '../views/LightboxView';
import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
-import { CurrentUserUtils } from './CurrentUserUtils';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
+import { CollectionView } from '../views/collections/CollectionView';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
export class DocumentManager {
-
//global holds all of the nodes (regardless of which collection they're in)
@observable public DocumentViews = new Set<DocumentView>();
@observable public LinkAnchorBoxViews: DocumentView[] = [];
@observable public RecordingEvent = 0;
- @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
+ @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
private static _instance: DocumentManager;
- public static get Instance(): DocumentManager { return this._instance || (this._instance = new this()); }
+ public static get Instance(): DocumentManager {
+ return this._instance || (this._instance = new this());
+ }
//private constructor so no other class can create a nodemanager
- private constructor() { }
+ private constructor() {}
@action
public AddView = (view: DocumentView) => {
//console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const viewAnchorIndex = view.props.LayoutTemplateString.includes("anchor2") ? "anchor2" : "anchor1";
+ const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1';
DocListCast(view.rootDoc.links).forEach(link => {
- this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).
- forEach(otherView => this.LinkedDocumentViews.push(
- {
- a: viewAnchorIndex === "anchor2" ? otherView : view,
- b: viewAnchorIndex === "anchor2" ? view : otherView,
- l: link
- })
- );
+ this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
+ this.LinkedDocumentViews.push({
+ a: viewAnchorIndex === 'anchor2' ? otherView : view,
+ b: viewAnchorIndex === 'anchor2' ? view : otherView,
+ l: link,
+ })
+ );
});
this.LinkAnchorBoxViews.push(view);
// this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
@@ -49,14 +48,16 @@ export class DocumentManager {
} else {
this.DocumentViews.add(view);
}
- }
+ };
public RemoveView = action((view: DocumentView) => {
- this.LinkedDocumentViews.slice().forEach(action(pair => {
- if (pair.a === view || pair.b === view) {
- const li = this.LinkedDocumentViews.indexOf(pair);
- li !== -1 && this.LinkedDocumentViews.splice(li, 1);
- }
- }));
+ this.LinkedDocumentViews.slice().forEach(
+ action(pair => {
+ if (pair.a === view || pair.b === view) {
+ const li = this.LinkedDocumentViews.indexOf(pair);
+ li !== -1 && this.LinkedDocumentViews.splice(li, 1);
+ }
+ })
+ );
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const index = this.LinkAnchorBoxViews.indexOf(view);
@@ -125,11 +126,11 @@ export class DocumentManager {
const views: DocumentView[] = [];
Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
- }
+ };
public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc);
return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
- }
+ };
public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
@@ -146,17 +147,16 @@ export class DocumentManager {
return toReturn;
}
-
static addView = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddSplit(doc, "right");
+ CollectionDockingView.AddSplit(doc, 'right');
finished?.();
- }
+ };
public jumpToDocument = async (
- targetDoc: Doc, // document to display
- willZoom: boolean, // whether to zoom doc to take up most of screen
+ targetDoc: Doc, // document to display
+ willZoom: boolean, // whether to zoom doc to take up most of screen
createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
- docContext: Doc[], // context to load that should contain the target
- linkDoc?: Doc, // link that's being followed
+ docContext: Doc[], // context to load that should contain the target
+ linkDoc?: Doc, // link that's being followed
closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
finished?: () => void,
@@ -172,14 +172,15 @@ export class DocumentManager {
var wasHidden = resolvedTarget.hidden;
if (wasHidden) {
runInAction(() => {
- resolvedTarget.hidden = false; // if the target is hidden, un-hide it here.
+ resolvedTarget.hidden = false; // if the target is hidden, un-hide it here.
docView?.props.bringToFront(resolvedTarget);
});
}
const focusAndFinish = (didFocus: boolean) => {
const finalTargetDoc = resolvedTarget;
if (originatingDoc?.isPushpin) {
- if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
+ if (!didFocus && !wasHidden) {
+ // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
finalTargetDoc.hidden = !finalTargetDoc.hidden;
}
} else {
@@ -192,77 +193,92 @@ export class DocumentManager {
const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined;
const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined;
const targetDocContext = contextDoc || annotatedDoc;
- const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
- (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
+ const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
if (annoContainerView) {
- if (annoContainerView.props.Document.layoutKey === "layout_icon") {
- annoContainerView.iconify(() => annoContainerView.focus(targetDoc, {
- originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- focusAndFinish(true);
- res(ViewAdjustment.doNothing);
- })
- }));
+ if (annoContainerView.props.Document.layoutKey === 'layout_icon') {
+ annoContainerView.iconify(() =>
+ annoContainerView.focus(targetDoc, {
+ originalTarget,
+ willZoom,
+ scale: presZoom,
+ afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ focusAndFinish(true);
+ res(ViewAdjustment.doNothing);
+ }),
+ })
+ );
return;
} else if (!docView && targetDoc.type !== DocumentType.MARKER) {
- annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+ annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
}
}
if (focusView) {
!noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
- const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, {
- originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- focusAndFinish(forceDidFocus || didFocus);
- res(ViewAdjustment.doNothing);
- })
- });
- if (focusView.props.Document.layoutKey === "layout_icon") {
+ const doFocus = (forceDidFocus: boolean) =>
+ focusView.focus(originalTarget ?? targetDoc, {
+ originalTarget,
+ willZoom,
+ scale: presZoom,
+ afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ focusAndFinish(forceDidFocus || didFocus);
+ res(ViewAdjustment.doNothing);
+ }),
+ });
+ if (focusView.props.Document.layoutKey === 'layout_icon') {
focusView.iconify(() => doFocus(true));
} else {
doFocus(false);
}
} else {
- 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
+ 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
createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
- } else { // otherwise try to get a view of the context of the target
- if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
+ } else {
+ // otherwise try to get a view of the context of the target
+ if (targetDocContextView) {
+ // we found a context view and aren't forced to create a new one ... focus on the context first..
wasHidden = wasHidden || targetDocContextView.rootDoc.hidden;
targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden
- targetDocContext._viewTransition = "transform 500ms";
+ targetDocContext._viewTransition = 'transform 500ms';
targetDocContextView.props.focus(targetDocContextView.rootDoc, {
- willZoom, afterFocus: async () => {
+ willZoom,
+ afterFocus: async () => {
targetDocContext._viewTransition = undefined;
- if (targetDocContext.layoutKey === "layout_icon") {
- targetDocContextView.iconify(() => this.jumpToDocument(
- resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
- finished, originalTarget, noSelect, presZoom));
+ if (targetDocContext.layoutKey === 'layout_icon') {
+ targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom));
}
return ViewAdjustment.doNothing;
- }
+ },
});
// now find the target document within the context
- if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
+ if (targetDoc._timecodeToShow) {
+ // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow;
finished?.();
- } else { // no timecode means we need to find the context view and focus on our target
+ } else {
+ // no timecode means we need to find the context view and focus on our target
const findView = (delay: number) => {
- const retryDocView = getFirstDocView(resolvedTarget); // 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.
+ const retryDocView = getFirstDocView(resolvedTarget); // 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.
Doc.linkFollowHighlight(retryDocView.rootDoc);
retryDocView.focus(targetDoc, {
- willZoom, afterFocus: (didFocus: boolean) =>
+ willZoom,
+ afterFocus: (didFocus: boolean) =>
new Promise<ViewAdjustment>(res => {
- !noSelect && focusAndFinish(didFocus);
+ !noSelect && focusAndFinish(true);
res(ViewAdjustment.doNothing);
- })
+ }),
}); // focus on the target in the context
} else if (delay > 1000) {
// 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.rootDoc);
- if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document
+ if (targetDoc.layout) {
+ // there will no layout for a TEXTANCHOR type document
createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
}
} else {
@@ -272,22 +288,24 @@ export class DocumentManager {
setTimeout(() => findView(0), 0);
}
} else {
- if (docContext.length && docContext[0]?.layoutKey === "layout_icon") {
+ if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') {
const docContextView = this.getFirstDocumentView(docContext[0]);
if (docContextView) {
- return docContextView.iconify(() => this.jumpToDocument(
- targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc,
- finished, originalTarget, noSelect, presZoom));
+ return docContextView.iconify(() =>
+ this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom)
+ );
}
}
// there's no context view so we need to create one first and try again when that finishes
const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget);
- createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
- finishFunc);
+ createViewFunc(
+ targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
+ finishFunc
+ );
}
}
}
- }
+ };
}
export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
@@ -295,14 +313,12 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) {
dv.props.focus(dv.props.Document, { willZoom: true });
Doc.linkFollowHighlight(dv?.props.Document, false);
- }
- else {
- const context = doc.context !== CurrentUserUtils.MyFilesystem && Cast(doc.context, Doc, null);
+ } else {
+ const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null);
const showDoc = context || doc;
const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc;
- CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), "right") && context &&
- setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc));
+ CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc));
}
}
-ScriptingGlobals.add(DocFocusOrOpen); \ No newline at end of file
+ScriptingGlobals.add(DocFocusOrOpen);
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 62d656c5d..59334f6a2 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,19 +1,19 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import Select from 'react-select';
-import * as RequestPromise from "request-promise";
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
-import { StrCast, Cast } from "../../fields/Types";
-import { Utils } from "../../Utils";
-import { MainViewModal } from "../views/MainViewModal";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import "./GroupManager.scss";
-import { GroupMemberView } from "./GroupMemberView";
-import { SharingManager, User } from "./SharingManager";
-import { listSpec } from "../../fields/Schema";
-import { DateField } from "../../fields/DateField";
+import * as RequestPromise from 'request-promise';
+import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
+import { StrCast, Cast } from '../../fields/Types';
+import { Utils } from '../../Utils';
+import { MainViewModal } from '../views/MainViewModal';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import './GroupManager.scss';
+import { GroupMemberView } from './GroupMemberView';
+import { SharingManager, User } from './SharingManager';
+import { listSpec } from '../../fields/Schema';
+import { DateField } from '../../fields/DateField';
/**
* Interface for options for the react-select component
@@ -25,7 +25,6 @@ export interface UserOptions {
@observer
export class GroupManager extends React.Component<{}> {
-
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@observable private users: string[] = []; // list of users populated from the database.
@@ -34,24 +33,26 @@ export class GroupManager extends React.Component<{}> {
@observable private createGroupModalOpen: boolean = false;
private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button
- @observable private buttonColour: "#979797" | "black" = "#979797";
- @observable private groupSort: "ascending" | "descending" | "none" = "none";
+ @observable private buttonColour: '#979797' | 'black' = '#979797';
+ @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none';
constructor(props: Readonly<{}>) {
super(props);
GroupManager.Instance = this;
}
- componentDidMount() { this.populateUsers(); }
+ componentDidMount() {
+ this.populateUsers();
+ }
/**
* Fetches the list of users stored on the database.
*/
populateUsers = async () => {
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
- }
+ };
/**
* @returns the options to be rendered in the dropdown menu to add users and create a group.
@@ -68,11 +69,11 @@ export class GroupManager extends React.Component<{}> {
// SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
- }
+ };
/**
* Hides the GroupManager.
- */
+ */
@action
close = () => {
this.isOpen = false;
@@ -81,26 +82,32 @@ export class GroupManager extends React.Component<{}> {
// this.users = [];
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
- }
+ };
/**
* @returns the database of groups.
*/
- @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; }
+ @computed get GroupManagerDoc(): Doc | undefined {
+ return Doc.UserDoc().globalGroupDatabase as Doc;
+ }
/**
* @returns a list of all group documents.
*/
- @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); }
+ @computed get allGroups(): Doc[] {
+ return DocListCast(this.GroupManagerDoc?.data);
+ }
/**
* @returns the members of the admin group.
*/
- @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; }
+ @computed get adminGroupMembers(): string[] {
+ return this.getGroup('Admin') ? JSON.parse(StrCast(this.getGroup('Admin')!.members)) : '';
+ }
/**
* @returns a group document based on the group name.
- * @param groupName
+ * @param groupName
*/
getGroup(groupName: string): Doc | undefined {
return this.allGroups.find(group => group.title === groupName);
@@ -114,10 +121,9 @@ export class GroupManager extends React.Component<{}> {
return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
}
-
/**
* @returns a boolean indicating whether the current user has access to edit group documents.
- * @param groupDoc
+ * @param groupDoc
*/
hasEditAccess(groupDoc: Doc): boolean {
if (!groupDoc) return false;
@@ -127,12 +133,12 @@ export class GroupManager extends React.Component<{}> {
/**
* Helper method that sets up the group document.
- * @param groupName
- * @param memberEmails
+ * @param groupName
+ * @param memberEmails
*/
createGroupDoc(groupName: string, memberEmails: string[] = []) {
- const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName;
- const groupDoc = new Doc("GROUP:" + name, true);
+ const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName;
+ const groupDoc = new Doc('GROUP:' + name, true);
groupDoc.title = name;
groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
groupDoc.members = JSON.stringify(memberEmails);
@@ -141,12 +147,12 @@ export class GroupManager extends React.Component<{}> {
/**
* Helper method that adds a group document to the database of group documents and @returns whether it was successfully added or not.
- * @param groupDoc
+ * @param groupDoc
*/
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
- Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc);
- this.GroupManagerDoc["data-lastModified"] = new DateField;
+ Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc);
+ this.GroupManagerDoc['data-lastModified'] = new DateField();
return true;
}
return false;
@@ -154,20 +160,20 @@ export class GroupManager extends React.Component<{}> {
/**
* Deletes a group from the database of group documents and @returns whether the group was deleted or not.
- * @param group
+ * @param group
*/
@action
deleteGroup(group: Doc): boolean {
if (group) {
if (this.GroupManagerDoc && this.hasEditAccess(group)) {
- Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
+ Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group);
SharingManager.Instance.removeGroup(group);
const members = JSON.parse(StrCast(group.members));
if (members.includes(Doc.CurrentUserEmail)) {
const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
- this.GroupManagerDoc["data-lastModified"] = new DateField;
+ this.GroupManagerDoc['data-lastModified'] = new DateField();
if (group === this.currentGroup) {
this.currentGroup = undefined;
}
@@ -179,8 +185,8 @@ export class GroupManager extends React.Component<{}> {
/**
* Adds a member to a group.
- * @param groupDoc
- * @param email
+ * @param groupDoc
+ * @param email
*/
addMemberToGroup(groupDoc: Doc, email: string) {
if (this.hasEditAccess(groupDoc)) {
@@ -188,14 +194,14 @@ 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["data-lastModified"] = new DateField);
+ this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField());
}
}
/**
* Removes a member from the group.
- * @param groupDoc
- * @param email
+ * @param groupDoc
+ * @param email
*/
removeMemberFromGroup(groupDoc: Doc, email: string) {
if (this.hasEditAccess(groupDoc)) {
@@ -205,27 +211,27 @@ 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["data-lastModified"] = new DateField);
+ this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField());
}
}
}
/**
* Handles changes in the users selected in the "Select users" dropdown.
- * @param selectedOptions
+ * @param selectedOptions
*/
@action
handleChange = (selectedOptions: any) => {
this.selectedUsers = selectedOptions as UserOptions[];
- }
+ };
/**
* Creates the group when the enter key has been pressed (when in the input).
- * @param e
+ * @param e
*/
handleKeyDown = (e: React.KeyboardEvent) => {
- e.key === "Enter" && this.createGroup();
- }
+ e.key === 'Enter' && this.createGroup();
+ };
/**
* Handles the input of required fields in the setup of a group and resets the relevant variables.
@@ -234,32 +240,37 @@ export class GroupManager extends React.Component<{}> {
createGroup = () => {
const { value } = this.inputRef.current!;
if (!value) {
- alert("Please enter a group name");
+ alert('Please enter a group name');
return;
}
- if (["admin", "public", "override"].includes(value.toLowerCase())) {
- if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) {
+ if (['admin', 'public', 'override'].includes(value.toLowerCase())) {
+ if (value.toLowerCase() !== 'admin' || (value.toLowerCase() === 'admin' && this.getGroup('Admin'))) {
alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`);
return;
}
}
if (this.getGroup(value)) {
- alert("Please select a unique group name");
+ alert('Please select a unique group name');
return;
}
- this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
+ this.createGroupDoc(
+ value,
+ this.selectedUsers?.map(user => user.value)
+ );
this.selectedUsers = null;
- this.inputRef.current!.value = "";
- this.buttonColour = "#979797";
+ this.inputRef.current!.value = '';
+ this.buttonColour = '#979797';
const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect();
TaskCompletionBox.popupX = left - 2 * width;
TaskCompletionBox.popupY = top;
- TaskCompletionBox.textDisplayed = "Group created!";
+ TaskCompletionBox.textDisplayed = 'Group created!';
TaskCompletionBox.taskCompleted = true;
- setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
-
- }
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2000
+ );
+ };
/**
* @returns the MainViewModal which allows the user to create groups.
@@ -268,50 +279,43 @@ export class GroupManager extends React.Component<{}> {
const contents = (
<div className="group-create">
<div className="group-heading" style={{ marginBottom: 0 }}>
- <p><b>New Group</b></p>
- <div className={"close-button"} onClick={action(() => {
- this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false;
- })}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ <p>
+ <b>New Group</b>
+ </p>
+ <div
+ className={'close-button'}
+ onClick={action(() => {
+ this.createGroupModalOpen = false;
+ TaskCompletionBox.taskCompleted = false;
+ })}>
+ <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
</div>
- <input
- className="group-input"
- ref={this.inputRef}
- onKeyDown={this.handleKeyDown}
- autoFocus
- type="text"
- placeholder="Group name"
- onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} />
+ <input className="group-input" ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
<Select
isMulti
options={this.options}
onChange={this.handleChange}
- placeholder={"Select users"}
+ placeholder={'Select users'}
value={this.selectedUsers}
closeMenuOnSelect={false}
styles={{
dropdownIndicator: (base, state) => ({
...base,
transition: '0.5s all ease',
- transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined
+ transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined,
}),
- multiValue: (base) => ({
+ multiValue: base => ({
...base,
- maxWidth: "50%",
+ maxWidth: '50%',
'&:hover': {
- maxWidth: "unset"
- }
- })
+ maxWidth: 'unset',
+ },
+ }),
}}
/>
- <button
- ref={this.createGroupButtonRef}
- onClick={this.createGroup}
- style={{ background: this.buttonColour }}
- disabled={this.buttonColour === "#979797"}
- >
+ <button ref={this.createGroupButtonRef} onClick={this.createGroup} style={{ background: this.buttonColour }} disabled={this.buttonColour === '#979797'}>
Create
</button>
</div>
@@ -322,8 +326,12 @@ export class GroupManager extends React.Component<{}> {
isDisplayed={this.createGroupModalOpen}
interactive={true}
contents={contents}
- dialogueBoxStyle={{ width: "90%", height: "70%" }}
- closeOnExternalClick={action(() => { this.createGroupModalOpen = false; this.selectedUsers = null; TaskCompletionBox.taskCompleted = false; })}
+ dialogueBoxStyle={{ width: '90%', height: '70%' }}
+ closeOnExternalClick={action(() => {
+ this.createGroupModalOpen = false;
+ this.selectedUsers = null;
+ TaskCompletionBox.taskCompleted = false;
+ })}
/>
);
}
@@ -332,7 +340,6 @@ export class GroupManager extends React.Component<{}> {
* A getter that @returns the main interface for the GroupManager.
*/
private get groupInterface() {
-
const sortGroups = (d1: Doc, d2: Doc) => {
const g1 = StrCast(d1.title);
const g2 = StrCast(d2.title);
@@ -340,62 +347,50 @@ export class GroupManager extends React.Component<{}> {
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
};
- const groups = this.groupSort === "ascending" ? this.allGroups.sort(sortGroups) : this.groupSort === "descending" ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
+ const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
return (
<div className="group-interface">
{this.groupCreationModal}
- {this.currentGroup ?
- <GroupMemberView
- group={this.currentGroup}
- onCloseButtonClick={action(() => this.currentGroup = undefined)}
- />
- : null}
+ {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
<div className="group-heading">
- <p><b>Manage Groups</b></p>
- <button onClick={action(() => this.createGroupModalOpen = true)}>
- <FontAwesomeIcon icon={"plus"} size={"sm"} /> Create Group
+ <p>
+ <b>Manage Groups</b>
+ </p>
+ <button onClick={action(() => (this.createGroupModalOpen = true))}>
+ <FontAwesomeIcon icon={'plus'} size={'sm'} /> Create Group
</button>
- <div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ <div className={'close-button'} onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
</div>
<div className="main-container">
- <div
- className="sort-groups"
- onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
- : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
- : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />
- }
+ <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ Name{' '}
+ {this.groupSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.groupSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
</div>
<div className="group-body">
- {groups.map(group =>
- <div
- className="group-row"
- key={StrCast(group.title || group.groupName)}
- >
- <div className="group-name" >{group.title || group.groupName}</div>
- <div className="group-info" onClick={action(() => this.currentGroup = group)}>
- <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
+ {groups.map(group => (
+ <div className="group-row" key={StrCast(group.title || group.groupName)}>
+ <div className="group-name">{StrCast(group.title || group.groupName)}</div>
+ <div className="group-info" onClick={action(() => (this.currentGroup = group))}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
</div>
</div>
- )}
+ ))}
</div>
</div>
-
</div>
);
}
render() {
- return <MainViewModal
- contents={this.groupInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- dialogueBoxStyle={{ zIndex: 1002 }}
- overlayStyle={{ zIndex: 1001 }}
- closeOnExternalClick={this.close}
- />;
+ return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 7dcff9c56..18aee6444 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,8 +1,8 @@
-import { Doc } from "../../fields/Doc";
-import { DocServer } from "../DocServer";
import * as qs from 'query-string';
-import { Utils, OmitKeys } from "../../Utils";
-import { CurrentUserUtils } from "./CurrentUserUtils";
+import { Doc } from '../../fields/Doc';
+import { OmitKeys, Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
+import { DashboardView } from '../views/DashboardView';
export namespace HistoryUtil {
export interface DocInitializerList {
@@ -10,7 +10,7 @@ export namespace HistoryUtil {
}
export interface DocUrl {
- type: "doc";
+ type: 'doc';
docId: string;
initializers?: {
[docId: string]: DocInitializerList;
@@ -25,11 +25,11 @@ export namespace HistoryUtil {
// const handlers: ((state: ParsedUrl | null) => void)[] = [];
function onHistory(e: PopStateEvent) {
- if (window.location.pathname !== "/home") {
- const url = e.state as ParsedUrl || parseUrl(window.location);
+ if (window.location.pathname !== '/home') {
+ const url = (e.state as ParsedUrl) || parseUrl(window.location);
if (url) {
switch (url.type) {
- case "doc":
+ case 'doc':
onDocUrl(url);
break;
}
@@ -43,13 +43,13 @@ export namespace HistoryUtil {
let _lastStatePush = 0;
export function pushState(state: ParsedUrl) {
if (Date.now() - _lastStatePush > 1000) {
- history.pushState(state, "", createUrl(state));
+ history.pushState(state, '', createUrl(state));
}
_lastStatePush = Date.now();
}
export function replaceState(state: ParsedUrl) {
- history.replaceState(state, "", createUrl(state));
+ history.replaceState(state, '', createUrl(state));
}
function copyState(state: ParsedUrl): ParsedUrl {
@@ -61,7 +61,7 @@ export namespace HistoryUtil {
if (state) {
state.initializers = state.initializers || {};
}
- return state ?? {initializers:{}};
+ return state ?? { initializers: {} };
}
// export function addHandler(handler: (state: ParsedUrl | null) => void) {
@@ -78,10 +78,10 @@ export namespace HistoryUtil {
const parsers: { [type: string]: (pathname: string[], opts: qs.ParsedQuery) => ParsedUrl | undefined } = {};
const stringifiers: { [type: string]: (state: ParsedUrl) => string } = {};
- type ParserValue = true | "none" | "json" | ((value: string) => any);
+ type ParserValue = true | 'none' | 'json' | ((value: string) => any);
type Parser = {
- [key: string]: ParserValue
+ [key: string]: ParserValue;
};
function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: qs.ParsedQuery, current: ParsedUrl) => ParsedUrl | null | undefined) {
@@ -90,9 +90,9 @@ export namespace HistoryUtil {
return value;
}
if (Array.isArray(value)) {
- } else if (parser === true || parser === "json") {
+ } else if (parser === true || parser === 'json') {
value = JSON.parse(value);
- } else if (parser === "none") {
+ } else if (parser === 'none') {
} else {
value = parser(value);
}
@@ -142,29 +142,28 @@ export namespace HistoryUtil {
}
const queryObj = OmitKeys(state, keys).extract;
const query: any = {};
- Object.keys(queryObj).forEach(key => query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]));
+ Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
const queryString = qs.stringify(query);
- return path + (queryString ? `?${queryString}` : "");
+ return path + (queryString ? `?${queryString}` : '');
};
}
- addParser("doc", {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
+ addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
if (pathname.length !== 2) return undefined;
current.initializers = current.initializers || {};
const docId = pathname[1];
current.docId = docId;
});
- addStringifier("doc", ["initializers", "readonly", "nro"], (state, current) => {
+ addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
return `${current}/${state.docId}`;
});
-
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
const search = location.search;
const opts = search.length ? qs.parse(search, { sort: false }) : {};
- const pathnameSplit = pathname.split("/");
+ const pathnameSplit = pathname.split('/');
const type = pathnameSplit[0];
@@ -179,7 +178,7 @@ export namespace HistoryUtil {
if (params.type in stringifiers) {
return stringifiers[params.type](params);
}
- return "";
+ return '';
}
export async function initDoc(id: string, initializer: DocInitializerList) {
@@ -197,7 +196,7 @@ export namespace HistoryUtil {
await Promise.all(Object.keys(init).map(id => initDoc(id, init[id])));
}
if (field instanceof Doc) {
- CurrentUserUtils.openDashboard(field, true);
+ DashboardView.openDashboard(field, true);
}
}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
new file mode 100644
index 000000000..f75ac24f5
--- /dev/null
+++ b/src/client/util/LinkFollower.ts
@@ -0,0 +1,112 @@
+import { action, observable, observe } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { BoolCast, Cast, StrCast } from '../../fields/Types';
+import { LightboxView } from '../views/LightboxView';
+import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocumentManager } from './DocumentManager';
+import { UndoManager } from './UndoManager';
+
+type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
+/*
+ * link doc:
+ * - anchor1: doc
+ * - anchor2: doc
+ *
+ * group doc:
+ * - type: string representing the group type/name/category
+ * - metadata: doc representing the metadata kvps
+ *
+ * metadata doc:
+ * - user defined kvps
+ */
+export class LinkFollower {
+ // follows a link - if the target is on screen, it highlights/pans to it.
+ // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
+ // depending on the followLinkLocation property of the source (or the link itself as a fallback);
+ public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => {
+ const batch = UndoManager.StartBatch('follow link click');
+ // open up target if it's not already in view ...
+ const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
+ const createTabForTarget = (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ const where = LightboxView.LightboxDoc ? 'lightbox' : StrCast(sourceDoc.followLinkLocation, followLoc);
+ docViewProps.addDocTab(doc, where);
+ setTimeout(() => {
+ const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
+ const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
+ if (targDocView) {
+ targDocView.props.focus(doc, {
+ willZoom: BoolCast(sourceDoc.followLinkZoom, false),
+ afterFocus: (didFocus: boolean) => {
+ finished?.();
+ res(ViewAdjustment.resetView);
+ return new Promise<ViewAdjustment>(res2 => res2(ViewAdjustment.doNothing));
+ },
+ });
+ } else {
+ res(where !== 'inPlace' ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target
+ }
+ });
+ });
+
+ if (!sourceDoc.followLinkZoom) {
+ createTabForTarget(false);
+ } else {
+ // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
+ docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
+ }
+ };
+ LinkFollower.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
+ };
+
+ public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+ const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
+ const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
+ const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // 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 followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1);
+ var count = 0;
+ const allFinished = () => ++count === followLinks.length && finished?.();
+ followLinks.forEach(async linkDoc => {
+ if (linkDoc) {
+ const target = (
+ sourceDoc === linkDoc.anchor1
+ ? linkDoc.anchor2
+ : sourceDoc === linkDoc.anchor2
+ ? linkDoc.anchor1
+ : Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)
+ ? linkDoc.anchor2
+ : linkDoc.anchor1
+ ) as Doc;
+ if (target) {
+ if (target.TourMap) {
+ const fieldKey = Doc.LayoutFieldKey(target);
+ const tour = DocListCast(target[fieldKey]).reverse();
+ LightboxView.SetLightboxDoc(currentContext, undefined, tour);
+ setTimeout(LightboxView.Next);
+ allFinished();
+ } else {
+ const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
+ const containerDoc = containerAnnoDoc || target;
+ var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]);
+ while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
+ containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+ }
+ const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'lightbox'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished);
+ }
+ } else {
+ allFinished();
+ }
+ } else {
+ allFinished();
+ }
+ });
+ }
+}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index d51cd350d..7a12a8580 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,35 +1,30 @@
-import { action, observable, observe } from "mobx";
-import { computedFn } from "mobx-utils";
-import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { ProxyField } from "../../fields/Proxy";
-import { BoolCast, Cast, StrCast } from "../../fields/Types";
-import { LightboxView } from "../views/LightboxView";
-import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
-import { DocumentManager } from "./DocumentManager";
-import { UndoManager } from "./UndoManager";
-
-type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
-/*
- * link doc:
- * - anchor1: doc
+import { action, observable, observe } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { Cast, StrCast } from '../../fields/Types';
+/*
+ * link doc:
+ * - anchor1: doc
* - anchor2: doc
- *
+ *
* group doc:
* - type: string representing the group type/name/category
* - metadata: doc representing the metadata kvps
- *
+ *
* metadata doc:
- * - user defined kvps
+ * - user defined kvps
*/
export class LinkManager {
-
@observable static _instance: LinkManager;
@observable static userLinkDBs: Doc[] = [];
public static currentLink: Opt<Doc>;
- public static get Instance() { return LinkManager._instance; }
+ public static get Instance() {
+ return LinkManager._instance;
+ }
public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb);
- public static AutoKeywords = "keywords:Usages";
+ public static AutoKeywords = 'keywords:Usages';
static links: Doc[] = [];
constructor() {
LinkManager._instance = this;
@@ -38,50 +33,61 @@ export class LinkManager {
const addLinkToDoc = (link: Doc) => {
const a1Prom = link?.anchor1;
const a2Prom = link?.anchor2;
- Promise.all([a1Prom, a2Prom]).then((all) => {
+ Promise.all([a1Prom, a2Prom]).then(all => {
const a1 = all[0];
const a2 = all[1];
const a1ProtoProm = (link?.anchor1 as Doc)?.proto;
const a2ProtoProm = (link?.anchor2 as Doc)?.proto;
- Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => {
- if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
- Doc.GetProto(a1)[DirectLinksSym].add(link);
- Doc.GetProto(a2)[DirectLinksSym].add(link);
- Doc.GetProto(link)[DirectLinksSym].add(link);
- }
- }));
+ Promise.all([a1ProtoProm, a2ProtoProm]).then(
+ action(all => {
+ if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+ Doc.GetProto(a1)[DirectLinksSym].add(link);
+ Doc.GetProto(a2)[DirectLinksSym].add(link);
+ Doc.GetProto(link)[DirectLinksSym].add(link);
+ }
+ })
+ );
});
};
const remLinkFromDoc = (link: Doc) => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
- Promise.all([a1, a2]).then(action(() => {
- if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
- Doc.GetProto(a1)[DirectLinksSym].delete(link);
- Doc.GetProto(a2)[DirectLinksSym].delete(link);
- Doc.GetProto(link)[DirectLinksSym].delete(link);
- }
- }));
+ Promise.all([a1, a2]).then(
+ action(() => {
+ if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+ Doc.GetProto(a1)[DirectLinksSym].delete(link);
+ Doc.GetProto(a2)[DirectLinksSym].delete(link);
+ Doc.GetProto(link)[DirectLinksSym].delete(link);
+ }
+ })
+ );
};
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
LinkManager.links.push(...DocListCast(userLinkDBDoc.data));
- 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(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
- switch (change.type as any) {
- case "splice":
- (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
- (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
- break;
- case "update": //let oldValue = change.oldValue;
- }
- }, true);
- observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link)
+ 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(
+ userLinkDBDoc.data as Doc,
change => {
+ // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
switch (change.type as any) {
- case "update":
- Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => {
- const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length);
- const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length);
+ case 'splice':
+ (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
+ (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
+ break;
+ case 'update': //let oldValue = change.oldValue;
+ }
+ },
+ true
+ );
+ observe(
+ userLinkDBDoc,
+ 'data', // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link)
+ change => {
+ switch (change.type as any) {
+ case 'update':
+ Promise.all([...((change.oldValue as any as Doc[]) || []), ...((change.newValue as any as Doc[]) || [])]).then(doclist => {
+ const oldDocs = doclist.slice(0, ((change.oldValue as any as Doc[]) || []).length);
+ const newDocs = doclist.slice(((change.oldValue as any as Doc[]) || []).length, doclist.length);
const added = newDocs?.filter(link => !(oldDocs || []).includes(link));
const removed = oldDocs?.filter(link => !(newDocs || []).includes(link));
@@ -89,38 +95,54 @@ export class LinkManager {
removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
});
}
- }, true);
+ },
+ true
+ );
};
- observe(LinkManager.userLinkDBs, change => {
- switch (change.type as any) {
- case "splice": (change as any).added.forEach(watchUserLinkDB); break;
- case "update": //let oldValue = change.oldValue;
- }
- }, true);
+ observe(
+ LinkManager.userLinkDBs,
+ change => {
+ switch (change.type as any) {
+ case 'splice':
+ (change as any).added.forEach(watchUserLinkDB);
+ break;
+ case 'update': //let oldValue = change.oldValue;
+ }
+ },
+ true
+ );
LinkManager.addLinkDB(Doc.LinkDBDoc());
- DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist => dblist?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
- const a1 = await Cast(link?.anchor1, Doc, null);
- const a2 = await Cast(link?.anchor2, Doc, null);
- }));
+ DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist =>
+ dblist?.forEach(async link => {
+ // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager
+ const a1 = await Cast(link?.anchor1, Doc, null);
+ const a2 = await Cast(link?.anchor2, Doc, null);
+ })
+ );
}
-
public createLinkrelationshipLists = () => {
//create new lists for link relations and their associated colors if the lists don't already exist
!Doc.UserDoc().linkRelationshipList && (Doc.UserDoc().linkRelationshipList = new List<string>());
!Doc.UserDoc().linkColorList && (Doc.UserDoc().linkColorList = new List<string>());
!Doc.UserDoc().linkRelationshipSizes && (Doc.UserDoc().linkRelationshipSizes = new List<number>());
- }
+ };
public addLink(linkDoc: Doc, checkExists = false) {
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
- Doc.AddDocToList(Doc.LinkDBDoc(), "data", 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)); }
+ 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));
+ }
- public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
+ public getAllRelatedLinks(anchor: Doc) {
+ return this.relatedLinker(anchor);
+ } // finds all links that contain the given anchor
public getAllDirectLinks(anchor: Doc): Doc[] {
// FIXME:glr Why is Doc undefined?
if (Doc.GetProto(anchor)[DirectLinksSym]) {
@@ -133,18 +155,16 @@ export class LinkManager {
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
const lfield = Doc.LayoutFieldKey(anchor);
if (!anchor || anchor instanceof Promise || Doc.GetProto(anchor) instanceof Promise) {
- console.log("WAITING FOR DOC/PROTO IN LINKMANAGER");
+ console.log('WAITING FOR DOC/PROTO IN LINKMANAGER');
return [];
}
const dirLinks = Doc.GetProto(anchor)[DirectLinksSym];
- const annos = DocListCast(anchor[lfield + "-annotations"]);
- const timelineAnnos = DocListCast(anchor[lfield + "-annotations-timeline"]);
+ const annos = DocListCast(anchor[lfield + '-annotations']);
+ const timelineAnnos = DocListCast(anchor[lfield + '-annotations-timeline']);
if (!annos || !timelineAnnos) {
debugger;
}
- const related = [...annos, ...timelineAnnos].reduce((list, anno) =>
- [...list, ...LinkManager.Instance.relatedLinker(anno)],
- Array.from(dirLinks).slice());
+ const related = [...annos, ...timelineAnnos].reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice());
return related;
}, true);
@@ -152,15 +172,15 @@ export class LinkManager {
public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
const anchorGroups = new Map<string, Array<Doc>>();
this.relatedLinker(anchor).forEach(link => {
- if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") {
+ if (link.linkRelationship && link.linkRelationship !== '-ungrouped-') {
const relation = StrCast(link.linkRelationship);
- const anchorRelation = relation.indexOf(":") !== -1 ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation;
+ const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation;
const group = anchorGroups.get(anchorRelation);
anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
} else {
// if link is in no groups then put it in default group
- const group = anchorGroups.get("*");
- anchorGroups.set("*", group ? [...group, link] : [link]);
+ const group = anchorGroups.get('*');
+ anchorGroups.set('*', group ? [...group, link] : [link]);
}
});
return anchorGroups;
@@ -178,85 +198,4 @@ export class LinkManager {
if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
-
-
- // follows a link - if the target is on screen, it highlights/pans to it.
- // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
- // depending on the followLinkLocation property of the source (or the link itself as a fallback);
- public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => {
- const batch = UndoManager.StartBatch("follow link click");
- // open up target if it's not already in view ...
- const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
- const createTabForTarget = (didFocus: boolean) => new Promise<ViewAdjustment>(res => {
- const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation, followLoc);
- docViewProps.addDocTab(doc, where);
- setTimeout(() => {
- const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
- const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
- if (targDocView) {
- targDocView.props.focus(doc, {
- willZoom: BoolCast(sourceDoc.followLinkZoom, false),
- afterFocus: (didFocus: boolean) => {
- finished?.();
- res(ViewAdjustment.resetView);
- return new Promise<ViewAdjustment>(res2 => res2(ViewAdjustment.doNothing));
- }
- });
- } else {
- res(where !== "inPlace" ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target
- }
- });
- });
-
- if (!sourceDoc.followLinkZoom) {
- createTabForTarget(false);
- } else {
- // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
- }
- };
- LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
- }
-
- public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
- const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
- const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // 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 followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1);
- var count = 0;
- const allFinished = () => ++count === followLinks.length && finished?.();
- followLinks.forEach(async linkDoc => {
- if (linkDoc) {
- const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 :
- (Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
- if (target) {
- if (target.TourMap) {
- const fieldKey = Doc.LayoutFieldKey(target);
- const tour = DocListCast(target[fieldKey]).reverse();
- LightboxView.SetLightboxDoc(currentContext, undefined, tour);
- setTimeout(LightboxView.Next);
- allFinished();
- } else {
- const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
- const containerDoc = containerAnnoDoc || target;
- var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : [] as Doc[];
- while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
- containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
- }
- const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished);
- }
- } else {
- allFinished();
- }
- } else {
- allFinished();
- }
- });
- }
-
-} \ No newline at end of file
+}
diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts
new file mode 100644
index 000000000..7bffb0379
--- /dev/null
+++ b/src/client/util/RecordingApi.ts
@@ -0,0 +1,269 @@
+import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
+import { IReactionDisposer, observable, reaction } from 'mobx';
+import { NumCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
+import { VideoBox } from '../views/nodes/VideoBox';
+import { scaleDiverging } from 'd3-scale';
+import { Transform } from './Transform';
+
+type Movement = {
+ time: number;
+ panX: number;
+ panY: number;
+ scale: number;
+};
+
+type Presentation = {
+ movements: Array<Movement> | null;
+ meta: Object;
+};
+
+export class RecordingApi {
+ private static NULL_PRESENTATION: Presentation = {
+ movements: null,
+ meta: {},
+ };
+
+ // instance variables
+ private currentPresentation: Presentation;
+ private isRecording: boolean;
+ private absoluteStart: number;
+
+ // create static instance and getter for global use
+ @observable static _instance: RecordingApi;
+ public static get Instance(): RecordingApi {
+ return RecordingApi._instance;
+ }
+ public constructor() {
+ // init the global instance
+ RecordingApi._instance = this;
+
+ // init the instance variables
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION;
+ this.isRecording = false;
+ this.absoluteStart = -1;
+
+ // used for tracking movements in the view frame
+ this.disposeFunc = null;
+ this.recordingFFView = null;
+
+ // for now, set playFFView
+ this.playFFView = null;
+ this.timers = null;
+ }
+
+ // little helper :)
+ private get isInitPresenation(): boolean {
+ return this.currentPresentation.movements === null;
+ }
+
+ public start = (meta?: Object): Error | undefined => {
+ // check if already init a presentation
+ if (!this.isInitPresenation) {
+ console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.');
+ return new Error('[recordingApi.ts] start()');
+ }
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'recording';
+
+ // (1a) get start date for presenation
+ const startDate = new Date();
+ // (1b) set start timestamp to absolute timestamp
+ this.absoluteStart = startDate.getTime();
+
+ // (2) assign meta content if it exists
+ this.currentPresentation.meta = meta || {};
+ // (3) assign start date to currentPresenation
+ this.currentPresentation.movements = [];
+ // (4) set isRecording true to allow trackMovements
+ this.isRecording = true;
+ };
+
+ public clear = (): Error | Presentation => {
+ // TODO: maybe archive the data?
+ if (this.isRecording) {
+ console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first');
+ return new Error('[recordingApi.ts] clear()');
+ }
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'none';
+ // set the previus recording view to the play view
+ this.playFFView = this.recordingFFView;
+
+ const presCopy = { ...this.currentPresentation };
+
+ // clear presenation data
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION;
+ // clear isRecording
+ this.isRecording = false;
+ // clear absoluteStart
+ this.absoluteStart = -1;
+ // clear the disposeFunc
+ this.removeRecordingFFView();
+
+ return presCopy;
+ };
+
+ public pause = (): Error | undefined => {
+ if (this.isInitPresenation) {
+ console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first');
+ return new Error('[recordingApi.ts] pause(): no presentation');
+ }
+ // don't allow track movments
+ this.isRecording = false;
+
+ // set adjust absoluteStart to add the time difference
+ const timestamp = new Date().getTime();
+ this.absoluteStart = timestamp - this.absoluteStart;
+ };
+
+ public resume = () => {
+ this.isRecording = true;
+ // set absoluteStart to the difference in time
+ this.absoluteStart = new Date().getTime() - this.absoluteStart;
+ };
+
+ private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => {
+ // ensure we are recording
+ if (!this.isRecording) {
+ return new Error('[recordingApi.ts] trackMovements()');
+ }
+ // check to see if the presetation is init
+ if (this.isInitPresenation) {
+ return new Error('[recordingApi.ts] trackMovements(): no presentation');
+ }
+
+ // get the time
+ const time = new Date().getTime() - this.absoluteStart;
+ // make new movement object
+ const movement: Movement = { time, panX, panY, scale };
+
+ // add that movement to the current presentation data's movement array
+ this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
+ };
+
+ // instance variable for the FFView
+ private disposeFunc: IReactionDisposer | null;
+ private recordingFFView: CollectionFreeFormView | null;
+
+ // set the FFView that will be used in a reaction to track the movements
+ public setRecordingFFView = (view: CollectionFreeFormView): void => {
+ // set the view to the current view
+ if (view === this.recordingFFView || view == null) return;
+
+ // this.recordingFFView = view;
+ // set the reaction to track the movements
+ this.disposeFunc = reaction(
+ () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }),
+ res => res.x !== -1 && res.y !== -1 && this.isRecording && this.trackMovements(res.x, res.y, res.scale)
+ );
+
+ // for now, set the most recent recordingFFView to the playFFView
+ this.recordingFFView = view;
+ };
+
+ // call on dispose function to stop tracking movements
+ public removeRecordingFFView = (): void => {
+ this.disposeFunc?.();
+ this.disposeFunc = null;
+ };
+
+ // TODO: extract this into different class with pause and resume recording
+ // TODO: store the FFview with the movements
+ private playFFView: CollectionFreeFormView | null;
+ private timers: NodeJS.Timeout[] | null;
+
+ public setPlayFFView = (view: CollectionFreeFormView): void => {
+ this.playFFView = view;
+ };
+
+ // pausing movements will dispose all timers that are planned to replay the movements
+ // play movemvents will recreate them when the user resumes the presentation
+ public pauseMovements = (): undefined | Error => {
+ if (this.playFFView === null) {
+ return new Error('[recordingApi.ts] pauseMovements() failed: no view');
+ }
+
+ if (!this._isPlaying) {
+ //return new Error('[recordingApi.ts] pauseMovements() failed: not playing')
+ return;
+ }
+ this._isPlaying = false;
+ // TODO: set userdoc presentMode to browsing
+ this.timers?.map(timer => clearTimeout(timer));
+
+ // this.videoBox = null;
+ };
+
+ private videoBox: VideoBox | null = null;
+
+ // by calling pause on the VideoBox, the pauseMovements will be called
+ public pauseVideoAndMovements = (): boolean => {
+ this.videoBox?.Pause();
+
+ this.pauseMovements();
+ return this.videoBox == null;
+ };
+
+ public _isPlaying = false;
+
+ public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => {
+ if (presentation.movements === null || this.playFFView === null) {
+ return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view');
+ }
+ if (this._isPlaying) return;
+
+ this._isPlaying = true;
+ Doc.UserDoc().presentationMode = 'watching';
+
+ // TODO: consider this bug at the end of the clip on seek
+ this.videoBox = videoBox || null;
+
+ // only get the movements that are remaining in the video time left
+ const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000);
+
+ // helper to replay a movement
+ const document = this.playFFView;
+ let preScale = -1;
+ const zoomAndPan = (movement: Movement) => {
+ const { panX, panY, scale } = movement;
+ scale !== -1 && preScale !== scale && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
+ document.Document._panX = panX;
+ document.Document._panY = panY;
+
+ preScale = scale;
+ };
+
+ // set the first frame to be at the start of the pres
+ zoomAndPan(filteredMovements[0]);
+
+ // make timers that will execute each movement at the correct replay time
+ this.timers = filteredMovements.map(movement => {
+ const timeDiff = movement.time - timeViewed * 1000;
+ return setTimeout(() => {
+ // replay the movement
+ zoomAndPan(movement);
+ // if last movement, presentation is done -> set the instance var
+ if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false;
+ }, timeDiff);
+ });
+ };
+
+ // Unfinished code for tracing multiple free form views
+ // export let pres: Map<CollectionFreeFormView, IReactionDisposer> = new Map()
+
+ // export function AddRecordingFFView(ffView: CollectionFreeFormView): void {
+ // pres.set(ffView,
+ // reaction(() => ({ x: ffView.panX, y: ffView.panY }),
+ // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y)))
+ // )
+ // }
+
+ // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void {
+ // const disposer = pres.get(ffView);
+ // disposer?.();
+ // pres.delete(ffView)
+ // }
+}
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index a209874a3..d4620ae3f 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,21 +1,24 @@
-import React = require("react");
+import React = require('react');
-export class ScrollBox extends React.Component {
+export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> {
onWheel = (e: React.WheelEvent) => {
- if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom
+ if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+ // If the element has a scroll bar, then we don't want the containing collection to zoom
e.stopPropagation();
}
- }
+ };
render() {
return (
- <div style={{
- overflow: "auto",
- width: "100%",
- height: "100%",
- }} onWheel={this.onWheel}>
+ <div
+ style={{
+ overflow: 'auto',
+ width: '100%',
+ height: '100%',
+ }}
+ onWheel={this.onWheel}>
{this.props.children}
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index b71086561..1c84af94a 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,15 +1,13 @@
-import { action, observable, ObservableMap } from "mobx";
-import { computedFn } from "mobx-utils";
-import { Doc, Opt } from "../../fields/Doc";
-import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { CurrentUserUtils } from "./CurrentUserUtils";
+import { action, observable, ObservableMap } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { Doc, Opt } from '../../fields/Doc';
+import { DocCast } from '../../fields/Types';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { ScriptingGlobals } from './ScriptingGlobals';
export namespace SelectionManager {
-
class Manager {
-
@observable IsDragging: boolean = false;
SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
@observable SelectedSchemaDocument: Doc | undefined;
@@ -63,16 +61,21 @@ export namespace SelectionManager {
manager.SelectSchemaViewDoc(document);
}
- const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
+ const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {
+ // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
return manager.SelectedViews.get(doc) ? true : false;
});
// 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 | undefined, outsideReaction?: boolean): boolean {
- return !doc ? false : outsideReaction ?
- manager.SelectedViews.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
- IsSelectedCache(doc);
+ return !doc
+ ? false
+ : outsideReaction
+ ? manager.SelectedViews.get(doc)
+ ? true
+ : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
+ : IsSelectedCache(doc);
}
export function DeselectAll(except?: Doc): void {
@@ -96,4 +99,8 @@ export namespace SelectionManager {
export function Docs(): Doc[] {
return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
}
-} \ No newline at end of file
+}
+ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
+ let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
+ return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
+});
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 080237649..12d1793af 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,29 +1,28 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { ColorState, SketchPicker } from "react-color";
-import { Doc } from "../../fields/Doc";
-import { BoolCast, StrCast, Cast } from "../../fields/Types";
-import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils";
-import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager";
-import { DocServer } from "../DocServer";
-import { Networking } from "../Network";
-import { MainViewModal } from "../views/MainViewModal";
-import { FontIconBox } from "../views/nodes/button/FontIconBox";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { DragManager } from "./DragManager";
-import { GroupManager } from "./GroupManager";
-import "./SettingsManager.scss";
-import { undoBatch } from "./UndoManager";
-const higflyout = require("@hig/flyout");
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ColorState, SketchPicker } from 'react-color';
+import { Doc } from '../../fields/Doc';
+import { BoolCast, Cast, StrCast } from '../../fields/Types';
+import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
+import { DocServer } from '../DocServer';
+import { Networking } from '../Network';
+import { MainViewModal } from '../views/MainViewModal';
+import { FontIconBox } from '../views/nodes/button/FontIconBox';
+import { DragManager } from './DragManager';
+import { GroupManager } from './GroupManager';
+import './SettingsManager.scss';
+import { undoBatch } from './UndoManager';
+const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
export enum ColorScheme {
- Dark = "-Dark",
- Light = "-Light",
- System = "-MatchSystem"
+ Dark = '-Dark',
+ Light = '-Light',
+ System = '-MatchSystem',
}
@observer
@@ -31,67 +30,77 @@ export class SettingsManager extends React.Component<{}> {
public static Instance: SettingsManager;
static _settingsStyle = addStyleSheet();
@observable private isOpen = false;
- @observable private passwordResultText = "";
+ @observable private passwordResultText = '';
@observable private playgroundMode = false;
- @observable private curr_password = "";
- @observable private new_password = "";
- @observable private new_confirm = "";
- @observable activeTab = "Accounts";
+ @observable private curr_password = '';
+ @observable private new_password = '';
+ @observable private new_confirm = '';
+ @observable activeTab = 'Accounts';
- @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
- @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme; }
+ @observable public static propertiesWidth: number = 0;
+ @observable public static headerBarHeight: number = 0;
+
+ @computed get backgroundColor() {
+ return Doc.UserDoc().activeCollectionBackground;
+ }
+ @computed get colorScheme() {
+ return Doc.ActiveDashboard?.colorScheme;
+ }
constructor(props: {}) {
super(props);
SettingsManager.Instance = this;
}
- public close = action(() => this.isOpen = false);
- public open = action(() => this.isOpen = true);
+ public close = action(() => (this.isOpen = false));
+ public open = action(() => (this.isOpen = true));
private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
private changePassword = async () => {
if (!(this.curr_password && this.new_password && this.new_confirm)) {
- runInAction(() => this.passwordResultText = "Error: Hey, we're missing some fields!");
+ runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!"));
} else {
const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm };
const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
- runInAction(() => this.passwordResultText = error ? "Error: " + error[0].msg + "..." : "Password successfully updated!");
+ runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!'));
}
- }
-
- @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.noviceMode = (e.currentTarget as any)?.value === "Novice");
- @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined);
- @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value);
- @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value);
- @undoBatch switchActiveBackgroundColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex));
- @undoBatch switchUserColor = action((color: ColorState) => { Doc.SharingDoc().userColor = undefined; Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex); });
+ };
+
+ @undoBatch selectUserMode = action((e: React.ChangeEvent) => (Doc.noviceMode = (e.currentTarget as any)?.value === 'Novice'));
+ @undoBatch changeShowTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().showTitle = (e.currentTarget as any).value ? 'title' : undefined));
+ @undoBatch changeFontFamily = action((e: React.ChangeEvent) => (Doc.UserDoc().fontFamily = (e.currentTarget as any).value));
+ @undoBatch changeFontSize = action((e: React.ChangeEvent) => (Doc.UserDoc().fontSize = (e.currentTarget as any).value));
+ @undoBatch switchActiveBackgroundColor = action((color: ColorState) => (Doc.UserDoc().activeCollectionBackground = String(color.hex)));
+ @undoBatch switchUserColor = action((color: ColorState) => {
+ Doc.SharingDoc().userColor = undefined;
+ Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex);
+ });
@undoBatch playgroundModeToggle = action(() => {
this.playgroundMode = !this.playgroundMode;
if (this.playgroundMode) {
DocServer.Control.makeReadOnly();
- addStyleSheetRule(SettingsManager._settingsStyle, "topbar-inner-container", { background: "red !important" });
- }
- else DocServer.Control.makeEditable();
+ addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
+ } else DocServer.Control.makeEditable();
});
@undoBatch
@action
changeColorScheme = action((e: React.ChangeEvent) => {
- const activeDashboard= CurrentUserUtils.ActiveDashboard;
+ const activeDashboard = Doc.ActiveDashboard;
if (!activeDashboard) return;
const scheme: ColorScheme = (e.currentTarget as any).value;
switch (scheme) {
case ColorScheme.Light:
activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
- addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: '#d3d3d3 !important' });
break;
case ColorScheme.Dark:
activeDashboard.colorScheme = ColorScheme.Dark;
- addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' });
break;
- case ColorScheme.System: default:
+ case ColorScheme.System:
+ default:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
});
@@ -99,223 +108,285 @@ export class SettingsManager extends React.Component<{}> {
}
});
-
@computed get colorsContent() {
- const colorBox = (func: (color: ColorState) => void) => <SketchPicker onChange={func} color={StrCast(this.backgroundColor)}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', 'transparent']} />;
-
- const colorFlyout = <div className="colorFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchActiveBackgroundColor)}>
- <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()} >
- <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
- </div>
- </Flyout>
- </div>;
- const userColorFlyout = <div className="colorFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchUserColor)}>
- <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()} >
- <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
- </div>
- </Flyout>
- </div>;
+ const colorBox = (func: (color: ColorState) => void) => (
+ <SketchPicker
+ onChange={func}
+ color={StrCast(this.backgroundColor)}
+ presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ />
+ );
+
+ const colorFlyout = (
+ <div className="colorFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchActiveBackgroundColor)}>
+ <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
+ </div>
+ </Flyout>
+ </div>
+ );
+ const userColorFlyout = (
+ <div className="colorFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchUserColor)}>
+ <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()}>
+ <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
+ </div>
+ </Flyout>
+ </div>
+ );
const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
- const schemeMap = ["Light", "Dark", "Match system"];
+ const schemeMap = ['Light', 'Dark', 'Match system'];
- return <div className="colors-content">
- <div className="preferences-color">
- <div className="preferences-color-text">Background Color</div>
- {colorFlyout}
- </div>
- <div className="preferences-color">
- <div className="preferences-color-text">Border/Header Color</div>
- {userColorFlyout}
- </div>
- <div className="preferences-colorScheme">
- <div className="preferences-color-text">Color Scheme</div>
- <div className="preferences-color-controls">
- <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme)}>
- {colorSchemes.map((scheme, i) => <option key={scheme} value={scheme}> {schemeMap[i]} </option>)}
- </select>
+ return (
+ <div className="colors-content">
+ <div className="preferences-color">
+ <div className="preferences-color-text">Background Color</div>
+ {colorFlyout}
+ </div>
+ <div className="preferences-color">
+ <div className="preferences-color-text">Border/Header Color</div>
+ {userColorFlyout}
+ </div>
+ <div className="preferences-colorScheme">
+ <div className="preferences-color-text">Color Scheme</div>
+ <div className="preferences-color-controls">
+ <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.ActiveDashboard?.colorScheme)}>
+ {colorSchemes.map((scheme, i) => (
+ <option key={scheme} value={scheme}>
+ {' '}
+ {schemeMap[i]}{' '}
+ </option>
+ ))}
+ </select>
+ </div>
</div>
</div>
- </div>;
+ );
}
@computed get formatsContent() {
- return <div className="prefs-content">
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : "creationDate"} checked={Doc.UserDoc().showTitle !== undefined} />
- <div className="preferences-check">Show doc header</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => Doc.UserDoc()["documentLinksButton-fullMenu"] = !Doc.UserDoc()["documentLinksButton-fullMenu"]}
- checked={BoolCast(Doc.UserDoc()["documentLinksButton-fullMenu"])} />
- <div className="preferences-check">Show full toolbar</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())}
- checked={DragManager.GetRaiseWhenDragged()} />
- <div className="preferences-check">Raise on drag</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())}
- checked={FontIconBox.GetShowLabels()} />
- <div className="preferences-check">Show button labels</div>
+ return (
+ <div className="prefs-content">
+ <div>
+ <input type="checkbox" onChange={e => (Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : 'creationDate')} checked={Doc.UserDoc().showTitle !== undefined} />
+ <div className="preferences-check">Show doc header</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} checked={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} />
+ <div className="preferences-check">Show full toolbar</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} checked={DragManager.GetRaiseWhenDragged()} />
+ <div className="preferences-check">Raise on drag</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} />
+ <div className="preferences-check">Show button labels</div>
+ </div>
</div>
- </div>;
+ );
}
@computed get appearanceContent() {
-
- return <div className="tab-content appearances-content">
- <div className="tab-column">
- <div className="tab-column-title">Colors</div>
- <div className="tab-column-content">{this.colorsContent}</div>
- </div>
- <div className="tab-column">
- <div className="tab-column-title">Formats</div>
- <div className="tab-column-content">{this.formatsContent}</div>
+ return (
+ <div className="tab-content appearances-content">
+ <div className="tab-column">
+ <div className="tab-column-title">Colors</div>
+ <div className="tab-column-content">{this.colorsContent}</div>
+ </div>
+ <div className="tab-column">
+ <div className="tab-column-title">Formats</div>
+ <div className="tab-column-content">{this.formatsContent}</div>
+ </div>
</div>
- </div>;
+ );
}
@computed get textContent() {
-
- const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Roboto"];
- const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
+ const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto'];
+ const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px'];
return (
<div className="tab-content appearances-content">
<div className="preferences-font">
<div className="preferences-font-text">Default Font</div>
<div className="preferences-font-controls">
- <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
- {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
+ <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, '7px')}>
+ {fontSizes.map(size => (
+ <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}>
+ {' '}
+ {size}{' '}
+ </option>
+ ))}
</select>
- <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
- {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
+ <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, 'Times New Roman')}>
+ {fontFamilies.map(font => (
+ <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}>
+ {' '}
+ {font}{' '}
+ </option>
+ ))}
</select>
</div>
</div>
- </div>);
+ </div>
+ );
}
@action
changeVal = (e: React.ChangeEvent, pass: string) => {
const value = (e.target as any).value;
switch (pass) {
- case "curr": this.curr_password = value; break;
- case "new": this.new_password = value; break;
- case "conf": this.new_confirm = value; break;
+ case 'curr':
+ this.curr_password = value;
+ break;
+ case 'new':
+ this.new_password = value;
+ break;
+ case 'conf':
+ this.new_confirm = value;
+ break;
}
- }
+ };
@computed get passwordContent() {
- return <div className="password-content">
- <div className="password-content-inputs">
- <input className="password-inputs" type="password" placeholder="current password" onChange={e => this.changeVal(e, "curr")} />
- <input className="password-inputs" type="password" placeholder="new password" onChange={e => this.changeVal(e, "new")} />
- <input className="password-inputs" type="password" placeholder="confirm new password" onChange={e => this.changeVal(e, "conf")} />
- </div>
- <div className="password-content-buttons">
- {!this.passwordResultText ? (null) : <div className={`${this.passwordResultText.startsWith("Error") ? "error" : "success"}-text`}>{this.passwordResultText}</div>}
- <a className="password-forgot" href="/forgotPassword">forgot password?</a>
- <button className="password-submit" onClick={this.changePassword}>submit</button>
+ return (
+ <div className="password-content">
+ <div className="password-content-inputs">
+ <input className="password-inputs" type="password" placeholder="current password" onChange={e => this.changeVal(e, 'curr')} />
+ <input className="password-inputs" type="password" placeholder="new password" onChange={e => this.changeVal(e, 'new')} />
+ <input className="password-inputs" type="password" placeholder="confirm new password" onChange={e => this.changeVal(e, 'conf')} />
+ </div>
+ <div className="password-content-buttons">
+ {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
+ <a className="password-forgot" href="/forgotPassword">
+ forgot password?
+ </a>
+ <button className="password-submit" onClick={this.changePassword}>
+ submit
+ </button>
+ </div>
</div>
- </div>;
+ );
}
@computed get accountOthersContent() {
- return <div className="account-others-content">
- <button onClick={this.googleAuthorize} value="data">Authorize Google Acc</button>
- </div>;
+ return (
+ <div className="account-others-content">
+ <button onClick={this.googleAuthorize} value="data">
+ Authorize Google Acc
+ </button>
+ </div>
+ );
}
@computed get accountsContent() {
- return <div className="tab-content accounts-content">
- <div className="tab-column">
- <div className="tab-column-title">Password</div>
- <div className="tab-column-content">{this.passwordContent}</div>
- </div>
- <div className="tab-column">
- <div className="tab-column-title">Others</div>
- <div className="tab-column-content">{this.accountOthersContent}</div>
+ return (
+ <div className="tab-content accounts-content">
+ <div className="tab-column">
+ <div className="tab-column-title">Password</div>
+ <div className="tab-column-content">{this.passwordContent}</div>
+ </div>
+ <div className="tab-column">
+ <div className="tab-column-title">Others</div>
+ <div className="tab-column-content">{this.accountOthersContent}</div>
+ </div>
</div>
- </div>;
+ );
}
@computed get modesContent() {
- return <div className="tab-content modes-content">
- <div className="tab-column">
- <div className="tab-column-title">Modes</div>
- <div className="tab-column-content">
- <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.noviceMode ? "Novice" : "Developer"}>
- <option key={"Novice"} value={"Novice"}> Novice </option>
- <option key={"Developer"} value={"Developer"}> Developer</option>
- </select>
- <div className="modes-playground">
- <input className="playground-check" type="checkbox" checked={this.playgroundMode} onChange={this.playgroundModeToggle} />
- <div className="playground-text">Playground Mode</div>
+ return (
+ <div className="tab-content modes-content">
+ <div className="tab-column">
+ <div className="tab-column-title">Modes</div>
+ <div className="tab-column-content">
+ <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.noviceMode ? 'Novice' : 'Developer'}>
+ <option key={'Novice'} value={'Novice'}>
+ {' '}
+ Novice{' '}
+ </option>
+ <option key={'Developer'} value={'Developer'}>
+ {' '}
+ Developer
+ </option>
+ </select>
+ <div className="modes-playground">
+ <input className="playground-check" type="checkbox" checked={this.playgroundMode} onChange={this.playgroundModeToggle} />
+ <div className="playground-text">Playground Mode</div>
+ </div>
</div>
</div>
- </div>
- <div className="tab-column">
- <div className="tab-column-title">Permissions</div>
- <div className="tab-column-content">
- <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
- <div className="default-acl">
- <input className="acl-check" type="checkbox" checked={BoolCast(Doc.defaultAclPrivate)}
- onChange={action(() => Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} />
- <div className="acl-text">Default access private</div>
+ <div className="tab-column">
+ <div className="tab-column-title">Permissions</div>
+ <div className="tab-column-content">
+ <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
+ <div className="default-acl">
+ <input className="acl-check" type="checkbox" checked={BoolCast(Doc.defaultAclPrivate)} onChange={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} />
+ <div className="acl-text">Default access private</div>
+ </div>
</div>
</div>
</div>
-
- </div>;
+ );
}
-
private get settingsInterface() {
// const pairs = [{ title: "Password", ele: this.passwordContent }, { title: "Modes", ele: this.modesContent },
// { title: "Accounts", ele: this.accountsContent }, { title: "Preferences", ele: this.preferencesContent }];
- const tabs = [{ title: "Accounts", ele: this.accountsContent }, { title: "Modes", ele: this.modesContent },
- { title: "Appearance", ele: this.appearanceContent }, { title: "Text", ele: this.textContent }];
+ const tabs = [
+ { title: 'Accounts', ele: this.accountsContent },
+ { title: 'Modes', ele: this.modesContent },
+ { title: 'Appearance', ele: this.appearanceContent },
+ { title: 'Text', ele: this.textContent },
+ ];
- return <div className="settings-interface">
- <div className="settings-panel">
- <div className="settings-tabs">
- {tabs.map(tab => <div key={tab.title} className={"tab-control " + (this.activeTab === tab.title ? "active" : "inactive")} onClick={action(() => this.activeTab = tab.title)}>{tab.title}</div>)}
- </div>
+ return (
+ <div className="settings-interface">
+ <div className="settings-panel">
+ <div className="settings-tabs">
+ {tabs.map(tab => (
+ <div key={tab.title} className={'tab-control ' + (this.activeTab === tab.title ? 'active' : 'inactive')} onClick={action(() => (this.activeTab = tab.title))}>
+ {tab.title}
+ </div>
+ ))}
+ </div>
- <div className="settings-user">
- <div className="settings-username">{Doc.CurrentUserEmail}</div>
- <button className="logout-button" onClick={() => window.location.assign(Utils.prepend("/logout"))} >
- {CurrentUserUtils.GuestDashboard ? "Exit" : "Log Out"}
- </button>
+ <div className="settings-user">
+ <div className="settings-username">{Doc.CurrentUserEmail}</div>
+ <button className="logout-button" onClick={() => window.location.assign(Utils.prepend('/logout'))}>
+ {Doc.GuestDashboard ? 'Exit' : 'Log Out'}
+ </button>
+ </div>
</div>
- </div>
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color="black" size={"lg"} />
- </div>
+ <div className="close-button" onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ </div>
- <div className="settings-content">
- {tabs.map(tab => <div key={tab.title} className={"tab-section " + (this.activeTab === tab.title ? "active" : "inactive")}>{tab.ele}</div>)}
+ <div className="settings-content">
+ {tabs.map(tab => (
+ <div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
+ {tab.ele}
+ </div>
+ ))}
+ </div>
</div>
- </div>;
-
+ );
}
render() {
- return <MainViewModal
- contents={this.settingsInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: "500px", height: "300px", background: Cast(Doc.SharingDoc().userColor, "string", null) }} />;
+ return (
+ <MainViewModal
+ contents={this.settingsInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ closeOnExternalClick={this.close}
+ dialogueBoxStyle={{ width: '500px', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
+ />
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index d77633b8d..793027ea1 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,29 +1,28 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { intersection } from "lodash";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import Select from "react-select";
-import * as RequestPromise from "request-promise";
-import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { Cast, NumCast, StrCast } from "../../fields/Types";
-import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util";
-import { Utils } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { CollectionView } from "../views/collections/CollectionView";
-import { DictationOverlay } from "../views/DictationOverlay";
-import { MainViewModal } from "../views/MainViewModal";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { SearchBox } from "../views/search/SearchBox";
-import { CurrentUserUtils } from "./CurrentUserUtils";
-import { DocumentManager } from "./DocumentManager";
-import { GroupManager, UserOptions } from "./GroupManager";
-import { GroupMemberView } from "./GroupMemberView";
-import { SelectionManager } from "./SelectionManager";
-import "./SharingManager.scss";
-import { LinkManager } from "./LinkManager";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { intersection } from 'lodash';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import Select from 'react-select';
+import * as RequestPromise from 'request-promise';
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
+import { Utils } from '../../Utils';
+import { DocServer } from '../DocServer';
+import { CollectionView } from '../views/collections/CollectionView';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { MainViewModal } from '../views/MainViewModal';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import { SearchBox } from '../views/search/SearchBox';
+import { DocumentManager } from './DocumentManager';
+import { GroupManager, UserOptions } from './GroupManager';
+import { GroupMemberView } from './GroupMemberView';
+import { SelectionManager } from './SelectionManager';
+import './SharingManager.scss';
+import { LinkManager } from './LinkManager';
export interface User {
email: string;
@@ -44,19 +43,19 @@ interface GroupedOptions {
// const DefaultColor = "black";
// used to differentiate between individuals and groups when sharing
-const indType = "!indType/";
-const groupType = "!groupType/";
+const indType = '!indType/';
+const groupType = '!groupType/';
-const storage = "data";
+const storage = 'data';
/**
* A user who also has a sharing doc.
*/
interface ValidatedUser {
- user: User; // database minimal info to identify / communicate with a user (email, sharing doc id)
- sharingDoc: Doc; // document to share/message another user
+ user: User; // database minimal info to identify / communicate with a user (email, sharing doc id)
+ sharingDoc: Doc; // document to share/message another user
linkDatabase: Doc;
- userColor: string; // stored on the sharinDoc, extracted for convenience?
+ userColor: string; // stored on the sharinDoc, extracted for convenience?
}
@observer
@@ -71,8 +70,8 @@ export class SharingManager extends React.Component<{}> {
@observable private overlayOpacity = 0.4; // for the modal
@observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
@observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
- @observable private individualSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of individuals
- @observable private groupSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of groups
+ @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
+ @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
private distributeAclsButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the distribute button, used for the position of the popup
// if both showUserOptions and showGroupOptions are false then both are displayed
@@ -89,7 +88,7 @@ export class SharingManager extends React.Component<{}> {
[AclAugment, SharingPermissions.Augment],
[AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
- [AclAdmin, SharingPermissions.Admin]
+ [AclAdmin, SharingPermissions.Admin],
]);
// private get linkVisible() {
@@ -105,17 +104,20 @@ export class SharingManager extends React.Component<{}> {
this.isOpen = this.targetDoc !== undefined;
this.permissions = SharingPermissions.Augment;
});
- }
+ };
public close = action(() => {
this.isOpen = false;
this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
TaskCompletionBox.taskCompleted = false;
- setTimeout(action(() => {
- // this.copied = false;
- DictationOverlay.Instance.hasActiveModal = false;
- this.targetDoc = undefined;
- }), 500);
+ setTimeout(
+ action(() => {
+ // this.copied = false;
+ DictationOverlay.Instance.hasActiveModal = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
});
constructor(props: {}) {
@@ -136,7 +138,7 @@ export class SharingManager extends React.Component<{}> {
populateUsers = async () => {
if (!this.populating) {
this.populating = true;
- const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
+ const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
const sharingDocs: ValidatedUser[] = [];
const evaluating = raw.map(async user => {
@@ -146,7 +148,8 @@ export class SharingManager extends React.Component<{}> {
const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId);
if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
await DocListCastAsync(linkDatabase.data);
- (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => { // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
+ (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => {
+ // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
const a1 = await Cast(link?.anchor1, Doc, null);
const a2 = await Cast(link?.anchor2, Doc, null);
});
@@ -166,7 +169,7 @@ export class SharingManager extends React.Component<{}> {
this.populating = false;
});
}
- }
+ };
/**
* Shares the document with a user.
@@ -176,74 +179,77 @@ export class SharingManager extends React.Component<{}> {
const target = targetDoc || this.targetDoc!;
const acl = `acl-${normalizeEmail(user.email)}`;
const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
- const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1;
+ const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
- return !docs.map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
-
- if (permission === SharingPermissions.None) {
- if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
- }
- else {
- if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
- }
+ return !docs
+ .map(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
+
+ if (permission === SharingPermissions.None) {
+ if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
+ } else {
+ if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
+ }
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
- else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
- }).some(success => !success);
- }
+ this.setDashboardBackground(doc, permission as SharingPermissions);
+ if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
+ else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc);
+ })
+ .some(success => !success);
+ };
/**
* Sets the permission on the target for the group.
- * @param group
- * @param permission
+ * @param group
+ * @param permission
*/
setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
-
const target = targetDoc || this.targetDoc!;
const key = normalizeEmail(StrCast(group.title));
const acl = `acl-${key}`;
- const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1;
+ const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
// ! ensures it returns true if document has been shared successfully, false otherwise
- return !docs.map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
-
- if (permission === SharingPermissions.None) {
- if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
- }
- else {
- if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
- }
+ return !docs
+ .map(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
+
+ if (permission === SharingPermissions.None) {
+ if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
+ } else {
+ if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
+ }
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
+ this.setDashboardBackground(doc, permission as SharingPermissions);
- if (group instanceof Doc) {
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+ if (group instanceof Doc) {
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
- // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc
- group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]);
+ // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc
+ group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : (group.docsShared = new List<Doc>([doc]));
- return users.map(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists
- }).some(success => !success);
- }
- }).some(success => success);
- }
+ return users
+ .map(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); // remove the doc from the list if it already exists
+ })
+ .some(success => !success);
+ }
+ })
+ .some(success => success);
+ };
/**
* Shares the documents shared with a group with a new user who has been added to that group.
- * @param group
- * @param emailId
+ * @param group
+ * @param emailId
*/
shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
const user = this.users.find(({ user: { email } }) => email === emailId)!;
@@ -255,54 +261,53 @@ export class SharingManager extends React.Component<{}> {
DocListCastAsync(group.docsShared).then(dl => {
const filtered = dl?.filter(doc => !userdocs?.includes(doc));
filtered && userdocs?.push(...filtered);
- }));
+ })
+ );
}
}
- }
+ };
/**
* Called from the properties sidebar to change permissions of a user.
*/
shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
- if (shareWith !== "Public" && shareWith !== "Override") {
- const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith));
+ if (shareWith !== 'Public' && shareWith !== 'Override') {
+ const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
docs.forEach(doc => {
if (user) this.setInternalSharing(user, permission, doc);
else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc);
});
- }
- else {
- const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+ } else {
+ const dashboards = DocListCast(Doc.MyDashboards.data);
docs.forEach(doc => {
const isDashboard = dashboards.indexOf(doc) !== -1;
if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
});
}
- }
+ };
/**
* Sets the background of the Dashboard if it has been shared as a visual indicator
*/
setDashboardBackground = async (doc: Doc, permission: SharingPermissions) => {
- if (Doc.IndexOf(doc, DocListCast(CurrentUserUtils.MyDashboards.data)) !== -1) {
+ if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) {
if (permission !== SharingPermissions.None) {
doc.isShared = true;
- doc.backgroundColor = "green";
- }
- else {
+ doc.backgroundColor = 'green';
+ } else {
const acls = doc[DataSym][AclSym];
- if (Object.keys(acls).every(key => key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key]))) {
+ if (Object.keys(acls).every(key => (key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key])))) {
doc.isShared = undefined;
doc.backgroundColor = undefined;
}
}
}
- }
+ };
/**
* Removes the documents shared with a user through a group when the user is removed from the group.
- * @param group
- * @param emailId
+ * @param group
+ * @param emailId
*/
removeMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
@@ -315,15 +320,15 @@ export class SharingManager extends React.Component<{}> {
})
);
}
- }
+ };
/**
* Removes a group's permissions from documents that have been shared with it.
- * @param group
+ * @param group
*/
removeGroup = (group: Doc) => {
if (group.docsShared) {
- const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+ const dashboards = DocListCast(Doc.MyDashboards.data);
DocListCast(group.docsShared).forEach(doc => {
const acl = `acl-${StrCast(group.title)}`;
const isDashboard = dashboards.indexOf(doc) !== -1;
@@ -335,9 +340,7 @@ export class SharingManager extends React.Component<{}> {
users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
});
}
- }
-
-
+ };
// private setExternalSharing = (permission: string) => {
// const targetDoc = this.targetDoc;
@@ -367,23 +370,23 @@ export class SharingManager extends React.Component<{}> {
*/
private sharingOptions(uniform: boolean, override?: boolean) {
const dropdownValues: string[] = Object.values(SharingPermissions);
- if (!uniform) dropdownValues.unshift("-multiple-");
- if (override) dropdownValues.unshift("None");
- return dropdownValues.filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
- (
- <option key={permission} value={permission}>
- {permission}
- </option>
- )
- );
+ if (!uniform) dropdownValues.unshift('-multiple-');
+ if (override) dropdownValues.unshift('None');
+ return dropdownValues
+ .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any))
+ .map(permission => (
+ <option key={permission} value={permission}>
+ {permission}
+ </option>
+ ));
}
private focusOn = (contents: string) => {
- const title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc];
return (
<span
- className={"focus-span"}
+ className={'focus-span'}
title={title}
onClick={() => {
let context: Opt<CollectionView>;
@@ -404,12 +407,11 @@ export class SharingManager extends React.Component<{}> {
this.dialogueBoxOpacity = 1;
this.overlayOpacity = 0.4;
}
- })}
- >
+ })}>
{contents}
</span>
);
- }
+ };
/**
* Handles changes in the users selected in react-select
@@ -417,7 +419,7 @@ export class SharingManager extends React.Component<{}> {
@action
handleUsersChange = (selectedOptions: any) => {
this.selectedUsers = selectedOptions as UserOptions[];
- }
+ };
/**
* Handles changes in the permission chosen to share with someone with
@@ -425,7 +427,7 @@ export class SharingManager extends React.Component<{}> {
@action
handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
this.permissions = event.currentTarget.value as SharingPermissions;
- }
+ };
/**
* Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
@@ -436,8 +438,7 @@ export class SharingManager extends React.Component<{}> {
this.selectedUsers.forEach(user => {
if (user.value.includes(indType)) {
this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
- }
- else {
+ } else {
this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
}
});
@@ -445,13 +446,16 @@ export class SharingManager extends React.Component<{}> {
const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
TaskCompletionBox.popupX = left - 1.5 * width;
TaskCompletionBox.popupY = top - 1.5 * height;
- TaskCompletionBox.textDisplayed = "Document shared!";
+ TaskCompletionBox.textDisplayed = 'Document shared!';
TaskCompletionBox.taskCompleted = true;
- setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2000
+ );
this.selectedUsers = null;
}
- }
+ };
// distributeOverCollection = (targetDoc?: Doc) => {
// const target = targetDoc || this.targetDoc!;
@@ -471,7 +475,7 @@ export class SharingManager extends React.Component<{}> {
const { email: e1 } = u1.user;
const { email: e2 } = u2.user;
return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
- }
+ };
/**
* Sorting algorithm to sort groups.
@@ -480,7 +484,7 @@ export class SharingManager extends React.Component<{}> {
const g1 = StrCast(group1.title);
const g2 = StrCast(group2.title);
return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
- }
+ };
/**
* @returns the main interface of the SharingManager.
@@ -488,28 +492,29 @@ export class SharingManager extends React.Component<{}> {
@computed get sharingInterface() {
TraceMobx();
const groupList = GroupManager.Instance?.allGroups || [];
- const sortedUsers = this.users.slice().sort(this.sortUsers).map(({ user: { email } }) => ({ label: email, value: indType + email }));
- const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
+ const sortedUsers = this.users
+ .slice()
+ .sort(this.sortUsers)
+ .map(({ user: { email } }) => ({ label: email, value: indType + email }));
+ const sortedGroups = groupList
+ .slice()
+ .sort(this.sortGroups)
+ .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
// the next block handles the users shown (individuals/groups/both)
const options: GroupedOptions[] = [];
if (GroupManager.Instance) {
if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) {
- options.push(
- { label: 'Individuals', options: sortedUsers },
- { label: 'Groups', options: sortedGroups });
- }
- else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
+ options.push({ label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups });
+ } else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
else options.push({ label: 'Groups', options: sortedGroups });
}
- const users = this.individualSort === "ascending" ? this.users.slice().sort(this.sortUsers) : this.individualSort === "descending" ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
- const groups = this.groupSort === "ascending" ? groupList.slice().sort(this.sortGroups) : this.groupSort === "descending" ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
+ const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
+ const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
// handles the case where multiple documents are selected
- let docs = SelectionManager.Views().length < 2 ?
- [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]]
- : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]);
+ let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]));
if (this.myDocAcls) {
const newDocs: Doc[] = [];
@@ -524,107 +529,78 @@ export class SharingManager extends React.Component<{}> {
const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
// users in common between all docs
- const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
+ const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))));
// the list of users shared with
- const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, linkDatabase, sharingDoc, userColor }) => {
- const userKey = `acl-${normalizeEmail(user.email)}`;
- const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]);
- const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-";
-
- return !permissions ? (null) : (
- <div
- key={userKey}
- className={"container"}
- >
- <span className={"padding"}>{user.email}</span>
- <div className="edit-actions">
- {admin || this.myDocAcls ? (
- <select
- className={"permissions-dropdown"}
- value={permissions}
- onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}
- >
- {this.sharingOptions(uniform)}
- </select>
- ) : (
- <div className={"permissions-dropdown"}>
- {permissions}
- </div>
- )}
+ const userListContents: (JSX.Element | null)[] = users
+ .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ .map(({ user, linkDatabase, sharingDoc, userColor }) => {
+ const userKey = `acl-${normalizeEmail(user.email)}`;
+ const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]));
+ const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+
+ return !permissions ? null : (
+ <div key={userKey} className={'container'}>
+ <span className={'padding'}>{user.email}</span>
+ <div className="edit-actions">
+ {admin || this.myDocAcls ? (
+ <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}>
+ {this.sharingOptions(uniform)}
+ </select>
+ ) : (
+ <div className={'permissions-dropdown'}>{permissions}</div>
+ )}
+ </div>
</div>
- </div>
- );
- });
+ );
+ });
// checks if every doc has the same author
const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
// the owner of the doc and the current user are placed at the top of the user list.
userListContents.unshift(
- sameAuthor ?
- (
- <div
- key={"owner"}
- className={"container"}
- >
- <span className={"padding"}>{targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author}</span>
- <div className="edit-actions">
- <div className={"permissions-dropdown"}>
- Owner
- </div>
- </div>
+ sameAuthor ? (
+ <div key={'owner'} className={'container'}>
+ <span className={'padding'}>{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : targetDoc?.author}</span>
+ <div className="edit-actions">
+ <div className={'permissions-dropdown'}>Owner</div>
</div>
- ) : null,
- sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ?
- (
- <div
- key={"me"}
- className={"container"}
- >
- <span className={"padding"}>Me</span>
- <div className="edit-actions">
- <div className={"permissions-dropdown"}>
- {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"}
- </div>
- </div>
+ </div>
+ ) : null,
+ sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? (
+ <div key={'me'} className={'container'}>
+ <span className={'padding'}>Me</span>
+ <div className="edit-actions">
+ <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : '-multiple-'}</div>
</div>
- ) : null
+ </div>
+ ) : null
);
-
// the list of groups shared with
- const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true);
- groupListMap.unshift({ title: "Public" });//, { title: "ALL" });
+ const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
+ groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" });
const groupListContents = groupListMap.map(group => {
const groupKey = `acl-${StrCast(group.title)}`;
- const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
- const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : "-multiple-";
-
- return !permissions ? (null) : (
- <div
- key={groupKey}
- className={"container"}
- >
- <div className={"padding"}>{group.title}</div>
- {group instanceof Doc ?
- (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}>
- <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} />
- </div>)
- : (null)}
+ const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]));
+ const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
+
+ return !permissions ? null : (
+ <div key={groupKey} className={'container'}>
+ <div className={'padding'}>{StrCast(group.title)}</div>
+ {group instanceof Doc ? (
+ <div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
+ </div>
+ ) : null}
<div className="edit-actions">
{admin || this.myDocAcls ? (
- <select
- className={"permissions-dropdown"}
- value={permissions}
- onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}
- >
- {this.sharingOptions(uniform, group.title === "Override")}
+ <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+ {this.sharingOptions(uniform, group.title === 'Override')}
</select>
) : (
- <div className={"permissions-dropdown"}>
- {permissions}
- </div>
+ <div className={'permissions-dropdown'}>{permissions}</div>
)}
</div>
</div>
@@ -632,102 +608,95 @@ export class SharingManager extends React.Component<{}> {
});
return (
- <div className={"sharing-interface"}>
- {GroupManager.Instance?.currentGroup ?
- <GroupMemberView
- group={GroupManager.Instance.currentGroup}
- onCloseButtonClick={action(() => GroupManager.Instance.currentGroup = undefined)}
- /> :
- null}
+ <div className={'sharing-interface'}>
+ {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
<div className="sharing-contents">
- <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p>
- <div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
+ <p className={'share-title'}>
+ <b>Share </b>
+ {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
+ </p>
+ <div className={'close-button'} onClick={this.close}>
+ <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
{/* {this.linkVisible ?
<div>
{this.sharingUrl}
</div> :
(null)} */}
- {<div className="share-container">
- <div className="share-setup">
- <Select
- className="user-search"
- placeholder="Enter user or group name..."
- isMulti
- isSearchable
- closeMenuOnSelect={false}
- options={options}
- onKeyDown={e => e.stopPropagation()}
- onChange={this.handleUsersChange}
- value={this.selectedUsers}
- styles={{
- indicatorSeparator: () => ({
- visibility: "hidden"
- })
- }}
- />
- <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
- {this.sharingOptions(true)}
- </select>
- <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
- Share
- </button>
- </div>
- <div className="sort-checkboxes">
- <input type="checkbox" onChange={action(() => this.showUserOptions = !this.showUserOptions)} /> <label style={{ marginRight: 10 }}>Individuals</label>
- <input type="checkbox" onChange={action(() => this.showGroupOptions = !this.showGroupOptions)} /> <label>Groups</label>
- </div>
+ {
+ <div className="share-container">
+ <div className="share-setup">
+ <Select
+ className="user-search"
+ placeholder="Enter user or group name..."
+ isMulti
+ isSearchable
+ closeMenuOnSelect={false}
+ options={options}
+ onKeyDown={e => e.stopPropagation()}
+ onChange={this.handleUsersChange}
+ value={this.selectedUsers}
+ styles={{
+ indicatorSeparator: () => ({
+ visibility: 'hidden',
+ }),
+ }}
+ />
+ <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
+ {this.sharingOptions(true)}
+ </select>
+ <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
+ Share
+ </button>
+ </div>
+ <div className="sort-checkboxes">
+ <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label>
+ <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label>
+ </div>
- <div className="acl-container">
- {Doc.noviceMode ? (null) :
- <div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label>
- </div>}
+ <div className="acl-container">
+ {Doc.noviceMode ? null : (
+ <div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ </div>
+ )}
+ </div>
</div>
- </div>
}
<div className="main-container">
- <div className={"individual-container"}>
- <div
- className="user-sort"
- onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}>
- Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
- : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
- : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
- </div>
- <div className={"users-list"}>
- {userListContents}
+ <div className={'individual-container'}>
+ <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
+ Individuals{' '}
+ {this.individualSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.individualSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
</div>
+ <div className={'users-list'}>{userListContents}</div>
</div>
- <div className={"group-container"}>
- <div
- className="user-sort"
- onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}>
- Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} />
- : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} />
- : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />}
-
- </div>
- <div className={"groups-list"}>
- {groupListContents}
+ <div className={'group-container'}>
+ <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ Groups{' '}
+ {this.groupSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.groupSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
</div>
+ <div className={'groups-list'}>{groupListContents}</div>
</div>
</div>
-
</div>
</div>
);
}
render() {
- return <MainViewModal
- contents={this.sharingInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
- overlayDisplayedOpacity={this.overlayOpacity}
- closeOnExternalClick={this.close}
- />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 057843c68..2c0a1da8b 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,9 +1,8 @@
-import { observable, action, runInAction } from "mobx";
-import { computedFn } from "mobx-utils";
-import { Doc } from "../../fields/Doc";
+import { observable, action, runInAction } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { Doc } from '../../fields/Doc';
export namespace SnappingManager {
-
class Manager {
@observable IsDragging: boolean = false;
@observable public horizSnapLines: number[] = [];
@@ -16,28 +15,34 @@ export namespace SnappingManager {
this.horizSnapLines = horizLines;
this.vertSnapLines = vertLines;
}
-
- @observable cachedGroups: string[] = [];
- @action setCachedGroups(groups: string[]) { this.cachedGroups = groups; }
}
const manager = new Manager();
- export function clearSnapLines() { manager.clearSnapLines(); }
- export function setSnapLines(horizLines: number[], vertLines: number[]) { manager.setSnapLines(horizLines, vertLines); }
- export function horizSnapLines() { return manager.horizSnapLines; }
- export function vertSnapLines() { return manager.vertSnapLines; }
-
- export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
- export function GetIsDragging() { return manager.IsDragging; }
+ export function clearSnapLines() {
+ manager.clearSnapLines();
+ }
+ export function setSnapLines(horizLines: number[], vertLines: number[]) {
+ manager.setSnapLines(horizLines, vertLines);
+ }
+ export function horizSnapLines() {
+ return manager.horizSnapLines;
+ }
+ export function vertSnapLines() {
+ return manager.vertSnapLines;
+ }
- export function SetShowSnapLines(show: boolean) { runInAction(() => Doc.UserDoc().showSnapLines = show); }
- export function GetShowSnapLines() { return Doc.UserDoc().showSnapLines; }
+ export function SetIsDragging(dragging: boolean) {
+ runInAction(() => (manager.IsDragging = dragging));
+ }
+ export function GetIsDragging() {
+ return manager.IsDragging;
+ }
- /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
- // need to investigate further what caused the mobx update problems and move to a better location.
- const getCachedGroupByNameCache = computedFn(function (name: string) { return manager.cachedGroups.includes(name); }, true);
- export function GetCachedGroupByName(name: string) { return getCachedGroupByNameCache(name); }
- export function SetCachedGroups(groups: string[]) { manager.setCachedGroups(groups); }
+ export function SetShowSnapLines(show: boolean) {
+ runInAction(() => (Doc.UserDoc().showSnapLines = show));
+ }
+ export function GetShowSnapLines() {
+ return Doc.UserDoc().showSnapLines;
+ }
}
-