diff options
author | eleanor-park <eleanor_park@brown.edu> | 2024-07-02 14:35:58 -0400 |
---|---|---|
committer | eleanor-park <eleanor_park@brown.edu> | 2024-07-02 14:35:58 -0400 |
commit | f84a8d79c07bdd55e48acc976227a2ceeb457e5f (patch) | |
tree | 0aee68aa5cd5c492ddea4c692f4f62025ce1f5ad /src | |
parent | 33f416349c00bdec405455340befb0ca85b2fee4 (diff) |
bug creating drawing groups
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/MainView.tsx | 2964 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 22 | ||||
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 14 |
3 files changed, 1070 insertions, 1930 deletions
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> - μ<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>θ</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>θ</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> - μ<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> - μ<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> - <> - θ: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad - <br /> - μ <sub>s</sub>: {this.dataDoc.coefficientOfStaticFriction} - <br /> - μ <sub>k</sub>: {this.dataDoc.coefficientOfKineticFriction} - </> - </Typography> - )} - {this.simulationType === 'Pendulum' && !this.dataDoc.simulation_paused && ( - <Typography> - θ: {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>⁄ - <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π√<sup>m</sup>⁄ - <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>⁄ - <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>⁄ - <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π√<sup>l</sup>⁄ - <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'); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 467191735..09bb3649d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1270,7 +1270,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection _drawing: Doc[] = []; @undoBatch - createDrawing = (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => { + createDrawing = (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => { strokeData.forEach((stroke: [InkData, string, string]) => { const bounds = InkField.getBounds(stroke[0]); const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); @@ -1291,16 +1291,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._drawing.push(inkDoc); this.addDocument(inkDoc); }); - // const collection = this._marqueeViewRef.current?.collection(undefined, false, this._drawing); - // if (collection) { - // const docData = collection[DocData]; - // docData.title = opts.text; - // docData.drawingInput = opts.text; - // docData.drawingComplexity = opts.complexity; - // docData.drawingColored = opts.autoColor; - // docData.drawingSize = opts.size; - // docData.drawingData = gptRes; - // } + const collection = this._marqueeViewRef.current?.collection(undefined, true, this._drawing); + if (collection) { + const docData = collection[DocData]; + docData.title = opts.text; + docData.drawingInput = opts.text; + docData.drawingComplexity = opts.complexity; + docData.drawingColored = opts.autoColor; + docData.drawingSize = opts.size; + docData.drawingData = gptRes; + } this._batch?.end(); }; diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 6d2cc0593..d24cc9d50 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -43,7 +43,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { @observable private _size: number = 200; @observable private _autoColor: boolean = true; @observable private _regenInput: string = ''; - private _addFunc: (e: React.PointerEvent<Element>, strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => void = () => {}; + private _addFunc: (e: React.PointerEvent<Element>, strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => void = () => {}; private _deleteFunc: (doc?: Doc) => void = () => {}; private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 300, autoColor: true, x: 0, y: 0 }; private _lastResponse: string = ''; @@ -86,7 +86,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { }; @action - displaySmartDrawHandler = (x: number, y: number, addFunc: (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => void, deleteFunc: (doc?: Doc) => void) => { + displaySmartDrawHandler = (x: number, y: number, addFunc: (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => void, deleteFunc: (doc?: Doc) => void) => { this._pageX = x; this._pageY = y; this._display = true; @@ -95,9 +95,9 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { }; @action - displayRegenerate = (x: number, y: number, addFunc: (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => void, deleteFunc: (doc?: Doc) => void) => { - const selectedDoc: Doc = DocumentView.SelectedDocs().lastElement(); - const docData = selectedDoc[DocData]; + displayRegenerate = (x: number, y: number, addFunc: (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => void, deleteFunc: (doc?: Doc) => void) => { + this._selectedDoc = DocumentView.SelectedDocs().lastElement(); + const docData = this._selectedDoc[DocData]; this._addFunc = addFunc; this._deleteFunc = deleteFunc; this._pageX = x; @@ -210,8 +210,10 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> { }); if (regenerate) { this._deleteFunc(this._selectedDoc); + this._addFunc(e, strokeData, this._lastInput, svg[0], this._selectedDoc); + } else { + this._addFunc(e, strokeData, this._lastInput, svg[0]); } - this._addFunc(e, strokeData, this._lastInput, svg[0]); } }; |