aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
authoreperelm2 <emily_perelman@brown.edu>2023-07-18 11:40:12 -0400
committereperelm2 <emily_perelman@brown.edu>2023-07-18 11:40:12 -0400
commit5100a643fb0d98b6dd738e7024f4fe15f56ba1a8 (patch)
tree92fa39d2d5cc8f584e3346c8fe0efaa5b184a9e5 /src/client/util
parentc9779f31d9ce2363e61c3c9fa7e3446203622dde (diff)
parent16a1b7de3ec26187b3a426eb037a5e4f4b9fcc55 (diff)
Merge branch 'master' into secondpropertiesmenu-emily
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CaptureManager.scss18
-rw-r--r--src/client/util/CurrentUserUtils.ts155
-rw-r--r--src/client/util/DocumentManager.ts2
-rw-r--r--src/client/util/DropConverter.ts2
-rw-r--r--src/client/util/GroupManager.scss12
-rw-r--r--src/client/util/LinkFollower.ts2
-rw-r--r--src/client/util/LinkManager.ts65
-rw-r--r--src/client/util/RTFMarkup.tsx2
-rw-r--r--src/client/util/ReportManager.tsx4
-rw-r--r--src/client/util/ScriptManager.ts38
-rw-r--r--src/client/util/ServerStats.tsx22
-rw-r--r--src/client/util/SettingsManager.scss141
-rw-r--r--src/client/util/SettingsManager.tsx470
-rw-r--r--src/client/util/SharingManager.scss169
-rw-r--r--src/client/util/SharingManager.tsx406
-rw-r--r--src/client/util/UndoManager.ts9
16 files changed, 834 insertions, 683 deletions
diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss
index a5024247e..11e31fe2e 100644
--- a/src/client/util/CaptureManager.scss
+++ b/src/client/util/CaptureManager.scss
@@ -155,21 +155,3 @@
}
}
-.close-button {
- position: absolute;
- top: 10;
- right: 10;
- background:transparent;
- border-radius:100%;
- width: 25px;
- height: 25px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: 0.2s;
-}
-
-.close-button:hover {
- background: rgba(0,0,0,0.1);
-}
-
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 074e89753..6977fb0b6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -12,7 +12,7 @@ import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { OmitKeys, Utils } from "../../Utils";
+import { OmitKeys, Utils, addStyleSheetRule } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
@@ -20,7 +20,7 @@ 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/button/FontIconBox";
+import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
import { OpenWhere } from "../views/nodes/DocumentView";
import { OverlayView } from "../views/OverlayView";
import { DragManager, dropActionType } from "./DragManager";
@@ -28,7 +28,7 @@ import { MakeTemplate } from "./DropConverter";
import { FollowLinkScript } from "./LinkFollower";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
-import { ColorScheme } from "./SettingsManager";
+import { ColorScheme, SettingsManager } from "./SettingsManager";
import { UndoManager } from "./UndoManager";
import { PresElementBox } from "../views/nodes/trails";
import { ImportElementBox } from "../views/nodes/importBox/importElementBox";
@@ -267,7 +267,7 @@ export class CurrentUserUtils {
}[] = [
{key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }},
{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
- {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, }},
+ {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }},
{key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
{key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }},
{key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }},
@@ -340,19 +340,19 @@ 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, 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}}[] {
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", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
- { title: "Search", target: this.setupSearcher(doc, "mySearcher"), icon: "search", },
- { title: "Files", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", },
- { title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} },
- { title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", },
- { title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", },
- { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue:badgeValue}},
- { title: "Trails", target: Doc.UserDoc(), icon: "pres-trail", funcs: {target: getActiveDashTrails}},
- { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Dashboards", toolTip: "Dashboards ⌘D", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Search", toolTip: "Search ⌘F", target: this.setupSearcher(doc, "mySearcher"), icon: "search", },
+ { title: "Files", toolTip: "Files ⌘⇧F", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", },
+ { title: "Tools", toolTip: "Tools ⌘T", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Imports", toolTip: "Imports ⌘I", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", },
+ { title: "Closed", toolTip: "Recently Closed ⌘R", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", },
+ { title: "Shared", toolTip: "Shared Docs ⌘⇧S", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue:badgeValue}},
+ { title: "Trails", toolTip: "Trails ⌘⇧", target: Doc.UserDoc(), icon: "pres-trail", funcs: {target: getActiveDashTrails}},
+ { title: "User Doc", toolTip: "User Doc ⌘U", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}}));
}
@@ -365,17 +365,17 @@ export class CurrentUserUtils {
static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") {
this.setupLeftSidebarPanel(doc);
const myLeftSidebarMenu = DocCast(doc[field]);
- const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => {
+ const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, scripts, funcs }) => {
const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
- title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
+ title, icon, target, toolTip, 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);
});
const reqdStackOpts:DocumentOptions ={
- title: "menuItemPanel", childDragAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
+ 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,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
@@ -525,7 +525,7 @@ export class CurrentUserUtils {
const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
_forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
- title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
+ 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);
@@ -559,7 +559,7 @@ export class CurrentUserUtils {
const clearAll = (target:string) => `getProto(${target}).data = new List([])`;
const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
- title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", isSystem: true,
+ title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, color: Colors.BLACK, buttonText: "Empty", icon: "trash", isSystem: true,
toolTip: "Empty recently closed",};
DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
@@ -602,8 +602,8 @@ export class CurrentUserUtils {
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
- { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }},
- { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }},
+ { scripts: { onClick: "undo()"}, opts: { title: "Undo", icon: "undo-alt", toolTip: "Undo ⌘Z" }},
+ { scripts: { onClick: "redo()"}, opts: { title: "Redo", icon: "redo-alt", toolTip: "Redo ⌘R" }},
{ 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"}},
@@ -611,10 +611,10 @@ export class CurrentUserUtils {
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 = {
title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
- childDontRegisterViews: true, linearView_IsExpanded: true, linearView_Expandable: true, ignoreClick: true
+ childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
};
- reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
- reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+ reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
+ reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
@@ -627,30 +627,34 @@ export class CurrentUserUtils {
}
static viewTools(): Button[] {
return [
- { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '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, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '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
+ { 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[] {
return [
{ title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
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, width: 75, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0 },
+ { 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: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
- { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _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: "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, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
+ 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: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
{ title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
// { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
@@ -695,19 +699,19 @@ export class CurrentUserUtils {
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: 20, scripts: { onClick: 'pinWithView(altKey)'}},
- { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
- { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}},
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '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: 20, 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: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
- { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
- { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsExpanded: `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_IsExpanded: `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_IsExpanded: `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_IsExpanded: `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_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
+ { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}},
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // 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_)'}},
+ { 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_)'}},
+ { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+ { 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: "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
];
}
@@ -715,7 +719,6 @@ export class CurrentUserUtils {
static setupContextMenuButton(params:Button, btnDoc?:Doc) {
const reqdOpts:DocumentOptions = {
...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
- backgroundColor: params.backgroundColor ??"transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background
color: Colors.WHITE, isSystem: true,
_nativeWidth: params.width ?? 30, _width: params.width ?? 30,
_height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
@@ -724,23 +727,29 @@ export class CurrentUserUtils {
};
const reqdFuncs:{[key:string]:any} = {
...params.funcs,
- backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally
}
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
}
/// Initializes all the default buttons for the top bar context menu
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
- const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsExpanded"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+ 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) {
+ if (!params.subMenu) { // button does not have a sub menu
return this.setupContextMenuButton(params, menuBtnDoc);
- } else {
- const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsExpanded"]),
+ } 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))
);
@@ -767,7 +776,7 @@ export class CurrentUserUtils {
linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
linkDocs.author = Doc.CurrentUserEmail;
linkDocs.data = new List<Doc>([]);
- linkDocs["acl-Public"] = SharingPermissions.Augment;
+ linkDocs["acl-Guest"] = SharingPermissions.Augment;
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
}
@@ -777,8 +786,6 @@ export class CurrentUserUtils {
// When the user views one of these documents, it will be added to the sharing documents 'viewed' list field
// The sharing document also stores the user's color value which helps distinguish shared documents from personal documents
static setupSharedDocs(doc: Doc, sharingDocumentId: string) {
- const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
- const dashboardFilter = ScriptField.MakeFunction(`doc._type_collection === '${CollectionViewType.Docking}'`, { doc: Doc.name });
const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}";
const sharedScripts = { treeViewChildDoubleClick: dblClkScript, }
@@ -786,11 +793,11 @@ export class CurrentUserUtils {
title: "My Shared Docs",
userColor: "rgb(202, 202, 202)",
isFolder:true, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
- childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
- childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
- childContextMenuLabels: new List<string>(["Add to Dashboards",]),
- childContextMenuIcons: new List<string>(["user-plus",]),
- "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
+ // 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,
@@ -798,6 +805,8 @@ export class CurrentUserUtils {
};
DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
+ if (!Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards) Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards = new List<Doc>();
+ console.log(doc.mySharedDocs);
}
/// Import option on the left side button panel
@@ -812,7 +821,7 @@ export class CurrentUserUtils {
const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
- _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
+ _width: 30, _height: 30, color: Colors.BLACK, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
buttonText: "Import", icon: "upload", isSystem: true };
DocUtils.AssignDocField(myImports, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
return myImports;
@@ -826,7 +835,7 @@ export class CurrentUserUtils {
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
+ SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
doc.isSystem ?? (doc.isSystem = true);
doc.title ?? (doc.title = Doc.CurrentUserEmail);
@@ -850,8 +859,14 @@ export class CurrentUserUtils {
doc.fontHighlight ?? (doc.fontHighlight = "");
doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
+ doc.userBackgroundColor ?? (doc.userBackgroundColor = Colors.DARK_GRAY);
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${doc.userBackgroundColor} !important` });
+ doc.userVariantColor ?? (doc.userVariantColor = Colors.MEDIUM_BLUE);
+ doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY);
+ doc.userTheme ?? (doc.userTheme = ColorScheme.Dark);
doc.filterDocCount = 0;
doc.treeViewFreezeChildren = "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
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3f0848d00..b921b3116 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -194,7 +194,7 @@ export class DocumentManager {
var containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
- containerDocContext[0]?.embedContainer &&
+ DocCast(containerDocContext[0]?.embedContainer) &&
DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking &&
(includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0]))
) {
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 47997cc5c..f235be192 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -9,7 +9,7 @@ import { RichTextField } from '../../fields/RichTextField';
import { ImageField } from '../../fields/URLField';
import { ScriptingGlobals } from './ScriptingGlobals';
import { listSpec } from '../../fields/Schema';
-import { ButtonType } from '../views/nodes/button/FontIconBox';
+import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox';
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.scss b/src/client/util/GroupManager.scss
index 9438bdd72..253ed5d2a 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,6 +1,7 @@
.group-interface {
width: 380px;
height: 300px;
+ position: relative;
.dialogue-box {
.group-create {
@@ -56,8 +57,9 @@
flex-direction: column;
.overlay {
- transform: translate(-20px, -20px);
- border-radius: 10px;
+ transform: translate(-10px, -10px);
+ width: 400px;
+ height: 320px;
}
.delete-button {
@@ -66,10 +68,8 @@
.close-button {
position: absolute;
- right: 1em;
- top: 1em;
- cursor: pointer;
- z-index: 999;
+ right: 2px;
+ top: 2px;
}
.group-heading {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index f74409e42..3e526c4c0 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -103,7 +103,7 @@ export class LinkFollower {
Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
movedTarget = true;
}
- target.embedContainer = sourceDocParent;
+ Doc.SetContainer(target, sourceDocParent);
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
target.x = moveTo[0];
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ce422f849..c7f092565 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -46,38 +46,41 @@ export class LinkManager {
LinkManager._instance = this;
this.createlink_relationshipLists();
LinkManager.userLinkDBs = [];
- const addLinkToDoc = (link: Doc) => {
- Promise.all([link]).then(linkdoc => {
- const link = DocCast(linkdoc[0]);
- Promise.all([link.proto]).then(linkproto => {
- Promise.all([link.link_anchor_1, link.link_anchor_2]).then(linkdata => {
- const a1 = DocCast(linkdata[0]);
- const a2 = DocCast(linkdata[1]);
- a1 &&
- a2 &&
- Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then(
- action(protos => {
- (protos[0] as Doc)?.[DirectLinks].add(link);
- (protos[1] as Doc)?.[DirectLinks].add(link);
+ // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
+ // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
+ // Then add the link to the anchor protos.
+ const addLinkToDoc = (lprom: Doc) =>
+ PromiseValue(lprom).then((link: Opt<Doc>) =>
+ PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) =>
+ 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);
})
- );
- });
- });
- });
- };
- const remLinkFromDoc = (link: Doc) => {
- const a1 = link?.link_anchor_1;
- const a2 = link?.link_anchor_2;
- Promise.all([a1, a2]).then(
- action(() => {
- if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
- Doc.GetProto(a1)[DirectLinks].delete(link);
- Doc.GetProto(a2)[DirectLinks].delete(link);
- Doc.GetProto(link)[DirectLinks].delete(link);
- }
- })
+ )
+ )
+ )
+ )
);
- };
+
+ const remLinkFromDoc = (lprom: Doc) =>
+ PromiseValue(lprom).then((link: Opt<Doc>) =>
+ PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) =>
+ 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].delete(link);
+ link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].delete(link);
+ })
+ )
+ )
+ )
+ )
+ );
+
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
LinkManager._links.push(...DocListCast(userLinkDBDoc.data));
const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
@@ -156,7 +159,7 @@ export class LinkManager {
return this.relatedLinker(anchor);
} // finds all links that contain the given anchor
public getAllDirectLinks(anchor: Doc): Doc[] {
- return Array.from(Doc.GetProto(anchor)[DirectLinks] ?? []);
+ return Array.from(Doc.GetProto(anchor)[DirectLinks]);
} // finds all links that contain the given anchor
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 247267710..b93d4f293 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -52,7 +52,7 @@ export class RTFMarkup extends React.Component<{}> {
{` add a sidebar text document inline`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`\`\` `}</b>
+ <b style={{ fontSize: 'larger' }}>{`\`\`\` `}</b>
{` create a code snippet block`}
</p>
<p>
diff --git a/src/client/util/ReportManager.tsx b/src/client/util/ReportManager.tsx
index 4c1020455..89c17e42f 100644
--- a/src/client/util/ReportManager.tsx
+++ b/src/client/util/ReportManager.tsx
@@ -11,7 +11,7 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
import { MainViewModal } from '../views/MainViewModal';
-import { FontIconBox } from '../views/nodes/button/FontIconBox';
+import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
import { DragManager } from './DragManager';
import { GroupManager } from './GroupManager';
import './SettingsManager.scss';
@@ -290,7 +290,7 @@ export class ReportManager extends React.Component<{}> {
isDisplayed={this.isOpen}
interactive={true}
closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
+ dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
/>
);
}
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 42a6493ea..87509f2ea 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -1,12 +1,11 @@
-import { Doc, DocListCast } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast, StrCast } from "../../fields/Types";
-import { Docs } from "../documents/Documents";
-import { ScriptingGlobals } from "./ScriptingGlobals";
+import { Doc, DocListCast } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast, StrCast } from '../../fields/Types';
+import { Docs } from '../documents/Documents';
+import { ScriptingGlobals } from './ScriptingGlobals';
export class ScriptManager {
-
static _initialized = false;
private static _instance: ScriptManager;
public static get Instance(): ScriptManager {
@@ -24,11 +23,7 @@ export class ScriptManager {
}
public getAllScripts(): Doc[] {
const sdoc = ScriptManager.Instance.ScriptManagerDoc;
- if (sdoc) {
- const docs = DocListCast(sdoc.data);
- return docs;
- }
- return [];
+ return sdoc ? DocListCast(sdoc.data) : [];
}
public addScript(scriptDoc: Doc): boolean {
@@ -59,36 +54,35 @@ export class ScriptManager {
}
public static addScriptToGlobals(scriptDoc: Doc): void {
-
ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name));
- const params = Cast(scriptDoc["data-params"], listSpec("string"), []);
+ const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
const paramNames = params.reduce((o: string, p: string) => {
if (params.indexOf(p) === params.length - 1) {
- o = o + p.split(":")[0].trim();
+ o = o + p.split(':')[0].trim();
} else {
- o = o + p.split(":")[0].trim() + ",";
+ o = o + p.split(':')[0].trim() + ',';
}
return o;
- }, "" as string);
+ }, '' as string);
const f = new Function(paramNames, StrCast(scriptDoc.script));
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
- let parameters = "(";
+ let parameters = '(';
params.forEach((element: string, i: number) => {
if (i === params.length - 1) {
- parameters = parameters + element + ")";
+ parameters = parameters + element + ')';
} else {
- parameters = parameters + element + ", ";
+ parameters = parameters + element + ', ';
}
});
- if (parameters === "(") {
+ if (parameters === '(') {
ScriptingGlobals.add(f, StrCast(scriptDoc.description));
} else {
ScriptingGlobals.add(f, StrCast(scriptDoc.description), parameters);
}
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index f84ad8598..b6aa4d95a 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
import './SharingManager.scss';
+import { PingManager } from './PingManager';
@observer
export class ServerStats extends React.Component<{}> {
@@ -39,11 +40,22 @@ export class ServerStats extends React.Component<{}> {
*/
@computed get sharingInterface() {
return (
- <div>
- <span>Active users:{this._stats?.socketMap.length}</span>
- {this._stats?.socketMap.map((user: any) => (
- <p>{user.username}</p>
- ))}
+ <div style={{
+ display: "flex",
+ height: 300,
+ width: 400,
+ background: "black",
+ 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.scss b/src/client/util/SettingsManager.scss
index 1289ca2b4..bca649bc3 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -2,41 +2,20 @@
.settings-interface {
//background-color: whitesmoke !important;
- color: grey;
width: 450px;
-
- button {
- background: #315a96;
- outline: none;
- border-radius: 5px;
- border: 0px;
- color: #fcfbf7;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- padding: 10px;
- margin: 10px;
- transition: transform 0.2s;
- margin: 2px;
- }
}
.settings-title {
font-size: 25px;
font-weight: bold;
padding-right: 10px;
- color: black;
}
.settings-username {
font-size: 12px;
padding-right: 15px;
- color: black;
margin-top: 10px;
margin-bottom: 10px;
- /* right: 135; */
- // position: absolute;
- // left: 243;
}
.settings-section {
@@ -49,7 +28,6 @@
font-size: 16;
font-weight: bold;
text-align: left;
- color: black;
width: 80;
margin-right: 50px;
}
@@ -61,40 +39,10 @@
.password-content {
display: flex;
+ width: 100%;
flex-direction: column;
-
- .password-content-inputs {
- width: 100;
- // margin-bottom: 10px;
- font-size: 10px;
-
- .password-inputs {
- border: 1px solid rgb(160, 160, 160);
- margin-bottom: 8px;
- width: 130;
- color: black;
- border-radius: 5px;
- padding: 7px;
- }
- }
-
- .password-content-buttons {
- //margin-left: 84px;
- //width: 100;
- padding: 7px;
-
- .password-submit {
- //margin-left: 85px;
- margin-top: 5px;
- }
-
- .password-forgot {
- //margin-left: 65px;
- //margin-top: -20px;
- font-size: 12px;
- white-space: nowrap;
- }
- }
+ align-items: flex-start;
+ gap: 5px;
}
.accounts-content {
@@ -103,7 +51,6 @@
.modes-content {
display: flex;
- margin-left: 10px;
font-size: 12px;
.modes-select {
@@ -111,7 +58,6 @@
width: 80%;
height: 35px;
margin-right: 10px;
- color: black;
border-radius: 5px;
&:hover {
@@ -135,12 +81,6 @@
}
}
- .playground-text {
- color: black;
- margin-right: 10px;
- margin-top: 2;
- }
-
.acl-text {
color: black;
margin-top: 2;
@@ -172,12 +112,11 @@
.appearances-content {
display: flex;
- margin-top: 4px;
- color: black;
font-size: 10px;
.preferences-color {
display: flex;
+ align-items: center;
margin-top: 2px;
.preferences-color-text {
@@ -197,7 +136,6 @@
margin-top: 2px;
.preferences-font-text {
- color: black;
margin-top: 4;
margin-right: 4;
margin-bottom: 2px;
@@ -212,7 +150,6 @@
.font-select {
height: 35px;
- color: black;
font-size: 9;
margin-right: 6;
border-radius: 5px;
@@ -225,7 +162,6 @@
.size-select {
height: 35px;
- color: black;
font-size: 9;
border-radius: 5px;
width: 30%;
@@ -237,7 +173,6 @@
}
.preferences-check {
- color: black;
margin-right: 4;
margin-bottom: -3;
margin-left: 5;
@@ -252,40 +187,17 @@
display: flex;
flex-direction: column;
- button {
- width: auto;
- align-self: center;
- background: #252b33;
- margin-right: 15px;
-
- //margin-top: 4px;
-
- &:hover {
- background: $medium-gray;
- }
- }
-
- // .delete-button {
- // background: rgb(227, 86, 86);
- // }
.close-button {
position: absolute;
- right: 1em;
- top: 1em;
- cursor: pointer;
+ right: 2px;
+ top: 2px;
}
- // .logout-button {
- // right: 355;
- // position: absolute;
- // }
.settings-content {
- background: #e4e4e4;
- //border-radius: 6px;
padding: 10px;
- //width: 560px;
+ width: 500px;
flex: 1 1 auto;
}
@@ -296,11 +208,8 @@
.error-text {
color: #c40233;
- width: 300;
- margin-left: -20;
font-size: 10;
margin-bottom: 4;
- margin-top: -3;
}
.success-text {
@@ -317,7 +226,6 @@
}
h1 {
- color: #121721;
text-transform: uppercase;
letter-spacing: 2px;
font-size: 19;
@@ -346,7 +254,6 @@
.padding {
padding: 0 0 0 20px;
- color: black;
}
}
}
@@ -357,21 +264,19 @@
min-height: 250px;
height: 100%;
width: 100%;
-
- .settings-content {
- background-color: $off-white;
- }
}
.settings-panel {
position: relative;
min-width: 150px;
- background-color: $light-blue;
.settings-user {
position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
bottom: 10px;
- text-align: center;
left: 0;
right: 0;
@@ -388,16 +293,11 @@
.settings-tabs {
// font-size: 16px;
font-weight: 600;
- color: black;
.tab-control {
padding: 10px;
border-bottom: 1px solid #9f9e9e;
cursor: pointer;
-
- &.active {
- background-color: #fdfdfd;
- }
}
}
@@ -416,20 +316,31 @@
.tab-content {
display: flex;
+ flex-flow: row wrap;
+ gap: 10px;
+ overflow: hidden;
.tab-column {
- flex: 0 0 50%;
+ flex: 0 0 calc(50% - 10px);
+ flex-direction: column;
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
.tab-column-title {
- color: black;
font-size: 16px;
font-weight: bold;
- margin-bottom: 16px;
+ margin-bottom: 10px;
}
.tab-column-title,
.tab-column-content {
- padding-left: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 10px;
+ width: 100%;
}
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f886ce2ca..b6df5f26a 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -11,19 +11,23 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
import { MainViewModal } from '../views/MainViewModal';
-import { FontIconBox } from '../views/nodes/button/FontIconBox';
+import { 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',
- Light = '-Light',
- System = '-MatchSystem',
+ Dark = 'Dark',
+ Light = 'Light',
+ System = 'Match System',
+ Custom = 'Custom'
}
export enum freeformScrollMode {
@@ -50,8 +54,8 @@ export class SettingsManager extends React.Component<{}> {
@computed get backgroundColor() {
return Doc.UserDoc().activeCollectionBackground;
}
- @computed get colorScheme() {
- return Doc.ActiveDashboard?.colorScheme;
+ @computed get userTheme() {
+ return Doc.UserDoc().userTheme;
}
constructor(props: {}) {
@@ -73,14 +77,31 @@ export class SettingsManager extends React.Component<{}> {
}
};
- @undoBatch selectUserMode = action((e: React.ChangeEvent) => (Doc.noviceMode = (e.currentTarget as any)?.value === 'Novice'));
+ @computed get userColor() {
+ return StrCast(Doc.UserDoc().userColor)
+ }
+
+ @computed get userVariantColor() {
+ return StrCast(Doc.UserDoc().userVariantColor)
+ }
+
+ @computed get userBackgroundColor() {
+ return StrCast(Doc.UserDoc().userBackgroundColor)
+ }
+
+ @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice'));
@undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_showTitle = (e.currentTarget as any).value ? 'title' : undefined));
- @undoBatch changeFontFamily = action((e: React.ChangeEvent) => (Doc.UserDoc().fontFamily = (e.currentTarget as any).value));
- @undoBatch changeFontSize = action((e: React.ChangeEvent) => (Doc.UserDoc().fontSize = (e.currentTarget as any).value));
- @undoBatch switchActiveBackgroundColor = action((color: ColorState) => (Doc.UserDoc().activeCollectionBackground = String(color.hex)));
- @undoBatch switchUserColor = action((color: ColorState) => {
- Doc.SharingDoc().userColor = undefined;
- Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex);
+ @undoBatch changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font));
+ @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val));
+ @undoBatch switchUserBackgroundColor = action((color: string) => {
+ 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 playgroundModeToggle = action(() => {
this.playgroundMode = !this.playgroundMode;
@@ -92,82 +113,59 @@ export class SettingsManager extends React.Component<{}> {
@undoBatch
@action
- changeColorScheme = action((e: React.ChangeEvent) => {
- const activeDashboard = Doc.ActiveDashboard;
- if (!activeDashboard) return;
- const scheme: ColorScheme = (e.currentTarget as any).value;
+ changeColorScheme = action((scheme: string) => {
+ Doc.UserDoc().userTheme = scheme;
switch (scheme) {
case ColorScheme.Light:
- activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: '#d3d3d3 !important' });
+ this.switchUserColor("#323232")
+ this.switchUserBackgroundColor("#DFDFDF")
+ this.switchUserVariantColor("#BDDDF5")
break;
case ColorScheme.Dark:
- activeDashboard.colorScheme = ColorScheme.Dark;
- addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' });
+ this.switchUserColor("#DFDFDF")
+ this.switchUserBackgroundColor("#323232")
+ this.switchUserVariantColor("#4476F7")
+ break;
+ case ColorScheme.Custom:
break;
case ColorScheme.System:
default:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
- activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+ e.matches ? ColorScheme.Dark : ColorScheme.Light; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
});
break;
}
});
@computed get colorsContent() {
- const colorBox = (func: (color: ColorState) => void) => (
- <SketchPicker
- onChange={func}
- color={StrCast(this.backgroundColor)}
- presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
- />
- );
-
- const colorFlyout = (
- <div className="colorFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchActiveBackgroundColor)}>
- <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
- </div>
- </Flyout>
- </div>
- );
- const userColorFlyout = (
- <div className="colorFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={colorBox(this.switchUserColor)}>
- <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} />
- </div>
- </Flyout>
- </div>
- );
- const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
- const schemeMap = ['Light', 'Dark', 'Match system'];
+ const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.Custom, ColorScheme.System];
+ const schemeMap = ['Light', 'Dark', 'Custom', 'Match System'];
+ const userTheme = StrCast(Doc.UserDoc().userTheme);
return (
- <div className="colors-content">
- <div className="preferences-color">
- <div className="preferences-color-text">Background Color</div>
- {colorFlyout}
- </div>
- <div className="preferences-color">
- <div className="preferences-color-text">Border/Header Color</div>
- {userColorFlyout}
- </div>
- <div className="preferences-colorScheme">
- <div className="preferences-color-text">Color Scheme</div>
- <div className="preferences-color-controls">
- <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.ActiveDashboard?.colorScheme)}>
- {colorSchemes.map((scheme, i) => (
- <option key={scheme} value={scheme}>
- {' '}
- {schemeMap[i]}{' '}
- </option>
- ))}
- </select>
- </div>
- </div>
+ <div style={{width: '100%'}}>
+ <Dropdown
+ formLabel='Theme'
+ size={Size.SMALL}
+ type={Type.TERT}
+ selectedVal={userTheme}
+ setSelectedVal={(scheme) => this.changeColorScheme(scheme as string)}
+ items={colorSchemes.map((scheme, i) => (
+ {
+ text: schemeMap[i],
+ val: scheme
+ }
+ ))}
+ dropdownType={DropdownType.SELECT}
+ color={this.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}/>
+ </Group>}
</div>
);
}
@@ -175,30 +173,65 @@ export class SettingsManager extends React.Component<{}> {
@computed get formatsContent() {
return (
<div className="prefs-content">
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} checked={Doc.UserDoc().layout_showTitle !== undefined} />
- <div className="preferences-check">Show doc header</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} checked={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} />
- <div className="preferences-check">Show full toolbar</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} />
- <div className="preferences-check">Show button labels</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} checked={FontIconBox.GetRecognizeGestures()} />
- <div className="preferences-check">Recognize ink Gestures</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} checked={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} />
- <div className="preferences-check">Hide Labels In Ink Shapes</div>
- </div>
- <div>
- <input type="checkbox" onChange={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} checked={BoolCast(Doc.UserDoc().openInkInLightbox)} />
- <div className="preferences-check">Open Ink Docs in Lightbox</div>
- </div>
+ <Toggle
+ formLabel={'Show document header'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ 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}
+
+ />
+ <Toggle
+ formLabel={'Show Full Toolbar'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])}
+ toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])}
+ size={Size.XSMALL}
+ color={this.userColor}
+
+ />
+ <Toggle
+ formLabel={'Show Button Labels'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())}
+ toggleStatus={FontIconBox.GetShowLabels()}
+ size={Size.XSMALL}
+ color={this.userColor}
+
+ />
+ <Toggle
+ formLabel={'Recognize Ink Gestures'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())}
+ toggleStatus={FontIconBox.GetRecognizeGestures()}
+ size={Size.XSMALL}
+ color={this.userColor}
+
+ />
+ <Toggle
+ formLabel={'Hide Labels In Ink Shapes'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
+ toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
+ size={Size.XSMALL}
+ color={this.userColor}
+
+ />
+ <Toggle
+ formLabel={'Open Ink Docs in Lightbox'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
+ toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
+ size={Size.XSMALL}
+ color={this.userColor}
+
+ />
</div>
);
}
@@ -224,25 +257,38 @@ export class SettingsManager extends React.Component<{}> {
return (
<div className="tab-content appearances-content">
- <div className="preferences-font">
- <div className="preferences-font-text">Default Font</div>
- <div className="preferences-font-controls">
- <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, '7px')}>
- {fontSizes.map(size => (
- <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}>
- {' '}
- {size}{' '}
- </option>
- ))}
- </select>
- <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, 'Times New Roman')}>
- {fontFamilies.map(font => (
- <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}>
- {' '}
- {font}{' '}
- </option>
- ))}
- </select>
+ <div className="tab-column">
+ <div className="tab-column-title">Text</div>
+ <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={() => {}}
+ />
+ <Dropdown
+ items={fontFamilies.map((val) => {
+ return {
+ text: val,
+ val: val,
+ style: {
+ fontFamily: val
+ }
+ }
+ })}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ selectedVal={StrCast(Doc.UserDoc().fontFamily)}
+ setSelectedVal={(val) => {this.changeFontFamily(val as string)}}
+ color={this.userColor}
+ fillWidth
+ />
+ </Group>
</div>
</div>
</div>
@@ -250,8 +296,7 @@ export class SettingsManager extends React.Component<{}> {
}
@action
- changeVal = (e: React.ChangeEvent, pass: string) => {
- const value = (e.target as any).value;
+ changeVal = (value: string, pass: string) => {
switch (pass) {
case 'curr':
this.curr_password = value;
@@ -268,20 +313,33 @@ export class SettingsManager extends React.Component<{}> {
@computed get passwordContent() {
return (
<div className="password-content">
- <div className="password-content-inputs">
- <input className="password-inputs" type="password" placeholder="current password" onChange={e => this.changeVal(e, 'curr')} />
- <input className="password-inputs" type="password" placeholder="new password" onChange={e => this.changeVal(e, 'new')} />
- <input className="password-inputs" type="password" placeholder="confirm new password" onChange={e => this.changeVal(e, 'conf')} />
- </div>
- <div className="password-content-buttons">
- {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
- <a className="password-forgot" href="/forgotPassword">
- forgot password?
- </a>
- <button className="password-submit" onClick={this.changePassword}>
- submit
- </button>
- </div>
+ <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
+ />
+ {!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}/>
</div>
);
}
@@ -289,9 +347,7 @@ export class SettingsManager extends React.Component<{}> {
@computed get accountOthersContent() {
return (
<div className="account-others-content">
- <button onClick={this.googleAuthorize} value="data">
- Authorize Google Acc
- </button>
+ <Button type={Type.TERT} text={'Connect to Google'} iconPlacement='left' icon={<BsGoogle/>} onClick={() => this.googleAuthorize()}/>
</div>
);
}
@@ -311,7 +367,7 @@ export class SettingsManager extends React.Component<{}> {
);
}
- setFreeformScrollMode = (mode: freeformScrollMode) => {
+ setFreeformScrollMode = (mode: string) => {
Doc.UserDoc().freeformScrollMode = mode;
};
@@ -321,45 +377,78 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column">
<div className="tab-column-title">Modes</div>
<div className="tab-column-content">
- <select className="modes-select" onChange={this.selectUserMode} defaultValue={Doc.noviceMode ? 'Novice' : 'Developer'}>
- <option key={'Novice'} value={'Novice'}>
- {' '}
- Novice{' '}
- </option>
- <option key={'Developer'} value={'Developer'}>
- {' '}
- Developer
- </option>
- </select>
- <div className="modes-playground">
- <input className="playground-check" type="checkbox" checked={this.playgroundMode} onChange={this.playgroundModeToggle} />
- <div className="playground-text">Playground Mode</div>
- </div>
+ <Dropdown
+ formLabel={"Mode"}
+ items={[
+ {
+ text: 'Novice',
+ description: 'Novice mode is a user-friendly setting designed to cater to those who are new to Dash',
+ val: "Novice"
+ },
+ {
+ text: 'Developer',
+ description: 'Developer mode is an advanced setting that grants you greater control and access to the underlying mechanics and tools of a software or system. Developer mode is still under development as there are experimental features.',
+ val: "Developer"
+ },
+ ]}
+ selectedVal={Doc.noviceMode ? 'Novice' : 'Developer'}
+ setSelectedVal={(val) => {this.selectUserMode(val as string)}}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ placement='bottom-start'
+ color={this.userColor}
+ fillWidth
+ />
+ <Toggle
+ formLabel={'Playground Mode'}
+ toggleType={ToggleType.SWITCH}
+ toggleStatus={this.playgroundMode}
+ onClick={this.playgroundModeToggle}
+ color={this.userColor}
+ />
</div>
- <div className="tab-column-title" style={{ marginTop: 10, marginBottom: 0 }}>
- Freeform scrolling
+ <div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}>
+ Freeform Navigation
</div>
<div className="tab-column-content">
- <button style={{ backgroundColor: Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? 'blue' : '' }} onClick={() => this.setFreeformScrollMode(freeformScrollMode.Pan)}>
- Scroll to pan
- </button>
- <div>
- <div>Scrolling pans canvas, shift + scrolling zooms</div>
- </div>
- <button style={{ backgroundColor: Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom ? 'blue' : '' }} onClick={() => this.setFreeformScrollMode(freeformScrollMode.Zoom)}>
- Scroll to zoom
- </button>
- <div>Scrolling zooms canvas</div>
+ <Dropdown
+ formLabel={"Scroll Mode"}
+ items={[
+ {
+ text: 'Scroll to Pan',
+ description: 'Scrolling pans canvas, shift + scrolling zooms',
+ val: freeformScrollMode.Pan
+ },
+ {
+ text: 'Scroll to Zoom',
+ description: 'Scrolling zooms canvas',
+ val: freeformScrollMode.Zoom
+ },
+ ]}
+ selectedVal={StrCast(Doc.UserDoc().freeformScrollMode)}
+ setSelectedVal={(val) => this.setFreeformScrollMode(val as string)}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ placement='bottom-start'
+ color={this.userColor}
+ />
</div>
</div>
<div className="tab-column">
<div className="tab-column-title">Permissions</div>
<div className="tab-column-content">
- <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
- <div className="default-acl">
- <input className="acl-check" type="checkbox" checked={BoolCast(Doc.defaultAclPrivate)} onChange={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} />
- <div className="acl-text">Default access private</div>
- </div>
+ <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))}/>
</div>
</div>
</div>
@@ -376,31 +465,50 @@ export class SettingsManager extends React.Component<{}> {
{ title: 'Appearance', ele: this.appearanceContent },
{ title: 'Text', ele: this.textContent },
];
-
return (
<div className="settings-interface">
- <div className="settings-panel">
+ <div className="settings-panel" style={{ background: this.userColor }}>
<div className="settings-tabs">
- {tabs.map(tab => (
- <div key={tab.title} className={'tab-control ' + (this.activeTab === tab.title ? 'active' : 'inactive')} onClick={action(() => (this.activeTab = tab.title))}>
- {tab.title}
- </div>
- ))}
+ {tabs.map(tab => {
+ const isActive = this.activeTab === tab.title
+ return (
+ <div key={tab.title}
+ style={{
+ background: isActive ? this.userBackgroundColor : this.userColor,
+ color: isActive ? this.userColor : this.userBackgroundColor,
+ }}
+ className={'tab-control ' + (isActive ? 'active' : 'inactive')}
+ onClick={action(() => (this.activeTab = tab.title))
+ }>
+ {tab.title}
+ </div>
+ )
+ })}
</div>
<div className="settings-user">
- <div className="settings-username">{Doc.CurrentUserEmail}</div>
- <button className="logout-button" onClick={() => window.location.assign(Utils.prepend('/logout'))}>
- {Doc.GuestDashboard ? 'Exit' : 'Log Out'}
- </button>
+ <div className="settings-username"
+ style={{color: this.userBackgroundColor}}
+ >{Doc.CurrentUserEmail}</div>
+ <Button
+ text={Doc.GuestDashboard ? 'Exit' : 'Log Out'}
+ type={Type.TERT}
+ color={this.userVariantColor}
+ onClick={() => window.location.assign(Utils.prepend('/logout'))}
+ />
</div>
</div>
- <div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+
+ <div className="close-button">
+ <Button
+ icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
+ onClick={this.close}
+ color={this.userColor}
+ />
</div>
- <div className="settings-content">
+ <div className="settings-content" style={{color: this.userColor, background: this.userBackgroundColor}}>
{tabs.map(tab => (
<div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
{tab.ele}
@@ -418,7 +526,7 @@ export class SettingsManager extends React.Component<{}> {
isDisplayed={this.isOpen}
interactive={true}
closeOnExternalClick={this.close}
- dialogueBoxStyle={{ width: '500px', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }}
+ dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
/>
);
}
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 932e94664..b11e694ff 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -6,24 +6,46 @@
transform: translate(-20px, -20px);
}
- select {
+ .select {
text-align: justify;
text-align-last: end
}
.sharing-contents {
+ padding: 10px;
display: flex;
flex-direction: column;
.close-button {
position: absolute;
- right: 1em;
- top: 1em;
- cursor: pointer;
- z-index: 999;
+ right: 2px;
+ top: 2px;
+ }
+
+ .share-title {
+ display: inline-flex;
+ gap: 5px;
+
+ .share-info {
+ align-self: center;
+ cursor: pointer;
+ }
+ }
+
+ .share-copy-link {
+ display: inline;
+ border-radius: 4px;
+ border: solid gray 1px;
+ font-size: x-small;
+ background: #E8E8E8;
+ color: black;
+ margin-top: -15px;
+ margin-bottom: 15px;
+ width: fit-content;
}
.share-container {
+
.share-setup {
display: flex;
margin-bottom: 20px;
@@ -44,11 +66,15 @@
outline: none;
text-align: justify; // for Edge
text-align-last: end;
+ font-size: 13px;
+ min-width: 90px;
+ height: 36;
+ margin-left: 2px;
}
.share-button {
- height: 105%;
- margin-left: 2%;
+ height: 36;
+ margin-left: 3%;
background-color: black;
}
}
@@ -76,15 +102,16 @@
float: right;
align-items: baseline;
margin-top: -12;
+ margin-bottom: 10;
.layoutDoc-acls,
.myDocs-acls {
flex-direction: column;
- margin-right: 12;
label {
font-weight: normal;
font-style: italic;
+ padding-right: 12;
}
input {
@@ -102,6 +129,7 @@
.group-container {
width: 50%;
display: flex;
+ top:0;
flex-direction: column;
.user-sort {
@@ -120,9 +148,10 @@
.users-list {
font-style: italic;
background: #e8e8e8;
+ border: 2px solid gray;
padding-left: 10px;
padding-right: 10px;
- width: 100%;
+ width: 97%;
overflow-y: scroll;
overflow-x: hidden;
text-align: left;
@@ -190,53 +219,149 @@
}
}
+ .title-individual{
+ height: 25px;
+ padding-left: 2;
+ width: 97%;
+ margin-top: 10px;
+ margin-left: -8px;
+ font-size: 14;
+ margin-bottom: -4;
+ border: 2px solid gray;
+ border-bottom: none;
+ align-items: center;
+ display: flex;
+ }
+
+ .title-group{
+ height: 25px;
+ padding-left: 2;
+ width: 97%;
+ margin-top: 10px;
+ margin-left: -.5px;
+ font-size: 14;
+ margin-bottom: -4;
+ border: 2px solid gray;
+ border-bottom: none;
+ align-items: center;
+ display: flex;
+ }
+
.container {
display: flex;
position: relative;
margin-top: 5px;
- margin-bottom: 10px;
+ margin-left: -5px;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- width: 100%;
+ width: 97%;
text-align: left;
font-style: normal;
- font-size: 14;
+ font-size: 12.5;
font-weight: normal;
- padding: 0;
- align-items: center;
+
+ padding: 3px;
+ border-bottom: 0.5px solid grey;
.group-info {
cursor: pointer;
}
&:hover .padding {
+ overflow-x: unset;
white-space: unset;
+ overflow-wrap: break-word;
}
.padding {
- padding: 0 10px 0 0;
- color: black;
+ max-width: 150px;
+ overflow-x: hidden;
+ display: inline-block;
text-overflow: ellipsis;
- overflow: hidden;
white-space: nowrap;
- max-width: 40%;
}
.permissions-dropdown {
- border: none;
- height: 25;
- background-color: #e8e8e8;
+ display: flex;
+ align-items: flex-end;
+ text-align: right;
+ margin-left: auto;
+ margin-right: -12px;
+
}
.edit-actions {
display: flex;
position: absolute;
- right: -10;
+ align-items: flex-end;
+ right: -10;
}
+ }
+ .permissions-dropdown-None{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 6px;
+ border: 1px solid rgb(71, 71, 71);
+ }
+ .permissions-dropdown-Edit,
+ .permissions-dropdown-Admin {
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 6px;
+ border: 1px solid rgb(75, 75, 5);
+ }
+ .permissions-dropdown-Augment{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(208, 255, 208);
+ color:rgb(19, 80, 19);
+ border-radius: 6px;
+ border: 1px solid rgb(19, 80, 19);
+
+ }
+ .permissions-dropdown-View{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 6px;
+ border: 1px solid rgb(25, 25, 101);
+ }
+ .permissions-dropdown-Not-Shared{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(255, 207, 207);
+ color: rgb(138, 47, 47);
+ border-radius: 6px;
+ border: 1px solid rgb(138, 47, 47);
}
.no-users {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 97e64ab71..33c9992d0 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,15 +1,15 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { intersection } from 'lodash';
+import { Colors } from 'browndash-components';
+import { concat, intersection } from 'lodash';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
-import { Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
-import { AclAdmin, AclPrivate, DocAcl, AclUnset, DocData } from '../../fields/DocSymbols';
+import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
-import { List } from '../../fields/List';
-import { NumCast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
import { Utils } from '../../Utils';
import { DocServer } from '../DocServer';
@@ -21,8 +21,10 @@ import { SearchBox } from '../views/search/SearchBox';
import { DocumentManager } from './DocumentManager';
import { GroupManager, UserOptions } from './GroupManager';
import { GroupMemberView } from './GroupMemberView';
+import { LinkManager } from './LinkManager';
import { SelectionManager } from './SelectionManager';
import './SharingManager.scss';
+import { undoable } from './UndoManager';
export interface User {
email: string;
@@ -47,6 +49,7 @@ const indType = '!indType/';
const groupType = '!groupType/';
const storage = 'data';
+const dashStorage = 'data_dashboards';
/**
* A user who also has a sharing doc.
@@ -73,13 +76,14 @@ export class SharingManager extends React.Component<{}> {
@observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
@observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
- private distributeAclsButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the distribute button, used for the position of the popup
// if both showUserOptions and showGroupOptions are false then both are displayed
@observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component)
@observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
private populating: boolean = false; // whether the list of users is populating or not
+ @observable private overrideNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is override
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
+ @observable private _buttonDown = false;
// private get linkVisible() {
// return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
@@ -93,6 +97,7 @@ export class SharingManager extends React.Component<{}> {
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
this.permissions = SharingPermissions.Augment;
+ this.overrideNested = true;
});
};
@@ -108,6 +113,7 @@ export class SharingManager extends React.Component<{}> {
}),
500
);
+ this.layoutDocAcls = false;
});
constructor(props: {}) {
@@ -138,7 +144,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(sharer.linkDatabase);
+ LinkManager.addLinkDB(linkDatabase);
}
}
})
@@ -150,79 +156,44 @@ export class SharingManager extends React.Component<{}> {
/**
* Shares the document with a user.
*/
- setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {
+ setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
const { user, sharingDoc } = recipient;
const target = targetDoc || this.targetDoc!;
const acl = `acl-${normalizeEmail(user.email)}`;
- const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
- const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
-
- const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
- return !docs
- .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
- .map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
-
- if (permission === SharingPermissions.None) {
- if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
- } else {
- if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
- }
-
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
-
- this.setDashboardBackground(doc, permission as SharingPermissions);
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc);
- return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc);
- })
- .some(success => !success);
- };
+ const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined);
+ if (permission !== SharingPermissions.None) {
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
+ } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
+ });
+ }, 'set Doc permissions');
/**
* Sets the permission on the target for the group.
* @param group
* @param permission
*/
- setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
+ setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
const target = targetDoc || this.targetDoc!;
- const key = normalizeEmail(StrCast(group.title));
- const acl = `acl-${key}`;
- const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1;
-
- const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
-
- // ! ensures it returns true if document has been shared successfully, false otherwise
- return !docs
- .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
- .map(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
-
- if (permission === SharingPermissions.None) {
- if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
- } else {
- if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
- }
+ const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
+ const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined);
- if (group instanceof Doc) {
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+ if (group instanceof Doc) {
+ Doc.AddDocToList(group, 'docsShared', doc);
- // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc
- group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : (group.docsShared = new List<Doc>([doc]));
-
- return users
- .map(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
- })
- .some(success => !success);
- }
- })
- .some(success => success);
- };
+ this.users
+ .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
+ .forEach(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
+ });
+ }
+ });
+ }, 'set group permissions');
/**
* Shares the documents shared with a group with a new user who has been added to that group.
@@ -237,7 +208,13 @@ export class SharingManager extends React.Component<{}> {
else {
DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => !userdocs?.includes(doc));
+ const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
filtered && userdocs?.push(...filtered);
})
);
@@ -248,44 +225,23 @@ export class SharingManager extends React.Component<{}> {
/**
* Called from the properties sidebar to change permissions of a user.
*/
- shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
- if (shareWith !== 'Public' && shareWith !== 'Override') {
+ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
+ if (layout) this.layoutDocAcls = true;
+ if (shareWith !== 'Guest') {
const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
docs.forEach(doc => {
if (user) this.setInternalSharing(user, permission, doc);
- else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc);
+ else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
});
} else {
- const dashboards = DocListCast(Doc.MyDashboards.data);
docs.forEach(doc => {
- const isDashboard = dashboards.indexOf(doc) !== -1;
- if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
- this.setDashboardBackground(doc, permission as SharingPermissions);
- });
- }
- };
-
- /**
- * Sets the background of the Dashboard if it has been shared as a visual indicator
- */
- setDashboardBackground = (doc: Doc, permission: SharingPermissions) => {
- if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) {
- if (permission !== SharingPermissions.None) {
- doc.isShared = true;
- doc.backgroundColor = 'green';
- } else {
- const acls = doc[DocData][DocAcl];
- if (
- Object.keys(acls)
- .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && key !== 'acl-Me')
- .every(key => [AclUnset, AclPrivate].includes(acls[key]))
- ) {
- doc.isShared = undefined;
- doc.backgroundColor = undefined;
+ if (GetEffectiveAcl(doc) === AclAdmin) {
+ distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
}
- }
+ });
}
- };
+ this.layoutDocAcls = false;
+ }, 'sidebar set permissions');
/**
* Removes the documents shared with a user through a group when the user is removed from the group.
@@ -295,13 +251,19 @@ export class SharingManager extends React.Component<{}> {
removeMember = (group: Doc, emailId: string) => {
const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
- if (group.docsShared) {
+ if (group.docsShared && user) {
DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
DocListCastAsync(group.docsShared).then(dl => {
const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
userdocs?.splice(0, userdocs.length, ...remaining);
})
);
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
}
};
@@ -311,11 +273,9 @@ export class SharingManager extends React.Component<{}> {
*/
removeGroup = (group: Doc) => {
if (group.docsShared) {
- const dashboards = DocListCast(Doc.MyDashboards.data);
DocListCast(group.docsShared).forEach(doc => {
const acl = `acl-${StrCast(group.title)}`;
- const isDashboard = dashboards.indexOf(doc) !== -1;
- distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined, isDashboard);
+ distributeAcls(acl, SharingPermissions.None, doc);
const members: string[] = JSON.parse(StrCast(group.members));
const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -331,37 +291,26 @@ export class SharingManager extends React.Component<{}> {
// return;
// }
// targetDoc["acl-" + PublicKey] = permission;
- // }
+ // }s
- // private get sharingUrl() {
- // if (!this.targetDoc) {
- // return undefined;
- // }
- // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
- // return `${baseUrl}?sharing=true`;
- // }
-
- // copy = action(() => {
- // if (this.sharingUrl) {
- // Utils.CopyText(this.sharingUrl);
- // this.copied = true;
- // }
- // });
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = (e: any) => {
+ Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
+ };
/**
* Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
*/
- private sharingOptions(uniform: boolean, override?: boolean) {
- const dropdownValues: string[] = Object.values(SharingPermissions);
+ private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
if (!uniform) dropdownValues.unshift('-multiple-');
- if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
- return dropdownValues
- .filter(permission => !Doc.noviceMode || ![SharingPermissions.SelfEdit].includes(permission as any))
- .map(permission => (
- <option key={permission} value={permission}>
- {permission}
- </option>
- ));
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
}
private focusOn = (contents: string) => {
@@ -406,38 +355,43 @@ export class SharingManager extends React.Component<{}> {
/**
* Handles changes in the permission chosen to share with someone with
*/
- @action
- handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
- this.permissions = event.currentTarget.value as SharingPermissions;
- };
+ handlePermissionsChange = undoable(
+ action((event: React.ChangeEvent<HTMLSelectElement>) => {
+ this.permissions = event.currentTarget.value as SharingPermissions;
+ }),
+ 'permission change'
+ );
/**
* Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
*/
- @action
- share = () => {
- if (this.selectedUsers) {
- this.selectedUsers.forEach(user => {
- if (user.value.includes(indType)) {
- this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
- } else {
- this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
- }
- });
-
- 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
- );
+ share = undoable(
+ action(() => {
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
+ } else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ 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.selectedUsers = null;
- }
- };
+ this.layoutDocAcls = false;
+ this.selectedUsers = null;
+ }
+ }),
+ 'share Doc'
+ );
/**
* Sorting algorithm to sort users.
@@ -464,6 +418,7 @@ export class SharingManager extends React.Component<{}> {
if (!this.targetDoc) return null;
TraceMobx();
const groupList = GroupManager.Instance?.allGroups || [];
+
const sortedUsers = this.users
.slice()
.sort(this.sortUsers)
@@ -485,8 +440,7 @@ export class SharingManager extends React.Component<{}> {
const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
- // handles the case where multiple documents are selected
- let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DocData]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DocData]));
+ let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
if (this.myDocAcls) {
const newDocs: Doc[] = [];
@@ -501,26 +455,32 @@ export class SharingManager extends React.Component<{}> {
const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
// users in common between all docs
- const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl])));
+ const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]);
// the list of users shared with
- const userListContents: (JSX.Element | null)[] = users
- .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ const userListContents = users
+ // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ .filter(({ user }) => docs[0]?.author !== user.email)
.map(({ user, linkDatabase, sharingDoc, userColor }) => {
const userKey = `acl-${normalizeEmail(user.email)}`;
- const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
- const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+ const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
+ // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+ let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
+ permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
return !permissions ? null : (
<div key={userKey} className={'container'}>
<span className={'padding'}>{user.email}</span>
<div className="edit-actions">
{admin || this.myDocAcls ? (
- <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}>
+ <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
{this.sharingOptions(uniform)}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -531,6 +491,9 @@ export class SharingManager extends React.Component<{}> {
const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
// the owner of the doc and the current user are placed at the top of the user list.
+ const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
+ const curUserPermission = StrCast(targetDoc[userKey]);
+ // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
<div key={'owner'} className={'container'}>
@@ -544,7 +507,10 @@ export class SharingManager extends React.Component<{}> {
<div key={'me'} className={'container'}>
<span className={'padding'}>Me</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-'}</div>
+ <div className={`permissions-dropdown-${curUserPermission}`}>
+ {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
+ &nbsp;
+ </div>
</div>
</div>
) : null
@@ -552,29 +518,31 @@ export class SharingManager extends React.Component<{}> {
// the list of groups shared with
const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
- groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" });
+ groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- const groupKey = `acl-${StrCast(group.title)}`;
- const uniform = docs
- .map(doc => (this.layoutDocAcls ? doc : doc[DocData]))
- .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey]));
- const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-';
+ let groupKey = `acl-${StrCast(group.title)}`;
+ const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
+ const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
<div key={groupKey} className={'container'}>
<div className={'padding'}>{StrCast(group.title)}</div>
+ &nbsp;
{group instanceof Doc ? (
<div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}>
<FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
</div>
) : null}
- <div className="edit-actions">
+ <div className={'edit-actions'}>
{admin || this.myDocAcls ? (
- <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
- {this.sharingOptions(uniform, group.title === 'Override')}
+ <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+ {this.sharingOptions(uniform, group.title === 'Guest')}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -583,20 +551,32 @@ export class SharingManager extends React.Component<{}> {
return (
<div className="sharing-interface">
{GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
- <div className="sharing-contents">
- <p className={'share-title'}>
+ <div className="sharing-contents"
+ style={{
+ background: StrCast(Doc.UserDoc().userBackgroundColor),
+ color: StrCast(Doc.UserDoc().userColor)
+ }}
+ >
+ <p className="share-title">
+ <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')}>
+ <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} />
+ </div>
<b>Share </b>
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
- <div className={'close-button'} onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
+ <button
+ className="share-copy-link"
+ style={{ background: this._buttonDown ? Colors.LIGHT_BLUE : undefined }}
+ onPointerDown={action(e => (this._buttonDown = true))}
+ onPointerUp={action(e => (this._buttonDown = false))}
+ onClick={this.copyURL}>
+ <FontAwesomeIcon title="Copy Public URL" icon="copy" size="sm" />
+ &nbsp; Copy Public URL
+ </button>
+ <div className="close-button" onClick={this.close}>
+ <FontAwesomeIcon icon="times" color="black" size="lg" />
</div>
- {/* {this.linkVisible ?
- <div>
- {this.sharingUrl}
- </div> :
- (null)} */}
- {
+ {admin ? (
<div className="share-container">
<div className="share-setup">
<Select
@@ -615,9 +595,11 @@ export class SharingManager extends React.Component<{}> {
}),
}}
/>
- <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
- {this.sharingOptions(true)}
- </select>
+ <div className="permissions-select">
+ <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
+ {this.sharingOptions(true)}
+ </select>
+ </div>
<button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
Share
</button>
@@ -630,36 +612,41 @@ export class SharingManager extends React.Component<{}> {
<div className="acl-container">
{Doc.noviceMode ? null : (
<div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} /> <label>Override Nested </label>
<input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
</div>
)}
</div>
</div>
- }
+ ) : (
+ <div className="share-container">
+ <div className="acl-container">
+ <div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ </div>
+ </div>
+ </div>
+ )}
<div className="main-container">
<div className={'individual-container'}>
<div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
- Individuals{' '}
- {this.individualSort === 'ascending' ? (
- <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
- ) : this.individualSort === 'descending' ? (
- <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
- ) : (
- <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
- )}
+ <div className="title-individual">
+ Individuals &nbsp;
+ <FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} size="xs" />
+ </div>
</div>
- <div className={'users-list'}>{userListContents}</div>
+ <div className="users-list">{userListContents}</div>
</div>
<div className={'group-container'}>
<div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
- Groups{' '}
- {this.groupSort === 'ascending' ? (
- <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
- ) : this.groupSort === 'descending' ? (
- <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
- ) : (
- <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
- )}
+ <div className="title-group">
+ Groups &nbsp;
+ <div className="group-info" onClick={action(() => GroupManager.Instance?.open())}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
+ </div>
+ &nbsp;
+ <FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} size="xs" />
+ </div>
</div>
<div className={'groups-list'}>{groupListContents}</div>
</div>
@@ -670,6 +657,13 @@ export class SharingManager extends React.Component<{}> {
}
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal
+ contents={this.sharingInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ closeOnExternalClick={this.close}
+ />;
}
}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index b59af6656..9a6719ea5 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,5 +1,6 @@
import { observable, action, runInAction } from 'mobx';
import { Field } from '../../fields/Doc';
+import { RichTextField } from '../../fields/RichTextField';
import { Without } from '../../Utils';
function getBatchName(target: any, key: string | symbol): string {
@@ -96,7 +97,13 @@ export namespace UndoManager {
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- console.log(' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + (value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value)));
+ console.log(
+ ' '.slice(0, batchCounter.get()) +
+ 'UndoEvent : ' +
+ event.prop +
+ ' = ' +
+ (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value))
+ );
currentBatch.push(event);
tempEvents?.push(event);
}