import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ColorPicker, FontSize, IconButton, Size } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DataSym, Doc, DocListCast, DocListCastAsync } from '../../fields/Doc'; 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, DocCast, ImageCast, StrCast } from '../../fields/Types'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions, DocUtils } from '../documents/Documents'; import { CollectionViewType } from '../documents/DocumentTypes'; import { HistoryUtil } from '../util/History'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SharingManager } from '../util/SharingManager'; import { undoBatch } 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/button/FontIconBox'; import { FaPlus } from 'react-icons/fa'; 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 { //TODO: delete dashboard, share dashboard, etc. public static _urlState: HistoryUtil.DocUrl; @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName: string | undefined = undefined; @observable private newDashboardColor: string | undefined = undefined; @action abortCreateNewDashboard = () => { this.newDashboardName = undefined; }; @action setNewDashboardName(name: string) { this.newDashboardName = name; } @action selectDashboardGroup = (group: DashboardGroup) => { this.selectedDashboardGroup = group; }; clickDashboard = (e: React.MouseEvent, dashboard: Doc) => { Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); Doc.ActiveDashboard = dashboard; Doc.ActivePage = 'dashboard'; }; getDashboards = () => { const allDashboards = DocListCast(Doc.MyDashboards.data); if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) { return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail); } else { const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking); return sharedDashboards; } }; isUnviewedSharedDashboard = (dashboard: Doc): boolean => { // const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking); return !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard); }; getSharedDashboards = () => { const sharedDashs = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking); return sharedDashs.filter(dashboard => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard)); }; @undoBatch createNewDashboard = async (name: string, background?: string) => { setTimeout(() => { this.abortCreateNewDashboard(); }, 100); DashboardView.createNewDashboard(undefined, name, background); }; @computed get namingInterface() { const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const placeholder = `Dashboard ${dashboardCount}`; return (
Create New Dashboard
Title this.setNewDashboardName((e.target as any).value)} />
Background { this.newDashboardColor = color; }} />
); } _downX: number = 0; _downY: number = 0; @action onContextMenu = (dashboard: Doc, e?: React.MouseEvent, pageX?: number, pageY?: number) => { // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 if (e) { e.preventDefault(); e.stopPropagation(); e.persist(); if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) { return; } const cm = ContextMenu.Instance; cm.addItem({ description: 'Share Dashboard', event: async () => { SharingManager.Instance.open(undefined, dashboard); }, icon: 'edit', }); cm.addItem({ description: 'Delete Dashboard', event: async () => { DashboardView.removeDashboard(dashboard); }, icon: 'trash', }); cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); } }; render() { return ( <>
this.selectDashboardGroup(DashboardGroup.MyDashboards)}> My Dashboards
this.selectDashboardGroup(DashboardGroup.SharedDashboards)}> Shared Dashboards
{this.getDashboards().map(dashboard => { const href = ImageCast(dashboard.thumb)?.url.href; return (
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}>
e.stopPropagation()} defaultValue={StrCast(dashboard.title)} onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
{ this._downX = e.clientX; this._downY = e.clientY; }} onClick={e => { e.preventDefault(); e.stopPropagation(); this.onContextMenu(dashboard, e); }}>
); })}
{ this.setNewDashboardName(''); }}> +
); } public static closeActiveDashboard() { Doc.ActiveDashboard = undefined; } public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); } /// 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); // 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 = async (dashboard: Doc) => { const dashboards = await DocListCastAsync(Doc.MyDashboards.data); if (dashboards?.length) { if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.find(doc => doc !== dashboard)); Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard); if (!dashboards.length) Doc.ActivePage = 'home'; } }; 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, }, ], }, ], }; Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true); return reset; }; public static createNewDashboard = (id?: string, name?: string, background?: string) => { const dashboards = Doc.MyDashboards; const dashboardCount = DocListCast(dashboards.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'); // switching the tabs from the datadoc to the regular doc const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); dashboardDoc.data = new List(dashboardTabs); dashboardDoc['pane-count'] = 1; Doc.AddDocToList(dashboards, '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, _stayInCollection: true, _hideContextMenu: true, title: 'New trail', toolTip: 'Create new trail', 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, treeViewHideTitle: true, _layout_fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: 'embed', treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: myTrailsBtn, contextMenuIcons: new List(['plus']), contextMenuLabels: new List(['Create New Trail']), _lockedPosition: true, boxShadow: '0 0', childDontRegisterViews: true, targetDropAction: 'same', isSystem: true, explainer: 'All of the trails that you have created will appear here.', }; const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeViewChildDoubleClick: '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)!)); } } } export function AddToList(MySharedDocs: Doc, arg1: string, dash: any) { throw new Error('Function not implemented.'); } 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(function snapshotDashboard() { DashboardView.snapshotDashboard(); }, 'creates a snapshot copy of a dashboard');