From f84a8d79c07bdd55e48acc976227a2ceeb457e5f Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Tue, 2 Jul 2024 14:35:58 -0400
Subject: bug creating drawing groups

---
 src/client/views/MainView.tsx | 2964 +++++++++++++++--------------------------
 1 file changed, 1051 insertions(+), 1913 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f88eb3bca..dd11ac684 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,1991 +1,1129 @@
-/* eslint-disable camelcase */
-/* eslint-disable jsx-a11y/control-has-associated-label */
-/* eslint-disable @typescript-eslint/no-unused-vars */
-/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
-/* eslint-disable react/no-array-index-key */
-/* eslint-disable react/jsx-props-no-spreading */
-/* eslint-disable no-return-assign */
-import ArrowLeftIcon from '@mui/icons-material/ArrowLeft';
-import ArrowRightIcon from '@mui/icons-material/ArrowRight';
-import PauseIcon from '@mui/icons-material/Pause';
-import PlayArrowIcon from '@mui/icons-material/PlayArrow';
-import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
-import ReplayIcon from '@mui/icons-material/Replay';
-import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material';
-import Typography from '@mui/material/Typography';
-import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
+/* eslint-disable node/no-unpublished-import */
+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, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { NumListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
-import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from '../FieldView';
-import './PhysicsSimulationBox.scss';
-import InputField from './PhysicsSimulationInputField';
-import questions from './PhysicsSimulationQuestions.json';
-import tutorials from './PhysicsSimulationTutorial.json';
-import Wall from './PhysicsSimulationWall';
-import Weight from './PhysicsSimulationWeight';
-import { Docs } from '../../../documents/Documents';
-import { DocumentType } from '../../../documents/DocumentTypes';
+// eslint-disable-next-line import/no-relative-packages
+import '../../../node_modules/browndash-components/dist/styles/global.min.css';
+import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
+import { DocCast, StrCast, toList } from '../../fields/Types';
+import { DocServer } from '../DocServer';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
+import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
+import { CalendarManager } from '../util/CalendarManager';
+import { CaptureManager } from '../util/CaptureManager';
+import { DocumentManager } from '../util/DocumentManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
+import { GroupManager } from '../util/GroupManager';
+import { HistoryUtil } from '../util/History';
+import { Hypothesis } from '../util/HypothesisUtils';
+import { UPDATE_SERVER_CACHE } from '../util/LinkManager';
+import { RTFMarkup } from '../util/RTFMarkup';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { ServerStats } from '../util/ServerStats';
+import { SettingsManager } from '../util/SettingsManager';
+import { SharingManager } from '../util/SharingManager';
+import { SnappingManager } from '../util/SnappingManager';
+import { Transform } from '../util/Transform';
+import { ReportManager } from '../util/reportManager/ReportManager';
+import { ComponentDecorations } from './ComponentDecorations';
+import { ContextMenu } from './ContextMenu';
+import { DashboardView } from './DashboardView';
+import { DictationOverlay } from './DictationOverlay';
+import { DocumentDecorations } from './DocumentDecorations';
+import { GestureOverlay } from './GestureOverlay';
+import { LightboxView } from './LightboxView';
+import './MainView.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { PreviewCursor } from './PreviewCursor';
+import { PropertiesView } from './PropertiesView';
+import { DashboardStyleProvider, DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider';
+import { TimelineMenu } from './animationtimeline/TimelineMenu';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionMenu } from './collections/CollectionMenu';
+import { TabDocView } from './collections/TabDocView';
+import './collections/TreeView.scss';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHandler';
+import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import { CollectionLinearView } from './collections/collectionLinear';
+import { LinkMenu } from './linking/LinkMenu';
+import { AudioBox } from './nodes/AudioBox';
+import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
+import { DocButtonState } from './nodes/DocumentLinksButton';
+import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
+import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
+import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
+import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
+import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
+import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
+import { TaskCompletionBox } from './nodes/TaskCompletedBox';
+import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
+import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
+import { PresBox } from './nodes/trails';
+import { AnchorMenu } from './pdf/AnchorMenu';
+import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
+import { TopBar } from './topbar/TopBar';
+import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
 
-interface IWallProps {
-    length: number;
-    xPos: number;
-    yPos: number;
-    angleInDegrees: number;
-}
-interface IForce {
-    description: string;
-    magnitude: number;
-    directionInDegrees: number;
-}
-interface VectorTemplate {
-    top: number;
-    left: number;
-    width: number;
-    height: number;
-    x1: number;
-    y1: number;
-    x2: number;
-    y2: number;
-    weightX: number;
-    weightY: number;
-}
-interface QuestionTemplate {
-    questionSetup: string[];
-    variablesForQuestionSetup: string[];
-    question: string;
-    answerParts: string[];
-    answerSolutionDescriptions: string[];
-    goal: string;
-    hints: { description: string; content: string }[];
-}
-
-interface TutorialTemplate {
-    question: string;
-    steps: {
-        description: string;
-        content: string;
-        forces: {
-            description: string;
-            magnitude: number;
-            directionInDegrees: number;
-            component: boolean;
-        }[];
-        showMagnitude: boolean;
-    }[];
-}
+const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
-export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(PhysicsSimulationBox, fieldKey);
-    }
-
-    _widthDisposer: IReactionDisposer | undefined;
-    @observable _simReset = 0;
-
-    // semi-Constants
-    xMin = 0;
-    yMin = 0;
-    xMax = this._props.PanelWidth() * 0.6;
-    yMax = this._props.PanelHeight();
-    color = `rgba(0,0,0,0.5)`;
-    radius = 50;
-    wallPositions: IWallProps[] = [];
-
-    @computed get circularMotionRadius() {
-        return (NumCast(this.dataDoc.circularMotionRadius, 150) * this._props.PanelWidth()) / 1000;
-    }
-    @computed get gravity() {
-        return NumCast(this.dataDoc.simulation_gravity, -9.81);
-    }
-    @computed get simulationType() {
-        return StrCast(this.dataDoc.simulation_type, 'Inclined Plane');
-    }
-    @computed get simulationMode() {
-        return StrCast(this.dataDoc.simulation_mode, 'Freeform');
-    }
-    // Used for spring simulation
-    @computed get springConstant() {
-        return NumCast(this.dataDoc.spring_constant, 0.5);
-    }
-    @computed get springLengthRest() {
-        return NumCast(this.dataDoc.spring_lengthRest, 200);
-    }
-    @computed get springLengthStart() {
-        return NumCast(this.dataDoc.spring_lengthStart, 200);
-    }
-
-    @computed get pendulumAngle() {
-        return NumCast(this.dataDoc.pendulum_angle);
-    }
-    @computed get pendulumAngleStart() {
-        return NumCast(this.dataDoc.pendulum_angleStart);
-    }
-    @computed get pendulumLength() {
-        return NumCast(this.dataDoc.pendulum_length);
-    }
-    @computed get pendulumLengthStart() {
-        return NumCast(this.dataDoc.pendulum_lengthStart);
-    }
+export class MainView extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
+    public static Instance: MainView;
+    public static Live: boolean = false;
+    private _docBtnRef = React.createRef<HTMLDivElement>();
 
-    // Used for wedge simulation
-    @computed get wedgeAngle() {
-        return NumCast(this.dataDoc.wedge_angle, 26);
-    }
-    @computed get wedgeHeight() {
-        return NumCast(this.dataDoc.wedge_height, Math.tan((26 * Math.PI) / 180) * this.xMax * 0.5);
-    }
-    @computed get wedgeWidth() {
-        return NumCast(this.dataDoc.wedge_width, this.xMax * 0.5);
-    }
-    @computed get mass1() {
-        return NumCast(this.dataDoc.mass1, 1);
-    }
-    @computed get mass2() {
-        return NumCast(this.dataDoc.mass2, 1);
+    @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 = Doc.MyLeftSidebarPanel;
+    @observable private _leftMenuFlyoutWidth: number = 0;
+    @computed get _hideUI() {
+        return this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking;
     }
 
-    @computed get mass1Radius() {
-        return NumCast(this.dataDoc.mass1_radius, 30);
-    }
-    @computed get mass1PosXStart() {
-        return NumCast(this.dataDoc.mass1_positionXstart);
-    }
-    @computed get mass1PosYStart() {
-        return NumCast(this.dataDoc.mass1_positionYstart);
+    @computed private get dashboardTabHeight() {
+        return this._hideUI ? 0 : 27;
+    } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
+    @computed private get topOfDashUI() {
+        return this._hideUI || DocumentView.LightboxDoc() ? 0 : Number(TOPBAR_HEIGHT.replace('px', ''));
     }
-    @computed get mass1VelXStart() {
-        return NumCast(this.dataDoc.mass1_velocityXstart);
+    @computed private get topOfHeaderBarDoc() {
+        return this.topOfDashUI;
     }
-    @computed get mass1VelYStart() {
-        return NumCast(this.dataDoc.mass1_velocityYstart);
+    @computed private get topOfSidebarDoc() {
+        return this.topOfDashUI + this.topMenuHeight();
     }
-
-    @computed get mass2PosXStart() {
-        return NumCast(this.dataDoc.mass2_positionXstart);
-    }
-    @computed get mass2PosYStart() {
-        return NumCast(this.dataDoc.mass2_positionYstart);
+    @computed private get topOfMainDoc() {
+        return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight();
     }
-    @computed get mass2VelXStart() {
-        return NumCast(this.dataDoc.mass2_velocityXstart);
+    @computed private get topOfMainDocContent() {
+        return this.topOfMainDoc + this.dashboardTabHeight;
     }
-    @computed get mass2VelYStart() {
-        return NumCast(this.dataDoc.mass2_velocityYstart);
-    }
-
-    @computed get selectedQuestion() {
-        return this.dataDoc.selectedQuestion ? (JSON.parse(StrCast(this.dataDoc.selectedQuestion)) as QuestionTemplate) : questions.inclinePlane[0];
+    @computed private get leftScreenOffsetOfMainDocView() {
+        return this.leftMenuWidth() - 2;
     }
-    @computed get tutorial() {
-        return this.dataDoc.tutorial ? (JSON.parse(StrCast(this.dataDoc.tutorial)) as TutorialTemplate) : tutorials.inclinePlane;
+    @computed private get userDoc() {
+        return Doc.UserDoc();
     }
-    @computed get selectedSolutions() {
-        return NumListCast(this.dataDoc.selectedSolutions);
+    @observable mainDoc: Opt<Doc> = undefined;
+    @computed private get mainContainer() {
+        if (window.location.pathname.startsWith('/doc/') && ClientUtils.CurrentUserEmail() === 'guest') {
+            DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main =>
+                runInAction(() => {
+                    this.mainDoc = main as Doc;
+                })
+            );
+            return this.mainDoc;
+        }
+        return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
     }
-    @computed get questionPartOne() {
-        return StrCast(this.dataDoc.questionPartOne);
+    @computed private get headerBarDoc() {
+        return Doc.MyHeaderBar;
     }
-    @computed get questionPartTwo() {
-        return StrCast(this.dataDoc.questionPartTwo);
+    @computed public get mainFreeform(): Opt<Doc> {
+        return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data));
     }
+    @observable public headerBarHeight: number = 0;
+    headerBarHeightFunc = () => this.headerBarHeight;
 
-    componentWillUnmount() {
-        this._widthDisposer?.();
-    }
+    @action
+    toggleTopBar = () => {
+        if (this.headerBarHeight > 0) {
+            this.headerBarHeight = 0;
+        } else {
+            this.headerBarHeight = 60;
+        }
+    };
+    headerBarDocWidth = () => this.mainDocViewWidth();
+    headerBarDocHeight = () => (this._hideUI ? 0 : this.headerBarHeight ?? 0);
+    topMenuHeight = () => (this._hideUI ? 0 : 35);
+    topMenuWidth = returnZero; // value is ignored ...
+    leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
+    leftMenuHeight = () => this._dashUIHeight;
+    leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
+    leftMenuFlyoutHeight = () => this._dashUIHeight;
+    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SnappingManager.PropertiesWidth || 0));
+    propertiesHeight = () => this._dashUIHeight;
+    mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
+    mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
 
     componentDidMount() {
-        // Setup and update simulation
-        this._widthDisposer = reaction(() => [this._props.PanelWidth(), this._props.PanelHeight()], this.setupSimulation, { fireImmediately: true });
+        // Utils.TraceConsoleLog();
+        reaction(
+            // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
+            () => DocumentView.Selected().slice(),
+            views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur()
+        );
+        reaction(
+            () => Doc.MyDockedBtns.linearView_IsOpen,
+            open => SnappingManager.SetPrintToConsole(!!open)
+        );
+        const scriptTag = document.createElement('script');
+        scriptTag.setAttribute('type', 'text/javascript');
+        scriptTag.setAttribute('src', 'https://www.bing.com/api/maps/mapcontrol?callback=makeMap');
+        scriptTag.async = true;
+        scriptTag.defer = true;
+        document.body.appendChild(scriptTag);
+        document.getElementById('root')?.addEventListener('scroll', () =>
+            (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(() => {
+                prog.style.transition = '1s';
+                prog.style.width = '100%';
+            }, 0);
+            setTimeout(() => {
+                ele.outerHTML = '';
+            }, 1000);
+        }
+        this._sidebarContent.proto = undefined;
+        if (!MainView.Live) {
+            DocServer.setLivePlaygroundFields([
+                'dataTransition',
+                'viewTransition',
+                'treeView_Open',
+                'treeView_ExpandedView',
+                'carousel_index',
+                'itemIndex', // for changing slides in presentations
+                'layout_sidebarWidthPercent',
+                'layout_currentTimecode',
+                'layout_timelineHeightPercent',
+                'layout_hideMinimap',
+                'layout_showSidebar',
+                'layout_scrollTop',
+                'layout_fitWidth',
+                'layout_curPage',
+                'presStatus',
+                'freeform_panX',
+                'freeform_panY',
+                'freeform_scale',
+                'overlayX',
+                'overlayY',
+                'text_scrollHeight',
+                'text_height',
+                'hidden',
+                // 'type_collection',
+                'chromeHidden',
+                'currentFrame',
+            ]); // can play with these fields on someone else's
+        }
 
-        // Create walls
-        this.wallPositions = [
-            { length: 100, xPos: 0, yPos: 0, angleInDegrees: 0 },
-            { length: 100, xPos: 0, yPos: 100, angleInDegrees: 0 },
-            { length: 100, xPos: 0, yPos: 0, angleInDegrees: 90 },
-            { length: 100, xPos: (this.xMax / this._props.PanelWidth()) * 100, yPos: 0, angleInDegrees: 90 },
-        ];
+        const tag = document.createElement('script');
+        tag.src = 'https://www.youtube.com/iframe_api';
+        const firstScriptTag = document.getElementsByTagName('script')[0];
+        firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
+        document.addEventListener('dash', (e: any) => {
+            // event used by chrome plugin to tell Dash which document to focus on
+            const id = GetDocFromUrl(e.detail);
+            DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentView.showDocument(doc, { willPan: false }) : null));
+        });
+        document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
+        this.initEventListeners();
     }
 
-    componentDidUpdate(prevProps: Readonly<FieldViewProps>) {
-        super.componentDidUpdate(prevProps);
-        if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax !== this._props.PanelHeight()) {
-            this.xMax = this._props.PanelWidth() * 0.6;
-            this.yMax = this._props.PanelHeight();
-            this.setupSimulation();
-        }
+    componentWillUnMount() {
+        // window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+        // window.removeEventListener('keydown', KeyManager.Instance.handle);
+        // window.removeEventListener('pointerdown', this.globalPointerDown, true);
+        // window.removeEventListener('pointermove', this.globalPointerMove, true);
+        // window.removeEventListener('pointerup', this.globalPointerClick, true);
+        // window.removeEventListener('paste', KeyManager.Instance.paste as any);
+        // document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
     }
 
-    gravityForce = (mass: number): IForce => ({
-        description: 'Gravity',
-        magnitude: mass * Math.abs(this.gravity),
-        directionInDegrees: 270,
-    });
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+        DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
+        MainView.Instance = this;
+        DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
 
-    @action
-    setupSimulation = () => {
-        const { simulationType } = this;
-        const mode = this.simulationMode;
-        this.dataDoc.simulation_paused = true;
-        if (simulationType !== 'Circular Motion') {
-            this.dataDoc.mass1_velocityXstart = 0;
-            this.dataDoc.mass1_velocityYstart = 0;
-            this.dataDoc.mass1_velocityX = 0;
-            this.dataDoc.mass1_velocityY = 0;
-        }
-        if (mode === 'Freeform') {
-            this.dataDoc.simulation_showForceMagnitudes = true;
-            // prettier-ignore
-            switch (simulationType) {
-                case 'One Weight': 
-                    this.dataDoc.simulation_showComponentForces = false;
-                    this.dataDoc.mass1_positionYstart = this.yMin + this.mass1Radius;
-                    this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-                    this.dataDoc.mass1_positionY = this.getDisplayYPos(this.yMin + this.mass1Radius);
-                    this.dataDoc.mass1_positionX = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-                    this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]);
-                    break;
-                case 'Inclined Plane':  this.setupInclinedPlane(); break;
-                case 'Pendulum':        this.setupPendulum(); break;
-                case 'Spring':          this.setupSpring(); break;
-                case 'Circular Motion': this.setupCircular(20); break;
-                case 'Pulley':          this.setupPulley(); break;
-                case 'Suspension':      this.setupSuspension();break;
-                default:
-            }
-            this._simReset++;
-        } else if (mode === 'Review') {
-            this.dataDoc.simulation_showComponentForces = false;
-            this.dataDoc.simulation_showForceMagnitudes = true;
-            this.dataDoc.simulation_showAcceleration = false;
-            this.dataDoc.simulation_showVelocity = false;
-            this.dataDoc.simulation_showForces = true;
-            this.generateNewQuestion();
-            // prettier-ignore
-            switch (simulationType) {
-                case 'One Weight' : break;// TODO - one weight review problems
-                case 'Spring':  this.setupSpring(); break; // TODO - spring review problems
-                case 'Inclined Plane': this.dataDoc.mass1_forcesUpdated =  this.dataDoc.mass1_forcesStart = ''; break;
-                case 'Pendulum':  this.setupPendulum(); break;  // TODO - pendulum review problems
-                case 'Circular Motion':  this.setupCircular(0); break;  // TODO - circular motion review problems
-                case 'Pulley':  this.setupPulley(); break; // TODO - pulley tutorial review problems
-                case 'Suspension': this.setupSuspension(); break;  // TODO - suspension tutorial review problems
-                default:
-            }
-        } else if (mode === 'Tutorial') {
-            this.dataDoc.simulation_showComponentForces = false;
-            this.dataDoc.tutorial_stepNumber = 0;
-            this.dataDoc.simulation_showAcceleration = false;
-            if (this.simulationType !== 'Circular Motion') {
-                this.dataDoc.mass1_velocityX = 0;
-                this.dataDoc.mass1_velocityY = 0;
-                this.dataDoc.simulation_showVelocity = false;
-            } else {
-                this.dataDoc.mass1_velocityX = 20;
-                this.dataDoc.mass1_velocityY = 0;
-                this.dataDoc.simulation_showVelocity = true;
-            }
+        // causes errors to be generated when modifying an observable outside of an action
+        configure({ enforceActions: 'observed' });
 
-            switch (this.simulationType) {
-                case 'One Weight':
-                    this.dataDoc.simulation_showForces = true;
-                    this.dataDoc.mass1_positionYstart = this.yMax - 100;
-                    this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.freeWeight);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.freeWeight.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.freeWeight.steps[0].showMagnitude;
-                    break;
-                case 'Spring':
-                    this.dataDoc.simulation_showForces = true;
-                    this.setupSpring();
-                    this.dataDoc.mass1_positionYstart = this.yMin + 200 + 19.62;
-                    this.dataDoc.mass1_positionXstart = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.spring);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.spring.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.spring.steps[0].showMagnitude;
-                    break;
-                case 'Pendulum':
-                    this.setupPendulum();
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.pendulum);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.pendulum.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.pendulum.steps[0].showMagnitude;
-                    break;
-                case 'Inclined Plane':
-                    this.dataDoc.wedge_angle = 26;
-                    this.setupInclinedPlane();
-                    this.dataDoc.simulation_showForces = true;
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.inclinePlane);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.inclinePlane.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.inclinePlane.steps[0].showMagnitude;
-                    break;
-                case 'Circular Motion':
-                    this.dataDoc.simulation_showForces = true;
-                    this.setupCircular(40);
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.circular);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.circular.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.circular.steps[0].showMagnitude;
-                    break;
-                case 'Pulley':
-                    this.dataDoc.simulation_showForces = true;
-                    this.setupPulley();
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.pulley);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.pulley.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.pulley.steps[0].showMagnitude;
-                    break;
-                case 'Suspension':
-                    this.dataDoc.simulation_showForces = true;
-                    this.setupSuspension();
-                    this.dataDoc.tutorial = JSON.stringify(tutorials.suspension);
-                    this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.suspension.steps[0].forces);
-                    this.dataDoc.simulation_showForceMagnitudes = tutorials.suspension.steps[0].showMagnitude;
-                    break;
-                default:
+        if (window.location.pathname !== '/home') {
+            const pathname = window.location.pathname.substr(1).split('/');
+            if (pathname.length > 1 && pathname[0] === 'doc') {
+                DocServer.GetRefField(pathname[1]).then(
+                    action(field => {
+                        if (field instanceof Doc && field._type_collection !== CollectionViewType.Docking) {
+                            Doc.GuestTarget = field;
+                        }
+                    })
+                );
             }
-            this._simReset++;
         }
-    };
 
-    // Helper function to go between display and real values
-    getDisplayYPos = (yPos: number) => this.yMax - yPos - 2 * this.mass1Radius + 5;
-    getYPosFromDisplay = (yDisplay: number) => this.yMax - yDisplay - 2 * this.mass1Radius + 5;
+        library.add(
+            ...[
+                fa.faExclamationCircle,
+                fa.faEdit,
+                fa.faArrowDownShortWide,
+                fa.faTrash,
+                fa.faTrashAlt,
+                fa.faShare,
+                fa.faTaxi,
+                fa.faDownload,
+                fa.faPallet,
+                fa.faExpandArrowsAlt,
+                fa.faAmbulance,
+                fa.faLayerGroup,
+                fa.faExternalLinkAlt,
+                fa.faCalendar,
+                fa.faSquare,
+                far.faSquare as any,
+                fa.faConciergeBell,
+                fa.faWindowRestore,
+                fa.faFolder,
+                fa.faFolderOpen,
+                fa.faFolderPlus,
+                fa.faFolderClosed,
+                fa.faBook,
+                fa.faMapPin,
+                fa.faMapMarker,
+                fa.faFingerprint,
+                fa.faCrosshairs,
+                fa.faDesktop,
+                fa.faUnlock,
+                fa.faLock,
+                fa.faLaptopCode,
+                fa.faMale,
+                fa.faCopy,
+                fa.faHome,
+                fa.faHandPointLeft,
+                fa.faHandPointRight,
+                fa.faCompass,
+                fa.faSnowflake,
+                fa.faStar,
+                fa.faSplotch,
+                fa.faMicrophone,
+                fa.faCircleHalfStroke,
+                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.faIdCard,
+                fa.faAddressCard,
+                fa.faQuestionCircle,
+                fa.faArrowLeft,
+                fa.faArrowRight,
+                fa.faArrowDown,
+                fa.faArrowUp,
+                fa.faBolt,
+                fa.faBullseye,
+                fa.faTurnUp,
+                fa.faTurnDown,
+                fa.faCaretUp,
+                fa.faCat,
+                fa.faCheck,
+                fa.faChevronRight,
+                fa.faChevronLeft,
+                fa.faChevronDown,
+                fa.faChevronUp,
+                fa.faClone,
+                fa.faCloudUploadAlt,
+                fa.faCommentAlt,
+                fa.faCommentDots,
+                fa.faCompressArrowsAlt,
+                fa.faCut,
+                fa.faEllipsisV,
+                fa.faEraser,
+                fa.faDeleteLeft,
+                fa.faXmarksLines,
+                fa.faCircleXmark,
+                fa.faXmark,
+                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.faArrowsLeftRight,
+                fa.faPause,
+                fa.faPen,
+                fa.faUserPen,
+                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.faVideoSlash,
+                fa.faVideo,
+                fa.faAsterisk,
+                fa.faBrain,
+                fa.faImage,
+                fa.faPaintBrush,
+                fa.faTimes,
+                fa.faFlag,
+                fa.faScroll,
+                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.faBarsStaggered,
+                fa.faBrush,
+                fa.faShapes,
+                fa.faEllipsisH,
+                fa.faHandPaper,
+                fa.faMap,
+                fa.faUser,
+                faHireAHelper as any,
+                fa.faTrashRestore,
+                fa.faUsers,
+                fa.faWrench,
+                fa.faCog,
+                fa.faMap,
+                fa.faBellSlash,
+                fa.faExpandAlt,
+                fa.faArchive,
+                fa.faBezierCurve,
+                fa.faCircle,
+                far.faCircle as any,
+                fa.faLongArrowAltRight,
+                fa.faPenFancy,
+                fa.faAngleDoubleRight,
+                fa.faAngleDoubleDown,
+                fa.faAngleDoubleLeft,
+                fa.faAngleDoubleUp,
+                faBuffer as any,
+                fa.faExpand,
+                fa.faUndo,
+                fa.faSlidersH,
+                fa.faAngleUp,
+                fa.faAngleDown,
+                fa.faPlayCircle,
+                fa.faClock,
+                fa.faRoute,
+                fa.faRocket,
+                fa.faExchangeAlt,
+                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.faTableCells,
+                fa.faTableColumns,
+                fa.faTh,
+                fa.faThList,
+                fa.faProjectDiagram,
+                fa.faSignature,
+                fa.faColumns,
+                fa.faChevronCircleUp,
+                fa.faUpload,
+                fa.faBorderAll,
+                fa.faBraille,
+                fa.faPersonChalkboard,
+                fa.faChalkboard,
+                fa.faPencilAlt,
+                fa.faEyeSlash,
+                fa.faSmile,
+                fa.faIndent,
+                fa.faOutdent,
+                fa.faChartBar,
+                fa.faBan,
+                fa.faPhoneSlash,
+                fa.faGripLines,
+                fa.faSave,
+                fa.faBook,
+                fa.faBookmark,
+                fa.faList,
+                fa.faListOl,
+                fa.faLightbulb,
+                fa.faBookOpen,
+                fa.faMapMarkerAlt,
+                fa.faSearchPlus,
+                fa.faSolarPanel,
+                fa.faVolumeUp,
+                fa.faVolumeDown,
+                fa.faSquareRootAlt,
+                fa.faVolumeMute,
+                fa.faUserCircle,
+                fa.faHeart,
+                fa.faHeartBroken,
+                fa.faHighlighter,
+                fa.faRemoveFormat,
+                fa.faHandPointUp,
+                fa.faXRay,
+                fa.faZ,
+                fa.faArrowsUpToLine,
+                fa.faArrowsDownToLine,
+                fa.faPalette,
+                fa.faHourglassHalf,
+                fa.faRobot,
+                fa.faSatellite,
+                fa.faStar,
+            ]
+        );
+    }
 
-    // Update forces when coefficient of static friction changes in freeform mode
-    updateForcesWithFriction = (coefficient: number, width = this.wedgeWidth, height = this.wedgeHeight) => {
-        const normalForce: IForce = {
-            description: 'Normal Force',
-            magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
-            directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
-        };
-        const frictionForce: IForce = {
-            description: 'Static Friction Force',
-            magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
-            directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI,
-        };
-        // reduce magnitude or friction force if necessary such that block cannot slide up plane
-        let yForce = -Math.abs(this.gravity) * this.mass1;
-        yForce += normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180);
-        yForce += frictionForce.magnitude * Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
-        if (yForce > 0) {
-            frictionForce.magnitude = (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + Math.abs(this.gravity) * this.mass1) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
+    private longPressTimer: NodeJS.Timeout | undefined;
+    globalPointerClick = action(() => {
+        this.longPressTimer && clearTimeout(this.longPressTimer);
+        DocumentView.LongPress = false;
+    });
+    globalPointerMove = action((e: PointerEvent) => {
+        if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer);
+    });
+    globalPointerDown = action((e: PointerEvent) => {
+        DocumentView.LongPress = false;
+        this.longPressTimer = setTimeout(
+            action(() => {
+                DocumentView.LongPress = true;
+            }),
+            1000
+        );
+        DocumentManager.removeOverlayViews();
+        Doc.linkFollowUnhighlight();
+        AudioBox.Enabled = true;
+        const targets = document.elementsFromPoint(e.x, e.y);
+        if (targets.length) {
+            let targClass = targets[0].className.toString();
+            for (let i = 0; i < targets.length - 1; i++) {
+                if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
+                else break;
+            }
+            !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
+            !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
         }
+    });
 
-        const normalForceComponent: IForce = {
-            description: 'Normal Force',
-            magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)),
-            directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
-        };
-        const gravityParallel: IForce = {
-            description: 'Gravity Parallel Component',
-            magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(Math.PI / 2 - Math.atan(height / width)),
-            directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI + 180,
-        };
-        const gravityPerpendicular: IForce = {
-            description: 'Gravity Perpendicular Component',
-            magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(Math.PI / 2 - Math.atan(height / width)),
-            directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI,
-        };
-        const gravityForce = this.gravityForce(this.mass1);
-        if (coefficient !== 0) {
-            this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce, frictionForce]);
-            this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce, frictionForce]);
-            this.dataDoc.mass1_componentForces = JSON.stringify([frictionForce, normalForceComponent, gravityParallel, gravityPerpendicular]);
-        } else {
-            this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce]);
-            this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce]);
-            this.dataDoc.mass1_componentForces = JSON.stringify([normalForceComponent, gravityParallel, gravityPerpendicular]);
-        }
+    initEventListeners = () => {
+        window.addEventListener('beforeunload', UPDATE_SERVER_CACHE);
+        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('pointerdown', this.globalPointerDown, true);
+        document.addEventListener('pointermove', this.globalPointerMove, true);
+        document.addEventListener('pointerup', this.globalPointerClick, true);
+        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')) {
+                        DocumentView.DeselectAll();
+                    }
+                }
+            },
+            false
+        );
+        document.oncontextmenu = () => false;
     };
 
-    // Change wedge height and width and weight position to match new wedge angle
-    changeWedgeBasedOnNewAngle = (angle: number) => {
-        const radAng = (angle * Math.PI) / 180;
-        this.dataDoc.wedge_width = this.xMax * 0.5;
-        this.dataDoc.wedge_height = Math.tan(radAng) * this.dataDoc.wedge_width;
-
-        // update weight position based on updated wedge width/height
-        const yPos = this.yMax - this.dataDoc.wedge_height - this.mass1Radius * Math.cos(radAng) - this.mass1Radius;
-        const xPos = this.xMax * 0.25 + this.mass1Radius * Math.sin(radAng) - this.mass1Radius;
-
-        this.dataDoc.mass1_positionXstart = xPos;
-        this.dataDoc.mass1_positionYstart = yPos;
-        if (this.simulationMode === 'Freeform') {
-            this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction), this.dataDoc.wedge_width, Math.tan(radAng) * this.dataDoc.wedge_width);
-        }
+    @action
+    createNewPresentation = () => {
+        const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
+        CollectionDockingView.AddSplit(pres, OpenWhereMod.right);
+        Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', pres); // Doc.MyTrails should be created in createDashboard
+        Doc.ActivePresentation = pres;
     };
 
-    // In review mode, update forces when coefficient of static friction changed
-    updateReviewForcesBasedOnCoefficient = (coefficient: number) => {
-        let theta = this.wedgeAngle;
-        const index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45');
-        if (index >= 0) {
-            theta = NumListCast(this.dataDoc.questionVariables)[index];
-        }
-        if (isNaN(theta)) {
-            return;
-        }
-        this.dataDoc.review_GravityMagnitude = Math.abs(this.gravity);
-        this.dataDoc.review_GravityAngle = 270;
-        this.dataDoc.review_NormalMagnitude = Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180);
-        this.dataDoc.review_NormalAngle = 90 - theta;
-        let yForce = -Math.abs(this.gravity);
-        yForce += Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180) * Math.sin(((90 - theta) * Math.PI) / 180);
-        yForce += coefficient * Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180) * Math.sin(((180 - theta) * Math.PI) / 180);
-        let friction = coefficient * Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180);
-        if (yForce > 0) {
-            friction = (-(Math.abs(this.gravity) * Math.cos((theta * Math.PI) / 180)) * Math.sin(((90 - theta) * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin(((180 - theta) * Math.PI) / 180);
+    @action
+    openPresentation = (pres: Doc) => {
+        if (pres.type === DocumentType.PRES) {
+            CollectionDockingView.AddSplit(pres, OpenWhereMod.right, undefined, PresBox.PanelName);
+            Doc.MyTrails && (Doc.ActivePresentation = pres);
+            Doc.AddDocToList(Doc.MyTrails, 'data', pres);
+            this.closeFlyout();
         }
-        this.dataDoc.review_StaticMagnitude = friction;
-        this.dataDoc.review_StaticAngle = 180 - theta;
     };
 
-    // In review mode, update forces when wedge angle changed
-    updateReviewForcesBasedOnAngle = (angle: number) => {
-        this.dataDoc.review_GravityMagnitude = Math.abs(this.gravity);
-        this.dataDoc.review_GravityAngle = 270;
-        this.dataDoc.review_NormalMagnitude = Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180);
-        this.dataDoc.review_NormalAngle = 90 - angle;
-        let yForce = -Math.abs(this.gravity);
-        yForce += Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180) * Math.sin(((90 - angle) * Math.PI) / 180);
-        yForce += NumCast(this.dataDoc.review_Coefficient) * Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180) * Math.sin(((180 - angle) * Math.PI) / 180);
-        let friction = NumCast(this.dataDoc.review_Coefficient) * Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180);
-        if (yForce > 0) {
-            friction = (-(Math.abs(this.gravity) * Math.cos((angle * Math.PI) / 180)) * Math.sin(((90 - angle) * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin(((180 - angle) * Math.PI) / 180);
-        }
-        this.dataDoc.review_StaticMagnitude = friction;
-        this.dataDoc.review_StaticAngle = 180 - angle;
+    @action
+    createNewFolder = async () => {
+        const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
+        Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
     };
 
-    // Solve for the correct answers to the generated problem
-    getAnswersToQuestion = (question: QuestionTemplate, questionVars: number[]) => {
-        const solutions: number[] = [];
+    waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
+    headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
+    mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
+    addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+    removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
+    @computed get headerBarDocView() {
+        return (
+            <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
+                <DocumentView
+                    key="headerBarDoc"
+                    Document={this.headerBarDoc}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
+                    pinToPres={emptyFunction}
+                    containerViewPath={returnEmptyDoclist}
+                    styleProvider={DefaultStyleProvider}
+                    addDocument={this.addHeaderDoc}
+                    removeDocument={this.removeHeaderDoc}
+                    fitContentsToBox={returnTrue}
+                    isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
+                    isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
+                    ScreenToLocalTransform={this.headerBarScreenXf}
+                    childHideResizeHandles
+                    childDragAction={dropActionType.move}
+                    dontRegisterView
+                    hideResizeHandles
+                    PanelWidth={this.headerBarDocWidth}
+                    PanelHeight={this.headerBarDocHeight}
+                    renderDepth={0}
+                    focus={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                />
+            </div>
+        );
+    }
+    @computed get mainDocView() {
+        const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
+        return (
+            <>
+                {headerBar}
+                <DocumentView
+                    key="main"
+                    Document={this.mainContainer!}
+                    addDocument={undefined}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
+                    pinToPres={emptyFunction}
+                    containerViewPath={returnEmptyDoclist}
+                    styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
+                    isContentActive={returnTrue}
+                    removeDocument={undefined}
+                    ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
+                    PanelWidth={this.mainDocViewWidth}
+                    PanelHeight={this.mainDocViewHeight}
+                    focus={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    suppressSetHeight
+                    renderDepth={this._hideUI ? 0 : -1}
+                />
+            </>
+        );
+    }
 
-        let theta = this.wedgeAngle;
-        let index = question.variablesForQuestionSetup.indexOf('theta - max 45');
-        if (index >= 0) {
-            theta = questionVars[index];
-        }
-        let muS: number = NumCast(this.dataDoc.coefficientOfStaticFriction);
-        index = question.variablesForQuestionSetup.indexOf('coefficient of static friction');
-        if (index >= 0) {
-            muS = questionVars[index];
-        }
+    @computed get dockingContent() {
+        return (
+            <GestureOverlay isActive={!DocumentView.LightboxDoc()}>
+                <div
+                    key="docking"
+                    className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
+                    onDrop={e => {
+                        e.stopPropagation();
+                        e.preventDefault();
+                    }}
+                    style={{
+                        width: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
+                        minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
+                        transform: DocumentView.LightboxDoc() ? 'scale(0.0001)' : undefined,
+                    }}>
+                    {!this.mainContainer ? null : this.mainDocView}
+                </div>
+            </GestureOverlay>
+        );
+    }
 
-        for (let i = 0; i < question.answerSolutionDescriptions.length; i++) {
-            const description = question.answerSolutionDescriptions[i];
-            if (!isNaN(NumCast(description))) {
-                solutions.push(NumCast(description));
-            } else if (description === 'solve normal force angle from wedge angle') {
-                solutions.push(90 - theta);
-            } else if (description === 'solve normal force magnitude from wedge angle') {
-                solutions.push(Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI));
-            } else if (description === 'solve static force magnitude from wedge angle given equilibrium') {
-                const normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
-                const normalForceAngle = 90 - theta;
-                const frictionForceAngle = 180 - theta;
-                const frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
-                solutions.push(frictionForceMagnitude);
-            } else if (description === 'solve static force angle from wedge angle given equilibrium') {
-                solutions.push(180 - theta);
-            } else if (description === 'solve minimum static coefficient from wedge angle given equilibrium') {
-                const normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
-                const normalForceAngle = 90 - theta;
-                const frictionForceAngle = 180 - theta;
-                const frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
-                const frictionCoefficient = frictionForceMagnitude / normalForceMagnitude;
-                solutions.push(frictionCoefficient);
-            } else if (description === 'solve maximum wedge angle from coefficient of static friction given equilibrium') {
-                solutions.push((Math.atan(muS) * 180) / Math.PI);
-            }
-        }
-        this.dataDoc.selectedSolutions = new List<number>(solutions);
-        return solutions;
+    @action
+    onPropertiesPointerDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            action(() => {
+                SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - e.clientX));
+                return !SnappingManager.PropertiesWidth;
+            }),
+            action(() => {
+                SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+            }),
+            action(() => {
+                SnappingManager.SetPropertiesWidth(this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0);
+            }),
+            false
+        );
     };
 
-    // In review mode, check if input answers match correct answers and optionally generate alert
-    checkAnswers = (showAlert: boolean = true) => {
-        let error: boolean = false;
-        const epsilon: number = 0.01;
-        if (this.selectedQuestion) {
-            for (let i = 0; i < this.selectedQuestion.answerParts.length; i++) {
-                if (this.selectedQuestion.answerParts[i] === 'force of gravity') {
-                    if (Math.abs(NumCast(this.dataDoc.review_GravityMagnitude) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'angle of gravity') {
-                    if (Math.abs(NumCast(this.dataDoc.review_GravityAngle) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'normal force') {
-                    if (Math.abs(NumCast(this.dataDoc.review_NormalMagnitude) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'angle of normal force') {
-                    if (Math.abs(NumCast(this.dataDoc.review_NormalAngle) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'force of static friction') {
-                    if (Math.abs(NumCast(this.dataDoc.review_StaticMagnitude) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'angle of static friction') {
-                    if (Math.abs(NumCast(this.dataDoc.review_StaticAngle) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'coefficient of static friction') {
-                    if (Math.abs(NumCast(this.dataDoc.coefficientOfStaticFriction) - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                } else if (this.selectedQuestion.answerParts[i] === 'wedge angle') {
-                    if (Math.abs(this.wedgeAngle - this.selectedSolutions[i]) > epsilon) {
-                        error = true;
-                    }
-                }
-            }
-        }
-        if (showAlert) {
-            this.dataDoc.simulation_paused = false;
-            setTimeout(() => (this.dataDoc.simulation_paused = true), 3000);
-        }
-        if (this.selectedQuestion.goal === 'noMovement') {
-            this.dataDoc.noMovement = !error;
-        }
+    @action
+    onFlyoutPointerDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            action(ev => {
+                this._leftMenuFlyoutWidth = Math.max(ev.clientX - 58, 0);
+                return false;
+            }),
+            () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
+            this.closeFlyout
+        );
     };
 
-    // Reset all review values to default
-    resetReviewValuesToDefault = () => {
-        this.dataDoc.review_GravityMagnitude = 0;
-        this.dataDoc.review_GravityAngle = 0;
-        this.dataDoc.review_NormalMagnitude = 0;
-        this.dataDoc.review_NormalAngle = 0;
-        this.dataDoc.review_StaticMagnitude = 0;
-        this.dataDoc.review_StaticAngle = 0;
-        this.dataDoc.coefficientOfKineticFriction = 0;
-        this.dataDoc.simulation_paused = true;
+    sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
+    mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
+    static addDocTabFunc_impl = (docs: Doc | Doc[], location: OpenWhere): boolean => {
+        const doc = toList(docs).lastElement();
+        const whereFields = location.split(':');
+        const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
+        const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
+        const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
+        if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
+        switch (whereFields[0]) {
+            case OpenWhere.lightbox:   return LightboxView.Instance.AddDocTab(doc, location);
+            case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, TabDocView.DontSelectOnActivate); // bcz: hack!  mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them
+            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, undefined, panelName);
+            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue);
+        } // prettier-ignore
     };
 
-    // In review mode, reset problem variables and generate a new question
-    generateNewQuestion = () => {
-        this.resetReviewValuesToDefault();
-
-        const vars: number[] = [];
-        let question: QuestionTemplate = questions.inclinePlane[0];
-
-        if (this.simulationType === 'Inclined Plane') {
-            this.dataDoc.questionNumber = (NumCast(this.dataDoc.questionNumber) + 1) % questions.inclinePlane.length;
-            question = questions.inclinePlane[NumCast(this.dataDoc.questionNumber)];
+    @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}
+                        addDocument={undefined}
+                        addDocTab={DocumentViewInternal.addDocTabFunc}
+                        pinToPres={DocumentView.PinDoc}
+                        containerViewPath={returnEmptyDoclist}
+                        styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
+                        removeDocument={returnFalse}
+                        ScreenToLocalTransform={this.mainContainerXf}
+                        PanelWidth={this.leftMenuFlyoutWidth}
+                        PanelHeight={this.leftMenuFlyoutHeight}
+                        renderDepth={0}
+                        isContentActive={returnTrue}
+                        focus={emptyFunction}
+                        whenChildContentsActiveChanged={emptyFunction}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
+                        searchFilterDocs={returnEmptyDoclist}
+                    />
+                </div>
+                {this.docButtons}
+            </div>
+        );
+    }
 
-            let coefficient = 0;
-            let wedge_angle = 0;
+    @computed get leftMenuPanel() {
+        return (
+            <div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: DocumentView.LightboxDoc() ? 'none' : undefined }}>
+                <DocumentView
+                    Document={Doc.MyLeftSidebarMenu}
+                    addDocument={undefined}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
+                    pinToPres={emptyFunction}
+                    removeDocument={returnFalse}
+                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                    PanelWidth={this.leftMenuWidth}
+                    PanelHeight={this.leftMenuHeight}
+                    renderDepth={0}
+                    containerViewPath={returnEmptyDoclist}
+                    focus={emptyFunction}
+                    styleProvider={DefaultStyleProvider}
+                    isContentActive={returnTrue}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                />
+            </div>
+        );
+    }
 
-            for (let i = 0; i < question.variablesForQuestionSetup.length; i++) {
-                if (question.variablesForQuestionSetup[i] === 'theta - max 45') {
-                    const randValue = Math.floor(Math.random() * 44 + 1);
-                    vars.push(randValue);
-                    wedge_angle = randValue;
-                } else if (question.variablesForQuestionSetup[i] === 'coefficient of static friction') {
-                    const randValue = Math.round(Math.random() * 1000) / 1000;
-                    vars.push(randValue);
-                    coefficient = randValue;
-                }
-            }
-            this.dataDoc.wedge_angle = wedge_angle;
-            this.changeWedgeBasedOnNewAngle(wedge_angle);
-            this.dataDoc.coefficientOfStaticFriction = coefficient;
-            this.dataDoc.review_Coefficient = coefficient;
-        }
-        let q = '';
-        for (let i = 0; i < question.questionSetup.length; i++) {
-            q += question.questionSetup[i];
-            if (i !== question.questionSetup.length - 1) {
-                q += vars[i];
-                if (question.variablesForQuestionSetup[i].includes('theta')) {
-                    q += ' degree (≈' + Math.round((1000 * (vars[i] * Math.PI)) / 180) / 1000 + ' rad)';
-                }
+    @action
+    selectMenu = (button: Doc) => {
+        const title = StrCast(button[DocData].title);
+        const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
+        this.closeFlyout();
+        if (willOpen) {
+            switch ((this._panelContent = title)) {
+                case 'Settings':
+                    SettingsManager.Instance.openMgr();
+                    break;
+                case 'Help':
+                    break;
+                default:
+                    this.expandFlyout(button);
             }
         }
-        this.dataDoc.questionVariables = new List<number>(vars);
-        this.dataDoc.selectedQuestion = JSON.stringify(question);
-        this.dataDoc.questionPartOne = q;
-        this.dataDoc.questionPartTwo = question.question;
-        this.dataDoc.answers = new List<number>(this.getAnswersToQuestion(question, vars));
-        // this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset);
+        return true;
     };
 
-    // Default setup for uniform circular motion simulation
-    @action
-    setupCircular = (value: number) => {
-        this.dataDoc.simulation_showComponentForces = false;
-        this.dataDoc.mass1_velocityYstart = 0;
-        this.dataDoc.mass1_velocityXstart = value;
-        const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-        const yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius;
-        this.dataDoc.mass1_positionYstart = yPos;
-        this.dataDoc.mass1_positionXstart = xPos;
-        const tensionForce: IForce = {
-            description: 'Centripetal Force',
-            magnitude: (this.dataDoc.mass1_velocityXstart ** 2 * this.mass1) / this.circularMotionRadius,
-            directionInDegrees: 90,
-        };
-        this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce]);
-        this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce]);
-        this._simReset++;
-    };
+    @computed get mainInnerContent() {
+        const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
+        const width = this.propertiesWidth() + leftMenuFlyoutWidth;
+        return (
+            <>
+                {this._hideUI ? null : this.leftMenuPanel}
+                <div key="inner" className="mainView-innerContent">
+                    {this.flyout}
+                    <div
+                        className="mainView-libraryHandle"
+                        style={{ background: SnappingManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
+                        onPointerDown={this.onFlyoutPointerDown}>
+                        <FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} style={{ opacity: '50%' }} size="sm" />
+                    </div>
+                    <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
+                        {this.dockingContent}
 
-    setupInclinedPlane = () => {
-        this.changeWedgeBasedOnNewAngle(this.wedgeAngle);
-        this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]);
-        this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction));
-    };
+                        {this._hideUI ? null : (
+                            <div
+                                className={`mainView-propertiesDragger${this.propertiesWidth() < 10 ? '-minified' : ''}`}
+                                key="props"
+                                onPointerDown={this.onPropertiesPointerDown}
+                                style={{ background: SnappingManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
+                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SnappingManager.userColor} size="sm" />
+                            </div>
+                        )}
+                        <div className="properties-container" style={{ width: this.propertiesWidth(), color: SnappingManager.userColor }}>
+                            <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
+                                <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </>
+        );
+    }
 
-    // Default setup for pendulum simulation
-    setupPendulum = () => {
-        const length = (300 * this._props.PanelWidth()) / 1000;
-        const angle = 30;
-        const x = length * Math.cos(((90 - angle) * Math.PI) / 180);
-        const y = length * Math.sin(((90 - angle) * Math.PI) / 180);
-        const xPos = this.xMax / 2 - x - this.mass1Radius;
-        const yPos = y - this.mass1Radius - 5;
-        this.dataDoc.mass1_positionXstart = xPos;
-        this.dataDoc.mass1_positionYstart = yPos;
-        const forceOfTension: IForce = {
-            description: 'Tension',
-            magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin((60 * Math.PI) / 180),
-            directionInDegrees: 90 - angle,
-        };
-        const gravityParallel: IForce = {
-            description: 'Gravity Parallel Component',
-            magnitude: this.mass1 * Math.abs(this.gravity) * Math.sin(((90 - angle) * Math.PI) / 180),
-            directionInDegrees: -angle - 90,
-        };
-        const gravityPerpendicular: IForce = {
-            description: 'Gravity Perpendicular Component',
-            magnitude: this.mass1 * Math.abs(this.gravity) * Math.cos(((90 - angle) * Math.PI) / 180),
-            directionInDegrees: -angle,
-        };
+    @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: 'black',
+                    height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
+                    width: '100%',
+                }}>
+                {this.mainInnerContent}
+            </div>
+        );
+    }
 
-        this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]);
-        this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]);
-        this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
-        this.dataDoc.pendulum_angle = this.dataDoc.pendulum_angleStart = 30;
-        this.dataDoc.pendulum_length = this.dataDoc.pendulum_lengthStart = 300;
-    };
+    expandFlyout = action((button: Doc) => {
+        // bcz: What's going on here!? --- may be fixed now, so commenting out ...
+        // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab.  All of the div below the CollectionFreeFormView's main div
+        // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
+        // interactively adjusting the flyout fixes the problem.  So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
+        this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
+        // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
 
-    // Default setup for spring simulation
-    @action
-    setupSpring = () => {
-        this.dataDoc.simulation_showComponentForces = false;
-        this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1)]);
-        this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1)]);
-        this.dataDoc.mass1_positionXstart = this.xMax / 2 - this.mass1Radius;
-        this.dataDoc.mass1_positionYstart = 200;
-        this.dataDoc.spring_constant = 0.5;
-        this.dataDoc.spring_lengthRest = 200;
-        this.dataDoc.spring_lengthStart = 200;
-        this._simReset++;
-    };
+        this._sidebarContent.proto = DocCast(button.target);
+        SnappingManager.SetLastPressedBtn(button[Id]);
+    });
 
-    // Default setup for suspension simulation
-    @action
-    setupSuspension = () => {
-        const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-        const yPos = this.yMin + 200;
-        this.dataDoc.mass1_positionYstart = yPos;
-        this.dataDoc.mass1_positionXstart = xPos;
-        this.dataDoc.mass1_positionY = this.getDisplayYPos(yPos);
-        this.dataDoc.mass1_positionX = xPos;
-        const tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4));
-        const tensionForce1: IForce = {
-            description: 'Tension',
-            magnitude: tensionMag,
-            directionInDegrees: 45,
-        };
-        const tensionForce2: IForce = {
-            description: 'Tension',
-            magnitude: tensionMag,
-            directionInDegrees: 135,
-        };
-        const gravity = this.gravityForce(this.mass1);
-        this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
-        this.dataDoc.mass1_forcesStart = JSON.stringify([tensionForce1, tensionForce2, gravity]);
-        this._simReset++;
-    };
+    closeFlyout = action(() => {
+        SnappingManager.SetLastPressedBtn('');
+        this._panelContent = 'none';
+        this._sidebarContent.proto = undefined;
+        this._leftMenuFlyoutWidth = 0;
+    });
 
-    // Default setup for pulley simulation
-    @action
-    setupPulley = () => {
-        this.dataDoc.simulation_showComponentForces = false;
-        this.dataDoc.mass1_positionYstart = (this.yMax + this.yMin) / 2;
-        this.dataDoc.mass1_positionXstart = (this.xMin + this.xMax) / 2 - 2 * this.mass1Radius - 5;
-        this.dataDoc.mass1_positionY = this.getDisplayYPos((this.yMax + this.yMin) / 2);
-        this.dataDoc.mass1_positionX = (this.xMin + this.xMax) / 2 - 2 * this.mass1Radius - 5;
-        const a = (-1 * ((this.mass1 - this.mass2) * Math.abs(this.gravity))) / (this.mass1 + this.mass2);
-        const gravityForce1 = this.gravityForce(this.mass1);
-        const tensionForce1: IForce = {
-            description: 'Tension',
-            magnitude: this.mass1 * a + this.mass1 * Math.abs(this.gravity),
-            directionInDegrees: 90,
-        };
-        this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce1, tensionForce1]);
-        this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce1, tensionForce1]);
+    remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+    moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs);
+    addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
 
-        const gravityForce2 = this.gravityForce(this.mass2);
-        const tensionForce2: IForce = {
-            description: 'Tension',
-            magnitude: -this.mass2 * a + this.mass2 * Math.abs(this.gravity),
-            directionInDegrees: 90,
-        };
-        this.dataDoc.mass2_positionYstart = (this.yMax + this.yMin) / 2;
-        this.dataDoc.mass2_positionXstart = (this.xMin + this.xMax) / 2 + 5;
-        this.dataDoc.mass2_positionY = this.getDisplayYPos((this.yMax + this.yMin) / 2);
-        this.dataDoc.mass2_positionX = (this.xMin + this.xMax) / 2 + 5;
-        this.dataDoc.mass2_forcesUpdated = JSON.stringify([gravityForce2, tensionForce2]);
-        this.dataDoc.mass2_forcesStart = JSON.stringify([gravityForce2, tensionForce2]);
-        this._simReset++;
+    buttonBarXf = () => {
+        if (!this._docBtnRef.current) return Transform.Identity();
+        const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
+        return new Transform(-translateX, -translateY, 1 / scale);
     };
 
-    public static parseJSON(json: string) {
-        return !json ? [] : (JSON.parse(json) as IForce[]);
+    @computed get docButtons() {
+        return !Doc.MyDockedBtns ? null : (
+            <div className="mainView-docButtons" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }} ref={this._docBtnRef}>
+                <CollectionLinearView
+                    Document={Doc.MyDockedBtns}
+                    docViewPath={returnEmptyDocViewList}
+                    fieldKey="data"
+                    dropAction={dropActionType.embed}
+                    styleProvider={DefaultStyleProvider}
+                    select={emptyFunction}
+                    isAnyChildContentActive={returnFalse}
+                    isContentActive={emptyFunction}
+                    isSelected={returnFalse}
+                    moveDocument={this.moveButtonDoc}
+                    addDocument={this.addButtonDoc}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
+                    pinToPres={emptyFunction}
+                    removeDocument={this.remButtonDoc}
+                    ScreenToLocalTransform={this.buttonBarXf}
+                    PanelWidth={this.leftMenuFlyoutWidth}
+                    PanelHeight={this.leftMenuFlyoutHeight}
+                    renderDepth={0}
+                    focus={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                />
+                {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : null}
+            </div>
+        );
+    }
+    @computed get snapLines() {
+        const dragged = DragManager.docsBeingDragged.lastElement() ?? DocumentView.SelectedDocs().lastElement();
+        const dragPar = dragged ? CollectionFreeFormView.from(DocumentView.getViews(dragged).lastElement()) : undefined;
+        return !dragPar?.layoutDoc.freeform_snapLines ? null : (
+            <div className="mainView-snapLines">
+                <svg style={{ width: '100%', height: '100%' }}>
+                    {[
+                        ...SnappingManager.HorizSnapLines.map((l, i) => (
+                            // eslint-disable-next-line react/no-array-index-key
+                            <line key={'horiz' + i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                        )),
+                        ...SnappingManager.VertSnapLines.map((l, i) => (
+                            // eslint-disable-next-line react/no-array-index-key
+                            <line key={'vert' + i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                        )),
+                    ]}
+                </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"
+                        />
+                        <feGaussianBlur in="color" stdDeviation="4" result="blur" />
+                        <feOffset in="blur" dx="0" dy="0" result="offset" />
+                        <feMerge>
+                            <feMergeNode in="bg" />
+                            <feMergeNode in="offset" />
+                            <feMergeNode in="SourceGraphic" />
+                        </feMerge>
+                    </filter>
+                </defs>
+            </svg>
+        );
     }
 
-    // Handle force change in review mode
-    updateReviewModeValues = () => {
-        const forceOfGravityReview: IForce = {
-            description: 'Gravity',
-            magnitude: NumCast(this.dataDoc.review_GravityMagnitude),
-            directionInDegrees: NumCast(this.dataDoc.review_GravityAngle),
-        };
-        const normalForceReview: IForce = {
-            description: 'Normal Force',
-            magnitude: NumCast(this.dataDoc.review_NormalMagnitude),
-            directionInDegrees: NumCast(this.dataDoc.review_NormalAngle),
-        };
-        const staticFrictionForceReview: IForce = {
-            description: 'Static Friction Force',
-            magnitude: NumCast(this.dataDoc.review_StaticMagnitude),
-            directionInDegrees: NumCast(this.dataDoc.review_StaticAngle),
-        };
-        this.dataDoc.mass1_forcesStart = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]);
-        this.dataDoc.mass1_forcesUpdated = JSON.stringify([forceOfGravityReview, normalForceReview, staticFrictionForceReview]);
+    togglePropertiesFlyout = () => {
+        if (MainView.Instance.propertiesWidth() > 0) {
+            SnappingManager.SetPropertiesWidth(0);
+        } else {
+            SnappingManager.SetPropertiesWidth(300);
+        }
     };
 
-    pause = () => (this.dataDoc.simulation_paused = true);
-    componentForces1 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass1_componentForces));
-    setComponentForces1 = (forces: IForce[]) => (this.dataDoc.mass1_componentForces = JSON.stringify(forces));
-    componentForces2 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass2_componentForces));
-    setComponentForces2 = (forces: IForce[]) => (this.dataDoc.mass2_componentForces = JSON.stringify(forces));
-    startForces1 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass1_forcesStart));
-    startForces2 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass2_forcesStart));
-    forcesUpdated1 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass1_forcesUpdated));
-    setForcesUpdated1 = (forces: IForce[]) => (this.dataDoc.mass1_forcesUpdated = JSON.stringify(forces));
-    forcesUpdated2 = () => PhysicsSimulationBox.parseJSON(StrCast(this.dataDoc.mass2_forcesUpdated));
-    setForcesUpdated2 = (forces: IForce[]) => (this.dataDoc.mass2_forcesUpdated = JSON.stringify(forces));
-    setPosition1 = (xPos: number | undefined, yPos: number | undefined) => {
-        yPos !== undefined && (this.dataDoc.mass1_positionY = Math.round(yPos * 100) / 100);
-        xPos !== undefined && (this.dataDoc.mass1_positionX = Math.round(xPos * 100) / 100);
-    };
-    setPosition2 = (xPos: number | undefined, yPos: number | undefined) => {
-        yPos !== undefined && (this.dataDoc.mass2_positionY = Math.round(yPos * 100) / 100);
-        xPos !== undefined && (this.dataDoc.mass2_positionX = Math.round(xPos * 100) / 100);
-    };
-    setVelocity1 = (xVel: number | undefined, yVel: number | undefined) => {
-        yVel !== undefined && (this.dataDoc.mass1_velocityY = (-1 * Math.round(yVel * 100)) / 100);
-        xVel !== undefined && (this.dataDoc.mass1_velocityX = Math.round(xVel * 100) / 100);
-    };
-    setVelocity2 = (xVel: number | undefined, yVel: number | undefined) => {
-        yVel !== undefined && (this.dataDoc.mass2_velocityY = (-1 * Math.round(yVel * 100)) / 100);
-        xVel !== undefined && (this.dataDoc.mass2_velocityX = Math.round(xVel * 100) / 100);
-    };
-    setAcceleration1 = (xAccel: number, yAccel: number) => {
-        this.dataDoc.mass1_accelerationY = yAccel;
-        this.dataDoc.mass1_accelerationX = xAccel;
-    };
-    setAcceleration2 = (xAccel: number, yAccel: number) => {
-        this.dataDoc.mass2_accelerationY = yAccel;
-        this.dataDoc.mass2_accelerationX = xAccel;
-    };
-    setPendulumAngle = (angle: number | undefined, length: number | undefined) => {
-        angle !== undefined && (this.dataDoc.pendulum_angle = angle);
-        length !== undefined && (this.dataDoc.pendulum_length = length);
-    };
-    setSpringLength = (length: number) => {
-        this.dataDoc.spring_lengthStart = length;
-    };
-    resetRequest = () => this._simReset;
+    lightboxMaxBorder = [200, 50];
     render() {
-        const commonWeightProps = {
-            pause: this.pause,
-            paused: BoolCast(this.dataDoc.simulation_paused),
-            panelWidth: this._props.PanelWidth,
-            panelHeight: this._props.PanelHeight,
-            resetRequest: this.resetRequest,
-            xMax: this.xMax,
-            xMin: this.xMin,
-            yMax: this.yMax,
-            yMin: this.yMin,
-            wallPositions: this.wallPositions,
-            gravity: Math.abs(this.gravity),
-            timestepSize: 0.05,
-            showComponentForces: BoolCast(this.dataDoc.simulation_showComponentForces),
-            coefficientOfKineticFriction: NumCast(this.dataDoc.coefficientOfKineticFriction),
-            elasticCollisions: BoolCast(this.dataDoc.elasticCollisions),
-            simulationMode: this.simulationMode,
-            noMovement: BoolCast(this.dataDoc.noMovement),
-            circularMotionRadius: this.circularMotionRadius,
-            wedgeHeight: this.wedgeHeight,
-            wedgeWidth: this.wedgeWidth,
-            springConstant: this.springConstant,
-            springStartLength: this.springLengthStart,
-            springRestLength: this.springLengthRest,
-            setSpringLength: this.setSpringLength,
-            setPendulumAngle: this.setPendulumAngle,
-            pendulumAngle: this.pendulumAngle,
-            pendulumLength: this.pendulumLength,
-            startPendulumAngle: this.pendulumAngleStart,
-            startPendulumLength: this.pendulumLengthStart,
-            radius: this.mass1Radius,
-            simulationSpeed: NumCast(this.dataDoc.simulation_speed, 2),
-            showAcceleration: BoolCast(this.dataDoc.simulation_showAcceleration),
-            showForceMagnitudes: BoolCast(this.dataDoc.simulation_showForceMagnitudes),
-            showForces: BoolCast(this.dataDoc.simulation_showForces),
-            showVelocity: BoolCast(this.dataDoc.simulation_showVelocity),
-            simulationType: this.simulationType,
-        };
         return (
-            <div className="physicsSimApp">
-                <div className="mechanicsSimulationContainer">
-                    <div className="mechanicsSimulationContentContainer">
-                        <div className="mechanicsSimulationButtonsAndElements">
-                            <div className="mechanicsSimulationButtons">
-                                {!this.dataDoc.simulation_paused && (
-                                    <div
-                                        style={{
-                                            position: 'fixed',
-                                            left: 0.1 * this._props.PanelWidth() + 'px',
-                                            top: 0.95 * this._props.PanelHeight() + 'px',
-                                            width: 0.5 * this._props.PanelWidth() + 'px',
-                                        }}>
-                                        <LinearProgress />
-                                    </div>
-                                )}
-                            </div>
-                            <div
-                                className="mechanicsSimulationElements"
-                                style={{
-                                    //
-                                    width: '60%',
-                                    height: '100%',
-                                    position: 'absolute',
-                                    background: 'yellow',
-                                }}>
-                                <Weight
-                                    {...commonWeightProps}
-                                    color="red"
-                                    componentForces={this.componentForces1}
-                                    setComponentForces={this.setComponentForces1}
-                                    displayXVelocity={NumCast(this.dataDoc.mass1_velocityX)}
-                                    displayYVelocity={NumCast(this.dataDoc.mass1_velocityY)}
-                                    mass={this.mass1}
-                                    startForces={this.startForces1}
-                                    startPosX={this.mass1PosXStart}
-                                    startPosY={this.mass1PosYStart}
-                                    startVelX={this.mass1VelXStart}
-                                    startVelY={this.mass1VelYStart}
-                                    updateMassPosX={NumCast(this.dataDoc.mass1_xChange)}
-                                    updateMassPosY={NumCast(this.dataDoc.mass1_yChange)}
-                                    forcesUpdated={this.forcesUpdated1}
-                                    setForcesUpdated={this.setForcesUpdated1}
-                                    setPosition={this.setPosition1}
-                                    setVelocity={this.setVelocity1}
-                                    setAcceleration={this.setAcceleration1}
-                                />
-                                {this.simulationType === 'Pulley' && (
-                                    <Weight
-                                        {...commonWeightProps}
-                                        color="green"
-                                        componentForces={this.componentForces2}
-                                        setComponentForces={this.setComponentForces2}
-                                        displayXVelocity={NumCast(this.dataDoc.mass2_velocityX)}
-                                        displayYVelocity={NumCast(this.dataDoc.mass2_velocityY)}
-                                        mass={this.mass2}
-                                        startForces={this.startForces2}
-                                        startPosX={this.mass2PosXStart}
-                                        startPosY={this.mass2PosYStart}
-                                        startVelX={this.mass2VelXStart}
-                                        startVelY={this.mass2VelYStart}
-                                        updateMassPosX={NumCast(this.dataDoc.mass2_xChange)}
-                                        updateMassPosY={NumCast(this.dataDoc.mass2_yChange)}
-                                        forcesUpdated={this.forcesUpdated2}
-                                        setForcesUpdated={this.setForcesUpdated2}
-                                        setPosition={this.setPosition2}
-                                        setVelocity={this.setVelocity2}
-                                        setAcceleration={this.setAcceleration2}
-                                    />
-                                )}
-                            </div>
-                            <div style={{ position: 'absolute', transformOrigin: 'top left', top: 0, left: 0, width: '100%', height: '100%' }}>
-                                {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane') &&
-                                    this.wallPositions?.map((element, index) => <Wall key={index} length={element.length} xPos={element.xPos} yPos={element.yPos} angleInDegrees={element.angleInDegrees} />)}
-                            </div>
-                        </div>
-                    </div>
-                    <div
-                        className="mechanicsSimulationEquationContainer"
-                        onWheel={e => this._props.isContentActive() && e.stopPropagation()}
-                        style={{ overflow: 'auto', height: `${Math.max(1, 800 / this._props.PanelWidth()) * 100}%`, transform: `scale(${Math.min(1, this._props.PanelWidth() / 850)})` }}>
-                        <div className="mechanicsSimulationControls">
-                            <Stack direction="row" spacing={1}>
-                                {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
-                                    <IconButton onClick={() => (this.dataDoc.simulation_paused = false)}>
-                                        <PlayArrowIcon />
-                                    </IconButton>
-                                )}
-                                {!this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
-                                    <IconButton onClick={() => (this.dataDoc.simulation_paused = true)}>
-                                        <PauseIcon />
-                                    </IconButton>
-                                )}
-                                {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
-                                    <IconButton onClick={action(() => this._simReset++)}>
-                                        <ReplayIcon />
-                                    </IconButton>
-                                )}
-                            </Stack>
-                            <div className="dropdownMenu">
-                                <select
-                                    value={StrCast(this.simulationType)}
-                                    onChange={event => {
-                                        this.dataDoc.simulation_type = event.target.value;
-                                        this.setupSimulation();
-                                    }}
-                                    style={{ height: '2em', width: '100%', fontSize: '16px' }}>
-                                    <option value="One Weight">Projectile</option>
-                                    <option value="Inclined Plane">Inclined Plane</option>
-                                    <option value="Pendulum">Pendulum</option>
-                                    <option value="Spring">Spring</option>
-                                    <option value="Circular Motion">Circular Motion</option>
-                                    <option value="Pulley">Pulley</option>
-                                    <option value="Suspension">Suspension</option>
-                                </select>
-                            </div>
-                            <div className="dropdownMenu">
-                                <select
-                                    value={this.simulationMode}
-                                    onChange={event => {
-                                        this.dataDoc.simulation_mode = event.target.value;
-                                        this.setupSimulation();
-                                    }}
-                                    style={{ height: '2em', width: '100%', fontSize: '16px' }}>
-                                    <option value="Tutorial">Tutorial Mode</option>
-                                    <option value="Freeform">Freeform Mode</option>
-                                    <option value="Review">Review Mode</option>
-                                </select>
-                            </div>
-                        </div>
-                        {this.simulationMode === 'Review' && this.simulationType !== 'Inclined Plane' && (
-                            <div className="wordProblemBox">
-                                <p>{this.simulationType} review problems in progress!</p>
-                                <hr />
-                            </div>
-                        )}
-                        {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
-                            <div>
-                                {!this.dataDoc.hintDialogueOpen && (
-                                    <IconButton
-                                        onClick={() => (this.dataDoc.hintDialogueOpen = true)}
-                                        sx={{
-                                            position: 'fixed',
-                                            left: this.xMax - 50 + 'px',
-                                            top: this.yMin + 14 + 'px',
-                                        }}>
-                                        <QuestionMarkIcon />
-                                    </IconButton>
-                                )}
-                                <Dialog maxWidth="sm" fullWidth open={BoolCast(this.dataDoc.hintDialogueOpen)} onClose={() => (this.dataDoc.hintDialogueOpen = false)}>
-                                    <DialogTitle>Hints</DialogTitle>
-                                    <DialogContent>
-                                        {this.selectedQuestion.hints?.map((hint: any, index: number) => (
-                                            <div key={index}>
-                                                <DialogContentText>
-                                                    <details>
-                                                        <summary>
-                                                            <b>
-                                                                Hint {index + 1}: {hint.description}
-                                                            </b>
-                                                        </summary>
-                                                        {hint.content}
-                                                    </details>
-                                                </DialogContentText>
-                                            </div>
-                                        ))}
-                                    </DialogContent>
-                                    <DialogActions>
-                                        <Button onClick={() => (this.dataDoc.hintDialogueOpen = false)}>Close</Button>
-                                    </DialogActions>
-                                </Dialog>
-                                <div className="wordProblemBox">
-                                    <div className="question">
-                                        <p>{this.questionPartOne}</p>
-                                        <p>{this.questionPartTwo}</p>
-                                    </div>
-                                    <div className="answers">
-                                        {this.selectedQuestion.answerParts.includes('force of gravity') && (
-                                            <InputField
-                                                label={<p>Gravity magnitude</p>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="review_GravityMagnitude"
-                                                step={0.1}
-                                                unit="N"
-                                                upperBound={50}
-                                                value={NumCast(this.dataDoc.review_GravityMagnitude)}
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of gravity')]}
-                                                labelWidth="7em"
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('angle of gravity') && (
-                                            <InputField
-                                                label={<p>Gravity angle</p>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="review_GravityAngle"
-                                                step={1}
-                                                unit="°"
-                                                upperBound={360}
-                                                value={NumCast(this.dataDoc.review_GravityAngle)}
-                                                radianEquivalent
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of gravity')]}
-                                                labelWidth="7em"
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('normal force') && (
-                                            <InputField
-                                                label={<p>Normal force magnitude</p>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="review_NormalMagnitude"
-                                                step={0.1}
-                                                unit="N"
-                                                upperBound={50}
-                                                value={NumCast(this.dataDoc.review_NormalMagnitude)}
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('normal force')]}
-                                                labelWidth="7em"
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('angle of normal force') && (
-                                            <InputField
-                                                label={<p>Normal force angle</p>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="review_NormalAngle"
-                                                step={1}
-                                                unit="°"
-                                                upperBound={360}
-                                                value={NumCast(this.dataDoc.review_NormalAngle)}
-                                                radianEquivalent
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of normal force')]}
-                                                labelWidth="7em"
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('force of static friction') && (
-                                            <InputField
-                                                label={<p>Static friction magnitude</p>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="review_StaticMagnitude"
-                                                step={0.1}
-                                                unit="N"
-                                                upperBound={50}
-                                                value={NumCast(this.dataDoc.review_StaticMagnitude)}
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of static friction')]}
-                                                labelWidth="7em"
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('angle of static friction') && (
-                                            <InputField
-                                                label={<p>Static friction angle</p>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="review_StaticAngle"
-                                                step={1}
-                                                unit="°"
-                                                upperBound={360}
-                                                value={NumCast(this.dataDoc.review_StaticAngle)}
-                                                radianEquivalent
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of static friction')]}
-                                                labelWidth="7em"
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('coefficient of static friction') && (
-                                            <InputField
-                                                label={
-                                                    <Box>
-                                                        &mu;<sub>s</sub>
-                                                    </Box>
-                                                }
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="coefficientOfStaticFriction"
-                                                step={0.1}
-                                                unit=""
-                                                upperBound={1}
-                                                value={NumCast(this.dataDoc.coefficientOfStaticFriction)}
-                                                effect={this.updateReviewForcesBasedOnCoefficient}
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('coefficient of static friction')]}
-                                            />
-                                        )}
-                                        {this.selectedQuestion.answerParts.includes('wedge angle') && (
-                                            <InputField
-                                                label={<Box>&theta;</Box>}
-                                                lowerBound={0}
-                                                dataDoc={this.dataDoc}
-                                                prop="wedge_angle"
-                                                step={1}
-                                                unit="°"
-                                                upperBound={49}
-                                                value={this.wedgeAngle}
-                                                effect={(val: number) => {
-                                                    this.changeWedgeBasedOnNewAngle(val);
-                                                    this.updateReviewForcesBasedOnAngle(val);
-                                                }}
-                                                radianEquivalent
-                                                showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
-                                                correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]}
-                                            />
-                                        )}
-                                    </div>
-                                </div>
-                            </div>
-                        )}
-                        {this.simulationMode === 'Tutorial' && (
-                            <div className="wordProblemBox">
-                                <div className="question">
-                                    <h2>Problem</h2>
-                                    <p>{this.tutorial.question}</p>
-                                </div>
-                                <div
-                                    style={{
-                                        display: 'flex',
-                                        justifyContent: 'spaceBetween',
-                                        width: '100%',
-                                    }}>
-                                    <IconButton
-                                        onClick={() => {
-                                            let step = NumCast(this.dataDoc.tutorial_stepNumber) - 1;
-                                            step = Math.max(step, 0);
-                                            step = Math.min(step, this.tutorial.steps.length - 1);
-                                            this.dataDoc.tutorial_stepNumber = step;
-                                            this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces);
-                                            this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces);
-                                            this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude;
-                                        }}
-                                        disabled={this.dataDoc.tutorial_stepNumber === 0}>
-                                        <ArrowLeftIcon />
-                                    </IconButton>
-                                    <div>
-                                        <h3>
-                                            Step {NumCast(this.dataDoc.tutorial_stepNumber) + 1}: {this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].description}
-                                        </h3>
-                                        <p>{this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].content}</p>
-                                    </div>
-                                    <IconButton
-                                        onClick={() => {
-                                            let step = NumCast(this.dataDoc.tutorial_stepNumber) + 1;
-                                            step = Math.max(step, 0);
-                                            step = Math.min(step, this.tutorial.steps.length - 1);
-                                            this.dataDoc.tutorial_stepNumber = step;
-                                            this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces);
-                                            this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces);
-                                            this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude;
-                                        }}
-                                        disabled={this.dataDoc.tutorial_stepNumber === this.tutorial.steps.length - 1}>
-                                        <ArrowRightIcon />
-                                    </IconButton>
-                                </div>
-                                <div>
-                                    {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && <p>Resources</p>}
-                                    {this.simulationType === 'One Weight' && (
-                                        <ul>
-                                            <li>
-                                                <a
-                                                    href="https://www.khanacademy.org/science/physics/one-dimensional-motion"
-                                                    target="_blank"
-                                                    rel="noreferrer"
-                                                    style={{
-                                                        color: 'blue',
-                                                        textDecoration: 'underline',
-                                                    }}>
-                                                    Khan Academy - One Dimensional Motion
-                                                </a>
-                                            </li>
-                                            <li>
-                                                <a
-                                                    href="https://www.khanacademy.org/science/physics/two-dimensional-motion"
-                                                    target="_blank"
-                                                    rel="noreferrer"
-                                                    style={{
-                                                        color: 'blue',
-                                                        textDecoration: 'underline',
-                                                    }}>
-                                                    Khan Academy - Two Dimensional Motion
-                                                </a>
-                                            </li>
-                                        </ul>
-                                    )}
-                                    {this.simulationType === 'Inclined Plane' && (
-                                        <ul>
-                                            <li>
-                                                <a
-                                                    href="https://www.khanacademy.org/science/physics/forces-newtons-laws#normal-contact-force"
-                                                    target="_blank"
-                                                    rel="noreferrer"
-                                                    style={{
-                                                        color: 'blue',
-                                                        textDecoration: 'underline',
-                                                    }}>
-                                                    Khan Academy - Normal Force
-                                                </a>
-                                            </li>
-                                            <li>
-                                                <a
-                                                    href="https://www.khanacademy.org/science/physics/forces-newtons-laws#inclined-planes-friction"
-                                                    target="_blank"
-                                                    rel="noreferrer"
-                                                    style={{
-                                                        color: 'blue',
-                                                        textDecoration: 'underline',
-                                                    }}>
-                                                    Khan Academy - Inclined Planes
-                                                </a>
-                                            </li>
-                                        </ul>
-                                    )}
-                                    {this.simulationType === 'Pendulum' && (
-                                        <ul>
-                                            <li>
-                                                <a
-                                                    href="https://www.khanacademy.org/science/physics/forces-newtons-laws#tension-tutorial"
-                                                    target="_blank"
-                                                    rel="noreferrer"
-                                                    style={{
-                                                        color: 'blue',
-                                                        textDecoration: 'underline',
-                                                    }}>
-                                                    Khan Academy - Tension
-                                                </a>
-                                            </li>
-                                        </ul>
-                                    )}
-                                </div>
-                            </div>
-                        )}
-                        {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
-                            <div
-                                style={{
-                                    display: 'flex',
-                                    justifyContent: 'space-between',
-                                    marginTop: '10px',
-                                }}>
-                                <p
-                                    style={{
-                                        color: 'blue',
-                                        textDecoration: 'underline',
-                                        cursor: 'pointer',
-                                    }}
-                                    onClick={() => (this.dataDoc.simulation_mode = 'Tutorial')}>
-                                    {' '}
-                                    Go to walkthrough{' '}
-                                </p>
-                                <div style={{ display: 'flex', flexDirection: 'column' }}>
-                                    <Button
-                                        onClick={action(() => {
-                                            this._simReset++;
-                                            this.checkAnswers();
-                                            this.dataDoc.simulation_showIcon = true;
-                                        })}
-                                        variant="outlined">
-                                        <p>Submit</p>
-                                    </Button>
-                                    <Button
-                                        onClick={() => {
-                                            this.generateNewQuestion();
-                                            this.dataDoc.simulation_showIcon = false;
-                                        }}
-                                        variant="outlined">
-                                        <p>New question</p>
-                                    </Button>
-                                </div>
-                            </div>
-                        )}
-                        {this.simulationMode === 'Freeform' && (
-                            <div className="vars">
-                                <FormControl component="fieldset">
-                                    <FormGroup>
-                                        {this.simulationType === 'One Weight' && (
-                                            <FormControlLabel
-                                                control={<Checkbox checked={BoolCast(this.dataDoc.elasticCollisions)} onChange={() => (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />}
-                                                label="Make collisions elastic"
-                                                labelPlacement="start"
-                                            />
-                                        )}
-                                        <FormControlLabel
-                                            control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showForces)} onChange={() => (this.dataDoc.simulation_showForces = !this.dataDoc.simulation_showForces)} />}
-                                            label="Show force vectors"
-                                            labelPlacement="start"
-                                        />
-                                        {(this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && (
-                                            <FormControlLabel
-                                                control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showComponentForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />}
-                                                label="Show component force vectors"
-                                                labelPlacement="start"
-                                            />
-                                        )}
-                                        <FormControlLabel
-                                            control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showAcceleration)} onChange={() => (this.dataDoc.simulation_showAcceleration = !this.dataDoc.simulation_showAcceleration)} />}
-                                            label="Show acceleration vector"
-                                            labelPlacement="start"
-                                        />
-                                        <FormControlLabel
-                                            control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showVelocity)} onChange={() => (this.dataDoc.simulation_showVelocity = !this.dataDoc.simulation_showVelocity)} />}
-                                            label="Show velocity vector"
-                                            labelPlacement="start"
-                                        />
-                                        <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit="x" upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth="5em" />
-                                        {this.dataDoc.simulation_paused && this.simulationType !== 'Circular Motion' && (
-                                            <InputField
-                                                label={<Box>Gravity</Box>}
-                                                lowerBound={-30}
-                                                dataDoc={this.dataDoc}
-                                                prop="gravity"
-                                                step={0.01}
-                                                unit="m/s2"
-                                                upperBound={0}
-                                                value={NumCast(this.dataDoc.simulation_gravity, -9.81)}
-                                                effect={(val: number) => this.setupSimulation()}
-                                                labelWidth="5em"
-                                            />
-                                        )}
-                                        {this.dataDoc.simulation_paused && this.simulationType !== 'Pulley' && (
-                                            <InputField
-                                                label={<Box>Mass</Box>}
-                                                lowerBound={1}
-                                                dataDoc={this.dataDoc}
-                                                prop="mass1"
-                                                step={0.1}
-                                                unit="kg"
-                                                upperBound={5}
-                                                value={this.mass1 ?? 1}
-                                                effect={(val: number) => this.setupSimulation()}
-                                                labelWidth="5em"
-                                            />
-                                        )}
-                                        {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
-                                            <InputField
-                                                label={<Box>Red mass</Box>}
-                                                lowerBound={1}
-                                                dataDoc={this.dataDoc}
-                                                prop="mass1"
-                                                step={0.1}
-                                                unit="kg"
-                                                upperBound={5}
-                                                value={this.mass1 ?? 1}
-                                                effect={(val: number) => this.setupSimulation()}
-                                                labelWidth="5em"
-                                            />
-                                        )}
-                                        {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
-                                            <InputField
-                                                label={<Box>Blue mass</Box>}
-                                                lowerBound={1}
-                                                dataDoc={this.dataDoc}
-                                                prop="mass2"
-                                                step={0.1}
-                                                unit="kg"
-                                                upperBound={5}
-                                                value={this.mass2 ?? 1}
-                                                effect={(val: number) => this.setupSimulation()}
-                                                labelWidth="5em"
-                                            />
-                                        )}
-                                        {this.dataDoc.simulation_paused && this.simulationType === 'Circular Motion' && (
-                                            <InputField
-                                                label={<Box>Rod length</Box>}
-                                                lowerBound={100}
-                                                dataDoc={this.dataDoc}
-                                                prop="circularMotionRadius"
-                                                step={5}
-                                                unit="m"
-                                                upperBound={250}
-                                                value={this.circularMotionRadius}
-                                                effect={(val: number) => this.setupSimulation()}
-                                                labelWidth="5em"
-                                            />
-                                        )}
-                                    </FormGroup>
-                                </FormControl>
-                                {this.simulationType === 'Spring' && this.dataDoc.simulation_paused && (
-                                    <div>
-                                        <InputField
-                                            label={<Typography color="inherit">Spring stiffness</Typography>}
-                                            lowerBound={0.1}
-                                            dataDoc={this.dataDoc}
-                                            prop="spring_constant"
-                                            step={1}
-                                            unit="N/m"
-                                            upperBound={500}
-                                            value={this.springConstant}
-                                            effect={action(() => this._simReset++)}
-                                            radianEquivalent={false}
-                                            mode="Freeform"
-                                            labelWidth="7em"
-                                        />
-                                        <InputField
-                                            label={<Typography color="inherit">Rest length</Typography>}
-                                            lowerBound={10}
-                                            dataDoc={this.dataDoc}
-                                            prop="spring_lengthRest"
-                                            step={100}
-                                            unit=""
-                                            upperBound={500}
-                                            value={this.springLengthRest}
-                                            effect={action(() => this._simReset++)}
-                                            radianEquivalent={false}
-                                            mode="Freeform"
-                                            labelWidth="7em"
-                                        />
-                                        <InputField
-                                            label={<Typography color="inherit">Starting displacement</Typography>}
-                                            lowerBound={-(this.springLengthRest - 10)}
-                                            dataDoc={this.dataDoc}
-                                            prop=""
-                                            step={10}
-                                            unit=""
-                                            upperBound={this.springLengthRest}
-                                            value={this.springLengthStart - this.springLengthRest}
-                                            effect={action((val: number) => {
-                                                this.dataDoc.mass1_positionYstart = this.springLengthRest + val;
-                                                this.dataDoc.spring_lengthStart = this.springLengthRest + val;
-                                                this._simReset++;
-                                            })}
-                                            radianEquivalent={false}
-                                            mode="Freeform"
-                                            labelWidth="7em"
-                                        />
-                                    </div>
-                                )}
-                                {this.simulationType === 'Inclined Plane' && this.dataDoc.simulation_paused && (
-                                    <div>
-                                        <InputField
-                                            label={<Box>&theta;</Box>}
-                                            lowerBound={0}
-                                            dataDoc={this.dataDoc}
-                                            prop="wedge_angle"
-                                            step={1}
-                                            unit="°"
-                                            upperBound={49}
-                                            value={this.wedgeAngle}
-                                            effect={action((val: number) => {
-                                                this.changeWedgeBasedOnNewAngle(val);
-                                                this._simReset++;
-                                            })}
-                                            radianEquivalent
-                                            mode="Freeform"
-                                            labelWidth="2em"
-                                        />
-                                        <InputField
-                                            label={
-                                                <Box>
-                                                    &mu;<sub>s</sub>
-                                                </Box>
-                                            }
-                                            lowerBound={0}
-                                            dataDoc={this.dataDoc}
-                                            prop="coefficientOfStaticFriction"
-                                            step={0.1}
-                                            unit=""
-                                            upperBound={1}
-                                            value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0}
-                                            effect={action((val: number) => {
-                                                this.updateForcesWithFriction(val);
-                                                if (val < NumCast(this.dataDoc.coefficientOfKineticFriction)) {
-                                                    this.dataDoc.soefficientOfKineticFriction = val;
-                                                }
-                                                this._simReset++;
-                                            })}
-                                            mode="Freeform"
-                                            labelWidth="2em"
-                                        />
-                                        <InputField
-                                            label={
-                                                <Box>
-                                                    &mu;<sub>k</sub>
-                                                </Box>
-                                            }
-                                            lowerBound={0}
-                                            dataDoc={this.dataDoc}
-                                            prop="coefficientOfKineticFriction"
-                                            step={0.1}
-                                            unit=""
-                                            upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)}
-                                            value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0}
-                                            effect={action(() => this._simReset++)}
-                                            mode="Freeform"
-                                            labelWidth="2em"
-                                        />
-                                    </div>
-                                )}
-                                {this.simulationType === 'Inclined Plane' && !this.dataDoc.simulation_paused && (
-                                    <Typography>
-                                        <>
-                                            &theta;: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad
-                                            <br />
-                                            &mu; <sub>s</sub>: {this.dataDoc.coefficientOfStaticFriction}
-                                            <br />
-                                            &mu; <sub>k</sub>: {this.dataDoc.coefficientOfKineticFriction}
-                                        </>
-                                    </Typography>
-                                )}
-                                {this.simulationType === 'Pendulum' && !this.dataDoc.simulation_paused && (
-                                    <Typography>
-                                        &theta;: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad
-                                    </Typography>
-                                )}
-                                {this.simulationType === 'Pendulum' && this.dataDoc.simulation_paused && (
-                                    <div>
-                                        <InputField
-                                            label={<Box>Angle</Box>}
-                                            lowerBound={0}
-                                            dataDoc={this.dataDoc}
-                                            prop="pendulum_angle"
-                                            step={1}
-                                            unit="°"
-                                            upperBound={59}
-                                            value={NumCast(this.dataDoc.pendulum_angle, 30)}
-                                            effect={action(value => {
-                                                this.dataDoc.pendulum_angleStart = value;
-                                                this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length;
-                                                if (this.simulationType === 'Pendulum') {
-                                                    const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180);
-
-                                                    const forceOfTension: IForce = {
-                                                        description: 'Tension',
-                                                        magnitude: mag,
-                                                        directionInDegrees: 90 - value,
-                                                    };
-                                                    const gravityParallel: IForce = {
-                                                        description: 'Gravity Parallel Component',
-                                                        magnitude: Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180),
-                                                        directionInDegrees: 270 - value,
-                                                    };
-                                                    const gravityPerpendicular: IForce = {
-                                                        description: 'Gravity Perpendicular Component',
-                                                        magnitude: Math.abs(this.gravity) * Math.sin((value * Math.PI) / 180),
-                                                        directionInDegrees: -value,
-                                                    };
-
-                                                    const length = this.pendulumLength;
-                                                    const x = length * Math.cos(((90 - value) * Math.PI) / 180);
-                                                    const y = length * Math.sin(((90 - value) * Math.PI) / 180);
-                                                    const xPos = this.xMax / 2 - x - NumCast(this.dataDoc.radius);
-                                                    const yPos = y - NumCast(this.dataDoc.radius) - 5;
-                                                    this.dataDoc.mass1_positionXstart = xPos;
-                                                    this.dataDoc.mass1_positionYstart = yPos;
-
-                                                    this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
-                                                    this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
-                                                    this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]);
-                                                    this._simReset++;
-                                                }
-                                            })}
-                                            radianEquivalent
-                                            mode="Freeform"
-                                            labelWidth="5em"
-                                        />
-                                        <InputField
-                                            label={<Box>Rod length</Box>}
-                                            lowerBound={0}
-                                            dataDoc={this.dataDoc}
-                                            prop="pendulum_length"
-                                            step={1}
-                                            unit="m"
-                                            upperBound={400}
-                                            value={Math.round(this.pendulumLength)}
-                                            effect={action(value => {
-                                                if (this.simulationType === 'Pendulum') {
-                                                    this.dataDoc.pendulum_angleStart = this.pendulumAngle;
-                                                    this.dataDoc.pendulum_lengthStart = value;
-                                                    this._simReset++;
-                                                }
-                                            })}
-                                            radianEquivalent={false}
-                                            mode="Freeform"
-                                            labelWidth="5em"
-                                        />
-                                    </div>
-                                )}
-                            </div>
-                        )}
-                        <div className="mechanicsSimulationEquation">
-                            {this.simulationMode === 'Freeform' && (
-                                <table>
-                                    <tbody>
-                                        <tr>
-                                            <td>{this.simulationType === 'Pulley' ? 'Red Weight' : ''}</td>
-                                            <td>X</td>
-                                            <td>Y</td>
-                                        </tr>
-                                        <tr>
-                                            <td
-                                                style={{ cursor: 'help' }}
-                                                // onClick={() => {
-                                                //   window.open(
-                                                //     "https://www.khanacademy.org/science/physics/two-dimensional-motion"
-                                                //   );
-                                                // }}
-                                            >
-                                                <Box>Position</Box>
-                                            </td>
-                                            {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
-                                                <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_positionX + ''} m</td>
-                                            )}{' '}
-                                            {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
-                                                <td
-                                                    style={{
-                                                        cursor: 'default',
-                                                    }}>
-                                                    <InputField
-                                                        lowerBound={this.simulationType === 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15}
-                                                        dataDoc={this.dataDoc}
-                                                        prop="mass1_positionX"
-                                                        step={1}
-                                                        unit="m"
-                                                        upperBound={this.simulationType === 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15}
-                                                        value={NumCast(this.dataDoc.mass1_positionX)}
-                                                        effect={value => {
-                                                            this.dataDoc.mass1_xChange = value;
-                                                            if (this.simulationType === 'Suspension') {
-                                                                const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
-                                                                const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
-                                                                const deltaX1 = value + this.radius - x1rod;
-                                                                const deltaX2 = x2rod - (value + this.radius);
-                                                                const deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius;
-                                                                let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
-                                                                let dir2T = Math.atan(deltaY / deltaX2);
-                                                                const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
-                                                                const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
-                                                                dir1T = (dir1T * 180) / Math.PI;
-                                                                dir2T = (dir2T * 180) / Math.PI;
-                                                                const tensionForce1: IForce = {
-                                                                    description: 'Tension',
-                                                                    magnitude: tensionMag1,
-                                                                    directionInDegrees: dir1T,
-                                                                };
-                                                                const tensionForce2: IForce = {
-                                                                    description: 'Tension',
-                                                                    magnitude: tensionMag2,
-                                                                    directionInDegrees: dir2T,
-                                                                };
-                                                                const gravity = this.gravityForce(this.mass1);
-                                                                this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
-                                                            }
-                                                        }}
-                                                        small
-                                                        mode="Freeform"
-                                                    />
-                                                </td>
-                                            )}{' '}
-                                            {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
-                                                <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_positionY)} m`}</td>
-                                            )}{' '}
-                                            {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
-                                                <td
-                                                    style={{
-                                                        cursor: 'default',
-                                                    }}>
-                                                    <InputField
-                                                        lowerBound={1}
-                                                        dataDoc={this.dataDoc}
-                                                        prop="mass1_positionY"
-                                                        step={1}
-                                                        unit="m"
-                                                        upperBound={this.yMax - 110}
-                                                        value={NumCast(this.dataDoc.mass1_positionY)}
-                                                        effect={value => {
-                                                            this.dataDoc.mass1_yChange = value;
-                                                            if (this.simulationType === 'Suspension') {
-                                                                const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
-                                                                const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
-                                                                const deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod;
-                                                                const deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius);
-                                                                const deltaY = this.getYPosFromDisplay(value) + this.radius;
-                                                                let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
-                                                                let dir2T = Math.atan(deltaY / deltaX2);
-                                                                const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
-                                                                const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
-                                                                dir1T = (dir1T * 180) / Math.PI;
-                                                                dir2T = (dir2T * 180) / Math.PI;
-                                                                const tensionForce1: IForce = {
-                                                                    description: 'Tension',
-                                                                    magnitude: tensionMag1,
-                                                                    directionInDegrees: dir1T,
-                                                                };
-                                                                const tensionForce2: IForce = {
-                                                                    description: 'Tension',
-                                                                    magnitude: tensionMag2,
-                                                                    directionInDegrees: dir2T,
-                                                                };
-                                                                const gravity = this.gravityForce(this.mass1);
-                                                                this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
-                                                            }
-                                                        }}
-                                                        small
-                                                        mode="Freeform"
-                                                    />
-                                                </td>
-                                            )}{' '}
-                                        </tr>
-                                        <tr>
-                                            <td
-                                                style={{ cursor: 'help' }}
-                                                // onClick={() => {
-                                                //   window.open(
-                                                //     "https://www.khanacademy.org/science/physics/two-dimensional-motion"
-                                                //   );
-                                                // }}
-                                            >
-                                                <Box>Velocity</Box>
-                                            </td>
-                                            {(!this.dataDoc.simulation_paused || (this.simulationType !== 'One Weight' && this.simulationType !== 'Circular Motion')) && (
-                                                <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_velocityX)} m/s`}</td>
-                                            )}{' '}
-                                            {this.dataDoc.simulation_paused && (this.simulationType === 'One Weight' || this.simulationType === 'Circular Motion') && (
-                                                <td
-                                                    style={{
-                                                        cursor: 'default',
-                                                    }}>
-                                                    <InputField
-                                                        lowerBound={-50}
-                                                        dataDoc={this.dataDoc}
-                                                        prop="mass1_velocityX"
-                                                        step={1}
-                                                        unit="m/s"
-                                                        upperBound={50}
-                                                        value={NumCast(this.dataDoc.mass1_velocityX)}
-                                                        effect={action(value => {
-                                                            this.dataDoc.mass1_velocityXstart = value;
-                                                            this._simReset++;
-                                                        })}
-                                                        small
-                                                        mode="Freeform"
-                                                    />
-                                                </td>
-                                            )}{' '}
-                                            {(!this.dataDoc.simulation_paused || this.simulationType !== 'One Weight') && <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_velocityY + ''} m/s</td>}{' '}
-                                            {this.dataDoc.simulation_paused && this.simulationType === 'One Weight' && (
-                                                <td
-                                                    style={{
-                                                        cursor: 'default',
-                                                    }}>
-                                                    <InputField
-                                                        lowerBound={-50}
-                                                        dataDoc={this.dataDoc}
-                                                        prop="mass1_velocityY"
-                                                        step={1}
-                                                        unit="m/s"
-                                                        upperBound={50}
-                                                        value={NumCast(this.dataDoc.mass1_velocityY)}
-                                                        effect={value => {
-                                                            this.dataDoc.mass1_velocityYstart = -value;
-                                                        }}
-                                                        small
-                                                        mode="Freeform"
-                                                    />
-                                                </td>
-                                            )}{' '}
-                                        </tr>
-                                        <tr>
-                                            <td
-                                                style={{ cursor: 'help' }}
-                                                // onClick={() => {
-                                                //   window.open(
-                                                //     "https://www.khanacademy.org/science/physics/two-dimensional-motion"
-                                                //   );
-                                                // }}
-                                            >
-                                                <Box>Acceleration</Box>
-                                            </td>
-                                            <td style={{ cursor: 'default' }}>
-                                                {this.dataDoc.mass1_accelerationX + ''} m/s<sup>2</sup>
-                                            </td>
-                                            <td style={{ cursor: 'default' }}>
-                                                {this.dataDoc.mass1_accelerationY + ''} m/s<sup>2</sup>
-                                            </td>
-                                        </tr>
-                                        <tr>
-                                            <td>
-                                                <Box>Momentum</Box>
-                                            </td>
-                                            <td>{Math.round(NumCast(this.dataDoc.mass1_velocityX) * this.mass1 * 10) / 10} kg*m/s</td>
-                                            <td>{Math.round(NumCast(this.dataDoc.mass1_velocityY) * this.mass1 * 10) / 10} kg*m/s</td>
-                                        </tr>
-                                    </tbody>
-                                </table>
-                            )}
-                            {this.simulationMode === 'Freeform' && this.simulationType === 'Pulley' && (
-                                <table>
-                                    <tbody>
-                                        <tr>
-                                            <td>Blue Weight</td>
-                                            <td>X</td>
-                                            <td>Y</td>
-                                        </tr>
-                                        <tr>
-                                            <td>
-                                                <Box>Position</Box>
-                                            </td>
-                                            <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionX} m`}</td>
-                                            <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionY} m`}</td>
-                                        </tr>
-                                        <tr>
-                                            <td>
-                                                <Box>Velocity</Box>
-                                            </td>
-                                            <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionX} m/s`}</td>
-                                            <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionY} m/s`}</td>
-                                        </tr>
-                                        <tr>
-                                            <td>
-                                                <Box>Acceleration</Box>
-                                            </td>
-                                            <td style={{ cursor: 'default' }}>
-                                                {this.dataDoc.mass2_accelerationX + ''} m/s<sup>2</sup>
-                                            </td>
-                                            <td style={{ cursor: 'default' }}>
-                                                {this.dataDoc.mass2_accelerationY + ''} m/s<sup>2</sup>
-                                            </td>
-                                        </tr>
-                                        <tr>
-                                            <td>
-                                                <Box>Momentum</Box>
-                                            </td>
-                                            <td>{Math.round(NumCast(this.dataDoc.mass2_velocityX) * this.mass1 * 10) / 10} kg*m/s</td>
-                                            <td>{Math.round(NumCast(this.dataDoc.mass2_velocityY) * this.mass1 * 10) / 10} kg*m/s</td>
-                                        </tr>
-                                    </tbody>
-                                </table>
-                            )}
-                        </div>
-                        {this.simulationType !== 'Pendulum' && this.simulationType !== 'Spring' && (
-                            <div>
-                                <p>Kinematic Equations</p>
-                                <ul>
-                                    <li>
-                                        Position: x<sub>1</sub>=x<sub>0</sub>+v<sub>0</sub>t+
-                                        <sup>1</sup>&frasl;
-                                        <sub>2</sub>at
-                                        <sup>2</sup>
-                                    </li>
-                                    <li>
-                                        Velocity: v<sub>1</sub>=v<sub>0</sub>+at
-                                    </li>
-                                    <li>Acceleration: a = F/m</li>
-                                </ul>
-                            </div>
-                        )}
-                        {this.simulationType === 'Spring' && (
-                            <div>
-                                <p>Harmonic Motion Equations: Spring</p>
-                                <ul>
-                                    <li>
-                                        Spring force: F<sub>s</sub>=kd
-                                    </li>
-                                    <li>
-                                        Spring period: T<sub>s</sub>=2&pi;&#8730;<sup>m</sup>&frasl;
-                                        <sub>k</sub>
-                                    </li>
-                                    <li>Equilibrium displacement for vertical spring: d = mg/k</li>
-                                    <li>
-                                        Elastic potential energy: U<sub>s</sub>=<sup>1</sup>&frasl;
-                                        <sub>2</sub>kd<sup>2</sup>
-                                    </li>
-                                    <ul>
-                                        <li>Maximum when system is at maximum displacement, 0 when system is at 0 displacement</li>
-                                    </ul>
-                                    <li>
-                                        Translational kinetic energy: K=<sup>1</sup>&frasl;
-                                        <sub>2</sub>mv<sup>2</sup>
-                                    </li>
-                                    <ul>
-                                        <li>Maximum when system is at maximum/minimum velocity (at 0 displacement), 0 when velocity is 0 (at maximum displacement)</li>
-                                    </ul>
-                                </ul>
-                            </div>
-                        )}
-                        {this.simulationType === 'Pendulum' && (
-                            <div>
-                                <p>Harmonic Motion Equations: Pendulum</p>
-                                <ul>
-                                    <li>
-                                        Pendulum period: T<sub>p</sub>=2&pi;&#8730;<sup>l</sup>&frasl;
-                                        <sub>g</sub>
-                                    </li>
-                                </ul>
-                            </div>
-                        )}
-                    </div>
-                </div>
-                <div
-                    style={{
-                        position: 'fixed',
-                        top: this.yMax - 120 + 20 + 'px',
-                        left: this.xMin + 90 - 80 + 'px',
-                        zIndex: -10000,
-                    }}>
-                    <svg width={100 + 'px'} height={100 + 'px'}>
-                        <defs>
-                            <marker id="miniArrow" markerWidth="20" markerHeight="20" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
-                                <path d="M0,0 L0,6 L9,3 z" fill="#000000" />
-                            </marker>
-                        </defs>
-                        <line x1={20} y1={70} x2={70} y2={70} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
-                        <line x1={20} y1={70} x2={20} y2={20} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
-                    </svg>
-                    <p
-                        style={{
-                            position: 'fixed',
-                            top: this.yMax - 120 + 40 + 'px',
-                            left: this.xMin + 90 - 80 + 'px',
-                        }}>
-                        {this.simulationType === 'Circular Motion' ? 'Z' : 'Y'}
-                    </p>
-                    <p
-                        style={{
-                            position: 'fixed',
-                            top: this.yMax - 120 + 80 + 'px',
-                            left: this.xMin + 90 - 40 + 'px',
-                        }}>
-                        X
-                    </p>
-                </div>
+            <div
+                className="mainView-container"
+                style={{
+                    color: SnappingManager.userColor,
+                    background: SnappingManager.userBackgroundColor,
+                }}
+                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 />
+                <CalendarManager />
+                <ServerStats />
+                <RTFMarkup />
+                <SettingsManager />
+                <ReportManager />
+                <CaptureManager />
+                <GroupManager />
+                <GoogleAuthenticationManager />
+                <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+                <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
+                {this._hideUI ? null : <TopBar />}
+                <LinkDescriptionPopup />
+                {DocButtonState.Instance.LinkEditorDocView ? (
+                    <LinkMenu
+                        clearLinkEditor={action(() => {
+                            DocButtonState.Instance.LinkEditorDocView = undefined;
+                        })}
+                        docView={DocButtonState.Instance.LinkEditorDocView}
+                    />
+                ) : null}
+                {LinkInfo.Instance?.LinkInfo ? (
+                    // eslint-disable-next-line react/jsx-props-no-spreading
+                    <LinkDocPreview {...LinkInfo.Instance.LinkInfo} />
+                ) : null}
+                {((page: string) => {
+                    // prettier-ignore
+                    switch (page) {
+                        case 'home': return <DashboardView />;
+                        case 'dashboard':
+                        default: return (<>
+                                        <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || DocumentView.LightboxDoc() ? 'none' : undefined, zIndex: 2001 }}>
+                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} togglePropertiesFlyout={this.togglePropertiesFlyout} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
+                                        </div>
+                                        {this.mainDashboardArea}
+                                    </> );
+                    }
+                })(Doc.ActivePage)}
+                <PreviewCursor />
+                <TaskCompletionBox />
+                <ContextMenu />
+                <ImageLabelHandler />
+                <SmartDrawHandler />
+                <AnchorMenu />
+                <MapAnchorMenu />
+                <DirectionsAnchorMenu />
+                <DashFieldViewMenu />
+                <MarqueeOptionsMenu />
+                <TimelineMenu />
+                <RichTextMenu />
+                {this.snapLines}
+                <LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
+                <GPTPopup key="gptpopup" />
+                <SchemaCSVPopUp key="schemacsvpopup" />
+                <GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
             </div>
         );
     }
 }
 
-Docs.Prototypes.TemplateMap.set(DocumentType.SIMULATION, {
-    data: '',
-    layout: { view: PhysicsSimulationBox, dataField: 'data' },
-    options: { acl: '', _width: 1000, _height: 800, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' },
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
+    MainView.Instance.selectMenu(doc);
 });
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function createNewPresentation() {
+    return MainView.Instance.createNewPresentation();
+}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function openPresentation(pres: Doc) {
+    return MainView.Instance.openPresentation(pres);
+}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function createNewFolder() {
+    return MainView.Instance.createNewFolder();
+}, 'creates a new folder in myFiles when called');
-- 
cgit v1.2.3-70-g09d2

