import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { FaPlus } from 'react-icons/fa'; import { Doc, DocListCast } from '../../fields/Doc'; import { AclPrivate, DocAcl } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { listSpec } from '../../fields/Schema'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, ImageCast, StrCast } from '../../fields/Types'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions, DocUtils } from '../documents/Documents'; import { HistoryUtil } from '../util/History'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionView } from './collections/CollectionView'; import { ContextMenu } from './ContextMenu'; import './DashboardView.scss'; import { Colors } from './global/globalEnums'; import { MainViewModal } from './MainViewModal'; import { ButtonType } from './nodes/FontIconBox/FontIconBox'; enum DashboardGroup { MyDashboards, SharedDashboards, } // DashboardView is the view with the dashboard previews, rendered when the app first loads @observer export class DashboardView extends React.Component { public static _urlState: HistoryUtil.DocUrl; @observable private openModal = false; @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName = ''; @observable private newDashboardColor = '#AFAFAF'; @action abortCreateNewDashboard = () => (this.openModal = false); @action setNewDashboardName = (name: string) => (this.newDashboardName = name); @action setNewDashboardColor = (color: string) => (this.newDashboardColor = color); @action selectDashboardGroup = (group: DashboardGroup) => (this.selectedDashboardGroup = group); clickDashboard = (e: React.MouseEvent, dashboard: Doc) => { if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) { DashboardView.openSharedDashboard(dashboard); } else { Doc.ActiveDashboard = dashboard; } Doc.ActivePage = 'dashboard'; }; getDashboards = (whichGroup: DashboardGroup) => { if (whichGroup === DashboardGroup.MyDashboards) { return DocListCast(Doc.MyDashboards.data).filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail); } return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); }; isUnviewedSharedDashboard = (dashboard: Doc) => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard); @undoBatch createNewDashboard = (name: string, background?: string) => { DashboardView.createNewDashboard(undefined, name, background); this.abortCreateNewDashboard(); }; @computed get namingInterface() { return (
Create New Dashboard
this.setNewDashboardName(val as string)} fillWidth />
); } @action openNewDashboardModal = () => { this.openModal = true; this.setNewDashboardName(`Dashboard ${DocListCast(Doc.MyDashboards.data).length + 1}`); }; _downX: number = 0; _downY: number = 0; onContextMenu = (dashboard: Doc, e: React.MouseEvent) => { // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 if (navigator.userAgent.includes('Mozilla') || (Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3)) { e.preventDefault(); e.stopPropagation(); ContextMenu.Instance.addItem({ description: `Share Dashboard`, event: () => SharingManager.Instance.open(undefined, dashboard), icon: 'edit', }); ContextMenu.Instance.addItem({ description: `Delete Dashboard ${Doc.noviceMode ? '(disabled)' : ''}`, event: () => !Doc.noviceMode && DashboardView.removeDashboard(dashboard), icon: 'trash', }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } }; render() { const color = SettingsManager.userColor; const variant = SettingsManager.userVariantColor; return ( <>
{this.selectedDashboardGroup === DashboardGroup.SharedDashboards && !this.getDashboards(this.selectedDashboardGroup).length ? 'No one has shared a dashboard with you' : this.getDashboards(this.selectedDashboardGroup).map(dashboard => { const href = ImageCast(dashboard.thumb)?.url?.href; const shared = Object.keys(dashboard[DocAcl]) .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key)) .some(key => dashboard[DocAcl][key] !== AclPrivate); return (
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}>
(Doc.GetProto(dashboard).title = val)} /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
{ this._downX = e.clientX; this._downY = e.clientY; }} onClick={e => this.onContextMenu(dashboard, e)}>
{shared ? 'shared' : ''}
); })} {this.selectedDashboardGroup === DashboardGroup.SharedDashboards ? null : (
+
)}
); } public static closeActiveDashboard() { Doc.ActiveDashboard = undefined; } public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); } public static openSharedDashboard = (dashboard: Doc) => { Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); DashboardView.openDashboard(Doc.BestEmbedding(dashboard)); }; /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) public static openDashboard = (doc: Doc | undefined, fromHistory = false) => { if (!doc) return false; Doc.AddDocToList(Doc.MyDashboards, 'data', doc); Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', doc); // this has the side-effect of setting the main container since we're assigning the active/guest dashboard Doc.UserDoc() ? (Doc.ActiveDashboard = doc) : (Doc.GuestDashboard = doc); const state = DashboardView._urlState; if (state.sharing === true && !Doc.UserDoc()) { DocServer.Control.makeReadOnly(); } else { fromHistory || HistoryUtil.pushState({ type: 'doc', docId: doc[Id], readonly: state.readonly, nro: state.nro, sharing: false, }); if (state.readonly === true || state.readonly === null) { DocServer.Control.makeReadOnly(); } else if (state.safe) { if (!state.nro) { DocServer.Control.makeReadOnly(); } CollectionView.SetSafeMode(true); } else if (state.nro || state.nro === null || state.readonly === false) { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); } else { Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); } } return true; }; public static removeDashboard = (dashboard: Doc) => { const dashboards = DocListCast(Doc.MyDashboards.data).filter(dash => dash !== dashboard); undoable(() => { if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.lastElement()); Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashboard, undefined, true, true); if (!dashboards.length) Doc.ActivePage = 'home'; }, 'remove dashboard')(); }; public static resetDashboard = (dashboard: Doc) => { const config = StrCast(dashboard.dockingConfig); const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; const components = docids.map(docid => ({ type: 'component', component: 'DocumentFrameRenderer', title: 'Untitled Tab 1', width: 600, props: { documentId: docid, }, componentName: 'lm-react-component', isClosable: true, reorderEnabled: true, componentState: null, })) ?? []; const reset = { isClosable: true, reorderEnabled: true, title: '', openPopouts: [], maximisedItemId: null, settings: { hasHeaders: true, constrainDragToContainer: true, reorderEnabled: true, selectionEnabled: false, popoutWholeStack: false, blockedPopoutsThrowError: true, closePopoutsOnUnload: true, showPopoutIcon: true, showMaximiseIcon: true, showCloseIcon: true, responsiveMode: 'onload', tabOverlapAllowance: 0, reorderOnTabMenuClick: false, tabControlOffset: 10, }, dimensions: { borderWidth: 3, borderGrabWidth: 5, minItemHeight: 10, minItemWidth: 20, headerHeight: 27, dragProxyWidth: 300, dragProxyHeight: 200, }, labels: { close: 'close', maximise: 'maximise', minimise: 'minimise', popout: 'new tab', popin: 'pop in', tabDropdown: 'additional tabs', }, content: [ { type: 'row', isClosable: true, reorderEnabled: true, title: '', content: [ { type: 'stack', width: 100, isClosable: true, reorderEnabled: true, title: '', activeItemIndex: 0, content: components, }, ], }, ], }; if (dashboard.dockingConfig && dashboard.dockingConfig !== Doc.GetProto(dashboard).dockingConfig) dashboard.dockingConfig = JSON.stringify(reset); else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true); return reset; }; public static createNewDashboard = (id?: string, name?: string, background?: string) => { const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, y: 400, _width: 1500, _height: 1000, _layout_fitWidth: true, _freeform_backgroundGrid: true, backgroundColor: background, title: `Untitled Tab 1`, }; const title = name ? name : `Dashboard ${dashboardCount}`; const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row'); Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc); dashboardDoc['pane-count'] = 1; freeformDoc.embedContainer = dashboardDoc; Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc); DashboardView.SetupDashboardTrails(dashboardDoc); // open this new dashboard Doc.ActiveDashboard = dashboardDoc; Doc.ActivePage = 'dashboard'; Doc.ActivePresentation = undefined; }; public static SetupDashboardTrails(dashboardDoc: Doc) { // this section is creating the button document itself === myTrails = new Button const reqdBtnOpts: DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: 'New trail', toolTip: 'Create new trail', color: Colors.BLACK, btnType: ButtonType.ClickButton, buttonText: 'New trail', icon: 'plus', isSystem: true, }; const reqdBtnScript = { onClick: `createNewPresentation()` }; const myTrailsBtn = DocUtils.AssignScripts(Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); // createa a list of presentations (as a tree view collection) and store it on the new dashboard // instead of assigning Doc.UserDoc().myrails we want to assign Doc.AxtiveDashboard.myTrails // but we don't want to create the list of trails here-- but rather in createDashboard const reqdOpts: DocumentOptions = { title: 'My Trails', _layout_showTitle: 'title', _height: 100, treeView_HideTitle: true, _layout_fitWidth: true, _gridGap: 5, _forceActive: true, childDragAction: 'embed', treeView_TruncateTitleWidth: 150, ignoreClick: true, layout_headerButton: myTrailsBtn, contextMenuIcons: new List(['plus']), contextMenuLabels: new List(['Create New Trail']), _lockedPosition: true, layout_boxShadow: '0 0', childDontRegisterViews: true, dropAction: 'same', isSystem: true, layout_explainer: 'All of the trails that you have created will appear here.', }; const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' }); dashboardDoc.myTrails = new PrefetchProxy(myTrails); const contextMenuScripts = [reqdBtnScript.onClick]; if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } } } ScriptingGlobals.add(function createNewDashboard() { return DashboardView.createNewDashboard(); }, 'creates a new dashboard when called'); ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, 'opens sharing dialog for Dashboard'); ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { DashboardView.removeDashboard(dashboard); }, 'Remove Dashboard from Dashboards'); ScriptingGlobals.add(function resetDashboard(dashboard: Doc) { DashboardView.resetDashboard(dashboard); }, 'move all dashboard tabs to single stack'); ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { DashboardView.openDashboard(Doc.MakeEmbedding(dashboard)); }, 'adds Dashboard to set of Dashboards'); ScriptingGlobals.add(async function snapshotDashboard() { const batch = UndoManager.StartBatch('snapshot'); await DashboardView.snapshotDashboard(); batch.end(); }, 'creates a snapshot copy of a dashboard');