import { library } from '@fortawesome/fontawesome-svg-core'; import { faAddressCard, faAlignLeft, faAlignRight, faAngleDoubleLeft, faAngleRight, faArrowDown, faArrowLeft, faArrowRight, faArrowUp, faArrowsAltH, faAsterisk, faBars, faBell, faBolt, faBook, faBrain, faBullseye, faCalculator, faCamera, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faCaretUp, faCat, faCheck, faChevronLeft, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEdit, faEllipsisV, faEraser, faExclamation, faExpand, faExternalLinkAlt, faExternalLinkSquareAlt, faEye, faFileAlt, faFileAudio, faFileDownload, faFilePdf, faFilm, faFilter, faFolderOpen, faFont, faGlobeAsia, faHandPointLeft, faHighlighter, faHome, faImage, faLocationArrow, faLongArrowAltLeft, faLongArrowAltRight, faMicrophone, faCircleHalfStroke, faMinus, faMobile, faMousePointer, faMusic, faObjectGroup, faPaintBrush, faPalette, faPause, faPen, faPenNib, faPhone, faPlay, faPlus, faPortrait, faQuestionCircle, faQuoteLeft, faRedoAlt, faReply, faSearch, faStamp, faStickyNote, faStop, faTasks, faTerminal, faTh, faThLarge, faThumbtack, faTimes, faToggleOn, faTrash, faTrashAlt, faTree, faTv, faUndoAlt, faVideo, faWindowClose, faWindowMaximize, faFile as fileSolid, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../ClientUtils'; import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes'; import { Docs, DocumentOptions } from '../client/documents/Documents'; import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { SettingsManager } from '../client/util/SettingsManager'; import { Transform } from '../client/util/Transform'; import { UndoManager } from '../client/util/UndoManager'; import { DashboardView } from '../client/views/DashboardView'; import { GestureOverlay } from '../client/views/GestureOverlay'; import { AudioBox } from '../client/views/nodes/AudioBox'; import { DocumentView } from '../client/views/nodes/DocumentView'; import { RadialMenu } from '../client/views/nodes/RadialMenu'; import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu'; import { Doc, DocListCast } from '../fields/Doc'; import { InkTool } from '../fields/InkField'; import { List } from '../fields/List'; import { ScriptField } from '../fields/ScriptField'; import { Cast, FieldValue, StrCast } from '../fields/Types'; import './AudioUpload.scss'; import { Uploader } from './ImageUpload'; import './ImageUpload.scss'; import './MobileInterface.scss'; import { emptyFunction } from '../Utils'; library.add( ...[ faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faCircleHalfStroke, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft, faAlignLeft, faAlignRight, ].map(m => m as any) ); @observer export class MobileInterface extends React.Component { static Instance: MobileInterface; private _library: Doc; private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc()); @observable private _sidebarActive: boolean = false; //to toggle sidebar display @observable private _imageUploadActive: boolean = false; //to toggle image upload @observable private _audioUploadActive: boolean = false; @observable private _menuListView: boolean = false; //to switch between menu view (list / icon) @observable private _ink: boolean = false; //toggle whether ink is being dispalyed @observable private _homeMenu: boolean = true; // to determine whether currently at home menu @observable private dashboards: Doc | null = null; // currently selected document @observable private _activeDoc: Doc = this._mainDoc; // doc updated as the active mobile page is updated (initially home menu) @observable private _homeDoc: Doc = this._mainDoc; // home menu as a document @observable private _parents: Array = []; // array of parent docs (for pathbar) @computed private get mainContainer() { return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : Doc.GuestMobile; } constructor(props: Readonly<{}>) { super(props); this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), 'myDashboards'); // to access documents in Dash Web MobileInterface.Instance = this; } @action componentDidMount() { // if the home menu is in list view -> adjust the menu toggle appropriately this._menuListView = this._homeDoc._type_collection === 'stacking' ? true : false; Doc.ActiveTool = InkTool.None; // ink should intially be set to none Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home AudioBox.Enabled = true; // remove double click to avoid mobile zoom in document.removeEventListener('dblclick', this.onReactDoubleClick); document.addEventListener('dblclick', this.onReactDoubleClick); } @action componentWillUnmount = () => { document.removeEventListener('dblclick', this.onReactDoubleClick); }; // Prevent zooming in when double tapping the screen onReactDoubleClick = (e: MouseEvent) => { e.stopPropagation(); }; // Switch the mobile view to the given doc @action switchCurrentView = (doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => { if (!Doc.UserDoc()) return; if (this._activeDoc === this._homeDoc) { this._parents.push(this._activeDoc); this._homeMenu = false; } this._activeDoc = doc; Doc.UserDoc().activeMobile = doc; onSwitch?.(); // Ensures that switching to home is not registed UndoManager.undoStack.length = 0; UndoManager.redoStack.length = 0; }; // For toggling the hamburger menu @action toggleSidebar = () => { this._sidebarActive = !this._sidebarActive; if (this._ink) { this.onSwitchInking(); } }; /** * Method called when 'Library' button is pressed on the home screen */ switchToLibrary = async () => { this.switchCurrentView(this._library); runInAction(() => (this._homeMenu = false)); this.toggleSidebar(); }; /** * Back method for navigating through items */ @action back = () => { const header = document.getElementById('header') as HTMLElement; const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document // Case 1: Parent document is 'dashboards' if (doc === (Cast(this._library, Doc) as Doc)) { this.dashboards = null; this.switchCurrentView(this._library); // Case 2: Parent document is the 'home' menu (root node) } else if (doc === (Cast(this._homeDoc, Doc) as Doc)) { this._homeMenu = true; this._parents = []; this.dashboards = null; this.switchCurrentView(this._homeDoc); // Case 3: Parent document is any document } else if (doc) { this.dashboards = doc; this.switchCurrentView(doc); this._homeMenu = false; header.textContent = String(doc.title); } this._ink = false; // turns ink off }; /** * Return 'Home", which implies returning to 'Home' menu buttons */ @action returnHome = () => { if (!this._homeMenu || this._sidebarActive) { this._homeMenu = true; this._parents = []; this.dashboards = null; this.switchCurrentView(this._homeDoc); } if (this._sidebarActive) { this.toggleSidebar(); } }; /** * Return to primary Dashboard in library (Dashboards Doc) */ @action returnMain = () => { this._parents = [this._homeDoc]; this.switchCurrentView(this._library); this._homeMenu = false; this.dashboards = null; }; /** * Note: window.innerWidth and window.screen.width compute different values. * window.screen.width is the display size, however window.innerWidth is the * display resolution which computes differently. */ returnWidth = () => window.innerWidth; //The windows width returnHeight = () => window.innerHeight - 300; //Calculating the windows height (-300 to account for topbar) whitebackground = () => 'white'; /** * DocumentView for graphic display of all documents */ @computed get displayDashboards() { return !this.mainContainer ? null : (
); } /** * Handles the click functionality in the library panel. * Navigates to the given doc and updates the sidebar. * @param doc: doc for which the method is called */ handleClick = async (doc: Doc) => { runInAction(() => { if (doc.type !== 'collection' && this._sidebarActive) { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.toggleSidebar(); } else { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.dashboards = doc; } }); }; /** * Called when an item in the library is clicked and should * be opened (open icon on RHS of all menu items) * @param doc doc to be opened */ @action openFromSidebar = (doc: Doc) => { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.dashboards = doc; this.toggleSidebar(); }; // Renders the graphical pathbar renderPathbar = () => { const docPath = [...this._parents, this._activeDoc]; const items = docPath.map((doc: Doc, index: any) => (
{index === 0 ? null : }
this.handlePathClick(doc, index)}> {StrCast(doc.title)}
)); return (
{items}
{!this._parents.length ? null : (
)}
); }; // Handles when user clicks on a document in the pathbar @action handlePathClick = async (doc: Doc, index: number) => { const library = await this._library; if (doc === library) { this.dashboards = null; this.switchCurrentView(doc); this._parents.length = index; } else if (doc === this._homeDoc) { this.returnHome(); } else { this.dashboards = doc; this.switchCurrentView(doc); this._parents.length = index; } }; // Renders the contents of the menu and sidebar @computed get renderDefaultContent() { if (this._homeMenu) { return (
e.stopPropagation()}>
{this.renderPathbar()}
); } // stores dashboards documents as 'dashboards' variable let dashboards = Doc.MyDashboards; if (this.dashboards) { dashboards = this.dashboards; } // returns a list of navbar buttons as 'buttons' const buttons = DocListCast(dashboards.data).map((doc: Doc, index: any) => { if (doc.type !== 'ink') { return (
this.handleClick(doc)}> {' '} {doc.title as string}{' '}
this.handleClick(doc)}> {doc.type as string}
this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === 'collection' ? 'block' : 'none'}` }} /> this.openFromSidebar(doc)} icon="external-link-alt" size="lg" />
); } }); return (
{this.renderPathbar()}
{this.dashboards ? ( <> {buttons}
Return to dashboards
) : ( <> {buttons}
this.createNewDashboard()}>
Create New Dashboard
)}
); } /** * Handles the 'Create New Dashboard' button in the menu (taken from MainView.tsx) */ @action createNewDashboard = (id?: string) => { const scens = Doc.MyDashboards; const dashboardCount = DocListCast(scens.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, y: 400, title: 'Collection ' + dashboardCount, }; const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row'); const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`); dashboardDoc.contextMenuScripts = new List([toggleComic!, cloneDashboard!]); dashboardDoc.contextMenuLabels = new List(['Toggle Comic Mode', 'New Dashboard Layout']); Doc.AddDocToList(scens, 'data', dashboardDoc); }; // Button for switching between pen and ink mode @action onSwitchInking = () => { const button = document.getElementById('inkButton') as HTMLElement; button.style.backgroundColor = this._ink ? 'white' : 'black'; button.style.color = this._ink ? 'black' : 'white'; if (!this._ink) { Doc.ActiveTool = InkTool.Pen; this._ink = true; } else { Doc.ActiveTool = InkTool.None; this._ink = false; } }; // The static ink menu that appears at the top @computed get inkMenu() { return this._activeDoc._type_collection !== CollectionViewType.Docking || !this._ink ? null :
{/* */}
; } // DocButton that uses UndoManager and handles the opacity change if CanUndo is true @computed get undo() { if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') { return (
{ UndoManager.Undo(); e.stopPropagation(); }}>
); } else return null; } // DocButton that uses UndoManager and handles the opacity change if CanRedo is true @computed get redo() { if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') { return (
{ UndoManager.Redo(); e.stopPropagation(); }}>
); } else return null; } // DocButton for switching into ink mode @computed get drawInk() { return !this.mainContainer || this._activeDoc._type_collection !== CollectionViewType.Docking ? null : (
); } // DocButton: Button that appears on the bottom of the screen to initiate image upload @computed get uploadImageButton() { if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') { return (
); } else return null; } // DocButton to download images on the mobile @computed get downloadDocument() { if (this._activeDoc.type === 'image' || this._activeDoc.type === 'pdf' || this._activeDoc.type === 'video') { return (
window.open(this._activeDoc['data-path']?.toString())}> {' '} {/* daa-path holds the url */}
); } else return null; } // DocButton for pinning images to presentation @computed get pinToPresentation() { // Only making button available if it is an image if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) { return (
DocumentView.PinDoc(this._activeDoc, {})}>
); } else return null; } // Buttons for switching the menu between large and small icons @computed get switchMenuView() { return this._activeDoc.title !== this._homeDoc.title ? null : (
); } // Logic for switching the menu into the icons @action changeToIconView = () => { if ((this._homeDoc._type_collection = 'stacking')) { this._menuListView = false; this._homeDoc._type_collection = 'masonry'; this._homeDoc.columnWidth = 300; this._homeDoc._columnWidth = 300; const menuButtons = DocListCast(this._homeDoc.data); menuButtons.map(doc => { const buttonData = DocListCast(doc.data); buttonData[1]._nativeWidth = 0.1; buttonData[1]._width = 0.1; buttonData[1]._dimMagnitude = 0; buttonData[1]._opacity = 0; doc._nativeWidth = 400; }); } }; // Logic for switching the menu into the stacking view @action changeToListView = () => { if ((this._homeDoc._type_collection = 'masonry')) { this._homeDoc._type_collection = 'stacking'; this._menuListView = true; const menuButtons = DocListCast(this._homeDoc.data); menuButtons.map(doc => { const buttonData = DocListCast(doc.data); buttonData[1]._nativeWidth = 450; buttonData[1]._dimMagnitude = 2; buttonData[1]._opacity = 1; doc._nativeWidth = 900; }); } }; // For setting up the presentation document for the home menu @action setupDefaultPresentation = () => { const presentation = Doc.ActivePresentation; if (presentation) { this.switchCurrentView(presentation); this._homeMenu = false; } }; // For toggling image upload pop up @action toggleUpload = () => (this._imageUploadActive = !this._imageUploadActive); // For toggling audio record and dictate pop up @action toggleAudio = () => (this._audioUploadActive = !this._audioUploadActive); // Button for toggling the upload pop up in a collection @action toggleUploadInCollection = () => { const button = document.getElementById('imageButton') as HTMLElement; button.style.backgroundColor = this._imageUploadActive ? 'white' : 'black'; button.style.color = this._imageUploadActive ? 'black' : 'white'; this._imageUploadActive = !this._imageUploadActive; }; // For closing the image upload pop up @action closeUpload = () => { this._imageUploadActive = false; }; // Returns the image upload pop up @computed get uploadImage() { const doc = !this._homeMenu ? this._activeDoc : (Cast(Doc.SharingDoc(), Doc) as Doc); return ; } // Radial menu can only be used if it is a colleciton and it is not a homeDoc // (and cannot be used on Dashboard to avoid pin to presentation opening on right) @computed get displayRadialMenu() { return this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking ? : null; } onDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }; /** * MENU BUTTON * Switch view from mobile menu to access the mobile uploads * Global function name: openMobileUploads() */ @action switchToMobileUploads = () => { const mobileUpload = Cast(Doc.SharingDoc(), Doc) as Doc; this.switchCurrentView(mobileUpload); this._homeMenu = false; }; render() { return (
{this.uploadImage}
{this.switchMenuView} {this.inkMenu}
{this.pinToPresentation} {this.downloadDocument} {this.undo} {this.redo} {this.drawInk} {this.uploadImageButton}
{this.displayDashboards} {this.renderDefaultContent}
{this.displayRadialMenu}
); } } //Global functions for mobile menu ScriptingGlobals.add(function switchToMobileLibrary() { return MobileInterface.Instance.switchToLibrary(); }, 'opens the library to navigate through dashboards on Dash Mobile'); ScriptingGlobals.add(function openMobileUploads() { return MobileInterface.Instance.toggleUpload(); }, 'opens the upload files menu for Dash Mobile'); ScriptingGlobals.add(function switchToMobileUploadCollection() { return MobileInterface.Instance.switchToMobileUploads(); }, 'opens the mobile uploads collection on Dash Mobile'); ScriptingGlobals.add(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); }, 'opens the record and dictate menu on Dash Mobile'); ScriptingGlobals.add(function switchToMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); }, 'opens the presentation on Dash Mobile'); ScriptingGlobals.add(function openMobileSettings() { return SettingsManager.Instance.openMgr(); }, 'opens settings on Dash Mobile'); // Other global functions for mobile ScriptingGlobals.add( function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); }, 'changes the active document displayed on the Dash Mobile', '(doc: any)' );