import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import Measure from 'react-measure'; import { Doc, DocListCast, Field, FieldResult, Opt } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { List } from '../../new_fields/List'; import { listSpec } from '../../new_fields/Schema'; import { Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from '../../Utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions } from '../documents/Documents'; import { HistoryUtil } from '../util/History'; import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { CollectionLinearView } from './CollectionLinearView'; import { CollectionViewType, CollectionView } from './collections/CollectionView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import KeyManager from './GlobalKeyHandler'; import "./MainView.scss"; import { MainViewNotifs } from './MainViewNotifs'; import { DocumentView } from './nodes/DocumentView'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; import InkSelectDecorations from './InkSelectDecorations'; import { Scripting } from '../util/Scripting'; import { AudioBox } from './nodes/AudioBox'; @observer export class MainView extends React.Component { public static Instance: MainView; private _buttonBarHeight = 75; private _flyoutSizeOnDown = 0; private _urlState: HistoryUtil.DocUrl; private _docBtnRef = React.createRef(); @observable private _panelWidth: number = 0; @observable private _panelHeight: number = 0; @observable private _flyoutTranslate: boolean = true; @observable public flyoutWidth: number = 250; @computed private get userDoc() { return CurrentUserUtils.UserDocument; } @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; } @computed public get mainFreeform(): Opt { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } public isPointerDown = false; componentWillMount() { var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.globalPointerDown); window.removeEventListener("pointerup", this.globalPointerUp); } constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; this._urlState = HistoryUtil.parseUrl(window.location) || {} as any; // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: "observed" }); if (window.location.pathname !== "/home") { let pathname = window.location.pathname.substr(1).split("/"); if (pathname.length > 1) { let type = pathname[0]; if (type === "doc") { CurrentUserUtils.MainDocId = pathname[1]; if (!this.userDoc) { runInAction(() => this.flyoutWidth = 0); DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt) => field instanceof Doc && (CurrentUserUtils.GuestTarget = field))); } } } } library.add(faFont); library.add(faExclamation); library.add(faPortrait); library.add(faCat); library.add(faFilePdf); library.add(faObjectGroup); library.add(faTv); library.add(faGlobeAsia); library.add(faUndoAlt); library.add(faRedoAlt); library.add(faMousePointer); library.add(faPen); library.add(faHighlighter); library.add(faEraser); library.add(faFileAudio); library.add(faPenNib); library.add(faMicrophone); library.add(faFilm); library.add(faMusic); library.add(faTree); library.add(faPlay); library.add(faCompressArrowsAlt); library.add(faPause); library.add(faClone); library.add(faCut); library.add(faCommentAlt); library.add(faThumbtack); library.add(faLongArrowAltRight); library.add(faCheck); library.add(faCaretUp); library.add(faArrowDown); library.add(faArrowUp); library.add(faCloudUploadAlt); library.add(faBolt); library.add(faChevronRight); library.add(faEllipsisV); library.add(faMusic); this.initEventListeners(); this.initAuthenticationRouters(); } globalPointerDown = action((e: PointerEvent) => { this.isPointerDown = true; AudioBox.Enabled = true; const targets = document.elementsFromPoint(e.x, e.y); if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { ContextMenu.Instance.closeMenu(); } }); globalPointerUp = () => this.isPointerDown = false; initEventListeners = () => { window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler // click interactions for the context menu document.addEventListener("pointerdown", this.globalPointerDown); document.addEventListener("pointerup", this.globalPointerUp); } initAuthenticationRouters = async () => { // Load the user's active workspace, or create a new one if initial session after signup let received = CurrentUserUtils.MainDocId; if (received && !this.userDoc) { reaction( () => CurrentUserUtils.GuestTarget, target => target && this.createNewWorkspace(), { fireImmediately: true } ); } else { if (received && this._urlState.sharing) { reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized, initialized => initialized && received && DocServer.GetRefField(received).then(field => { if (field instanceof Doc && field.viewType !== CollectionViewType.Docking) { CollectionDockingView.AddRightSplit(field, undefined); } }), ); } let doc = this.userDoc && await Cast(this.userDoc.activeWorkspace, Doc); if (doc) { this.openWorkspace(doc); } else { this.createNewWorkspace(); } } } @action createNewWorkspace = async (id?: string) => { let workspaces = Cast(this.userDoc.workspaces, Doc) as Doc; let workspaceCount = DocListCast(workspaces.data).length + 1; let freeformOptions: DocumentOptions = { x: 0, y: 400, width: this._panelWidth * .7, height: this._panelHeight, title: "Collection " + workspaceCount, backgroundColor: "white" }; let freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); Doc.AddDocToList(Doc.GetProto(CurrentUserUtils.UserDocument.documents as Doc), "data", freeformDoc); var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${workspaceCount}` }, id); Doc.AddDocToList(workspaces, "data", mainDoc); // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) setTimeout(() => this.openWorkspace(mainDoc), 0); } @action openWorkspace = async (doc: Doc, fromHistory = false) => { CurrentUserUtils.MainDocId = doc[Id]; if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest workspace !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); this.userDoc ? (this.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc); } let state = this._urlState; if (state.sharing === true && !this.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 { DocServer.Control.makeEditable(); } } // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized) setTimeout(async () => { const col = this.userDoc && await Cast(this.userDoc.optionalRightCollection, Doc); col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col); }, 100); return true; } onDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); console.log("Drop"); } @action onResize = (r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; } getPWidth = () => this._panelWidth; getPHeight = () => this._panelHeight; getContentsHeight = () => this._panelHeight - this._buttonBarHeight; @computed get dockingContent() { const mainContainer = this.mainContainer; let flyoutWidth = this.flyoutWidth; // bcz: need to be here because Measure messes with observables. let flyoutTranslate = this._flyoutTranslate; return {({ measureRef }) =>
{!mainContainer ? (null) : }
}
; } onPointerDown = (e: React.PointerEvent) => { if (this._flyoutTranslate) { this._flyoutSizeOnDown = e.clientX; document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); e.stopPropagation(); e.preventDefault(); } } @action pointerOverDragger = () => { if (this.flyoutWidth === 0) { this.flyoutWidth = 250; this._flyoutTranslate = false; } } @action pointerLeaveDragger = () => { if (!this._flyoutTranslate) { this.flyoutWidth = 0; this._flyoutTranslate = true; } } @action onPointerMove = (e: PointerEvent) => { this.flyoutWidth = Math.max(e.clientX, 0); } @action onPointerUp = (e: PointerEvent) => { if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4) { this.flyoutWidth = this.flyoutWidth < 5 ? 250 : 0; } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } flyoutWidthFunc = () => this.flyoutWidth; addDocTabFunc = (doc: Doc, data: Opt, where: string) => { if (where === "close") { return CollectionDockingView.CloseRightSplit(doc); } if (doc.dockingConfig) { this.openWorkspace(doc); return true; } else { return CollectionDockingView.AddRightSplit(doc, undefined); } } mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1); @computed get flyout() { let sidebarContent = this.userDoc && this.userDoc.sidebarContainer; if (!(sidebarContent instanceof Doc)) { return (null); } let sidebarButtonsDoc = Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc; sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; return
; } @computed get mainContent() { const sidebar = this.userDoc && this.userDoc.sidebarContainer; return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
{this.flyout} {this.expandButton}
{this.dockingContent}
); } public static expandFlyout = action(() => { MainView.Instance._flyoutTranslate = true; MainView.Instance.flyoutWidth = 250; }); @computed get expandButton() { return !this._flyoutTranslate ? (
) : (null); } addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); moveButtonDoc = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); let { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); return new Transform(-translateX, -translateY, 1 / scale); } @computed get docButtons() { if (CurrentUserUtils.UserDocument?.expandingButtons instanceof Doc) { return
; } return (null); } render() { return (
{this.mainContent} {this.docButtons}
); } } Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });