import { library } from '@fortawesome/fontawesome-svg-core'; import { faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, faTerminal, faToggleOn, faFile as 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, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } 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, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils'; import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { HistoryUtil } from '../util/History'; import RichTextMenu from './nodes/formattedText/RichTextMenu'; import { Scripting } from '../util/Scripting'; import SettingsManager from '../util/SettingsManager'; import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/CollectionLinearView'; import { CollectionView, CollectionViewType } from './collections/CollectionView'; import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import GestureOverlay from './GestureOverlay'; import KeyManager from './GlobalKeyHandler'; import "./MainView.scss"; import { MainViewNotifs } from './MainViewNotifs'; import { AudioBox } from './nodes/AudioBox'; import { DocumentView } from './nodes/DocumentView'; import { RadialMenu } from './nodes/RadialMenu'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { ScriptField } from '../../fields/ScriptField'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; import { DragManager } from '../util/DragManager'; import { SnappingManager } from '../util/SnappingManager'; @observer export class MainView extends React.Component { public static Instance: MainView; private _buttonBarHeight = 26; private _flyoutSizeOnDown = 0; private _urlState: HistoryUtil.DocUrl; private _docBtnRef = React.createRef(); private _mainViewRef = React.createRef(); @observable private _panelWidth: number = 0; @observable private _panelHeight: number = 0; @observable private _flyoutTranslate: boolean = true; @observable public flyoutWidth: number = 250; private get darkScheme() { return BoolCast(Cast(this.userDoc.activeWorkspace, Doc, null)?.darkScheme); } @computed private get userDoc() { return Doc.UserDoc(); } @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)); } @computed public get sidebarButtonsDoc() { return Cast(this.userDoc["tabs-buttons"], Doc) as Doc; } public isPointerDown = false; componentDidMount() { const tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("paste", KeyManager.Instance.paste as any); } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.globalPointerDown); window.removeEventListener("pointerup", this.globalPointerUp); window.removeEventListener("paste", KeyManager.Instance.paste as any); } 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") { const pathname = window.location.pathname.substr(1).split("/"); if (pathname.length > 1) { const 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(faTrashAlt); library.add(faAngleRight); library.add(faBell); library.add(faTrash); library.add(faCamera); library.add(faExpand); library.add(faCaretDown); library.add(faCaretUp); library.add(faCaretLeft); library.add(faCaretRight); library.add(faCaretSquareDown); library.add(faCaretSquareRight); library.add(faArrowsAltH); library.add(faPlus, faMinus); library.add(faTerminal); library.add(faToggleOn); library.add(faLocationArrow); library.add(faSearch); library.add(fileSolid); library.add(faFileDownload); library.add(faStop); library.add(faCalculator); library.add(faWindowMaximize); library.add(faFileAlt); library.add(faAddressCard); library.add(faQuestionCircle); library.add(faStickyNote); 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(faFilter); library.add(faBullseye); library.add(faArrowLeft); library.add(faArrowRight); library.add(faArrowDown); library.add(faArrowUp); library.add(faCloudUploadAlt); library.add(faBolt); library.add(faVideo); library.add(faChevronRight); library.add(faEllipsisV); library.add(faMusic); library.add(faPhone); library.add(faClipboard); library.add(faStamp); library.add(faExternalLinkAlt); 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(); } if (targets && (targets.length && targets[0].className.toString() !== "timeline-menu-desc" && targets[0].className.toString() !== "timeline-menu-item" && targets[0].className.toString() !== "timeline-menu-input")) { TimelineMenu.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 const 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(docField => { if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) { CollectionDockingView.AddRightSplit(docField); } }), ); } const doc = this.userDoc && await Cast(this.userDoc.activeWorkspace, Doc); if (doc) { this.openWorkspace(doc); } else { this.createNewWorkspace(); } } } @action createNewWorkspace = async (id?: string) => { const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc; const workspaceCount = DocListCast(workspaces.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, y: 400, _width: this._panelWidth * .7, _height: this._panelHeight, title: "Collection " + workspaceCount, _LODdisable: true }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row"); const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`); mainDoc.contextMenuScripts = new List([toggleTheme!]); mainDoc.contextMenuLabels = new List(["Toggle Theme Colors"]); 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 = (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); } const 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.rightSidebarCollection, 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; defaultBackgroundColors = (doc: Doc) => { if (this.darkScheme) { switch (doc?.type) { case DocumentType.RTF || DocumentType.LABEL || DocumentType.BUTTON: return "#2d2d2d"; case DocumentType.LINK: case DocumentType.COL: { if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; } default: return "black"; } } else { switch (doc?.type) { case DocumentType.RTF: return "#f1efeb"; case DocumentType.BUTTON: case DocumentType.LABEL: return "lightgray"; case DocumentType.LINK: case DocumentType.COL: { if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "lightgray"; } default: return "white"; } } } @computed get mainDocView() { return ; } @computed get dockingContent() { TraceMobx(); const mainContainer = this.mainContainer; const width = this.flyoutWidth; return {({ measureRef }) =>
{!mainContainer ? (null) : this.mainDocView}
}
; } _canClick = false; onPointerDown = (e: React.PointerEvent) => { if (this._flyoutTranslate) { this._canClick = true; 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.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; // 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); Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false); this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; } @action onPointerUp = (e: PointerEvent) => { if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4 && this._canClick) { this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0; this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30); } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } flyoutWidthFunc = () => this.flyoutWidth; addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => { return where === "close" ? CollectionDockingView.CloseRightSplit(doc) : doc.dockingConfig ? this.openWorkspace(doc) : CollectionDockingView.AddRightSplit(doc, libraryPath); } mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1); @computed get flyout() { const sidebarContent = this.userDoc?.["tabs-panelContainer"]; if (!(sidebarContent instanceof Doc)) { return (null); } return
{this.docButtons}
; } @computed get mainContent() { const sidebar = this.userDoc?.["tabs-panelContainer"]; return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
{this.flyout} {this.expandButton}
{this.dockingContent}
); } public static expandFlyout = action(() => { MainView.Instance._flyoutTranslate = true; MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250); MainView.Instance.sidebarButtonsDoc.columnWidth = MainView.Instance.flyoutWidth / 3 - 30; }); @computed get expandButton() { return !this._flyoutTranslate ? (
) : (null); } addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); return new Transform(-translateX, -translateY, 1 / scale); } @computed get docButtons() { const dockedBtns = Doc.UserDoc()?.dockedBtns; if (dockedBtns instanceof Doc) { return
; } return (null); } get mainViewElement() { return document.getElementById("mainView-container"); } get mainViewRef() { return this._mainViewRef; } @computed get snapLines() { return
{SnappingManager.horizSnapLines().map(l => )} {SnappingManager.vertSnapLines().map(l => )}
; } render() { return (
{this.mainContent} {this.snapLines}
); } } Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });