import { library } from '@fortawesome/fontawesome-svg-core';
import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, PromiseValue, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocUtils } from '../documents/Documents';
import { CaptureManager } from '../util/CaptureManager';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
import { Scripting } from '../util/Scripting';
import { SelectionManager } from '../util/SelectionManager';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/collectionLinear';
import { CollectionMenu } from './collections/CollectionMenu';
import { CollectionViewType } from './collections/CollectionView';
import "./collections/TreeView.scss";
import { ComponentDecorations } from './ComponentDecorations';
import { ContextMenu } from './ContextMenu';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import { GestureOverlay } from './GestureOverlay';
import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
import { Colors } from './global/globalEnums';
import { KeyManager } from './GlobalKeyHandler';
import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
import { AudioBox } from './nodes/AudioBox';
import { ButtonType } from './nodes/button/FontIconBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview } from './nodes/LinkDocPreview';
import { RadialMenu } from './nodes/RadialMenu';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { WebBox } from './nodes/WebBox';
import { OverlayView } from './OverlayView';
import { AnchorMenu } from './pdf/AnchorMenu';
import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
const _global = (window /* browser */ || global /* node */) as any;

@observer
export class MainView extends React.Component {
    public static Instance: MainView;
    public static Live: boolean = false;
    private _docBtnRef = React.createRef<HTMLDivElement>();
    @observable public LastButton: Opt<Doc>;
    @observable private _windowWidth: number = 0;
    @observable private _windowHeight: number = 0;
    @observable private _dashUIWidth: number = 0;   // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
    @observable private _dashUIHeight: number = 0;  // height of entire main dashboard region including top menu buttons
    @observable private _panelContent: string = "none";
    @observable private _sidebarContent: any = this.userDoc?.sidebar;
    @observable private _leftMenuFlyoutWidth: number = 0;

    @computed private get dashboardTabHeight() { return 27; } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
    @computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); }
    @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight(); }
    @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; }
    @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; }
    @computed private get userDoc() { return Doc.UserDoc(); }
    @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
    @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
    @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }

    topMenuHeight = () => 35;
    topMenuWidth = returnZero; // value is ignored ...
    leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", ""));
    leftMenuHeight = () => this._dashUIHeight;
    leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
    leftMenuFlyoutHeight = () => this._dashUIHeight;
    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0));
    propertiesHeight = () => this._dashUIHeight;
    mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth();
    mainDocViewHeight = () => this._dashUIHeight - this.topMenuHeight();

    componentDidMount() {
        document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
        const ele = document.getElementById("loader");
        const prog = document.getElementById("dash-progress");
        if (ele && prog) {
            // remove from DOM
            setTimeout(() => {
                clearTimeout();
                prog.style.transition = "1s";
                prog.style.width = "100%";
            }, 0);
            setTimeout(() => ele.outerHTML = '', 1000);
        }
        new InkStrokeProperties();
        this._sidebarContent.proto = undefined;
        if (!MainView.Live) {
            DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
                "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap",
                "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
        }
        DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));

        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.removeEventListener("keyup", KeyManager.Instance.unhandle);
        window.addEventListener("keyup", KeyManager.Instance.unhandle);
        window.addEventListener("paste", KeyManager.Instance.paste as any);
        document.addEventListener("dash", (e: any) => {  // event used by chrome plugin to tell Dash which document to focus on
            const id = FormattedTextBox.GetDocFromUrl(e.detail);
            DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined) : (null));
        });
        document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
        this.initEventListeners();
    }

    componentWillUnMount() {
        window.removeEventListener("keyup", KeyManager.Instance.unhandle);
        window.removeEventListener("keydown", KeyManager.Instance.handle);
        window.removeEventListener("pointerdown", this.globalPointerDown);
        window.removeEventListener("paste", KeyManager.Instance.paste as any);
        document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener);
    }

    constructor(props: Readonly<{}>) {
        super(props);
        MainView.Instance = this;
        CurrentUserUtils._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 && pathname[0] === "doc") {
                CurrentUserUtils.MainDocId = pathname[1];
                !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
            }
        }

        library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar,
            fa.faSquare, far.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
            fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
            fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
            fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
            fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
            fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp,
            fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
            fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
            fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV,
            fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
            fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
            fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
            fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper,
            fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle,
            fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
            fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
            fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
            fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
            fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll,
            fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines,
            fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen);
        this.initAuthenticationRouters();
    }

    globalPointerDown = action((e: PointerEvent) => {
        AudioBox.Enabled = true;
        const targets = document.elementsFromPoint(e.x, e.y);
        if (targets.length) {
            const targClass = targets[0].className.toString();
            !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
            !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
        }
    });

    initEventListeners = () => {
        window.addEventListener("drop", e => e.preventDefault(), false);  // prevent default behavior of navigating to a new web page
        window.addEventListener("dragover", e => e.preventDefault(), false);
        // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
        document.addEventListener("pointerdown", this.globalPointerDown);
        document.addEventListener("click", (e: MouseEvent) => {
            if (!e.cancelBubble) {
                const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
                if (pathstr?.includes("libraryFlyout")) {
                    SelectionManager.DeselectAll();
                }
            }
        }, false);
    }

    initAuthenticationRouters = async () => {
        // Load the user's active dashboard, or create a new one if initial session after signup
        const received = CurrentUserUtils.MainDocId;
        if (received && !this.userDoc) {
            reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true });
        } else {
            if (received && CurrentUserUtils._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.AddSplit(docField, "right");
                        }
                    }),
                );
            }
            const activeDash = PromiseValue(this.userDoc.activeDashboard);
            activeDash.then(dash => {
                if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash);
                else CurrentUserUtils.createNewDashboard(this.userDoc);
            });
        }
    }

    @action
    createNewPresentation = async () => {
        if (!await this.userDoc.myTrails) {
            this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
                title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true
            }));
        }
        const pres = Docs.Create.PresDocument(new List<Doc>(),
            { title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
        CollectionDockingView.AddSplit(pres, "right");
        this.userDoc.activePresentation = pres;
        Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres);
    }

    @action
    createNewFolder = async () => {
        if (!await this.userDoc.myFilesystem) {
            this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
            const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
            const newFolderButton: Doc = Docs.Create.FontIconDocument({
                onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder",
                btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true
            });
            this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], {
                title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
                treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
                treeViewTruncateTitleWidth: 150, ignoreClick: true,
                isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
                _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
                explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
            }));
        }
        const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
        Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder);
    }

    @computed get mainDocView() {
        return <DocumentView key="main"
            Document={this.mainContainer!}
            DataDoc={undefined}
            addDocument={undefined}
            addDocTab={this.addDocTabFunc}
            pinToPres={emptyFunction}
            docViewPath={returnEmptyDoclist}
            layerProvider={undefined}
            styleProvider={undefined}
            rootSelected={returnTrue}
            isContentActive={returnTrue}
            removeDocument={undefined}
            ScreenToLocalTransform={Transform.Identity}
            PanelWidth={this.mainDocViewWidth}
            PanelHeight={this.mainDocViewHeight}
            focus={DocUtils.DefaultFocus}
            whenChildContentsActiveChanged={emptyFunction}
            bringToFront={emptyFunction}
            docFilters={returnEmptyFilter}
            docRangeFilters={returnEmptyFilter}
            searchFilterDocs={returnEmptyDoclist}
            ContainingCollectionView={undefined}
            ContainingCollectionDoc={undefined}
            renderDepth={-1}
        />;
    }

    @computed get dockingContent() {
        return <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
            style={{
                minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
            }}>
            {!this.mainContainer ? (null) : this.mainDocView}
        </div>;
    }

    @action
    onPropertiesPointerDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(this, e,
            action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false),
            action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)),
            action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0), false);
    }

    @action
    onFlyoutPointerDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(this, e,
            action(e => (this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false),
            () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
            this.closeFlyout);
    }

    sidebarScreenToLocal = () => new Transform(0, -this.topOfMainDoc, 1);
    mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
    addDocTabFunc = (doc: Doc, where: string): boolean => {
        return where === "close" ? CollectionDockingView.CloseSplit(doc) :
            doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) : CollectionDockingView.AddSplit(doc, "right");
    }


    @computed get flyout() {
        return !this._leftMenuFlyoutWidth ? <div key="flyout" className={`mainView-libraryFlyout-out`}>
            {this.docButtons}
        </div> :
            <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }} >
                <div className="mainView-contentArea" >
                    <DocumentView
                        Document={this._sidebarContent.proto || this._sidebarContent}
                        DataDoc={undefined}
                        addDocument={undefined}
                        addDocTab={this.addDocTabFunc}
                        pinToPres={emptyFunction}
                        docViewPath={returnEmptyDoclist}
                        layerProvider={undefined}
                        styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards ? DashboardStyleProvider : DefaultStyleProvider}
                        rootSelected={returnTrue}
                        removeDocument={returnFalse}
                        ScreenToLocalTransform={this.mainContainerXf}
                        PanelWidth={this.leftMenuFlyoutWidth}
                        PanelHeight={this.leftMenuFlyoutHeight}
                        renderDepth={0}
                        isContentActive={returnTrue}
                        scriptContext={CollectionDockingView.Instance?.props.Document}
                        focus={DocUtils.DefaultFocus}
                        whenChildContentsActiveChanged={emptyFunction}
                        bringToFront={emptyFunction}
                        docFilters={returnEmptyFilter}
                        docRangeFilters={returnEmptyFilter}
                        searchFilterDocs={returnEmptyDoclist}
                        ContainingCollectionView={undefined}
                        ContainingCollectionDoc={undefined}
                    />
                </div>
                {this.docButtons}
            </div>;
    }

    @computed get leftMenuPanel() {
        return <div key="menu" className="mainView-leftMenuPanel">
            <DocumentView
                Document={Doc.UserDoc().menuStack as Doc}
                DataDoc={undefined}
                addDocument={undefined}
                addDocTab={this.addDocTabFunc}
                pinToPres={emptyFunction}
                rootSelected={returnTrue}
                removeDocument={returnFalse}
                ScreenToLocalTransform={this.sidebarScreenToLocal}
                PanelWidth={this.leftMenuWidth}
                PanelHeight={this.leftMenuHeight}
                renderDepth={0}
                docViewPath={returnEmptyDoclist}
                focus={DocUtils.DefaultFocus}
                styleProvider={DefaultStyleProvider}
                layerProvider={undefined}
                isContentActive={returnTrue}
                whenChildContentsActiveChanged={emptyFunction}
                bringToFront={emptyFunction}
                docFilters={returnEmptyFilter}
                docRangeFilters={returnEmptyFilter}
                searchFilterDocs={returnEmptyDoclist}
                ContainingCollectionView={undefined}
                ContainingCollectionDoc={undefined}
                scriptContext={this}
            />
        </div>;
    }

    @action
    selectMenu = (button: Doc) => {
        const title = StrCast(Doc.GetProto(button).title);
        const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
        this.closeFlyout();
        if (willOpen) {
            switch (this._panelContent = title) {
                case "Settings":
                    SettingsManager.Instance.open();
                    break;
                case "Help":
                    break;
                default:
                    this.expandFlyout(button);
            }
        }
        return true;
    }

    @computed get mainInnerContent() {
        const width = this.propertiesWidth() + this._leftMenuFlyoutWidth + this.leftMenuWidth();
        const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined;
        return <>
            {this.leftMenuPanel}
            <div key="inner" className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
                {this.flyout}
                <div className="mainView-libraryHandle" style={{ display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
                    <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                </div>
                <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
                    <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />

                    {this.dockingContent}

                    <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._leftMenuFlyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? Colors.WHITE : Colors.BLACK} size="sm" />
                    </div>
                    <div className="properties-container">
                        {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                    </div>
                </div>
            </div>
        </>;
    }

    @computed get mainDashboardArea() {
        return !this.userDoc ? (null) :
            <div className="mainView-dashboardArea" ref={r => {
                r && new _global.ResizeObserver(action(() => {
                    this._dashUIWidth = r.getBoundingClientRect().width;
                    this._dashUIHeight = r.getBoundingClientRect().height;
                })).observe(r);
            }} style={{
                color: this.darkScheme ? "rgb(205,205,205)" : "black",
                height: `calc(100% - ${this.topOfDashUI}px)`,
                width: "100%",
            }} >
                {this.mainInnerContent}
            </div>;
    }

    expandFlyout = action((button: Doc) => {
        this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250);
        this._sidebarContent.proto = button.target as any;
        this.LastButton = button;
        console.log(button.title);
    });

    closeFlyout = action(() => {
        this.LastButton = undefined;
        this._panelContent = "none";
        this._sidebarContent.proto = undefined;
        this._leftMenuFlyoutWidth = 0;
    });

    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);
    addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);

    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() {
        return !(this.userDoc.dockedBtns instanceof Doc) ? (null) :
            <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !this.userDoc.dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
                <CollectionLinearView
                    Document={this.userDoc.dockedBtns}
                    DataDoc={undefined}
                    fieldKey={"data"}
                    dropAction={"alias"}
                    setHeight={returnFalse}
                    styleProvider={DefaultStyleProvider}
                    layerProvider={undefined}
                    rootSelected={returnTrue}
                    bringToFront={emptyFunction}
                    select={emptyFunction}
                    isAnyChildContentActive={returnFalse}
                    isContentActive={returnFalse}
                    isSelected={returnFalse}
                    docViewPath={returnEmptyDoclist}
                    moveDocument={this.moveButtonDoc}
                    CollectionView={undefined}
                    addDocument={this.addButtonDoc}
                    addDocTab={this.addDocTabFunc}
                    pinToPres={emptyFunction}
                    removeDocument={this.remButtonDoc}
                    ScreenToLocalTransform={this.buttonBarXf}
                    PanelWidth={this.leftMenuFlyoutWidth}
                    PanelHeight={this.leftMenuFlyoutHeight}
                    renderDepth={0}
                    focus={DocUtils.DefaultFocus}
                    whenChildContentsActiveChanged={emptyFunction}
                    docFilters={returnEmptyFilter}
                    docRangeFilters={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    ContainingCollectionView={undefined}
                    ContainingCollectionDoc={undefined} />
            </div>;
    }
    @computed get snapLines() {
        return !this.userDoc.showSnapLines ? (null) : <div className="mainView-snapLines">
            <svg style={{ width: "100%", height: "100%" }}>
                {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
                {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
            </svg>
        </div>;
    }

    @computed get inkResources() {
        return <svg width={0} height={0}>
            <defs>
                <filter id="inkSelectionHalo">
                    <feColorMatrix type="matrix"
                        result="color"
                        values="1 0 0 0 0
                0 0 0 0 0
                0 0 0 0 0
                0 0 0 1 0">
                    </feColorMatrix>
                    <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
                    <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
                    <feMerge>
                        <feMergeNode in="bg"></feMergeNode>
                        <feMergeNode in="offset"></feMergeNode>
                        <feMergeNode in="SourceGraphic"></feMergeNode>
                    </feMerge>
                </filter>
            </defs>
        </svg>;
    }

    @computed get topbar() {
        TraceMobx();
        return <div className="mainView-topbar">
            <TopBar />
        </div>;
    }

    @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
        return !DocumentLinksButton.invisibleWebDoc ? null :
            <div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
                <WebBox
                    fieldKey={"data"}
                    ContainingCollectionView={undefined}
                    ContainingCollectionDoc={undefined}
                    Document={DocumentLinksButton.invisibleWebDoc}
                    dropAction={"move"}
                    layerProvider={undefined}
                    styleProvider={undefined}
                    isSelected={returnFalse}
                    select={returnFalse}
                    setHeight={returnFalse}
                    rootSelected={returnFalse}
                    renderDepth={0}
                    addDocTab={returnFalse}
                    pinToPres={returnFalse}
                    ScreenToLocalTransform={Transform.Identity}
                    bringToFront={returnFalse}
                    isContentActive={returnFalse}
                    whenChildContentsActiveChanged={returnFalse}
                    focus={returnFalse}
                    docViewPath={returnEmptyDoclist}
                    PanelWidth={() => 500}
                    PanelHeight={() => 800}
                    docFilters={returnEmptyFilter}
                    docRangeFilters={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                />
            </div>;
    }

    render() {
        return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")}
            onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
            ref={r => {
                r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
            }}>
            {this.inkResources}
            <DictationOverlay />
            <SharingManager />
            <SettingsManager />
            <CaptureManager />
            <GroupManager />
            <GoogleAuthenticationManager />
            <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
            <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
            {this.topbar}
            {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
            {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
            {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
            <GestureOverlay >
                {this.mainDashboardArea}
            </GestureOverlay>
            <PreviewCursor />
            <TaskCompletionBox />
            <ContextMenu />
            <RadialMenu />
            <AnchorMenu />
            <MarqueeOptionsMenu />
            <OverlayView />
            <TimelineMenu />
            <RichTextMenu />
            {this.snapLines}
            <div className="mainView-webRef" ref={this.makeWebRef} />
            <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
        </div >);
    }

    makeWebRef = (ele: HTMLDivElement) => {
        reaction(() => DocumentLinksButton.invisibleWebDoc,
            invisibleDoc => {
                ReactDOM.unmountComponentAtNode(ele);
                invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
                    <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}>
                        <WebBox
                            fieldKey={"data"}
                            ContainingCollectionView={undefined}
                            ContainingCollectionDoc={undefined}
                            Document={invisibleDoc}
                            dropAction={"move"}
                            isSelected={returnFalse}
                            docViewPath={returnEmptyDoclist}
                            select={returnFalse}
                            rootSelected={returnFalse}
                            renderDepth={0}
                            setHeight={returnFalse}
                            layerProvider={undefined}
                            styleProvider={undefined}
                            addDocTab={returnFalse}
                            pinToPres={returnFalse}
                            ScreenToLocalTransform={Transform.Identity}
                            bringToFront={returnFalse}
                            isContentActive={returnFalse}
                            whenChildContentsActiveChanged={returnFalse}
                            focus={returnFalse}
                            PanelWidth={() => 500}
                            PanelHeight={() => 800}
                            docFilters={returnEmptyFilter}
                            docRangeFilters={returnEmptyFilter}
                            searchFilterDocs={returnEmptyDoclist}
                        />
                    </div>;
                </span>, ele);

                let success = false;
                const onSuccess = () => {
                    success = true;
                    clearTimeout(interval);
                    document.removeEventListener("editSuccess", onSuccess);
                };

                // For some reason, Hypothes.is annotations don't load until a click is registered on the page,
                // so we keep simulating clicks until annotations have loaded and editing is successful
                const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500);
                setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
                document.addEventListener("editSuccess", onSuccess);
            });
    }
}

Scripting.addGlobal(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); });