aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/MainView.tsx
diff options
context:
space:
mode:
authoreleanor-park <eleanor_park@brown.edu>2024-07-02 14:35:58 -0400
committereleanor-park <eleanor_park@brown.edu>2024-07-02 14:35:58 -0400
commitf84a8d79c07bdd55e48acc976227a2ceeb457e5f (patch)
tree0aee68aa5cd5c492ddea4c692f4f62025ce1f5ad /src/client/views/MainView.tsx
parent33f416349c00bdec405455340befb0ca85b2fee4 (diff)
bug creating drawing groups
Diffstat (limited to 'src/client/views/MainView.tsx')
-rw-r--r--src/client/views/MainView.tsx2964
1 files changed, 1051 insertions, 1913 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>
- &mu;<sub>s</sub>
- </Box>
- }
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="coefficientOfStaticFriction"
- step={0.1}
- unit=""
- upperBound={1}
- value={NumCast(this.dataDoc.coefficientOfStaticFriction)}
- effect={this.updateReviewForcesBasedOnCoefficient}
- showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
- correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('coefficient of static friction')]}
- />
- )}
- {this.selectedQuestion.answerParts.includes('wedge angle') && (
- <InputField
- label={<Box>&theta;</Box>}
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="wedge_angle"
- step={1}
- unit="°"
- upperBound={49}
- value={this.wedgeAngle}
- effect={(val: number) => {
- this.changeWedgeBasedOnNewAngle(val);
- this.updateReviewForcesBasedOnAngle(val);
- }}
- radianEquivalent
- showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
- correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]}
- />
- )}
- </div>
- </div>
- </div>
- )}
- {this.simulationMode === 'Tutorial' && (
- <div className="wordProblemBox">
- <div className="question">
- <h2>Problem</h2>
- <p>{this.tutorial.question}</p>
- </div>
- <div
- style={{
- display: 'flex',
- justifyContent: 'spaceBetween',
- width: '100%',
- }}>
- <IconButton
- onClick={() => {
- let step = NumCast(this.dataDoc.tutorial_stepNumber) - 1;
- step = Math.max(step, 0);
- step = Math.min(step, this.tutorial.steps.length - 1);
- this.dataDoc.tutorial_stepNumber = step;
- this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces);
- this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces);
- this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude;
- }}
- disabled={this.dataDoc.tutorial_stepNumber === 0}>
- <ArrowLeftIcon />
- </IconButton>
- <div>
- <h3>
- Step {NumCast(this.dataDoc.tutorial_stepNumber) + 1}: {this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].description}
- </h3>
- <p>{this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].content}</p>
- </div>
- <IconButton
- onClick={() => {
- let step = NumCast(this.dataDoc.tutorial_stepNumber) + 1;
- step = Math.max(step, 0);
- step = Math.min(step, this.tutorial.steps.length - 1);
- this.dataDoc.tutorial_stepNumber = step;
- this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces);
- this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces);
- this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude;
- }}
- disabled={this.dataDoc.tutorial_stepNumber === this.tutorial.steps.length - 1}>
- <ArrowRightIcon />
- </IconButton>
- </div>
- <div>
- {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && <p>Resources</p>}
- {this.simulationType === 'One Weight' && (
- <ul>
- <li>
- <a
- href="https://www.khanacademy.org/science/physics/one-dimensional-motion"
- target="_blank"
- rel="noreferrer"
- style={{
- color: 'blue',
- textDecoration: 'underline',
- }}>
- Khan Academy - One Dimensional Motion
- </a>
- </li>
- <li>
- <a
- href="https://www.khanacademy.org/science/physics/two-dimensional-motion"
- target="_blank"
- rel="noreferrer"
- style={{
- color: 'blue',
- textDecoration: 'underline',
- }}>
- Khan Academy - Two Dimensional Motion
- </a>
- </li>
- </ul>
- )}
- {this.simulationType === 'Inclined Plane' && (
- <ul>
- <li>
- <a
- href="https://www.khanacademy.org/science/physics/forces-newtons-laws#normal-contact-force"
- target="_blank"
- rel="noreferrer"
- style={{
- color: 'blue',
- textDecoration: 'underline',
- }}>
- Khan Academy - Normal Force
- </a>
- </li>
- <li>
- <a
- href="https://www.khanacademy.org/science/physics/forces-newtons-laws#inclined-planes-friction"
- target="_blank"
- rel="noreferrer"
- style={{
- color: 'blue',
- textDecoration: 'underline',
- }}>
- Khan Academy - Inclined Planes
- </a>
- </li>
- </ul>
- )}
- {this.simulationType === 'Pendulum' && (
- <ul>
- <li>
- <a
- href="https://www.khanacademy.org/science/physics/forces-newtons-laws#tension-tutorial"
- target="_blank"
- rel="noreferrer"
- style={{
- color: 'blue',
- textDecoration: 'underline',
- }}>
- Khan Academy - Tension
- </a>
- </li>
- </ul>
- )}
- </div>
- </div>
- )}
- {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
- <div
- style={{
- display: 'flex',
- justifyContent: 'space-between',
- marginTop: '10px',
- }}>
- <p
- style={{
- color: 'blue',
- textDecoration: 'underline',
- cursor: 'pointer',
- }}
- onClick={() => (this.dataDoc.simulation_mode = 'Tutorial')}>
- {' '}
- Go to walkthrough{' '}
- </p>
- <div style={{ display: 'flex', flexDirection: 'column' }}>
- <Button
- onClick={action(() => {
- this._simReset++;
- this.checkAnswers();
- this.dataDoc.simulation_showIcon = true;
- })}
- variant="outlined">
- <p>Submit</p>
- </Button>
- <Button
- onClick={() => {
- this.generateNewQuestion();
- this.dataDoc.simulation_showIcon = false;
- }}
- variant="outlined">
- <p>New question</p>
- </Button>
- </div>
- </div>
- )}
- {this.simulationMode === 'Freeform' && (
- <div className="vars">
- <FormControl component="fieldset">
- <FormGroup>
- {this.simulationType === 'One Weight' && (
- <FormControlLabel
- control={<Checkbox checked={BoolCast(this.dataDoc.elasticCollisions)} onChange={() => (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />}
- label="Make collisions elastic"
- labelPlacement="start"
- />
- )}
- <FormControlLabel
- control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showForces)} onChange={() => (this.dataDoc.simulation_showForces = !this.dataDoc.simulation_showForces)} />}
- label="Show force vectors"
- labelPlacement="start"
- />
- {(this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && (
- <FormControlLabel
- control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showComponentForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />}
- label="Show component force vectors"
- labelPlacement="start"
- />
- )}
- <FormControlLabel
- control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showAcceleration)} onChange={() => (this.dataDoc.simulation_showAcceleration = !this.dataDoc.simulation_showAcceleration)} />}
- label="Show acceleration vector"
- labelPlacement="start"
- />
- <FormControlLabel
- control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showVelocity)} onChange={() => (this.dataDoc.simulation_showVelocity = !this.dataDoc.simulation_showVelocity)} />}
- label="Show velocity vector"
- labelPlacement="start"
- />
- <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit="x" upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth="5em" />
- {this.dataDoc.simulation_paused && this.simulationType !== 'Circular Motion' && (
- <InputField
- label={<Box>Gravity</Box>}
- lowerBound={-30}
- dataDoc={this.dataDoc}
- prop="gravity"
- step={0.01}
- unit="m/s2"
- upperBound={0}
- value={NumCast(this.dataDoc.simulation_gravity, -9.81)}
- effect={(val: number) => this.setupSimulation()}
- labelWidth="5em"
- />
- )}
- {this.dataDoc.simulation_paused && this.simulationType !== 'Pulley' && (
- <InputField
- label={<Box>Mass</Box>}
- lowerBound={1}
- dataDoc={this.dataDoc}
- prop="mass1"
- step={0.1}
- unit="kg"
- upperBound={5}
- value={this.mass1 ?? 1}
- effect={(val: number) => this.setupSimulation()}
- labelWidth="5em"
- />
- )}
- {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
- <InputField
- label={<Box>Red mass</Box>}
- lowerBound={1}
- dataDoc={this.dataDoc}
- prop="mass1"
- step={0.1}
- unit="kg"
- upperBound={5}
- value={this.mass1 ?? 1}
- effect={(val: number) => this.setupSimulation()}
- labelWidth="5em"
- />
- )}
- {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
- <InputField
- label={<Box>Blue mass</Box>}
- lowerBound={1}
- dataDoc={this.dataDoc}
- prop="mass2"
- step={0.1}
- unit="kg"
- upperBound={5}
- value={this.mass2 ?? 1}
- effect={(val: number) => this.setupSimulation()}
- labelWidth="5em"
- />
- )}
- {this.dataDoc.simulation_paused && this.simulationType === 'Circular Motion' && (
- <InputField
- label={<Box>Rod length</Box>}
- lowerBound={100}
- dataDoc={this.dataDoc}
- prop="circularMotionRadius"
- step={5}
- unit="m"
- upperBound={250}
- value={this.circularMotionRadius}
- effect={(val: number) => this.setupSimulation()}
- labelWidth="5em"
- />
- )}
- </FormGroup>
- </FormControl>
- {this.simulationType === 'Spring' && this.dataDoc.simulation_paused && (
- <div>
- <InputField
- label={<Typography color="inherit">Spring stiffness</Typography>}
- lowerBound={0.1}
- dataDoc={this.dataDoc}
- prop="spring_constant"
- step={1}
- unit="N/m"
- upperBound={500}
- value={this.springConstant}
- effect={action(() => this._simReset++)}
- radianEquivalent={false}
- mode="Freeform"
- labelWidth="7em"
- />
- <InputField
- label={<Typography color="inherit">Rest length</Typography>}
- lowerBound={10}
- dataDoc={this.dataDoc}
- prop="spring_lengthRest"
- step={100}
- unit=""
- upperBound={500}
- value={this.springLengthRest}
- effect={action(() => this._simReset++)}
- radianEquivalent={false}
- mode="Freeform"
- labelWidth="7em"
- />
- <InputField
- label={<Typography color="inherit">Starting displacement</Typography>}
- lowerBound={-(this.springLengthRest - 10)}
- dataDoc={this.dataDoc}
- prop=""
- step={10}
- unit=""
- upperBound={this.springLengthRest}
- value={this.springLengthStart - this.springLengthRest}
- effect={action((val: number) => {
- this.dataDoc.mass1_positionYstart = this.springLengthRest + val;
- this.dataDoc.spring_lengthStart = this.springLengthRest + val;
- this._simReset++;
- })}
- radianEquivalent={false}
- mode="Freeform"
- labelWidth="7em"
- />
- </div>
- )}
- {this.simulationType === 'Inclined Plane' && this.dataDoc.simulation_paused && (
- <div>
- <InputField
- label={<Box>&theta;</Box>}
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="wedge_angle"
- step={1}
- unit="°"
- upperBound={49}
- value={this.wedgeAngle}
- effect={action((val: number) => {
- this.changeWedgeBasedOnNewAngle(val);
- this._simReset++;
- })}
- radianEquivalent
- mode="Freeform"
- labelWidth="2em"
- />
- <InputField
- label={
- <Box>
- &mu;<sub>s</sub>
- </Box>
- }
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="coefficientOfStaticFriction"
- step={0.1}
- unit=""
- upperBound={1}
- value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0}
- effect={action((val: number) => {
- this.updateForcesWithFriction(val);
- if (val < NumCast(this.dataDoc.coefficientOfKineticFriction)) {
- this.dataDoc.soefficientOfKineticFriction = val;
- }
- this._simReset++;
- })}
- mode="Freeform"
- labelWidth="2em"
- />
- <InputField
- label={
- <Box>
- &mu;<sub>k</sub>
- </Box>
- }
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="coefficientOfKineticFriction"
- step={0.1}
- unit=""
- upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)}
- value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0}
- effect={action(() => this._simReset++)}
- mode="Freeform"
- labelWidth="2em"
- />
- </div>
- )}
- {this.simulationType === 'Inclined Plane' && !this.dataDoc.simulation_paused && (
- <Typography>
- <>
- &theta;: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad
- <br />
- &mu; <sub>s</sub>: {this.dataDoc.coefficientOfStaticFriction}
- <br />
- &mu; <sub>k</sub>: {this.dataDoc.coefficientOfKineticFriction}
- </>
- </Typography>
- )}
- {this.simulationType === 'Pendulum' && !this.dataDoc.simulation_paused && (
- <Typography>
- &theta;: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad
- </Typography>
- )}
- {this.simulationType === 'Pendulum' && this.dataDoc.simulation_paused && (
- <div>
- <InputField
- label={<Box>Angle</Box>}
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="pendulum_angle"
- step={1}
- unit="°"
- upperBound={59}
- value={NumCast(this.dataDoc.pendulum_angle, 30)}
- effect={action(value => {
- this.dataDoc.pendulum_angleStart = value;
- this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length;
- if (this.simulationType === 'Pendulum') {
- const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180);
-
- const forceOfTension: IForce = {
- description: 'Tension',
- magnitude: mag,
- directionInDegrees: 90 - value,
- };
- const gravityParallel: IForce = {
- description: 'Gravity Parallel Component',
- magnitude: Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180),
- directionInDegrees: 270 - value,
- };
- const gravityPerpendicular: IForce = {
- description: 'Gravity Perpendicular Component',
- magnitude: Math.abs(this.gravity) * Math.sin((value * Math.PI) / 180),
- directionInDegrees: -value,
- };
-
- const length = this.pendulumLength;
- const x = length * Math.cos(((90 - value) * Math.PI) / 180);
- const y = length * Math.sin(((90 - value) * Math.PI) / 180);
- const xPos = this.xMax / 2 - x - NumCast(this.dataDoc.radius);
- const yPos = y - NumCast(this.dataDoc.radius) - 5;
- this.dataDoc.mass1_positionXstart = xPos;
- this.dataDoc.mass1_positionYstart = yPos;
-
- this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
- this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]);
- this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]);
- this._simReset++;
- }
- })}
- radianEquivalent
- mode="Freeform"
- labelWidth="5em"
- />
- <InputField
- label={<Box>Rod length</Box>}
- lowerBound={0}
- dataDoc={this.dataDoc}
- prop="pendulum_length"
- step={1}
- unit="m"
- upperBound={400}
- value={Math.round(this.pendulumLength)}
- effect={action(value => {
- if (this.simulationType === 'Pendulum') {
- this.dataDoc.pendulum_angleStart = this.pendulumAngle;
- this.dataDoc.pendulum_lengthStart = value;
- this._simReset++;
- }
- })}
- radianEquivalent={false}
- mode="Freeform"
- labelWidth="5em"
- />
- </div>
- )}
- </div>
- )}
- <div className="mechanicsSimulationEquation">
- {this.simulationMode === 'Freeform' && (
- <table>
- <tbody>
- <tr>
- <td>{this.simulationType === 'Pulley' ? 'Red Weight' : ''}</td>
- <td>X</td>
- <td>Y</td>
- </tr>
- <tr>
- <td
- style={{ cursor: 'help' }}
- // onClick={() => {
- // window.open(
- // "https://www.khanacademy.org/science/physics/two-dimensional-motion"
- // );
- // }}
- >
- <Box>Position</Box>
- </td>
- {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
- <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_positionX + ''} m</td>
- )}{' '}
- {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
- <td
- style={{
- cursor: 'default',
- }}>
- <InputField
- lowerBound={this.simulationType === 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15}
- dataDoc={this.dataDoc}
- prop="mass1_positionX"
- step={1}
- unit="m"
- upperBound={this.simulationType === 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15}
- value={NumCast(this.dataDoc.mass1_positionX)}
- effect={value => {
- this.dataDoc.mass1_xChange = value;
- if (this.simulationType === 'Suspension') {
- const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
- const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
- const deltaX1 = value + this.radius - x1rod;
- const deltaX2 = x2rod - (value + this.radius);
- const deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius;
- let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
- let dir2T = Math.atan(deltaY / deltaX2);
- const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
- const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
- dir1T = (dir1T * 180) / Math.PI;
- dir2T = (dir2T * 180) / Math.PI;
- const tensionForce1: IForce = {
- description: 'Tension',
- magnitude: tensionMag1,
- directionInDegrees: dir1T,
- };
- const tensionForce2: IForce = {
- description: 'Tension',
- magnitude: tensionMag2,
- directionInDegrees: dir2T,
- };
- const gravity = this.gravityForce(this.mass1);
- this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
- }
- }}
- small
- mode="Freeform"
- />
- </td>
- )}{' '}
- {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
- <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_positionY)} m`}</td>
- )}{' '}
- {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
- <td
- style={{
- cursor: 'default',
- }}>
- <InputField
- lowerBound={1}
- dataDoc={this.dataDoc}
- prop="mass1_positionY"
- step={1}
- unit="m"
- upperBound={this.yMax - 110}
- value={NumCast(this.dataDoc.mass1_positionY)}
- effect={value => {
- this.dataDoc.mass1_yChange = value;
- if (this.simulationType === 'Suspension') {
- const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
- const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
- const deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod;
- const deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius);
- const deltaY = this.getYPosFromDisplay(value) + this.radius;
- let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
- let dir2T = Math.atan(deltaY / deltaX2);
- const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
- const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
- dir1T = (dir1T * 180) / Math.PI;
- dir2T = (dir2T * 180) / Math.PI;
- const tensionForce1: IForce = {
- description: 'Tension',
- magnitude: tensionMag1,
- directionInDegrees: dir1T,
- };
- const tensionForce2: IForce = {
- description: 'Tension',
- magnitude: tensionMag2,
- directionInDegrees: dir2T,
- };
- const gravity = this.gravityForce(this.mass1);
- this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
- }
- }}
- small
- mode="Freeform"
- />
- </td>
- )}{' '}
- </tr>
- <tr>
- <td
- style={{ cursor: 'help' }}
- // onClick={() => {
- // window.open(
- // "https://www.khanacademy.org/science/physics/two-dimensional-motion"
- // );
- // }}
- >
- <Box>Velocity</Box>
- </td>
- {(!this.dataDoc.simulation_paused || (this.simulationType !== 'One Weight' && this.simulationType !== 'Circular Motion')) && (
- <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_velocityX)} m/s`}</td>
- )}{' '}
- {this.dataDoc.simulation_paused && (this.simulationType === 'One Weight' || this.simulationType === 'Circular Motion') && (
- <td
- style={{
- cursor: 'default',
- }}>
- <InputField
- lowerBound={-50}
- dataDoc={this.dataDoc}
- prop="mass1_velocityX"
- step={1}
- unit="m/s"
- upperBound={50}
- value={NumCast(this.dataDoc.mass1_velocityX)}
- effect={action(value => {
- this.dataDoc.mass1_velocityXstart = value;
- this._simReset++;
- })}
- small
- mode="Freeform"
- />
- </td>
- )}{' '}
- {(!this.dataDoc.simulation_paused || this.simulationType !== 'One Weight') && <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_velocityY + ''} m/s</td>}{' '}
- {this.dataDoc.simulation_paused && this.simulationType === 'One Weight' && (
- <td
- style={{
- cursor: 'default',
- }}>
- <InputField
- lowerBound={-50}
- dataDoc={this.dataDoc}
- prop="mass1_velocityY"
- step={1}
- unit="m/s"
- upperBound={50}
- value={NumCast(this.dataDoc.mass1_velocityY)}
- effect={value => {
- this.dataDoc.mass1_velocityYstart = -value;
- }}
- small
- mode="Freeform"
- />
- </td>
- )}{' '}
- </tr>
- <tr>
- <td
- style={{ cursor: 'help' }}
- // onClick={() => {
- // window.open(
- // "https://www.khanacademy.org/science/physics/two-dimensional-motion"
- // );
- // }}
- >
- <Box>Acceleration</Box>
- </td>
- <td style={{ cursor: 'default' }}>
- {this.dataDoc.mass1_accelerationX + ''} m/s<sup>2</sup>
- </td>
- <td style={{ cursor: 'default' }}>
- {this.dataDoc.mass1_accelerationY + ''} m/s<sup>2</sup>
- </td>
- </tr>
- <tr>
- <td>
- <Box>Momentum</Box>
- </td>
- <td>{Math.round(NumCast(this.dataDoc.mass1_velocityX) * this.mass1 * 10) / 10} kg*m/s</td>
- <td>{Math.round(NumCast(this.dataDoc.mass1_velocityY) * this.mass1 * 10) / 10} kg*m/s</td>
- </tr>
- </tbody>
- </table>
- )}
- {this.simulationMode === 'Freeform' && this.simulationType === 'Pulley' && (
- <table>
- <tbody>
- <tr>
- <td>Blue Weight</td>
- <td>X</td>
- <td>Y</td>
- </tr>
- <tr>
- <td>
- <Box>Position</Box>
- </td>
- <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionX} m`}</td>
- <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionY} m`}</td>
- </tr>
- <tr>
- <td>
- <Box>Velocity</Box>
- </td>
- <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionX} m/s`}</td>
- <td style={{ cursor: 'default' }}>{`${this.dataDoc.mass2_positionY} m/s`}</td>
- </tr>
- <tr>
- <td>
- <Box>Acceleration</Box>
- </td>
- <td style={{ cursor: 'default' }}>
- {this.dataDoc.mass2_accelerationX + ''} m/s<sup>2</sup>
- </td>
- <td style={{ cursor: 'default' }}>
- {this.dataDoc.mass2_accelerationY + ''} m/s<sup>2</sup>
- </td>
- </tr>
- <tr>
- <td>
- <Box>Momentum</Box>
- </td>
- <td>{Math.round(NumCast(this.dataDoc.mass2_velocityX) * this.mass1 * 10) / 10} kg*m/s</td>
- <td>{Math.round(NumCast(this.dataDoc.mass2_velocityY) * this.mass1 * 10) / 10} kg*m/s</td>
- </tr>
- </tbody>
- </table>
- )}
- </div>
- {this.simulationType !== 'Pendulum' && this.simulationType !== 'Spring' && (
- <div>
- <p>Kinematic Equations</p>
- <ul>
- <li>
- Position: x<sub>1</sub>=x<sub>0</sub>+v<sub>0</sub>t+
- <sup>1</sup>&frasl;
- <sub>2</sub>at
- <sup>2</sup>
- </li>
- <li>
- Velocity: v<sub>1</sub>=v<sub>0</sub>+at
- </li>
- <li>Acceleration: a = F/m</li>
- </ul>
- </div>
- )}
- {this.simulationType === 'Spring' && (
- <div>
- <p>Harmonic Motion Equations: Spring</p>
- <ul>
- <li>
- Spring force: F<sub>s</sub>=kd
- </li>
- <li>
- Spring period: T<sub>s</sub>=2&pi;&#8730;<sup>m</sup>&frasl;
- <sub>k</sub>
- </li>
- <li>Equilibrium displacement for vertical spring: d = mg/k</li>
- <li>
- Elastic potential energy: U<sub>s</sub>=<sup>1</sup>&frasl;
- <sub>2</sub>kd<sup>2</sup>
- </li>
- <ul>
- <li>Maximum when system is at maximum displacement, 0 when system is at 0 displacement</li>
- </ul>
- <li>
- Translational kinetic energy: K=<sup>1</sup>&frasl;
- <sub>2</sub>mv<sup>2</sup>
- </li>
- <ul>
- <li>Maximum when system is at maximum/minimum velocity (at 0 displacement), 0 when velocity is 0 (at maximum displacement)</li>
- </ul>
- </ul>
- </div>
- )}
- {this.simulationType === 'Pendulum' && (
- <div>
- <p>Harmonic Motion Equations: Pendulum</p>
- <ul>
- <li>
- Pendulum period: T<sub>p</sub>=2&pi;&#8730;<sup>l</sup>&frasl;
- <sub>g</sub>
- </li>
- </ul>
- </div>
- )}
- </div>
- </div>
- <div
- style={{
- position: 'fixed',
- top: this.yMax - 120 + 20 + 'px',
- left: this.xMin + 90 - 80 + 'px',
- zIndex: -10000,
- }}>
- <svg width={100 + 'px'} height={100 + 'px'}>
- <defs>
- <marker id="miniArrow" markerWidth="20" markerHeight="20" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
- <path d="M0,0 L0,6 L9,3 z" fill="#000000" />
- </marker>
- </defs>
- <line x1={20} y1={70} x2={70} y2={70} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
- <line x1={20} y1={70} x2={20} y2={20} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
- </svg>
- <p
- style={{
- position: 'fixed',
- top: this.yMax - 120 + 40 + 'px',
- left: this.xMin + 90 - 80 + 'px',
- }}>
- {this.simulationType === 'Circular Motion' ? 'Z' : 'Y'}
- </p>
- <p
- style={{
- position: 'fixed',
- top: this.yMax - 120 + 80 + 'px',
- left: this.xMin + 90 - 40 + 'px',
- }}>
- X
- </p>
- </div>
+ <div
+ className="mainView-container"
+ style={{
+ color: SnappingManager.userColor,
+ background: SnappingManager.userBackgroundColor,
+ }}
+ onScroll={() =>
+ (ele => {
+ ele.scrollTop = ele.scrollLeft = 0;
+ })(document.getElementById('root')!)
+ }
+ ref={r => {
+ r &&
+ new _global.ResizeObserver(
+ action(() => {
+ this._windowWidth = r.getBoundingClientRect().width;
+ this._windowHeight = r.getBoundingClientRect().height;
+ })
+ ).observe(r);
+ }}>
+ {this.inkResources}
+ <DictationOverlay />
+ <SharingManager />
+ <CalendarManager />
+ <ServerStats />
+ <RTFMarkup />
+ <SettingsManager />
+ <ReportManager />
+ <CaptureManager />
+ <GroupManager />
+ <GoogleAuthenticationManager />
+ <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+ <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
+ {this._hideUI ? null : <TopBar />}
+ <LinkDescriptionPopup />
+ {DocButtonState.Instance.LinkEditorDocView ? (
+ <LinkMenu
+ clearLinkEditor={action(() => {
+ DocButtonState.Instance.LinkEditorDocView = undefined;
+ })}
+ docView={DocButtonState.Instance.LinkEditorDocView}
+ />
+ ) : null}
+ {LinkInfo.Instance?.LinkInfo ? (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ <LinkDocPreview {...LinkInfo.Instance.LinkInfo} />
+ ) : null}
+ {((page: string) => {
+ // prettier-ignore
+ switch (page) {
+ case 'home': return <DashboardView />;
+ case 'dashboard':
+ default: return (<>
+ <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || DocumentView.LightboxDoc() ? 'none' : undefined, zIndex: 2001 }}>
+ <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} togglePropertiesFlyout={this.togglePropertiesFlyout} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
+ </div>
+ {this.mainDashboardArea}
+ </> );
+ }
+ })(Doc.ActivePage)}
+ <PreviewCursor />
+ <TaskCompletionBox />
+ <ContextMenu />
+ <ImageLabelHandler />
+ <SmartDrawHandler />
+ <AnchorMenu />
+ <MapAnchorMenu />
+ <DirectionsAnchorMenu />
+ <DashFieldViewMenu />
+ <MarqueeOptionsMenu />
+ <TimelineMenu />
+ <RichTextMenu />
+ {this.snapLines}
+ <LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
+ <GPTPopup key="gptpopup" />
+ <SchemaCSVPopUp key="schemacsvpopup" />
+ <GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
</div>
);
}
}
-Docs.Prototypes.TemplateMap.set(DocumentType.SIMULATION, {
- data: '',
- layout: { view: PhysicsSimulationBox, dataField: 'data' },
- options: { acl: '', _width: 1000, _height: 800, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' },
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
+ MainView.Instance.selectMenu(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function createNewPresentation() {
+ return MainView.Instance.createNewPresentation();
+}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function openPresentation(pres: Doc) {
+ return MainView.Instance.openPresentation(pres);
+}, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function createNewFolder() {
+ return MainView.Instance.createNewFolder();
+}, 'creates a new folder in myFiles when called');