aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-09-18 17:40:01 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-09-18 17:40:01 -0400
commit013f25f01e729feee5db94900c61f4be4dd46869 (patch)
tree765dd5f2e06d6217ca79438e1098cefc8da627bf /src/client/util
parentf5e765adff1e7b32250eb503c9724a4ac99117f3 (diff)
parent84aa8806a62e2e957e8281d7d492139e3d8225f2 (diff)
Merge branch 'master' into sophie-report-manager
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/BranchingTrailManager.tsx137
-rw-r--r--src/client/util/CurrentUserUtils.ts300
-rw-r--r--src/client/util/DictationManager.ts2
-rw-r--r--src/client/util/DocumentManager.ts45
-rw-r--r--src/client/util/DragManager.ts35
-rw-r--r--src/client/util/DropConverter.ts14
-rw-r--r--src/client/util/GroupManager.tsx41
-rw-r--r--src/client/util/GroupMemberView.tsx105
-rw-r--r--src/client/util/InteractionUtils.tsx5
-rw-r--r--src/client/util/LinkFollower.ts1
-rw-r--r--src/client/util/LinkManager.ts15
-rw-r--r--src/client/util/PingManager.ts6
-rw-r--r--src/client/util/RTFMarkup.tsx15
-rw-r--r--src/client/util/ReplayMovements.ts1
-rw-r--r--src/client/util/SearchUtil.ts104
-rw-r--r--src/client/util/SelectionManager.ts36
-rw-r--r--src/client/util/ServerStats.tsx26
-rw-r--r--src/client/util/SettingsManager.tsx148
-rw-r--r--src/client/util/SharingManager.tsx44
-rw-r--r--src/client/util/reportManager/ReportManager.tsx10
20 files changed, 708 insertions, 382 deletions
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
new file mode 100644
index 000000000..a224b84f4
--- /dev/null
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -0,0 +1,137 @@
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { PresBox } from '../views/nodes/trails';
+import { OverlayView } from '../views/OverlayView';
+import { DocumentManager } from './DocumentManager';
+import { Docs } from '../documents/Documents';
+import { nullAudio } from '../../fields/URLField';
+
+@observer
+export class BranchingTrailManager extends React.Component {
+ public static Instance: BranchingTrailManager;
+
+ constructor(props: any) {
+ super(props);
+ if (!BranchingTrailManager.Instance) {
+ BranchingTrailManager.Instance = this;
+ }
+ }
+
+ setupUi = () => {
+ OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail'});
+ // OverlayView.Instance.forceUpdate();
+ console.log(OverlayView.Instance);
+ // let hi = Docs.Create.TextDocument("beee", {
+ // x: 100,
+ // y: 100,
+ // })
+ // hi.overlayX = 100;
+ // hi.overlayY = 100;
+
+ // Doc.AddToMyOverlay(hi);
+ console.log(DocumentManager._overlayViews);
+ };
+
+
+ // stack of the history
+ @observable private slideHistoryStack: String[] = [];
+ @action setSlideHistoryStack = action((newArr: String[]) => {
+ this.slideHistoryStack = newArr;
+ });
+
+ @observable private containsSet: Set<String> = new Set<String>();
+
+ // prev pres to copmare with
+ @observable private prevPresId: String | null = null;
+ @action setPrevPres = action((newId: String | null) => {
+ this.prevPresId = newId;
+ });
+
+ // docId to Doc map
+ @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
+
+ observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
+ const presId = pres.props.Document[Id];
+ if (this.prevPresId === presId) {
+ return;
+ }
+
+ const targetDocId = targetDoc[Id];
+ this.docIdToDocMap.set(targetDocId, targetDoc);
+
+ if (this.prevPresId === null) {
+ this.setupUi();
+ }
+
+ if (this.prevPresId === null || this.prevPresId !== presId) {
+ Doc.UserDoc().isBranchingMode = true;
+ this.setPrevPres(presId);
+
+ // REVERT THE SET
+ const stringified = [presId, targetDocId].toString();
+ if (this.containsSet.has([presId, targetDocId].toString())) {
+ // remove all the elements after the targetDocId
+ const newStack = this.slideHistoryStack.slice(0, this.slideHistoryStack.indexOf(stringified));
+ const removed = this.slideHistoryStack.slice(this.slideHistoryStack.indexOf(stringified));
+ this.setSlideHistoryStack(newStack);
+
+ removed.forEach(info => this.containsSet.delete(info.toString()));
+ } else {
+ this.setSlideHistoryStack([...this.slideHistoryStack, stringified]);
+ this.containsSet.add(stringified);
+ }
+ }
+ console.log(this.slideHistoryStack.length);
+ if (this.slideHistoryStack.length === 0) {
+ Doc.UserDoc().isBranchingMode = false;
+ }
+ };
+
+ clickHandler = (e: React.PointerEvent<HTMLButtonElement>, targetDocId: string, removeIndex: number) => {
+ const targetDoc = this.docIdToDocMap.get(targetDocId);
+ if (!targetDoc) {
+ return;
+ }
+
+ const newStack = this.slideHistoryStack.slice(0, removeIndex);
+ const removed = this.slideHistoryStack.slice(removeIndex);
+
+ this.setSlideHistoryStack(newStack);
+
+ removed.forEach(info => this.containsSet.delete(info.toString()));
+ DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true });
+ if (this.slideHistoryStack.length === 0) {
+ Doc.UserDoc().isBranchingMode = false;
+ }
+ //PresBox.NavigateToTarget(targetDoc, targetDoc);
+ };
+
+ @computed get trailBreadcrumbs() {
+ return (
+ <div style={{ border: '.5rem solid green', padding: '5px', backgroundColor: 'white', minHeight: '50px', minWidth: '1000px' }}>
+ {this.slideHistoryStack.map((info, index) => {
+ const [presId, targetDocId] = info.split(',');
+ const doc = this.docIdToDocMap.get(targetDocId);
+ if (!doc) {
+ return <></>;
+ }
+ return (
+ <span key={targetDocId}>
+ <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
+ {presId.slice(0, 3) + ':' + doc.title}
+ </button>
+ -{'>'}
+ </span>
+ );
+ })}
+ </div>
+ );
+ }
+
+ render() {
+ return <div>{BranchingTrailManager.Instance.trailBreadcrumbs}</div>;
+ }
+}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a86011042..452bb74cd 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,7 +1,6 @@
-import { reaction } from "mobx";
+import { observable, reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
-import { FieldLoader } from "../../fields/FieldLoader";
import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
@@ -9,19 +8,20 @@ import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
-import { nullAudio } from "../../fields/URLField";
+import { nullAudio, URLField, WebField } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { OmitKeys, Utils, addStyleSheetRule } from "../../Utils";
+import { addStyleSheetRule, OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
import { TreeViewType } from "../views/collections/CollectionTreeView";
import { DashboardView } from "../views/DashboardView";
import { Colors } from "../views/global/globalEnums";
-import { MainView } from "../views/MainView";
-import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
+import { media_state } from "../views/nodes/AudioBox";
import { OpenWhere } from "../views/nodes/DocumentView";
+import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
+import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
import { OverlayView } from "../views/OverlayView";
import { DragManager, dropActionType } from "./DragManager";
import { MakeTemplate } from "./DropConverter";
@@ -30,7 +30,6 @@ import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
import { ColorScheme, SettingsManager } from "./SettingsManager";
import { UndoManager } from "./UndoManager";
-import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
interface Button {
// DocumentOptions fields a button can set
@@ -53,7 +52,7 @@ interface Button {
// fields that do not correspond to DocumentOption fields
scripts?: { script?: string; onClick?: string; onDoubleClick?: string }
- funcs?: { [key:string]: string };
+ funcs?: { [key:string]: any};
subMenu?: Button[];
}
@@ -152,7 +151,7 @@ export class CurrentUserUtils {
{ noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
{ noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
const reqdNoteList = reqdTempOpts.map(opts => {
- const reqdOpts = {...opts, title: "text", width:200, layout_autoHeight: true, layout_fitWidth: true};
+ const reqdOpts = {...opts, isSystem:true, title: "text", width:200, layout_autoHeight: true, layout_fitWidth: true};
const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined;
return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
});
@@ -163,7 +162,6 @@ export class CurrentUserUtils {
/// Initializes collection of templates for notes and click functions
static setupDocTemplates(doc: Doc, field="myTemplates") {
- DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(), { });
const templates = [
CurrentUserUtils.setupNoteTemplates(doc),
CurrentUserUtils.setupClickEditorTemplates(doc)
@@ -273,18 +271,18 @@ export class CurrentUserUtils {
{key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }},
{key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }},
{key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
- {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, _layout_showSidebar: true, }},
+ {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
{key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }},
{key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
{key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
- {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeViewHideUnrendered: true}},
- {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
+ {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+ {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
{key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree,
- treeViewHasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
- dropAction:'move', treeViewType: TreeViewType.outline,
+ treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
+ dropAction:'move', treeView_Type: TreeViewType.outline,
backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true
}, funcs: {title: 'self.text?.Text'}},
];
@@ -295,26 +293,27 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)},
{ toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)},
{ toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)},
- { toolTip: "Tap or drag to create a physics simulation", title: "Simulation", icon: "atom", dragFactory: doc.emptySimulation as Doc, },
+ { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
{ toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
{ toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)},
{ toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
{ toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay},
{ toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)},
- { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay},
- { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay},
+ { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
- { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript)},
- { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
- { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay},
- { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true },
+ { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
+ { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
{ toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
- { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack>" as any, openFactoryLocation: OpenWhere.overlay},
+ // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack>" as any, openFactoryLocation: OpenWhere.overlay},
].map(tuple => (
{ openFactoryLocation: OpenWhere.addRight,
scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)',
onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'},
+ funcs: tuple.funcs,
...tuple, }))
}
@@ -323,7 +322,7 @@ export class CurrentUserUtils {
const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
- _width: 35, _height: 35, _layout_hideContextMenu: true, _dragOnlyWithinContainer: true,
+ _width: 60, _height: 60, _layout_hideContextMenu: true, _dragOnlyWithinContainer: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
@@ -339,16 +338,16 @@ export class CurrentUserUtils {
}
/// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents
- static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] {
+ static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}, hidden?: boolean}[] {
const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())";
const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails";
return [
{ title: "Dashboards", toolTip: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), ignoreClick: true, icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
{ title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), ignoreClick: true, icon: "search", },
{ title: "Files", toolTip: "Files", target: this.setupFilesystem(doc, "myFilesystem"), ignoreClick: true, icon: "folder-open", },
- { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), ignoreClick: true, icon: "wrench", funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Tools", toolTip: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), ignoreClick: true, icon: "wrench", },
{ title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), ignoreClick:false, icon: "upload", },
- { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), ignoreClick: true, icon: "archive", },
+ { title: "Closed", toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), ignoreClick: true, icon: "archive", hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field
{ title: "Shared", toolTip: "Shared Docs", target: Doc.MySharedDocs, ignoreClick: true, icon: "users", funcs: {badgeValue: badgeValue}},
{ title: "Trails", toolTip: "Trails ⌘R", target: Doc.UserDoc(), ignoreClick: true, icon: "pres-trail", funcs: {target: getActiveDashTrails}},
{ title: "User Doc", toolTip: "User Doc", target: this.setupUserDocView(doc, "myUserDocView"), ignoreClick: true, icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
@@ -357,17 +356,17 @@ 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") {
- DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as any), {isSystem:true, undoIgnoreFields: new List<string>(['proto'])});
+ DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as any), {title:"leftSidebarPanel", isSystem:true, undoIgnoreFields: new List<string>(['proto'])});
}
/// Initializes the left sidebar menu buttons and the panels they open up
static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") {
this.setupLeftSidebarPanel(doc);
const myLeftSidebarMenu = DocCast(doc[field]);
- const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, scripts, funcs }) => {
+ const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => {
const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
- title, icon, target, toolTip, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
+ title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
_width: 60, _height: 60, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
@@ -375,7 +374,7 @@ export class CurrentUserUtils {
const reqdStackOpts:DocumentOptions ={
title: "menuItemPanel", childDragAction: "same", layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
- _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
+ _chromeHidden: true, _gridGap: 0, _yMargin: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
@@ -489,13 +488,13 @@ export class CurrentUserUtils {
const contextMenuLabels = [/*"Create New Dashboard"*/] as string[];
const contextMenuIcons = [/*"plus"*/] as string[];
const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
- const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
+ const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
const childContextMenuLabels = ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
const reqdOpts:DocumentOptions = {
- title: "My Dashboards", childHideLinkButton: true, treeViewFreezeChildren: "remove|add", treeViewHideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- dropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeViewTruncateTitleWidth: 350, ignoreClick: true,
- layout_headerButton: newDashboardButton, childDragAction: "embed",
+ title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
+ dropAction: "same", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true,
+ layout_headerButton: newDashboardButton, childDragAction: "none",
_layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
contextMenuLabels:new List<string>(contextMenuLabels),
contextMenuIcons:new List<string>(contextMenuIcons),
@@ -519,25 +518,25 @@ export class CurrentUserUtils {
/// initializes the left sidebar File system pane
static setupFilesystem(doc: Doc, field:string) {
var myFilesystem = DocCast(doc[field]);
- const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", undoIgnoreFields:new List<string>(['treeViewSortCriterion']), _dragOnlyWithinContainer: true, isSystem: true, isFolder: true });
const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
- _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
+ _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
};
const newFolderScript = { onClick: newFolder};
const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
- const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true,
- title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, dropAction: "proto", isSystem: true,
- isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- treeViewTruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
+ const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true,
+ title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true,
+ isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
+ treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
childContextMenuLabels: new List<string>(["Create new folder"]),
childContextMenuIcons: new List<string>(["plus"]),
layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
};
- myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]);
+ const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data));
+ myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders));
const childContextMenuScripts = [newFolder];
if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
myFilesystem.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
@@ -547,9 +546,9 @@ export class CurrentUserUtils {
/// initializes the panel displaying docs that have been recently closed
static setupRecentlyClosed(doc: Doc, field:string) {
- const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true,
- title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDragAction: "embed", isSystem: true,
- treeViewTruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
+ const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, isFolder: true,
+ title: "My Recently Closed", childHideLinkButton: true, treeView_HideTitle: true, childDragAction: "move", isSystem: true,
+ treeView_TruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
contextMenuLabels: new List<string>(["Empty recently closed"]),
contextMenuIcons:new List<string>(["trash"]),
layout_explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -562,8 +561,6 @@ export class CurrentUserUtils {
toolTip: "Empty recently closed",};
DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
- //if (recentlyClosed.layout_headerButton !== clearDocsButton) Doc.GetProto(recentlyClosed).layout_headerButton = clearDocsButton;
-
if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) {
recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
}
@@ -575,9 +572,9 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
_lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same", ignoreClick: true, isSystem: true,
- treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
+ treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
};
- if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
+ if (!doc[field]) DocUtils.AssignOpts(doc, {treeView_Open: true, treeView_ExpandedView: "fields" });
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
}
@@ -586,11 +583,16 @@ export class CurrentUserUtils {
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
_lockedPosition: true, isSystem: true, flexDirection: "row"
})
+ static multiToggleList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.FontIconDocument({
+ ...opts, data:docs, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true,
+ dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
+ _lockedPosition: true, isSystem: true, flexDirection: "row"
+ })
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true,
_dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]),
- _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
+ /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
})
/// initializes the required buttons in the expanding button menu at the bottom of the Dash window
@@ -606,6 +608,7 @@ export class CurrentUserUtils {
{ scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
{ scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}},
{ scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
+ { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}}
];
const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
const dockBtnsReqdOpts:DocumentOptions = {
@@ -619,19 +622,26 @@ export class CurrentUserUtils {
static freeTools(): Button[] {
return [
- { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform
- { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform
- { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform
+ { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform
+ { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform
+ { title: "Z order", icon: "z", toolTip: "Keep Z order on Drag", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: '{ return toggleRaiseOnDrag(_readOnly_);}'}}, // Only when floating document is selected in freeform
+ ]
+ }
+ static stackTools(): Button[] {
+ return [
+ { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static viewTools(): Button[] {
return [
- { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
+ { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.ClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
@@ -640,17 +650,17 @@ export class CurrentUserUtils {
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
{ title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
{ title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
- { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Alignment",toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true,
+ { title: "Align", toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true,
subMenu: [
- { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
- { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+ { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
]
},
{ title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
@@ -665,7 +675,7 @@ export class CurrentUserUtils {
return [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} },
- { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
+ { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
{ title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
{ title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
{ title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
@@ -677,8 +687,8 @@ export class CurrentUserUtils {
static schemaTools():Button[] {
return [
- {title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
- {title: "Single Lines", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, buttonText: "Single Line", icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
+ {title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} },
+ {title: "1 Line",toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} },
];
}
@@ -689,7 +699,6 @@ export class CurrentUserUtils {
{ title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
-
static contextMenuTools():Button[] {
return [
{ btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
@@ -697,11 +706,11 @@ export class CurrentUserUtils {
CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid, CollectionViewType.NoteTaking]),
- title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
- { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+ { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
{ title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
- { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'return { toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+ { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
{ title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
{ title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
{ title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
@@ -709,9 +718,10 @@ export class CurrentUserUtils {
{ title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
{ title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
{ title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+ { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
{ title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
- { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
- ];
+ { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+ ];
}
/// initializes a context menu button for the top bar context menu
@@ -730,34 +740,59 @@ export class CurrentUserUtils {
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
}
+
+ static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
+ const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
+ const subMenu = params.subMenu;
+ if (!subMenu) { // button does not have a sub menu
+ return this.setupContextMenuButton(params, menuBtnDoc);
+ }
+ // linear view
+ const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
+ childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
+ linearView_SubMenu: true, linearView_Expandable: true};
+
+ const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+ const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
+ const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title),
+ (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
+ if (!menuBtnDoc) Doc.GetProto(btnDoc).data = new List<Doc>(items(btnDoc));
+ return btnDoc;
+ }
+
/// Initializes all the default buttons for the top bar context menu
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
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) { // button does not have a sub menu
- return this.setupContextMenuButton(params, menuBtnDoc);
- } else { // linear view
- let reqdSubMenuOpts;
- if (params.btnType === ButtonType.MultiToggleButton) {
- reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
- childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
- linearView_SubMenu: true, linearView_Dropdown: true, };
- } else {
- reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
- childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
- linearView_SubMenu: true, linearView_Expandable: true, };
- }
- const items = params.subMenu?.map(sub =>
- this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title))
- );
- return DocUtils.AssignScripts(
- DocUtils.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), params.scripts, params.funcs);
- }
- });
+ const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
}
+ /// Initializes all the default buttons for the top bar context menu
+ static setupTopbarButtons(doc: Doc, field="myTopBarBtns") {
+ Doc.UserDoc().currentRecording = undefined;
+ Doc.UserDoc().workspaceRecordingState = undefined;
+ Doc.UserDoc().workspaceReplayingState = undefined;
+ const dockedBtns = DocCast(doc[field]);
+ const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ CurrentUserUtils.createToolButton(opts), scripts, funcs);
+
+ 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
+ { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}},
+ { opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
+ { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}},
+ { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
+ ];
+ const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
+ const dockBtnsReqdOpts:DocumentOptions = {
+ title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+ childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true
+ };
+ return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
+ }
/// collection of documents rendered in the overlay layer above all tabs and other UI
static setupOverlays(doc: Doc, field = "myOverlayDocs") {
@@ -774,6 +809,7 @@ export class CurrentUserUtils {
const linkDocs = new Doc(linkDatabaseId, true);
linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
linkDocs.author = Doc.CurrentUserEmail;
+ linkDocs.isSystem = true;
linkDocs.data = new List<Doc>([]);
linkDocs["acl-Guest"] = SharingPermissions.Augment;
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
@@ -787,19 +823,19 @@ export class CurrentUserUtils {
static setupSharedDocs(doc: Doc, sharingDocumentId: string) {
const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}";
- const sharedScripts = { treeViewChildDoubleClick: dblClkScript, }
+ const sharedScripts = { treeView_ChildDoubleClick: dblClkScript, }
const sharedDocOpts:DocumentOptions = {
title: "My Shared Docs",
userColor: "rgb(202, 202, 202)",
- isFolder:true, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
+ isFolder:true, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
// childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
// childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
// childContextMenuLabels: new List<string>(["Add to Dashboards",]),
// childContextMenuIcons: new List<string>(["user-plus",]),
"acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
- // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
- _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
+ // NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
+ _layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
layout_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'"
};
@@ -837,7 +873,6 @@ export class CurrentUserUtils {
doc.isSystem ?? (doc.isSystem = true);
doc.title ?? (doc.title = Doc.CurrentUserEmail);
Doc.noviceMode ?? (Doc.noviceMode = true);
- doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true);
doc._showLabel ?? (doc._showLabel = true);
doc.textAlign ?? (doc.textAlign = "left");
doc.activeTool = InkTool.None;
@@ -862,7 +897,7 @@ export class CurrentUserUtils {
doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY);
doc.userTheme ?? (doc.userTheme = ColorScheme.Dark);
doc.filterDocCount = 0;
- doc.treeViewFreezeChildren = "remove|add";
+ doc.treeView_FreezeChildren = "remove|add";
doc.activePage = doc.activeDashboard === undefined ? 'home': doc.activePage;
this.setupLinkDocs(doc, linkDatabaseId);
this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing
@@ -871,14 +906,19 @@ export class CurrentUserUtils {
this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
+ this.setupTopbarButtons(doc);
this.setupDockedButtons(doc); // the bottom bar of font icons
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.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
- DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed)
+
+ Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
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)
@@ -886,7 +926,7 @@ export class CurrentUserUtils {
}
new LinkManager();
- setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
+ DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
setInterval(DocServer.UPDATE_SERVER_CACHE, 120000);
return doc;
}
@@ -909,22 +949,22 @@ export class CurrentUserUtils {
});
}
+ @observable public static ServerVersion: string = ';'
public static async loadCurrentUser() {
return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
if (response) {
- const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response);
+ const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response);
+ runInAction(() => CurrentUserUtils.ServerVersion = result.version);
Doc.CurrentUserEmail = result.email;
- resolvedPorts = JSON.parse(await (await fetch("/resolvedPorts")).text());
+ resolvedPorts = result.resolvedPorts as any;
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
if (result.cacheDocumentIds)
{
const ids = result.cacheDocumentIds.split(";");
const batch = 30000;
- FieldLoader.active = true;
for (let i = 0; i < ids.length; i = Math.min(ids.length, i+batch)) {
await DocServer.GetRefFields(ids.slice(i, i+batch));
}
- FieldLoader.active = false;
}
return result;
} else {
@@ -933,27 +973,24 @@ export class CurrentUserUtils {
});
}
- public static async loadUserDocument(id: string) {
- await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => {
- const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids);
- if (userDocumentId) {
- return DocServer.GetRefField(userDocumentId).then(async field => {
- Docs.newAccount = !(field instanceof Doc);
- await Docs.Prototypes.initialize();
- const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc;
- this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId);
- if (Docs.newAccount) {
- if (Doc.CurrentUserEmail === "guest") {
- DashboardView.createNewDashboard(undefined, "guest dashboard");
- } else {
- userDoc.activePage = "home";
- }
- }
- return userDoc;
- });
- } else {
- throw new Error("There should be a user id! Why does Dash think there isn't one?");
+ public static async loadUserDocument(info:{
+ userDocumentId: string;
+ sharingDocumentId: string;
+ linkDatabaseId: string;
+ }) {
+ return DocServer.GetRefField(info.userDocumentId).then(async field => {
+ Docs.newAccount = !(field instanceof Doc);
+ await Docs.Prototypes.initialize();
+ const userDoc = Docs.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
+ this.updateUserDocument(Doc.SetUserDoc(userDoc), info.sharingDocumentId, info.linkDatabaseId);
+ if (Docs.newAccount) {
+ if (Doc.CurrentUserEmail === "guest") {
+ DashboardView.createNewDashboard(undefined, "guest dashboard");
+ } else {
+ userDoc.activePage = "home";
+ }
}
+ return userDoc;
});
}
@@ -993,8 +1030,5 @@ export class CurrentUserUtils {
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 createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called");
-ScriptingGlobals.add(function openPresentation(pres:Doc) { return MainView.Instance.openPresentation(pres); }, "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 importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); \ No newline at end of file
+ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 717473aa1..0fd7e840c 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -11,7 +11,7 @@ import { Utils } from '../../Utils';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { DictationOverlay } from '../views/DictationOverlay';
-import { DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
import { SelectionManager } from './SelectionManager';
import { UndoManager } from './UndoManager';
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 8e4e0d8f3..b9f6059f4 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,9 +1,9 @@
-import { action, computed, observable, ObservableSet } from 'mobx';
+import { action, computed, observable, ObservableSet, observe } from 'mobx';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
-import { Cast, DocCast, StrCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
import { GetEffectiveAcl } from '../../fields/util';
import { CollectionViewType } from '../documents/DocumentTypes';
@@ -11,7 +11,6 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
import { TabDocView } from '../views/collections/TabDocView';
import { LightboxView } from '../views/LightboxView';
import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { KeyValueBox } from '../views/nodes/KeyValueBox';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
import { PresBox } from '../views/nodes/trails';
@@ -23,7 +22,6 @@ export class DocumentManager {
//global holds all of the nodes (regardless of which collection they're in)
@observable _documentViews = new Set<DocumentView>();
@observable public LinkAnchorBoxViews: DocumentView[] = [];
- @observable public RecordingEvent = 0;
@observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
@computed public get DocumentViews() {
return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
@@ -41,7 +39,22 @@ export class DocumentManager {
}
//private constructor so no other class can create a nodemanager
- private constructor() {}
+ private constructor() {
+ if (!Doc.CurrentlyLoading) Doc.CurrentlyLoading = [];
+ observe(Doc.CurrentlyLoading, change => {
+ // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
+ switch (change.type as any) {
+ case 'update':
+ break;
+ case 'remove':
+ // DocumentManager.Instance.getAllDocumentViews(change as any).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()));
+ break;
+ case 'splice':
+ (change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
+ break;
+ }
+ });
+ }
private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => {
@@ -75,7 +88,7 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
+ if (view.props.LayoutTemplateString?.includes(KeyValueBox.name)) return;
if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
const viewAnchorIndex = view.props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1';
const link = view.rootDoc;
@@ -87,8 +100,6 @@ export class DocumentManager {
})
);
this.LinkAnchorBoxViews.push(view);
- // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
- // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
} else {
this.AddDocumentView(view);
}
@@ -205,7 +216,7 @@ export class DocumentManager {
}
static playAudioAnno(doc: Doc) {
- const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement();
+ const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
if (anno instanceof AudioField) {
new Howl({
@@ -251,6 +262,7 @@ export class DocumentManager {
options: DocFocusOptions, // options for how to navigate to target
finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
+ Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
let rootContextView =
@@ -307,17 +319,20 @@ export class DocumentManager {
@action
restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
if (viewSpec && docView) {
- if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options);
+ //if (docView.ComponentView instanceof FormattedTextBox)
+ //viewSpec !== docView.rootDoc &&
+ docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
- Doc.linkFollowHighlight(docView.rootDoc, undefined, options.effect);
+ Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect);
+ if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.rootDoc._layout_currentTimecode));
if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc);
if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden;
if (options.effect) docView.rootDoc[Animation] = options.effect;
- if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.textHtml) {
+ if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
- contextView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect));
- contextView.textHtmlOverlay = StrCast(targetDoc.textHtml);
+ contextView.htmlOverlayEffect = StrCast(options?.effect?.presentation_effect, StrCast(options?.effect?.followLinkAnimEffect));
+ contextView.textHtmlOverlay = StrCast(targetDoc.text_html);
DocumentManager._overlayViews.add(contextView);
}
Doc.AddUnHighlightWatcher(() => {
@@ -335,7 +350,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) {
DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
} else {
- const container = DocCast(containingDoc ?? doc.embedContainer ?? doc);
+ const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
const showDoc = !Doc.IsSystem(container) ? container : doc;
options.toggleTarget = undefined;
DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 85101fcab..6d6eaebec 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -4,18 +4,17 @@ import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
+import { ScriptCast, StrCast } from '../../fields/Types';
import { emptyFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import * as globalCssVariables from '../views/global/globalCssVariables.scss';
-import { Colors } from '../views/global/globalEnums';
import { DocumentView } from '../views/nodes/DocumentView';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-export type dropActionType = 'embed' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
+export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
/**
* Initialize drag
@@ -61,12 +60,6 @@ export namespace DragManager {
export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void>;
export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
- export function GetRaiseWhenDragged() {
- return BoolCast(Doc.UserDoc()._raiseWhenDragged);
- }
- export function SetRaiseWhenDragged(val: boolean) {
- Doc.UserDoc()._raiseWhenDragged = val;
- }
export function Root() {
const root = document.getElementById('root');
if (!root) {
@@ -195,7 +188,7 @@ export namespace DragManager {
}
// drag a document and drop it (or make an embed/copy on drop)
- export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) {
+ export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
@@ -203,7 +196,7 @@ export namespace DragManager {
};
const finishDrag = async (e: DragCompleteEvent) => {
const docDragData = e.docDragData;
- dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
+ onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
docDragData.droppedDocuments = (
@@ -213,6 +206,8 @@ export namespace DragManager {
? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
: docDragData.dropAction === 'embed'
? Doc.BestEmbedding(d)
+ : docDragData.dropAction === 'add'
+ ? d
: docDragData.dropAction === 'proto'
? Doc.GetProto(d)
: docDragData.dropAction === 'copy'
@@ -343,6 +338,7 @@ export namespace DragManager {
dragLabel.style.zIndex = '100001';
dragLabel.style.fontSize = '10px';
dragLabel.style.position = 'absolute';
+ dragLabel.style.background = '#ffffff90';
dragLabel.innerText = 'drag titlebar to embed on drop'; // bcz: need to move this to a status bar
dragDiv.appendChild(dragLabel);
DragManager.Root().appendChild(dragDiv);
@@ -458,7 +454,7 @@ export namespace DragManager {
runInAction(() => docsBeingDragged.push(...docsToDrag));
const hideDragShowOriginalElements = (hide: boolean) => {
- dragLabel.style.display = hide ? '' : 'none';
+ dragLabel.style.display = hide && !CanEmbed ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
};
@@ -603,18 +599,9 @@ export namespace DragManager {
}
}
-ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) {
+ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
if (readOnly) {
- if (SelectionManager.Views().length)
- return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged)
- ? Colors.MEDIUM_BLUE
- : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false)
- ? 'transparent'
- : DragManager.GetRaiseWhenDragged()
- ? Colors.MEDIUM_BLUE_ALT
- : Colors.LIGHT_BLUE;
- return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE_ALT : 'transparent';
+ return SelectionManager.Views().some(dv => dv.rootDoc.keepZWhenDragged);
}
- if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false)));
- else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged());
+ SelectionManager.Views().map(dv => (dv.rootDoc.keepZWhenDragged = !dv.rootDoc.keepZWhenDragged));
});
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f235be192..dbdf580cd 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,15 +1,15 @@
-import { DragManager } from './DragManager';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { DocumentType } from '../documents/DocumentTypes';
import { ObjectField } from '../../fields/ObjectField';
-import { StrCast, Cast } from '../../fields/Types';
-import { Docs } from '../documents/Documents';
-import { ScriptField, ComputedField } from '../../fields/ScriptField';
import { RichTextField } from '../../fields/RichTextField';
-import { ImageField } from '../../fields/URLField';
-import { ScriptingGlobals } from './ScriptingGlobals';
import { listSpec } from '../../fields/Schema';
+import { ScriptField } from '../../fields/ScriptField';
+import { Cast, StrCast } from '../../fields/Types';
+import { ImageField } from '../../fields/URLField';
+import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox';
+import { DragManager } from './DragManager';
+import { ScriptingGlobals } from './ScriptingGlobals';
export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = '') {
if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 5802d5ee0..8973306bf 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,21 +1,22 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Size, Type } from 'browndash-components';
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 { DateField } from '../../fields/DateField';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { listSpec } from '../../fields/Schema';
+import { Cast, StrCast } 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 { SettingsManager } from './SettingsManager';
import { SharingManager, User } from './SharingManager';
-import { listSpec } from '../../fields/Schema';
-import { DateField } from '../../fields/DateField';
-import { Id } from '../../fields/FieldSymbols';
-import { Button, IconButton, Size } from 'browndash-components';
/**
* Interface for options for the react-select component
@@ -281,7 +282,7 @@ export class GroupManager extends React.Component<{}> {
*/
private get groupCreationModal() {
const contents = (
- <div className="group-create" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}>
+ <div className="group-create" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
<div className="group-heading" style={{ marginBottom: 0 }}>
<p>
<b>New Group</b>
@@ -297,10 +298,10 @@ export class GroupManager extends React.Component<{}> {
/>
</div>
</div>
- <div className="group-input" style={{border: StrCast(Doc.UserDoc().userColor)}} >
+ <div className="group-input" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<input ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
</div>
- <div style={{border: StrCast(Doc.UserDoc().userColor)}}>
+ <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
className="select-users"
isMulti
@@ -331,8 +332,8 @@ export class GroupManager extends React.Component<{}> {
}}
/>
</div>
- <div className={"create-button"}>
- <Button text={"Create"} type={"tertiary"} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+ <div className={'create-button'}>
+ <Button text={'Create'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
</div>
</div>
);
@@ -366,29 +367,25 @@ export class GroupManager extends React.Component<{}> {
const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
return (
- <div className="group-interface" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}>
+ <div className="group-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
{this.groupCreationModal}
{this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
<div className="group-heading">
<p>
<b>Manage Groups</b>
</p>
- <Button icon={<FontAwesomeIcon icon={'plus'}/>} iconPlacement={'left'} text={"Create Group"} type={"tertiary"} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} />
- <div className={'close-button'} >
- <Button
- icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
- onClick={this.close}
- color={StrCast(Doc.UserDoc().userColor)}
- />
+ <Button icon={<FontAwesomeIcon icon={'plus'} />} iconPlacement={'left'} text={'Create Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} />
+ <div className={'close-button'}>
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
</div>
</div>
<div className="main-container">
<div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
Name
- <IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'}/>} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} />
+ <IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} />
</div>
- <div className={'style-divider'} style={{background: StrCast(Doc.UserDoc().userColor)}}/>
- <div className="group-body" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}>
+ <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <div className="group-body" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
{groups.map(group => (
<div className="group-row" key={StrCast(group.title || group.groupName)}>
<div className="group-name">{StrCast(group.title || group.groupName)}</div>
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index f2050dc61..7de0f336f 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,14 +1,15 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import Select from "react-select";
-import { Doc } from "../../fields/Doc";
-import { StrCast } from "../../fields/Types";
-import { MainViewModal } from "../views/MainViewModal";
-import { GroupManager, UserOptions } from "./GroupManager";
-import "./GroupMemberView.scss";
-import { Button, IconButton, Size } from "browndash-components";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import Select from 'react-select';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
+import { MainViewModal } from '../views/MainViewModal';
+import { GroupManager, UserOptions } from './GroupManager';
+import './GroupMemberView.scss';
+import { Button, IconButton, Size, Type } from 'browndash-components';
+import { SettingsManager } from './SettingsManager';
interface GroupMemberViewProps {
group: Doc;
@@ -17,44 +18,37 @@ interface GroupMemberViewProps {
@observer
export class GroupMemberView extends React.Component<GroupMemberViewProps> {
-
- @observable private memberSort: "ascending" | "descending" | "none" = "none";
+ @observable private memberSort: 'ascending' | 'descending' | 'none' = 'none';
private get editingInterface() {
let members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
- members = this.memberSort === "ascending" ? members.sort() : this.memberSort === "descending" ? members.sort().reverse() : members;
+ members = this.memberSort === 'ascending' ? members.sort() : this.memberSort === 'descending' ? members.sort().reverse() : members;
const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group);
- return (!this.props.group ? null :
- <div className="editing-interface" style={{background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor)}}>
+ return !this.props.group ? null : (
+ <div className="editing-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
<div className="editing-header">
<input
className="group-title"
- style={{ marginLeft: !hasEditAccess ? "-14%" : 0 }}
+ style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
value={StrCast(this.props.group.title || this.props.group.groupName)}
- onChange={e => this.props.group.title = e.currentTarget.value}
- disabled={!hasEditAccess}
- >
- </input>
- <div className={"memberView-closeButton"} >
- <Button
- icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
- onClick={action(this.props.onCloseButtonClick)}
- color={StrCast(Doc.UserDoc().userColor)}
- />
+ onChange={e => (this.props.group.title = e.currentTarget.value)}
+ disabled={!hasEditAccess}></input>
+ <div className={'memberView-closeButton'}>
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
</div>
- {GroupManager.Instance.hasEditAccess(this.props.group) ?
+ {GroupManager.Instance.hasEditAccess(this.props.group) ? (
<div className="group-buttons">
- <div style={{border: StrCast(Doc.UserDoc().userColor)}} >
- <Select
+ <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
+ <Select
className="add-member-dropdown"
isSearchable={true}
options={options}
onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.props.group, (selectedOption as UserOptions).value)}
- placeholder={"Add members"}
+ placeholder={'Add members'}
value={null}
styles={{
control: () => ({
@@ -78,52 +72,33 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
}}
/>
</div>
- <div className={"delete-button"}>
- <Button text={"Delete Group"} type={"tertiary"} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.deleteGroup(this.props.group)} />
+ <div className={'delete-button'}>
+ <Button text={'Delete Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.deleteGroup(this.props.group)} />
</div>
- </div> :
- null}
- <div
- className="sort-emails"
- style={{ paddingTop: hasEditAccess ? 0 : 35 }}
- onClick={action(() => this.memberSort = this.memberSort === "ascending" ? "descending" : this.memberSort === "descending" ? "none" : "ascending")}>
- Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */}
+ </div>
+ ) : null}
+ <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}>
+ Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
</div>
</div>
- <div className={'style-divider'} style={{background: StrCast(Doc.UserDoc().userColor)}}/>
- <div className="editing-contents"
- style={{ height: hasEditAccess ? "62%" : "85%" }}
- >
+ <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <div className="editing-contents" style={{ height: hasEditAccess ? '62%' : '85%' }}>
{members.map(member => (
- <div
- className="editing-row"
- key={member}
- >
- <div className="user-email">
- {member}
- </div>
- {hasEditAccess ?
- <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
+ <div className="editing-row" key={member}>
+ <div className="user-email">{member}</div>
+ {hasEditAccess ? (
+ <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
<IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)} />
</div>
- : null}
+ ) : null}
</div>
))}
</div>
</div>
);
-
}
render() {
- return <MainViewModal
- isDisplayed={true}
- interactive={true}
- contents={this.editingInterface}
- dialogueBoxStyle={{ width: 400, height: 250 }}
- closeOnExternalClick={this.props.onCloseButtonClick}
- />;
+ return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
}
-
-
-} \ No newline at end of file
+}
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index d0f459291..4e32ed67f 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -108,7 +108,8 @@ export namespace InteractionUtils {
opacity: number,
nodefs: boolean,
downHdlr?: (e: React.PointerEvent) => void,
- mask?: boolean
+ mask?: boolean,
+ dropshadow?: string
) {
const pts = shape ? makePolygon(shape, points) : points;
@@ -161,7 +162,6 @@ export namespace InteractionUtils {
<polygon
style={{ stroke: color }}
strokeLinejoin={lineJoin as any}
- strokeDasharray={dashArray}
strokeWidth={(markerStrokeWidth * 2) / 3}
points={`0 ${-markerStrokeWidth * arrowWidthFactor}, ${markerStrokeWidth * arrowNotchFactor} 0, 0 ${markerStrokeWidth * arrowWidthFactor}, ${arrowLengthFactor * markerStrokeWidth} 0`}
/>
@@ -172,6 +172,7 @@ export namespace InteractionUtils {
<Tag
d={bezier ? strpts : undefined}
points={bezier ? undefined : strpts}
+ filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) drop-shadow(2px -1px 0px ${dropshadow}) drop-shadow(2px 2px 0px ${dropshadow}) drop-shadow(-1px 2px 0px ${dropshadow})`}
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== 'transparent' ? fill : 'none',
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index b8fea340f..2fc811b09 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -73,6 +73,7 @@ export class LinkFollower {
const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
const options: DocFocusOptions = {
playAudio: BoolCast(sourceDoc.followLinkAudio),
+ playMedia: BoolCast(sourceDoc.followLinkVideo),
toggleTarget,
noSelect: true,
willPan: true,
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index c7f092565..a533fdd1f 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,7 +1,8 @@
-import { action, observable, observe } from 'mobx';
+import { action, observable, observe, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
import { DirectLinks } from '../../fields/DocSymbols';
+import { FieldLoader } from '../../fields/FieldLoader';
import { List } from '../../fields/List';
import { ProxyField } from '../../fields/Proxy';
import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
@@ -55,10 +56,12 @@ export class LinkManager {
Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
- action(lAnchProtoProtos => {
- link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
- link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
- })
+ link &&
+ action(lAnchProtoProtos => {
+ Doc.AddDocToList(Doc.UserDoc(), 'links', link);
+ lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
+ lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
+ })
)
)
)
@@ -132,6 +135,7 @@ export class LinkManager {
},
true
);
+ runInAction(() => (FieldLoader.ServerLoadStatus.message = 'links'));
LinkManager.addLinkDB(Doc.LinkDBDoc());
}
@@ -143,6 +147,7 @@ export class LinkManager {
};
public addLink(linkDoc: Doc, checkExists = false) {
+ Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
setTimeout(DocServer.UPDATE_SERVER_CACHE, 100);
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 0c41a1ea7..4dd2fcd35 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,5 +1,6 @@
-import { action, observable } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
import { Networking } from '../Network';
+import { CurrentUserUtils } from './CurrentUserUtils';
export class PingManager {
// create static instance and getter for global use
@observable static _instance: PingManager;
@@ -18,7 +19,8 @@ export class PingManager {
};
sendPing = async (): Promise<void> => {
try {
- await Networking.PostToServer('/ping', { date: new Date() });
+ const res = await Networking.PostToServer('/ping', { date: new Date() });
+ runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
!this.IsBeating && this.setIsBeating(true);
} catch {
if (this.IsBeating) {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index b93d4f293..c8940194c 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -2,6 +2,9 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
+import { SettingsManager } from './SettingsManager';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
@observer
export class RTFMarkup extends React.Component<{}> {
@@ -30,7 +33,7 @@ export class RTFMarkup extends React.Component<{}> {
*/
@computed get cheatSheet() {
return (
- <div style={{ textAlign: 'initial', height: '100%' }}>
+ <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
<p>
<b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
@@ -132,6 +135,14 @@ export class RTFMarkup extends React.Component<{}> {
}
render() {
- return <MainViewModal contents={this.cheatSheet} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+ return (
+ <MainViewModal
+ dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, padding: '16px' }}
+ contents={this.cheatSheet}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ closeOnExternalClick={this.close}
+ />
+ );
}
}
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index cbc465d6a..d99630f82 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -187,7 +187,6 @@ export class ReplayMovements {
} else {
// tab wasn't open - open it and play the movement
const openedColFFView = this.openTab(movement.doc);
- console.log('openedColFFView', openedColFFView);
openedColFFView && this.zoomAndPan(movement, openedColFFView);
}
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index d154c48a4..560d6b30f 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,13 +1,115 @@
import * as rp from 'request-promise';
import { DocServer } from '../DocServer';
-import { Doc } from '../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { Utils } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
+import { StrCast } from '../../fields/Types';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
+ export function SearchCollection(rootDoc: Opt<Doc>, query: string) {
+ const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+ const blockedKeys = [
+ 'x',
+ 'y',
+ 'proto',
+ 'width',
+ 'layout_autoHeight',
+ 'acl-Override',
+ 'acl-Guest',
+ 'embedContainer',
+ 'zIndex',
+ 'height',
+ 'text_scrollHeight',
+ 'text_height',
+ 'cloneFieldFilter',
+ 'isDataDoc',
+ 'text_annotations',
+ 'dragFactory_count',
+ 'text_noTemplate',
+ 'proto_embeddings',
+ 'isSystem',
+ 'layout_fieldKey',
+ 'isBaseProto',
+ 'xMargin',
+ 'yMargin',
+ 'links',
+ 'layout',
+ 'layout_keyValue',
+ 'layout_fitWidth',
+ 'type_collection',
+ 'title_custom',
+ 'freeform_panX',
+ 'freeform_panY',
+ 'freeform_scale',
+ ];
+ query = query.toLowerCase();
+
+ const results = new Map<Doc, string[]>();
+ if (rootDoc) {
+ const docs = DocListCast(rootDoc[Doc.LayoutFieldKey(rootDoc)]);
+ const docIDs: String[] = [];
+ SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
+ const dtype = StrCast(doc.type) as DocumentType;
+ if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
+ const hlights = new Set<string>();
+ SearchUtil.documentKeys(doc).forEach(
+ key =>
+ (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field))
+ .toLowerCase() //
+ .includes(query) && hlights.add(key)
+ );
+ blockedKeys.forEach(key => hlights.delete(key));
+
+ if (Array.from(hlights.keys()).length > 0) {
+ results.set(doc, Array.from(hlights.keys()));
+ }
+ }
+ docIDs.push(doc[Id]);
+ });
+ }
+ return results;
+ }
+ /**
+ * @param {Doc} doc - doc for which keys are returned
+ *
+ * This method returns a list of a document doc's keys.
+ */
+ export function documentKeys(doc: Doc) {
+ const keys: { [key: string]: boolean } = {};
+ Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)));
+ return Array.from(Object.keys(keys));
+ }
+
+ /**
+ * @param {Doc[]} docs - docs to be searched through recursively
+ * @param {number, Doc => void} func - function to be called on each doc
+ *
+ * This method iterates through an array of docs and all docs within those docs, calling
+ * the function func on each doc.
+ */
+ export function foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+ let newarray: Doc[] = [];
+ var depth = 0;
+ const visited: Doc[] = [];
+ while (docs.length > 0) {
+ newarray = [];
+ docs.filter(d => d && !visited.includes(d)).forEach(d => {
+ visited.push(d);
+ const fieldKey = Doc.LayoutFieldKey(d);
+ const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+ const data = d[annos ? fieldKey + '_annotations' : fieldKey];
+ data && newarray.push(...DocListCast(data));
+ const sidebar = d[fieldKey + '_sidebar'];
+ sidebar && newarray.push(...DocListCast(sidebar));
+ func(depth, d);
+ });
+ docs = newarray;
+ depth++;
+ }
+ }
export interface IdSearchResult {
ids: string[];
lines: string[][];
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 4be9448b3..d0f66d124 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,11 +1,14 @@
import { action, observable, ObservableMap } from 'mobx';
import { computedFn } from 'mobx-utils';
import { Doc, Opt } from '../../fields/Doc';
-import { DocCast } from '../../fields/Types';
-import { CollectionViewType } from '../documents/DocumentTypes';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast, DocCast } from '../../fields/Types';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentView } from '../views/nodes/DocumentView';
import { LinkManager } from './LinkManager';
import { ScriptingGlobals } from './ScriptingGlobals';
+import { UndoManager } from './UndoManager';
export namespace SelectionManager {
class Manager {
@@ -23,9 +26,6 @@ export namespace SelectionManager {
// if doc is not in SelectedDocuments, add it
if (!manager.SelectedViewsMap.get(docView)) {
if (!ctrlPressed) {
- if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) {
- LinkManager.currentLink = undefined;
- }
this.DeselectAll();
}
@@ -51,6 +51,8 @@ export namespace SelectionManager {
}
@action
DeselectAll(): void {
+ LinkManager.currentLink = undefined;
+ LinkManager.currentLinkAnchor = undefined;
manager.SelectedSchemaDocument = undefined;
Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
manager.SelectedViewsMap.clear();
@@ -124,3 +126,27 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp
ScriptingGlobals.add(function deselectAll() {
SelectionManager.DeselectAll();
});
+ScriptingGlobals.add(function undo() {
+ SelectionManager.DeselectAll();
+ return UndoManager.Undo();
+});
+
+export function ShowUndoStack() {
+ SelectionManager.DeselectAll();
+ var buffer = '';
+ UndoManager.undoStack.forEach((batch, i) => {
+ buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
+ ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
+ });
+ alert(buffer);
+}
+ScriptingGlobals.add(function redo() {
+ SelectionManager.DeselectAll();
+ return UndoManager.Redo();
+});
+ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
+ const docs = SelectionManager.Views()
+ .map(dv => dv.props.Document)
+ .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
+ return docs.length ? new List(docs) : prevValue;
+});
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 6a6ec158e..ac9fecd5c 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -6,6 +6,7 @@ import './SharingManager.scss';
import { PingManager } from './PingManager';
import { StrCast } from '../../fields/Types';
import { Doc } from '../../fields/Doc';
+import { SettingsManager } from './SettingsManager';
@observer
export class ServerStats extends React.Component<{}> {
@@ -42,21 +43,22 @@ export class ServerStats extends React.Component<{}> {
*/
@computed get sharingInterface() {
return (
- <div style={{
- display: "flex",
- height: 300,
- width: 400,
- background: StrCast(Doc.UserDoc().userBackgroundColor),
- opacity: 0.6}}>
- <div style={{width: 300,height: 100,margin: "auto",display: "flex",flexDirection: "column"}}>
- {PingManager.Instance.IsBeating ? 'The server connection is active' :
- 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.'}
-
- <br/>
+ <div
+ style={{
+ display: 'flex',
+ height: 300,
+ width: 400,
+ background: SettingsManager.userBackgroundColor,
+ opacity: 0.6,
+ }}>
+ <div style={{ width: 300, height: 100, margin: 'auto', display: 'flex', flexDirection: 'column' }}>
+ {PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.'}
+
+ <br />
<span>Active users:{this._stats?.socketMap.length}</span>
{this._stats?.socketMap.map((user: any) => (
<p>{user.username}</p>
- ))}
+ ))}
</div>
</div>
);
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index b8e327968..085ff4bb1 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,27 +1,21 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { ColorState, SketchPicker } from 'react-color';
+import { BsGoogle } from 'react-icons/bs';
+import { FaFillDrip, FaPalette } from 'react-icons/fa';
import { Doc } from '../../fields/Doc';
-import { Id } from '../../fields/FieldSymbols';
+import { DashVersion } from '../../fields/DocSymbols';
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/FontIconBox/FontIconBox';
-import { DragManager } from './DragManager';
import { GroupManager } from './GroupManager';
import './SettingsManager.scss';
import { undoBatch } from './UndoManager';
-import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { BsGoogle } from 'react-icons/bs';
-import { FaFillDrip, FaPalette } from 'react-icons/fa';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
export enum ColorScheme {
Dark = 'Dark',
@@ -53,13 +47,6 @@ export class SettingsManager extends React.Component<{}> {
@observable public static propertiesWidth: number = 0;
@observable public static headerBarHeight: number = 0;
- @computed get backgroundColor() {
- return Doc.UserDoc().activeCollectionBackground;
- }
- @computed get userTheme() {
- return Doc.UserDoc().userTheme;
- }
-
constructor(props: {}) {
super(props);
SettingsManager.Instance = this;
@@ -79,15 +66,15 @@ export class SettingsManager extends React.Component<{}> {
}
};
- @computed get userColor() {
+ @computed public static get userColor() {
return StrCast(Doc.UserDoc().userColor);
}
- @computed get userVariantColor() {
+ @computed public static get userVariantColor() {
return StrCast(Doc.UserDoc().userVariantColor);
}
- @computed get userBackgroundColor() {
+ @computed public static get userBackgroundColor() {
return StrCast(Doc.UserDoc().userBackgroundColor);
}
@@ -99,12 +86,8 @@ export class SettingsManager extends React.Component<{}> {
Doc.UserDoc().userBackgroundColor = color;
addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
});
- @undoBatch switchUserColor = action((color: string) => {
- Doc.UserDoc().userColor = color;
- });
- @undoBatch switchUserVariantColor = action((color: string) => {
- Doc.UserDoc().userVariantColor = color;
- });
+ @undoBatch switchUserColor = action((color: string) => (Doc.UserDoc().userColor = color));
+ @undoBatch switchUserVariantColor = action((color: string) => (Doc.UserDoc().userVariantColor = color));
@undoBatch playgroundModeToggle = action(() => {
this.playgroundMode = !this.playgroundMode;
if (this.playgroundMode) {
@@ -159,6 +142,7 @@ export class SettingsManager extends React.Component<{}> {
formLabel="Theme"
size={Size.SMALL}
type={Type.TERT}
+ closeOnSelect={false}
selectedVal={userTheme}
setSelectedVal={scheme => this.changeColorScheme(scheme as string)}
items={colorSchemes.map((scheme, i) => ({
@@ -166,14 +150,38 @@ export class SettingsManager extends React.Component<{}> {
val: scheme,
}))}
dropdownType={DropdownType.SELECT}
- color={this.userColor}
+ color={SettingsManager.userColor}
fillWidth
/>
{userTheme === ColorScheme.Custom && (
<Group formLabel="Custom Theme">
- <ColorPicker tooltip={'User Color'} color={this.userColor} type={Type.SEC} icon={<FaFillDrip />} selectedColor={this.userColor} setSelectedColor={this.switchUserColor} />
- <ColorPicker tooltip={'User Background Color'} color={this.userColor} type={Type.SEC} icon={<FaPalette />} selectedColor={this.userBackgroundColor} setSelectedColor={this.switchUserBackgroundColor} />
- <ColorPicker tooltip={'User Variant Color'} color={this.userColor} type={Type.SEC} icon={<FaPalette />} selectedColor={this.userVariantColor} setSelectedColor={this.switchUserVariantColor} />
+ <ColorPicker
+ tooltip={'User Color'} //
+ color={SettingsManager.userColor}
+ type={Type.SEC}
+ icon={<FaFillDrip />}
+ selectedColor={SettingsManager.userColor}
+ setSelectedColor={this.switchUserColor}
+ setFinalColor={this.switchUserColor}
+ />
+ <ColorPicker
+ tooltip={'User Background Color'}
+ color={SettingsManager.userColor}
+ type={Type.SEC}
+ icon={<FaPalette />}
+ selectedColor={SettingsManager.userBackgroundColor}
+ setSelectedColor={this.switchUserBackgroundColor}
+ setFinalColor={this.switchUserBackgroundColor}
+ />
+ <ColorPicker
+ tooltip={'User Variant Color'}
+ color={SettingsManager.userColor}
+ type={Type.SEC}
+ icon={<FaPalette />}
+ selectedColor={SettingsManager.userVariantColor}
+ setSelectedColor={this.switchUserVariantColor}
+ setFinalColor={this.switchUserVariantColor}
+ />
</Group>
)}
</div>
@@ -190,7 +198,7 @@ export class SettingsManager extends React.Component<{}> {
onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
size={Size.XSMALL}
- color={this.userColor}
+ color={SettingsManager.userColor}
/>
<Toggle
formLabel={'Show Full Toolbar'}
@@ -199,25 +207,25 @@ export class SettingsManager extends React.Component<{}> {
onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])}
toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])}
size={Size.XSMALL}
- color={this.userColor}
+ color={SettingsManager.userColor}
/>
<Toggle
formLabel={'Show Button Labels'}
formLabelPlacement={'right'}
toggleType={ToggleType.SWITCH}
- onClick={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())}
- toggleStatus={FontIconBox.GetShowLabels()}
+ onClick={e => Doc.SetShowIconLabels(!Doc.GetShowIconLabels())}
+ toggleStatus={Doc.GetShowIconLabels()}
size={Size.XSMALL}
- color={this.userColor}
+ color={SettingsManager.userColor}
/>
<Toggle
formLabel={'Recognize Ink Gestures'}
formLabelPlacement={'right'}
toggleType={ToggleType.SWITCH}
- onClick={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())}
- toggleStatus={FontIconBox.GetRecognizeGestures()}
+ onClick={e => Doc.SetRecognizeGestures(!Doc.GetRecognizeGestures())}
+ toggleStatus={Doc.GetRecognizeGestures()}
size={Size.XSMALL}
- color={this.userColor}
+ color={SettingsManager.userColor}
/>
<Toggle
formLabel={'Hide Labels In Ink Shapes'}
@@ -226,7 +234,7 @@ export class SettingsManager extends React.Component<{}> {
onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
size={Size.XSMALL}
- color={this.userColor}
+ color={SettingsManager.userColor}
/>
<Toggle
formLabel={'Open Ink Docs in Lightbox'}
@@ -235,7 +243,16 @@ export class SettingsManager extends React.Component<{}> {
onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
size={Size.XSMALL}
- color={this.userColor}
+ color={SettingsManager.userColor}
+ />
+ <Toggle
+ formLabel={'Show Link Lines'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
+ toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
+ size={Size.XSMALL}
+ color={SettingsManager.userColor}
/>
</div>
);
@@ -267,7 +284,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
{/* <NumberInput/> */}
<Group formLabel={'Default Font'}>
- <NumberDropdown color={this.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} />
+ <NumberDropdown color={SettingsManager.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} />
<Dropdown
items={fontFamilies.map(val => {
return {
@@ -278,13 +295,14 @@ export class SettingsManager extends React.Component<{}> {
},
};
})}
+ closeOnSelect={true}
dropdownType={DropdownType.SELECT}
type={Type.TERT}
selectedVal={StrCast(Doc.UserDoc().fontFamily)}
setSelectedVal={val => {
this.changeFontFamily(val as string);
}}
- color={this.userColor}
+ color={SettingsManager.userColor}
fillWidth
/>
</Group>
@@ -312,12 +330,12 @@ export class SettingsManager extends React.Component<{}> {
@computed get passwordContent() {
return (
<div className="password-content">
- <EditableText placeholder="Current password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
- <EditableText placeholder="New password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
- <EditableText placeholder="Confirm new password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
+ <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
+ <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
+ <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
{!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
- <Button type={Type.SEC} text={'Forgot Password'} color={this.userColor} />
- <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={this.userColor} />
+ <Button type={Type.SEC} text={'Forgot Password'} color={SettingsManager.userColor} />
+ <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={SettingsManager.userColor} />
</div>
);
}
@@ -357,6 +375,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
<Dropdown
formLabel={'Mode'}
+ closeOnSelect={true}
items={[
{
text: 'Novice',
@@ -377,17 +396,18 @@ export class SettingsManager extends React.Component<{}> {
dropdownType={DropdownType.SELECT}
type={Type.TERT}
placement="bottom-start"
- color={this.userColor}
+ color={SettingsManager.userColor}
fillWidth
/>
- <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={this.userColor} />
+ <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
</div>
<div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}>
Freeform Navigation
</div>
<div className="tab-column-content">
<Dropdown
- formLabel={'Scroll Mode'}
+ formLabel="Scroll Mode"
+ closeOnSelect={true}
items={[
{
text: 'Scroll to Pan',
@@ -405,15 +425,22 @@ export class SettingsManager extends React.Component<{}> {
dropdownType={DropdownType.SELECT}
type={Type.TERT}
placement="bottom-start"
- color={this.userColor}
+ color={SettingsManager.userColor}
/>
</div>
</div>
<div className="tab-column">
<div className="tab-column-title">Permissions</div>
<div className="tab-column-content">
- <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={this.userColor} />
- <Toggle toggleType={ToggleType.SWITCH} formLabel={'Default access private'} color={this.userColor} toggleStatus={BoolCast(Doc.defaultAclPrivate)} onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} />
+ <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
+ <Toggle
+ toggleType={ToggleType.SWITCH}
+ formLabel={'Default access private'}
+ color={SettingsManager.userColor}
+ toggleStatus={BoolCast(Doc.defaultAclPrivate)}
+ onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}
+ />
+ <Toggle toggleType={ToggleType.SWITCH} formLabel={'Enable Sharing UI'} color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} />
</div>
</div>
</div>
@@ -432,7 +459,7 @@ export class SettingsManager extends React.Component<{}> {
];
return (
<div className="settings-interface">
- <div className="settings-panel" style={{ background: this.userColor }}>
+ <div className="settings-panel" style={{ background: SettingsManager.userColor }}>
<div className="settings-tabs">
{tabs.map(tab => {
const isActive = this.activeTab === tab.title;
@@ -440,8 +467,8 @@ export class SettingsManager extends React.Component<{}> {
<div
key={tab.title}
style={{
- background: isActive ? this.userBackgroundColor : this.userColor,
- color: isActive ? this.userColor : this.userBackgroundColor,
+ background: isActive ? SettingsManager.userBackgroundColor : SettingsManager.userColor,
+ color: isActive ? SettingsManager.userColor : SettingsManager.userBackgroundColor,
}}
className={'tab-control ' + (isActive ? 'active' : 'inactive')}
onClick={action(() => (this.activeTab = tab.title))}>
@@ -452,18 +479,19 @@ export class SettingsManager extends React.Component<{}> {
</div>
<div className="settings-user">
- <div className="settings-username" style={{ color: this.userBackgroundColor }}>
+ <div style={{ color: SettingsManager.userBackgroundColor }}>{DashVersion}</div>
+ <div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
{Doc.CurrentUserEmail}
</div>
- <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={this.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} />
+ <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} />
</div>
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={this.userColor} />
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
</div>
- <div className="settings-content" style={{ color: this.userColor, background: this.userBackgroundColor }}>
+ <div className="settings-content" style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
{tabs.map(tab => (
<div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
{tab.ele}
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index cadcb1f8a..8d59426ec 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -8,6 +8,7 @@ import Select from 'react-select';
import * as RequestPromise from 'request-promise';
import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
+import { FieldLoader } from '../../fields/FieldLoader';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
@@ -17,12 +18,12 @@ 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 { LinkManager } from './LinkManager';
+import { SearchUtil } from './SearchUtil';
import { SelectionManager } from './SelectionManager';
+import { SettingsManager } from './SettingsManager';
import './SharingManager.scss';
import { undoable } from './UndoManager';
@@ -136,6 +137,7 @@ export class SharingManager extends React.Component<{}> {
this.populating = true;
const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
+ runInAction(() => (FieldLoader.ServerLoadStatus.message = 'users'));
const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
raw.map(
action((newUser: User) => {
@@ -144,7 +146,7 @@ export class SharingManager extends React.Component<{}> {
if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
if (!this.users.find(user => user.user.email === newUser.email)) {
this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- LinkManager.addLinkDB(linkDatabase);
+ //LinkManager.addLinkDB(linkDatabase);
}
}
})
@@ -376,15 +378,17 @@ 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.taskCompleted = true;
- setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
- 2000
- );
+ if (this.shareDocumentButtonRef.current) {
+ 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.taskCompleted = true;
+ setTimeout(
+ action(() => (TaskCompletionBox.taskCompleted = false)),
+ 2000
+ );
+ }
this.layoutDocAcls = false;
this.selectedUsers = null;
@@ -444,7 +448,7 @@ export class SharingManager extends React.Component<{}> {
if (this.myDocAcls) {
const newDocs: Doc[] = [];
- SearchBox.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc));
+ SearchUtil.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc));
docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin);
}
@@ -525,10 +529,10 @@ export class SharingManager extends React.Component<{}> {
const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
- <div key={groupKey} className={'container'} style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
+ <div key={groupKey} className={'container'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
<div className={'padding'}>{StrCast(group.title)}</div>
&nbsp;
- {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
+ {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
<div className={'edit-actions'}>
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
@@ -550,10 +554,10 @@ export class SharingManager extends React.Component<{}> {
<div
className="sharing-contents"
style={{
- background: StrCast(Doc.UserDoc().userBackgroundColor),
+ background: SettingsManager.userBackgroundColor,
color: StrCast(Doc.UserDoc().userColor),
}}>
- <p className="share-title" style={{ color: StrCast(Doc.UserDoc().userColor) }}>
+ <p className="share-title" style={{ color: SettingsManager.userColor }}>
<div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
<FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
</div>
@@ -561,10 +565,10 @@ export class SharingManager extends React.Component<{}> {
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
<div className="share-copy-link">
- <Button type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
+ <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
</div>
{admin ? (
<div className="share-container">
@@ -606,7 +610,7 @@ export class SharingManager extends React.Component<{}> {
</select>
</div>
<div className="share-button">
- <Button text={'SHARE'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.share} />
+ <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
</div>
</div>
<div className="sort-checkboxes">
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index e6de410a7..802a2f09f 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -19,9 +19,7 @@ import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, d
import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents';
import { StrCast } from '../../../fields/Types';
import { MdRefresh } from 'react-icons/md';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
+import { SettingsManager } from '../SettingsManager';
/**
* Class for reporting and viewing Github issues within the app.
@@ -214,11 +212,11 @@ export class ReportManager extends React.Component<{}> {
* @returns the component that dispays all issues
*/
private viewIssuesComponent = () => {
- const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
+ const darkMode = isDarkMode(SettingsManager.userBackgroundColor);
const colors = darkMode ? darkColors : lightColors;
return (
- <div className="view-issues" style={{ backgroundColor: StrCast(Doc.UserDoc().userBackgroundColor), color: colors.text }}>
+ <div className="view-issues" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: colors.text }}>
<div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
<div className="report-header">
<h2 style={{ color: colors.text }}>Open Issues</h2>
@@ -311,6 +309,7 @@ export class ReportManager extends React.Component<{}> {
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
formLabel={'Type'}
+ closeOnSelect={true}
items={bugDropdownItems}
selectedVal={this.formData.type}
setSelectedVal={val => {
@@ -323,6 +322,7 @@ export class ReportManager extends React.Component<{}> {
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
formLabel={'Priority'}
+ closeOnSelect={true}
items={priorityDropdownItems}
selectedVal={this.formData.priority}
setSelectedVal={val => {