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