+
+ {activityErrors}
{pageControlsTop}
{title}
{pages}
diff --git a/src/Viewer/DoenetML.css b/src/Viewer/DoenetML.css
new file mode 100644
index 0000000000..17034740e6
--- /dev/null
+++ b/src/Viewer/DoenetML.css
@@ -0,0 +1,308 @@
+@font-face {
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: normal;
+ src:
+/* url(OpenSansRegularWoffTwo) format('woff2'),
+url(OpenSansRegularWoff) format('woff'); */ url("/fonts/files/open-sans-v18-latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/files/open-sans-v18-latin-regular.woff") format("woff");
+}
+
+@font-face {
+ font-family: "Open Sans";
+ font-style: italic;
+ font-weight: normal;
+ src: url("/fonts/files/open-sans-v18-latin-italic.woff2") format("woff2"),
+ url("/fonts/files/open-sans-v18-latin-italic.woff") format("woff");
+}
+
+@font-face {
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 700;
+ src: url("/fonts/files/open-sans-v18-latin-700.woff2") format("woff2"),
+ url("/fonts/files/open-sans-v18-latin-700.woff") format("woff");
+}
+
+@font-face {
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 300;
+ src: url("/fonts/files/open-sans-v18-latin-light.woff2") format("woff2"),
+ url("/fonts/files/open-sans-v18-latin-light.woff") format("woff");
+}
+
+@font-face {
+ font-family: "Open Sans";
+ font-style: italic;
+ font-weight: 300;
+ src: url("/fonts/files/open-sans-v18-latin-light-italic.woff2")
+ format("woff2"),
+ url("/fonts/files/open-sans-v18-latin-light-italic.woff") format("woff");
+}
+
+@font-face {
+ font-family: "Open Sans";
+ font-style: italic;
+ font-weight: 700;
+ src: url("/fonts/files/open-sans-v18-latin-700italic.woff2") format("woff2"),
+ url("/fonts/files/open-sans-v18-latin-700italic.woff") format("woff");
+}
+html {
+ font-family: "Open Sans" !important;
+ --menuWidth: 220px;
+ --mainBlue: #1a5a99;
+ --lightBlue: hsl(209, 54%, 82%);
+ --solidLightBlue: #8fb8de;
+ --mainGray: #e3e3e3;
+ --donutBody: #eea177;
+ --donutTopping: #6d4445;
+ --mainBorder: 2px solid black;
+ --mainBorderRadius: 5px;
+ --mainRed: #c1292e;
+ --lightRed: hsl(0, 54%, 82%);
+ --mainGreen: #459152;
+ --canvas: white;
+ --canvastext: black;
+ --lightGreen: #a6f19f;
+ --lightYellow: #f5ed85;
+ --whiteBlankLink: #6d4445;
+ --mainYellow: #94610a;
+ --mainPurple: #4a03d9;
+}
+
+[data-theme="dark"] {
+ font-family: "Open Sans" !important;
+ --menuWidth: 220px;
+ --mainBlue: #1a5a99;
+ --lightBlue: hsl(209, 54%, 82%);
+ --solidLightBlue: #1a5a99;
+ --mainGray: #a9a9a9;
+ --donutBody: #eea177;
+ --donutTopping: #6d4445;
+ --mainBorder: 2px solid white;
+ --mainBorderRadius: 5px;
+ --mainRed: #c1292e;
+ --lightRed: hsl(0, 54%, 82%);
+ --mainGreen: #459152;
+ --canvas: #121212;
+ --canvastext: white;
+ --lightGreen: #a6f19f;
+ --lightYellow: #f5ed85;
+ --whiteBlankLink: #a9abe5;
+ --mainYellow: #efab34;
+ --mainPurple: #4a03d9;
+}
+.doenet-viewer {
+ font-family: "Open Sans" !important;
+ color: var(--canvastext);
+}
+.doenet-viewer h1 {
+ font-size: 2em;
+ font-weight: bold;
+}
+.doenet-viewer h2 {
+ font-size: 1.5em;
+ font-weight: bold;
+ margin-block-start: 0.83em;
+ margin-block-end: 0.83em;
+}
+.doenet-viewer h3 {
+ font-size: 1.17em;
+ font-weight: bold;
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+.doenet-viewer h4 {
+ font-weight: bold;
+ margin-block-start: 1.33em;
+ margin-block-end: 1.33em;
+}
+.doenet-viewer h5 {
+ font-size: 0.83em;
+ font-weight: bold;
+ margin-block-start: 1.67em;
+ margin-block-end: 1.67em;
+}
+.doenet-viewer p {
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+.doenet-viewer input {
+ box-sizing: content-box;
+ padding: 2px 1px;
+}
+div.jxgbox input {
+ border-width: 2px;
+}
+div.jxgbox button {
+ margin: 0;
+ border-radius: var(--mainBorderRadius);
+ border-width: 2px;
+ border-color: var(--mainBlue);
+ border-style: solid;
+ padding: 1px 6px;
+ background-color: var(--mainBlue);
+ color: white;
+}
+div.jxgbox button:hover {
+ background-color: var(--lightBlue);
+ color: black;
+ border-color: var(--lightBlue);
+}
+div.jxgbox input[type="checkbox"] {
+ margin: 3px 3px 3px 4px;
+ height: 18px;
+ width: 18px;
+}
+
+/*
+ Copyright 2008-2023
+ Matthias Ehmann,
+ Michael Gerhaeuser,
+ Carsten Miller,
+ Bianca Valentin,
+ Andreas Walter,
+ Alfred Wassermann,
+ Peter Wilfahrt
+
+ This file is part of JSXGraph.
+
+ JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
+
+ You can redistribute it and/or modify it under the terms of the
+
+ * GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version
+ OR
+ * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
+
+ JSXGraph is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License and
+ the MIT License along with JSXGraph. If not, see
+ and
.
+ */
+
+.jxgbox {
+ /* for IE 7 */
+ position: relative;
+ overflow: hidden;
+ background-color: #fff;
+ border-style: solid;
+ border-width: 1px;
+ border-color: #356aa0;
+ border-radius: 10px;
+ margin: 0;
+ -webkit-border-radius: 10px;
+ -ms-touch-action: none;
+ /* "touch-action: none;" is set with JavaScript. */
+}
+
+.jxgbox svg text {
+ cursor: default;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.JXGtext {
+ font-family: Courier, monospace;
+ /*
+ * The default font family is now set in
+ * JXG.Options.text.cssdefaultstyle = 'font-family: Arial, Helvetica, Geneva, sans-serif;'
+ */
+
+ /* "background-color: transparent;" may produce artefacts in IE. Solution: setting a color explicitly. */
+ background-color: transparent;
+ padding: 0;
+ margin: 0;
+}
+
+.JXGinfobox {
+ border-style: none;
+ border-width: 0;
+ border-color: black;
+}
+
+/* CSS attributes will (permantely) overwrite attributes set in JSXGraph */
+.JXGimage {
+ /* opacity: 1.0; */
+}
+.JXGimageHighlight {
+ /* opacity: 0.6; */
+}
+
+.jxgbox :focus {
+ outline-width: 0.5px;
+ outline-style: dotted;
+}
+
+/* CSS rules for the navigation bar */
+.JXG_navigation {
+ position: absolute;
+ right: 5px;
+ bottom: 5px;
+
+ z-index: 100;
+
+ background-color: transparent;
+ padding: 2px;
+ font-size: 14px;
+ cursor: pointer;
+}
+
+.JXG_navigation_button {
+ color: #666;
+}
+
+.JXG_navigation_button:hover {
+ border-radius: 2px;
+ background: rgba(184, 184, 184, 0.5);
+}
+
+.JXG_navigation_button svg {
+ top: 0.2em;
+ position: relative;
+ padding: 0;
+}
+
+/* CSS rules for the wrapping div in fullscreen mode */
+
+.JXG_wrap_private:-moz-full-screen {
+ background-color: #ccc;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.JXG_wrap_private:-webkit-full-screen {
+ background-color: #ccc;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.JXG_wrap_private:fullscreen {
+ background-color: #ccc;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.JXG_wrap_private:-ms-fullscreen {
+ background-color: #ccc;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/**************************************************
+ End JSXGraph styles
+ **************************************************/
diff --git a/src/Viewer/DoenetML.jsx b/src/Viewer/DoenetML.jsx
new file mode 100644
index 0000000000..8346af1a4a
--- /dev/null
+++ b/src/Viewer/DoenetML.jsx
@@ -0,0 +1,206 @@
+import "./DoenetML.css";
+import { prng_alea } from "esm-seedrandom";
+import React, { useRef } from "react";
+import { ActivityViewer } from "./ActivityViewer.jsx";
+import { RecoilRoot } from "recoil";
+import { MathJaxContext } from "better-react-mathjax";
+import { mathjaxConfig } from "../Core/utils/math";
+import VirtualKeyboard from "../Tools/_framework/Footers/VirtualKeyboard";
+import { ChakraProvider, extendTheme } from "@chakra-ui/react";
+
+let rngClass = prng_alea;
+
+/**
+ * this is a hack for react-mathqill
+ * error: global is not defined
+ */
+window.global = window.global || window;
+
+const theme = extendTheme({
+ fonts: {
+ body: "Jost",
+ },
+ textStyles: {
+ primary: {
+ fontFamily: "Jost",
+ },
+ },
+ config: {
+ initialColorMode: "light",
+ useSystemColorMode: false,
+ // initialColorMode: "system",
+ // useSystemColorMode: true,
+ },
+ colors: {
+ doenet: {
+ mainBlue: "#1a5a99",
+ lightBlue: "#b8d2ea",
+ solidLightBlue: "#8fb8de",
+ mainGray: "#e3e3e3",
+ mediumGray: "#949494",
+ lightGray: "#e7e7e7",
+ donutBody: "#eea177",
+ donutTopping: "#6d4445",
+ mainRed: "#c1292e",
+ lightRed: "#eab8b8",
+ mainGreen: "#459152",
+ canvas: "#ffffff",
+ canvastext: "#000000",
+ lightGreen: "#a6f19f",
+ lightYellow: "#f5ed85",
+ whiteBlankLink: "#6d4445",
+ mainYellow: "#94610a",
+ mainPurple: "#4a03d9",
+ },
+ },
+});
+
+export function DoenetML({
+ doenetML,
+ flags = {},
+ cid,
+ activityId = "",
+ userId,
+ attemptNumber = 1,
+ requestedVariantIndex,
+ updateCreditAchievedCallback,
+ updateActivityStatusCallback,
+ updateAttemptNumber,
+ pageChangedCallback,
+ paginate,
+ showFinishButton,
+ cidChangedCallback,
+ checkIfCidChanged,
+ setActivityAsCompleted,
+ setIsInErrorState,
+ apiURLs,
+ generatedVariantCallback,
+ setErrorsAndWarningsCallback,
+ forceDisable,
+ forceShowCorrectness,
+ forceShowSolution,
+ forceUnsuppressCheckwork,
+ addVirtualKeyboard = true,
+ addBottomPadding = true,
+ location,
+ navigate,
+ updateDataOnContentChange = false,
+ idsIncludeActivityId = true,
+ linkSettings,
+ scrollableContainer,
+ darkMode,
+}) {
+ const thisPropSet = [
+ doenetML,
+ cid,
+ activityId,
+ userId,
+ requestedVariantIndex,
+ ];
+ const lastPropSet = useRef([]);
+
+ const variantIndex = useRef(undefined);
+
+ const defaultFlags = {
+ showCorrectness: true,
+ readOnly: false,
+ solutionDisplayMode: "button",
+ showFeedback: true,
+ showHints: true,
+ allowLoadState: false,
+ allowSaveState: false,
+ allowLocalState: false,
+ allowSaveSubmissions: false,
+ allowSaveEvents: false,
+ autoSubmit: false,
+ };
+
+ flags = { ...defaultFlags, ...flags };
+
+ if (userId) {
+ // if userId was specified, then we're viewing results of someone other than the logged in person
+ // so disable saving state
+ // and disable even looking up state from local storage (as we want to get the state from the database)
+ flags.allowLocalState = false;
+ flags.allowSaveState = false;
+ } else if (flags.allowSaveState) {
+ // allowSaveState implies allowLoadState
+ // Rationale: saving state will result in loading a new state if another device changed it
+ flags.allowLoadState = true;
+ }
+
+ // Normalize variant index to an integer.
+ // Generate a random variant index if the requested variant index is undefined.
+ // To preserve the generated variant index on rerender,
+ // regenerate only if one of the props in propSet has changed
+ if (thisPropSet.some((v, i) => v !== lastPropSet.current[i])) {
+ if (requestedVariantIndex === undefined) {
+ let rng = new rngClass(new Date());
+ requestedVariantIndex = Math.floor(rng() * 1000000) + 1;
+ }
+ variantIndex.current = Math.round(requestedVariantIndex);
+ if (!Number.isInteger(variantIndex.current)) {
+ variantIndex.current = 1;
+ }
+ lastPropSet.current = thisPropSet;
+ }
+
+ let keyboard = null;
+
+ if (addVirtualKeyboard) {
+ keyboard =
;
+ }
+
+ return (
+
+
+ (mathJax.Hub.processSectionDelay = 0)}
+ >
+
+
+ {keyboard}
+
+
+
+ );
+}
diff --git a/src/Viewer/PageViewer.jsx b/src/Viewer/PageViewer.jsx
index 0a5b3ac85c..5f9f2674b1 100644
--- a/src/Viewer/PageViewer.jsx
+++ b/src/Viewer/PageViewer.jsx
@@ -1,6 +1,11 @@
-import React, { useEffect, useRef, useState } from "react";
+import React, {
+ createContext,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
import { nanoid } from "nanoid";
-import { useToast, toastType } from "@Toast";
import {
serializedComponentsReplacer,
serializedComponentsReviver,
@@ -10,14 +15,7 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
import { rendererState } from "./useDoenetRenderer";
import { atom, atomFamily, useRecoilCallback, useRecoilValue } from "recoil";
import { get as idb_get, set as idb_set } from "idb-keyval";
-import { cidFromText } from "../Core/utils/cid";
-import { retrieveTextFileForCid } from "../Core/utils/retrieveTextFile";
import axios from "axios";
-import { returnAllPossibleVariants } from "../Core/utils/returnAllPossibleVariants";
-import { useLocation, useNavigate } from "react-router";
-import { pageToolViewAtom } from "../Tools/_framework/NewToolRoot";
-import { itemByDoenetId } from "../_reactComponents/Course/CourseActions";
-import { darkModeAtom } from "../Tools/_framework/DarkmodeController";
import { cesc } from "../_utils/url";
const rendererUpdatesToIgnore = atomFamily({
@@ -25,20 +23,49 @@ const rendererUpdatesToIgnore = atomFamily({
default: {},
});
-export const scrollableContainerAtom = atom({
- key: "scollParentAtom",
- default: null,
-});
+const sendAlert = (msg, type) => console.log(msg);
-// Two notes about props.flags of PageViewer
-// 1. In Core, flags.allowSaveState implies flags.allowLoadState
-// Rationale: saving state will result in loading a new state if another device changed it,
-// so having allowLoadState false in that case would lead to inconsistent behavior
-// 2. In Core, if props.userId is defined, both
-// flags.allowLocalState and flags.allowSaveState are set to false
+export const PageContext = createContext();
-export default function PageViewer(props) {
- const toast = useToast();
+export function PageViewer({
+ userId,
+ activityId,
+ cidForActivity,
+ cid,
+ doenetML,
+ preliminarySerializedComponents,
+ pageNumber = "1",
+ previousComponentTypeCounts,
+ pageIsActive,
+ pageIsCurrent,
+ itemNumber,
+ attemptNumber = 1,
+ forceDisable,
+ forceShowCorrectness,
+ forceShowSolution,
+ forceUnsuppressCheckwork,
+ generatedVariantCallback, // currently not passed in
+ flags,
+ activityVariantIndex,
+ requestedVariantIndex,
+ setErrorsAndWarningsCallback,
+ updateCreditAchievedCallback,
+ setIsInErrorState,
+ updateAttemptNumber,
+ saveStateCallback,
+ updateDataOnContentChange,
+ coreCreatedCallback,
+ renderersInitializedCallback,
+ hideWhenNotCurrent,
+ prefixForIds = "",
+ apiURLs = {},
+ location = {},
+ navigate,
+ linkSettings = { viewURL: "/portfolioviewer", editURL: "/publiceditor" },
+ errorsActivitySpecific = {},
+ scrollableContainer,
+ darkMode,
+}) {
const updateRendererSVsWithRecoil = useRecoilCallback(
({ snapshot, set }) =>
async ({
@@ -90,7 +117,7 @@ export default function PageViewer(props) {
childrenInstructions,
sourceOfUpdate,
ignoreUpdate,
- prefixForIds: prefixForIds,
+ prefixForIds,
};
if (childrenInstructions === undefined) {
@@ -122,17 +149,13 @@ export default function PageViewer(props) {
const [errMsg, setErrMsg] = useState(null);
- const [cidFromProps, setCidFromProps] = useState(null);
- const [doenetMLFromProps, setDoenetMLFromProps] = useState(null);
- const [cid, setCid] = useState(null);
- const [doenetML, setDoenetML] = useState(null);
-
- const [pageNumber, setPageNumber] = useState(null);
- const [attemptNumber, setAttemptNumber] = useState(null);
- const [requestedVariantIndex, setRequestedVariantIndex] = useState(null);
+ const lastCid = useRef(null);
+ const lastDoenetML = useRef(null);
+ const lastPageNumber = useRef(null);
+ const lastAttemptNumber = useRef(null);
+ const lastRequestedVariantIndex = useRef(null);
const [stage, setStage] = useState("initial");
- const [pageContentChanged, setPageContentChanged] = useState(false);
const [documentRenderer, setDocumentRenderer] = useState(null);
@@ -157,8 +180,6 @@ export default function PageViewer(props) {
const resolveActionPromises = useRef({});
const actionTentativelySkipped = useRef(null);
- const prefixForIds = props.prefixForIds || "";
-
const previousLocationKeys = useRef([]);
const errorInitializingRenderers = useRef(false);
@@ -166,20 +187,22 @@ export default function PageViewer(props) {
const [ignoreRendererError, setIgnoreRendererError] = useState(false);
- const darkMode = useRecoilValue(darkModeAtom);
+ let hash = location.hash;
- const pageToolView = useRecoilValue(pageToolViewAtom);
- const itemInCourse = useRecoilValue(itemByDoenetId(props.doenetId));
- const scrollableContainer = useRecoilValue(scrollableContainerAtom);
-
- // Note: useRef for location
- // to make sure get current value of location in navigateToHash
- const location = useRef(null);
-
- let navigate = useNavigate();
+ const contextForRenderers = {
+ navigate,
+ location,
+ linkSettings,
+ scrollableContainer,
+ darkMode,
+ };
- location.current = useLocation();
- let hash = location.current.hash;
+ const postfixForWindowFunctions =
+ prefixForIds
+ .replace(/^page/, "")
+ .replaceAll("/", "")
+ .replaceAll("\\", "")
+ .replaceAll("-", "_") || "1";
useEffect(() => {
if (coreWorker.current) {
@@ -199,7 +222,7 @@ export default function PageViewer(props) {
updateRenderers(e.data.args);
if (errorInsideRenderers.current) {
setIgnoreRendererError(true);
- props.setIsInErrorState?.(false);
+ setIsInErrorState?.(false);
}
}
} else if (e.data.messageType === "requestAnimationFrame") {
@@ -218,7 +241,7 @@ export default function PageViewer(props) {
});
}
setStage("coreCreated");
- props.coreCreatedCallback?.(coreWorker.current);
+ coreCreatedCallback?.(coreWorker.current);
} else if (e.data.messageType === "initializeRenderers") {
if (
coreInfo.current &&
@@ -233,35 +256,38 @@ export default function PageViewer(props) {
initializeRenderers(e.data.args);
if (errorInsideRenderers.current) {
setIgnoreRendererError(true);
- props.setIsInErrorState?.(false);
+ setIsInErrorState?.(false);
}
}
} else if (e.data.messageType === "updateCreditAchieved") {
- props.updateCreditAchievedCallback?.(e.data.args);
+ updateCreditAchievedCallback?.(e.data.args);
} else if (e.data.messageType === "savedState") {
- props.saveStateCallback?.();
- } else if (e.data.messageType === "sendToast") {
- console.log(`Sending toast message: ${e.data.args.message}`);
- toast(e.data.args.message, e.data.args.toastType);
+ saveStateCallback?.();
+ } else if (e.data.messageType === "sendAlert") {
+ console.log(`Sending alert message: ${e.data.args.message}`);
+ sendAlert(e.data.args.message, e.data.args.alertType);
} else if (e.data.messageType === "resolveAction") {
resolveAction(e.data.args);
} else if (e.data.messageType === "returnAllStateVariables") {
console.log(e.data.args);
resolveAllStateVariables.current(e.data.args);
} else if (e.data.messageType === "returnErrorWarnings") {
- console.log(e.data.args);
- resolveErrorWarnings.current(e.data.args);
+ let errorWarnings = e.data.args;
+ errorWarnings.errors = [
+ ...errorsActivitySpecific,
+ ...errorWarnings.errors,
+ ];
+ console.log(errorWarnings);
+ resolveErrorWarnings.current(errorWarnings);
} else if (e.data.messageType === "componentRangePieces") {
window["componentRangePieces" + pageNumber] =
e.data.args.componentRangePieces;
} else if (e.data.messageType === "inErrorState") {
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
+ setIsInErrorState?.(true);
setErrMsg(e.data.args.errMsg);
} else if (e.data.messageType === "setErrorWarnings") {
errorWarnings.current = e.data.errorWarnings;
- props.setErrorsAndWarningsCallback?.(errorWarnings.current);
+ setErrorsAndWarningsCallback?.(errorWarnings.current);
} else if (e.data.messageType === "resetPage") {
resetPage(e.data.args);
} else if (e.data.messageType === "copyToClipboard") {
@@ -269,7 +295,7 @@ export default function PageViewer(props) {
} else if (e.data.messageType === "navigateToTarget") {
navigateToTarget(e.data.args);
} else if (e.data.messageType === "navigateToHash") {
- navigate(location.current.search + e.data.args.hash, {
+ navigate(location.search + e.data.args.hash, {
replace: true,
});
} else if (e.data.messageType === "terminated") {
@@ -277,7 +303,7 @@ export default function PageViewer(props) {
}
};
}
- }, [coreWorker.current]);
+ }, [coreWorker.current, location]);
useEffect(() => {
return () => {
@@ -291,17 +317,18 @@ export default function PageViewer(props) {
useEffect(() => {
if (pageNumber !== null) {
- window["returnAllStateVariables" + pageNumber] = function () {
- coreWorker.current.postMessage({
- messageType: "returnAllStateVariables",
- });
+ window["returnAllStateVariables" + postfixForWindowFunctions] =
+ function () {
+ coreWorker.current.postMessage({
+ messageType: "returnAllStateVariables",
+ });
- return new Promise((resolve, reject) => {
- resolveAllStateVariables.current = resolve;
- });
- };
+ return new Promise((resolve, reject) => {
+ resolveAllStateVariables.current = resolve;
+ });
+ };
- window["returnErrorWarnings" + pageNumber] = function () {
+ window["returnErrorWarnings" + postfixForWindowFunctions] = function () {
coreWorker.current.postMessage({
messageType: "returnErrorWarnings",
});
@@ -311,7 +338,7 @@ export default function PageViewer(props) {
});
};
- window["callAction" + pageNumber] = async function ({
+ window["callAction" + postfixForWindowFunctions] = async function ({
actionName,
componentName,
args,
@@ -362,22 +389,22 @@ export default function PageViewer(props) {
});
}
}
- }, [location.current, hash, coreCreated.current, coreWorker.current]);
+ }, [location, hash, coreCreated.current, coreWorker.current]);
useEffect(() => {
- if (hash && documentRenderer && props.pageIsActive) {
+ if (hash && documentRenderer && pageIsActive) {
let anchor = hash.slice(1);
if (
- (!previousLocationKeys.current.includes(location.current.key) ||
- location.current.key === "default") &&
+ (!previousLocationKeys.current.includes(location.key) ||
+ location.key === "default") &&
anchor.length > prefixForIds.length &&
anchor.substring(0, prefixForIds.length) === prefixForIds
) {
document.getElementById(anchor)?.scrollIntoView();
}
- previousLocationKeys.current.push(location.current.key);
+ previousLocationKeys.current.push(location.key);
}
- }, [location.current, hash, documentRenderer, props.pageIsActive]);
+ }, [location, hash, documentRenderer, pageIsActive]);
useEffect(() => {
callAction({
@@ -386,6 +413,56 @@ export default function PageViewer(props) {
});
}, [darkMode]);
+ const navigateToTarget = useCallback(
+ async ({
+ cid,
+ activityId,
+ variantIndex,
+ edit,
+ hash,
+ page,
+ uri,
+ targetName,
+ actionId,
+ componentName,
+ effectiveName,
+ }) => {
+ let id = prefixForIds + cesc(effectiveName);
+ let { targetForATag, url, haveValidTarget, externalUri } = getURLFromRef({
+ cid,
+ activityId,
+ variantIndex,
+ edit,
+ hash,
+ page,
+ givenUri: uri,
+ targetName,
+ linkSettings,
+ search: location.search,
+ id,
+ });
+
+ if (haveValidTarget) {
+ if (targetForATag === "_blank") {
+ window.open(url, targetForATag);
+ } else {
+ // TODO: when fix regular ref navigation to scroll back to previous scroll position
+ // when click the back button
+ // add that ability to this navigation as well
+
+ // let scrollAttribute = scrollableContainer === window ? "scrollY" : "scrollTop";
+ // let stateObj = { fromLink: true }
+ // Object.defineProperty(stateObj, 'previousScrollPosition', { get: () => scrollableContainer?.[scrollAttribute], enumerable: true });
+
+ navigate?.(url);
+ }
+ }
+
+ resolveAction({ actionId });
+ },
+ [location],
+ );
+
function terminateCoreAndAnimations() {
preventMoreAnimations.current = true;
coreWorker.current.terminate();
@@ -576,12 +653,18 @@ export default function PageViewer(props) {
if (args.rendererState) {
delete args.rendererState.__componentNeedingUpdateValue;
if (
- props.forceDisable ||
- props.forceShowCorrectness ||
- props.forceShowSolution ||
- props.forceUnsuppressCheckwork
+ forceDisable ||
+ forceShowCorrectness ||
+ forceShowSolution ||
+ forceUnsuppressCheckwork
) {
- forceRendererState({ rendererState: args.rendererState, ...props });
+ forceRendererState({
+ rendererState: args.rendererState,
+ forceDisable,
+ forceShowCorrectness,
+ forceShowSolution,
+ forceUnsuppressCheckwork,
+ });
}
for (let componentName in args.rendererState) {
updateRendererSVsWithRecoil({
@@ -596,15 +679,16 @@ export default function PageViewer(props) {
coreInfo.current = args.coreInfo;
- if (props.generatedVariantCallback) {
- props.generatedVariantCallback(
- JSON.parse(
+ generatedVariantCallback?.({
+ pageVariant: {
+ variantInfo: JSON.parse(
coreInfo.current.generatedVariantString,
serializedComponentsReviver,
),
- coreInfo.current.allPossibleVariants,
- );
- }
+ allPossibleVariants: coreInfo.current.allPossibleVariants,
+ itemNumber,
+ },
+ });
let renderPromises = [];
let rendererClassNames = [];
@@ -628,13 +712,17 @@ export default function PageViewer(props) {
key: coreId.current + documentComponentInstructions.componentName,
componentInstructions: documentComponentInstructions,
rendererClasses: newRendererClasses,
- flags: props.flags,
+ flags,
coreId: coreId.current,
callAction,
+ navigate,
+ location,
+ linkSettings,
+ scrollableContainer,
}),
);
- props.renderersInitializedCallback?.();
+ renderersInitializedCallback?.();
})
.catch((e) => {
errorInitializingRenderers.current = true;
@@ -692,26 +780,24 @@ export default function PageViewer(props) {
// console.log('resetPage', changedOnDevice, newCid, newAttemptNumber);
if (newAttemptNumber !== attemptNumber) {
- toast(
+ sendAlert(
`Reverted activity as attempt number changed on other device`,
- toastType.ERROR,
+ "info",
);
- if (props.updateAttemptNumber) {
- props.updateAttemptNumber(newAttemptNumber);
+ if (updateAttemptNumber) {
+ updateAttemptNumber(newAttemptNumber);
} else {
// what do we do in this case?
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
+ setIsInErrorState?.(true);
setErrMsg(
"how to reset attempt number when not given updateAttemptNumber function?",
);
}
} else {
// TODO: are there cases where will get an infinite loop here?
- toast(
+ sendAlert(
`Reverted page to state saved on device ${changedOnDevice}`,
- toastType.ERROR,
+ "info",
);
coreId.current = nanoid();
@@ -719,84 +805,23 @@ export default function PageViewer(props) {
}
}
- function calculateCidDoenetML() {
- const coreIdWhenCalled = coreId.current;
- // compare with undefined as doenetML could be empty string
- if (doenetMLFromProps !== undefined) {
- if (cidFromProps) {
- // check to see if doenetML matches cid
- cidFromText(doenetMLFromProps).then((calcCid) => {
- //Guard against the possiblity that parameters changed while waiting
-
- if (coreIdWhenCalled === coreId.current) {
- if (calcCid === cidFromProps) {
- setDoenetML(doenetMLFromProps);
- setCid(cidFromProps);
- setStage("continue");
- } else {
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
- setErrMsg(
- `doenetML did not match specified cid: ${cidFromProps}`,
- );
- }
- }
- });
- } else {
- // if have doenetML and no cid, then calculate cid
- cidFromText(doenetMLFromProps).then((cid) => {
- //Guard against the possiblity that parameters changed while waiting
- if (coreIdWhenCalled === coreId.current) {
- setDoenetML(doenetMLFromProps);
- setCid(cid);
- setStage("continue");
- }
- });
- }
- } else {
- // if don't have doenetML, then retrieve doenetML from cid
-
- retrieveTextFileForCid(cidFromProps, "doenet")
- .then((retrievedDoenetML) => {
- //Guard against the possiblity that parameters changed while waiting
-
- if (coreIdWhenCalled === coreId.current) {
- setDoenetML(retrievedDoenetML);
- setCid(cidFromProps);
- setStage("continue");
- }
- })
- .catch((e) => {
- //Guard against the possiblity that parameters changed while waiting
-
- if (coreIdWhenCalled === coreId.current) {
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
- setErrMsg(`doenetML not found for cid: ${cidFromProps}`);
- }
- });
- }
- }
-
async function loadStateAndInitialize() {
const coreIdWhenCalled = coreId.current;
let loadedState = false;
- if (props.flags.allowLocalState) {
+ if (flags.allowLocalState) {
let localInfo;
try {
localInfo = await idb_get(
- `${props.doenetId}|${pageNumber}|${attemptNumber}|${cid}`,
+ `${activityId}|${pageNumber}|${attemptNumber}|${cid}`,
);
} catch (e) {
// ignore error
}
if (localInfo) {
- if (props.flags.allowSaveState) {
+ if (flags.allowSaveState) {
// attempt to save local info to database,
// reseting data to that from database if it has changed since last save
@@ -812,20 +837,18 @@ export default function PageViewer(props) {
return;
} else if (result.newCid !== cid) {
// if cid changes for the same attempt number, then something went wrong
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
+ setIsInErrorState?.(true);
setErrMsg(`content changed unexpectedly!`);
}
// if just the localInfo changed, use that instead
localInfo = result.newLocalInfo;
console.log(
- `sending toast: Reverted page to state saved on device ${result.changedOnDevice}`,
+ `sending alert: Reverted page to state saved on device ${result.changedOnDevice}`,
);
- toast(
+ sendAlert(
`Reverted page to state saved on device ${result.changedOnDevice}`,
- toastType.ERROR,
+ "info",
);
}
}
@@ -859,7 +882,7 @@ export default function PageViewer(props) {
}
}
- if (!loadedState) {
+ if (!loadedState && apiURLs.loadPageState) {
// if didn't load state from local storage, try to load from database
// even if allowLoadState is false,
@@ -870,25 +893,23 @@ export default function PageViewer(props) {
cid,
pageNumber,
attemptNumber,
- doenetId: props.doenetId,
- userId: props.userId,
+ activityId,
+ userId,
requestedVariantIndex,
- allowLoadState: props.flags.allowLoadState,
- showCorrectness: props.flags.showCorrectness,
- solutionDisplayMode: props.flags.solutionDisplayMode,
- showFeedback: props.flags.showFeedback,
- showHints: props.flags.showHints,
- autoSubmit: props.flags.autoSubmit,
+ allowLoadState: flags.allowLoadState,
+ showCorrectness: flags.showCorrectness,
+ solutionDisplayMode: flags.solutionDisplayMode,
+ showFeedback: flags.showFeedback,
+ showHints: flags.showHints,
+ autoSubmit: flags.autoSubmit,
},
};
try {
- let resp = await axios.get("/api/loadPageState.php", payload);
+ let resp = await axios.get(apiURLs.loadPageState, payload);
if (!resp.data.success) {
- if (props.flags.allowLoadState) {
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
+ if (flags.allowLoadState) {
+ setIsInErrorState?.(true);
setErrMsg(`Error loading page state: ${resp.data.message}`);
return;
} else {
@@ -913,6 +934,7 @@ export default function PageViewer(props) {
actionName: "updateValue",
componentName: rendererState.__componentNeedingUpdateValue,
},
+ args: { doNotIgnore: true },
});
}
@@ -934,10 +956,8 @@ export default function PageViewer(props) {
};
}
} catch (e) {
- if (props.flags.allowLoadState) {
- if (props.setIsInErrorState) {
- props.setIsInErrorState(true);
- }
+ if (flags.allowLoadState) {
+ setIsInErrorState?.(true);
setErrMsg(`Error loading page state: ${e.message}`);
return;
} else {
@@ -948,7 +968,7 @@ export default function PageViewer(props) {
//Guard against the possiblity that parameters changed while waiting
if (coreIdWhenCalled === coreId.current) {
- if (props.pageIsActive) {
+ if (pageIsActive) {
startCore();
} else {
setStage("readyToCreateCore");
@@ -957,8 +977,12 @@ export default function PageViewer(props) {
}
async function saveLoadedLocalStateToDatabase(localInfo) {
+ if (!flags.allowSaveState || !apiURLs.savePageState) {
+ return;
+ }
+
let serverSaveId = await idb_get(
- `${props.doenetId}|${pageNumber}|${attemptNumber}|${cid}|ServerSaveId`,
+ `${activityId}|${pageNumber}|${attemptNumber}|${cid}|ServerSaveId`,
);
let pageStateToBeSavedToDatabase = {
@@ -977,17 +1001,17 @@ export default function PageViewer(props) {
),
pageNumber,
attemptNumber,
- doenetId: props.doenetId,
+ activityId,
saveId: localInfo.saveId,
serverSaveId,
- updateDataOnContentChange: props.updateDataOnContentChange,
+ updateDataOnContentChange,
};
let resp;
try {
resp = await axios.post(
- "/api/savePageState.php",
+ apiURLs.savePageState,
pageStateToBeSavedToDatabase,
);
} catch (e) {
@@ -1003,7 +1027,7 @@ export default function PageViewer(props) {
}
await idb_set(
- `${props.doenetId}|${pageNumber}|${attemptNumber}|${cid}|ServerSaveId`,
+ `${activityId}|${pageNumber}|${attemptNumber}|${cid}|ServerSaveId`,
data.saveId,
);
@@ -1019,7 +1043,7 @@ export default function PageViewer(props) {
};
await idb_set(
- `${props.doenetId}|${pageNumber}|${data.attemptNumber}|${data.cid}`,
+ `${activityId}|${pageNumber}|${data.attemptNumber}|${data.cid}`,
newLocalInfo,
);
@@ -1052,24 +1076,27 @@ export default function PageViewer(props) {
messageType: "createCore",
args: {
coreId: coreId.current,
- userId: props.userId,
+ userId,
+ cid,
doenetML,
- doenetId: props.doenetId,
- previousComponentTypeCounts: props.previousComponentTypeCounts,
- activityCid: props.activityCid,
- flags: props.flags,
+ preliminarySerializedComponents,
+ activityId,
+ previousComponentTypeCounts,
+ cidForActivity,
+ flags,
theme: darkMode,
requestedVariantIndex,
pageNumber,
attemptNumber,
- itemNumber: props.itemNumber,
- updateDataOnContentChange: props.updateDataOnContentChange,
+ itemNumber,
+ updateDataOnContentChange,
serverSaveId: initialCoreData.current.serverSaveId,
- activityVariantIndex: props.activityVariantIndex,
+ activityVariantIndex,
requestedVariant: initialCoreData.current.requestedVariant,
stateVariableChanges: initialCoreData.current.coreState
? initialCoreData.current.coreState
: undefined,
+ apiURLs: apiURLs,
},
});
@@ -1128,55 +1155,6 @@ export default function PageViewer(props) {
resolveAction({ actionId });
}
- async function navigateToTarget({
- cid,
- doenetId,
- variantIndex,
- edit,
- hash,
- page,
- uri,
- targetName,
- actionId,
- componentName,
- effectiveName,
- }) {
- let id = prefixForIds + cesc(effectiveName);
- let { targetForATag, url, haveValidTarget, externalUri } = getURLFromRef({
- cid,
- doenetId,
- variantIndex,
- edit,
- hash,
- page,
- givenUri: uri,
- targetName,
- pageToolView,
- inCourse: Object.keys(itemInCourse).length > 0,
- pathname: location.current.pathname,
- search: location.current.search,
- id,
- });
-
- if (haveValidTarget) {
- if (targetForATag === "_blank") {
- window.open(url, targetForATag);
- } else {
- // TODO: when fix regular ref navigation to scroll back to previous scroll position
- // when click the back button
- // add that ability to this navigation as well
-
- // let scrollAttribute = scrollableContainer === window ? "scrollY" : "scrollTop";
- // let stateObj = { fromLink: true }
- // Object.defineProperty(stateObj, 'previousScrollPosition', { get: () => scrollableContainer?.[scrollAttribute], enumerable: true });
-
- navigate(url);
- }
- }
-
- resolveAction({ actionId });
- }
-
function errorHandler() {
errorInsideRenderers.current = true;
@@ -1185,68 +1163,54 @@ export default function PageViewer(props) {
}
}
- // first, if cidFromProps or doenetMLFromProps don't match props
+ // first, if lastCid or lastDoenetML don't match props
// set state to props and record that that need a new core
let changedState = false;
- if (doenetMLFromProps !== props.doenetML) {
- setDoenetMLFromProps(props.doenetML);
+ if (lastDoenetML.current !== doenetML) {
+ lastDoenetML.current = doenetML;
changedState = true;
}
- if (cidFromProps !== props.cid) {
- setCidFromProps(props.cid);
+ if (lastCid.current !== cid) {
+ lastCid.current = cid;
changedState = true;
}
- //If no pageNumber prop then set to '1'
- let propPageNumber = props.pageNumber;
- if (propPageNumber === undefined) {
- propPageNumber = "1";
- }
-
- if (propPageNumber !== pageNumber) {
- setPageNumber(propPageNumber);
+ if (lastPageNumber.current !== pageNumber) {
+ lastPageNumber.current = pageNumber;
changedState = true;
}
- //If no attemptNumber prop then set to 1
- let propAttemptNumber = props.attemptNumber;
- if (propAttemptNumber === undefined) {
- propAttemptNumber = 1;
- }
-
- if (propAttemptNumber !== attemptNumber) {
- setAttemptNumber(propAttemptNumber);
+ if (lastAttemptNumber.current !== attemptNumber) {
+ lastAttemptNumber.current = attemptNumber;
changedState = true;
}
- // attemptNumber is used for requestedVariantIndex if not specified
- let adjustedRequestedVariantIndex = props.requestedVariantIndex;
- if (adjustedRequestedVariantIndex === undefined) {
- adjustedRequestedVariantIndex = propAttemptNumber;
- }
-
- if (requestedVariantIndex !== adjustedRequestedVariantIndex) {
- setRequestedVariantIndex(adjustedRequestedVariantIndex);
+ if (lastRequestedVariantIndex.current !== requestedVariantIndex) {
+ lastRequestedVariantIndex.current = requestedVariantIndex;
changedState = true;
}
- // Next time through will recalculate, after state variables are set
if (changedState) {
+ // Reset error messages, core.
+ // Then load state and initialize
+
if (errMsg !== null) {
setErrMsg(null);
- if (props.setIsInErrorState) {
- props.setIsInErrorState(false);
- }
+ setIsInErrorState?.(false);
}
- if (coreWorker.current) {
- terminateCoreAndAnimations();
- }
- setStage("recalcParams");
coreId.current = nanoid();
initialCoreData.current = {};
- setPageContentChanged(true);
+ coreInfo.current = null;
+ setDocumentRenderer(null);
+ coreCreated.current = false;
+ coreCreationInProgress.current = false;
+
+ setStage("wait");
+
+ loadStateAndInitialize();
+
return null;
}
@@ -1267,30 +1231,9 @@ export default function PageViewer(props) {
return null;
}
- if (stage == "recalcParams") {
- setStage("wait");
- calculateCidDoenetML();
- return null;
- }
-
- if (pageContentChanged) {
- setPageContentChanged(false);
-
- coreInfo.current = null;
- setDocumentRenderer(null);
- coreCreated.current = false;
- coreCreationInProgress.current = false;
-
- setStage("wait");
-
- loadStateAndInitialize();
-
- return null;
- }
-
- if (stage === "readyToCreateCore" && props.pageIsActive) {
+ if (stage === "readyToCreateCore" && pageIsActive) {
startCore();
- } else if (stage === "waitingOnCore" && !props.pageIsActive) {
+ } else if (stage === "waitingOnCore" && !pageIsActive) {
// we've moved off this page, but core is still being initialized
// kill the core worker
@@ -1299,7 +1242,7 @@ export default function PageViewer(props) {
setStage("readyToCreateCore");
}
- if (props.hideWhenNotCurrent && !props.pageIsCurrent) {
+ if (hideWhenNotCurrent && !pageIsCurrent) {
return null;
}
@@ -1338,7 +1281,7 @@ export default function PageViewer(props) {
//Spacing around the whole doenetML document
return (
{errorOverview}
- {documentRenderer}
+
+ {documentRenderer}
+
);
@@ -1393,72 +1338,64 @@ class ErrorBoundary extends React.Component {
export function getURLFromRef({
cid,
- doenetId,
+ activityId,
variantIndex,
edit,
hash,
page,
givenUri,
targetName = "",
- pageToolView = {},
- inCourse = false,
- pathname = "",
+ linkSettings,
search = "",
id = "",
}) {
+ // possible linkSettings
+ // - viewURL
+ // - editURL
+ // - useQueryParameters
+
let url = "";
let targetForATag = "_blank";
let haveValidTarget = false;
let externalUri = false;
- let portfolioModeURI =
- pathname.substring(0, 10) === "/portfolio" ||
- pathname.substring(0, 13) === "/publiceditor";
-
- if (cid || doenetId) {
+ if (cid || activityId) {
if (cid) {
- url = `cid=${cid}`;
- portfolioModeURI = false;
+ if (linkSettings.useQueryParameters) {
+ url = `cid=${cid}`;
+ } else {
+ // TODO: make this URL work for create another URL to reference by cid
+ url = `/${cid}`;
+ }
} else {
- if (portfolioModeURI) {
- url = `/${doenetId}`;
+ if (linkSettings.useQueryParameters) {
+ url = `doenetId=${activityId}`;
} else {
- url = `doenetId=${doenetId}`;
+ url = `/${activityId}`;
}
}
if (variantIndex) {
- if (!portfolioModeURI) {
+ // TODO: how to specify variant if don't useQueryParameters
+ if (linkSettings.useQueryParameters) {
url += `&variant=${variantIndex}`;
}
}
- if (portfolioModeURI) {
- if (edit == true) {
- url = "/publiceditor" + url;
+ if (linkSettings.useQueryParameters) {
+ let baseUrl = edit == true ? linkSettings.editURL : linkSettings.viewURL;
+ if (baseUrl.includes("?")) {
+ if (baseUrl[baseUrl.length - 1] !== "?") {
+ baseUrl += "&";
+ }
} else {
- url = "/portfolioviewer" + url;
+ baseUrl += "?";
}
+ url = baseUrl + url;
} else {
- let usePublic = false;
- if (pageToolView.page === "public") {
- usePublic = true;
- } else if (!inCourse) {
- usePublic = true;
- }
- if (usePublic) {
- if (
- edit === true ||
- (edit === null &&
- pageToolView.page === "public" &&
- pageToolView.tool === "editor")
- ) {
- url = `tool=editor&${url}`;
- }
- url = `/public?${url}`;
- } else if (pageToolView.page === "placementexam") {
- url = `?tool=exam&${url}`;
+ if (edit == true) {
+ url = linkSettings.editURL + url;
} else {
- url = `?tool=assignment&${url}`;
+ url = linkSettings.viewURL + url;
}
}
@@ -1487,7 +1424,7 @@ export function getURLFromRef({
externalUri = true;
}
} else {
- url += search;
+ url = search;
if (page) {
url += `#page${page}`;
diff --git a/src/Viewer/renderers/_error.jsx b/src/Viewer/renderers/_error.jsx
index 7e43d79e14..60a064609c 100644
--- a/src/Viewer/renderers/_error.jsx
+++ b/src/Viewer/renderers/_error.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Error(props) {
- let { id, SVs, children } = useDoenetRender(props);
+ let { id, SVs, children } = useDoenetRenderer(props);
let displayedMessage = null;
diff --git a/src/Viewer/renderers/alert.jsx b/src/Viewer/renderers/alert.jsx
index 9142775f96..cc92b52816 100644
--- a/src/Viewer/renderers/alert.jsx
+++ b/src/Viewer/renderers/alert.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Alert(props) {
- let { name, id, SVs, children } = useDoenetRender(props);
+ let { name, id, SVs, children } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/angle.jsx b/src/Viewer/renderers/angle.jsx
index d919204503..30bce7805d 100644
--- a/src/Viewer/renderers/angle.jsx
+++ b/src/Viewer/renderers/angle.jsx
@@ -1,11 +1,11 @@
import React, { useEffect, useContext, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { BoardContext, LINE_LAYER_OFFSET } from "./graph";
import me from "math-expressions";
import { MathJax } from "better-react-mathjax";
export default React.memo(function Angle(props) {
- let { name, id, SVs } = useDoenetRender(props);
+ let { name, id, SVs } = useDoenetRenderer(props);
const board = useContext(BoardContext);
diff --git a/src/Viewer/renderers/answer.jsx b/src/Viewer/renderers/answer.jsx
index 8084fba338..47fcedc6da 100644
--- a/src/Viewer/renderers/answer.jsx
+++ b/src/Viewer/renderers/answer.jsx
@@ -1,5 +1,5 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
@@ -29,7 +29,8 @@ const Button = styled.button`
`;
export default React.memo(function Answer(props) {
- let { name, id, SVs, actions, children, callAction } = useDoenetRender(props);
+ let { name, id, SVs, actions, children, callAction } =
+ useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/asList.jsx b/src/Viewer/renderers/asList.jsx
index 5acfe58da7..c73c9e0d06 100644
--- a/src/Viewer/renderers/asList.jsx
+++ b/src/Viewer/renderers/asList.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function AsList(props) {
- let { name, id, SVs, children } = useDoenetRender(props);
+ let { name, id, SVs, children } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/boolean.jsx b/src/Viewer/renderers/boolean.jsx
index 6460c1d728..954d93e7f9 100644
--- a/src/Viewer/renderers/boolean.jsx
+++ b/src/Viewer/renderers/boolean.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Boolean(props) {
- let { name, id, SVs } = useDoenetRender(props, false);
+ let { name, id, SVs } = useDoenetRenderer(props, false);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/booleanInput.jsx b/src/Viewer/renderers/booleanInput.jsx
index 1bd8538976..52bef4f565 100644
--- a/src/Viewer/renderers/booleanInput.jsx
+++ b/src/Viewer/renderers/booleanInput.jsx
@@ -1,5 +1,5 @@
import React, { useContext, useEffect, useRef, useState } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
@@ -42,7 +42,7 @@ const Button = styled.button`
export default React.memo(function BooleanInput(props) {
let { name, id, SVs, actions, ignoreUpdate, rendererName, callAction } =
- useDoenetRender(props);
+ useDoenetRenderer(props);
BooleanInput.baseStateVariable = "value";
BooleanInput.ignoreActionsWithoutCore = (actionName) =>
diff --git a/src/Viewer/renderers/button.jsx b/src/Viewer/renderers/button.jsx
index 86ed767fd9..e9a63ed2a2 100644
--- a/src/Viewer/renderers/button.jsx
+++ b/src/Viewer/renderers/button.jsx
@@ -1,12 +1,12 @@
import React, { useContext, useEffect, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import Button from "../../_reactComponents/PanelHeaderComponents/Button";
import { BoardContext } from "./graph";
import me from "math-expressions";
import { getPositionFromAnchorByCoordinate } from "../../Core/utils/graphical";
export default React.memo(function ButtonComponent(props) {
- let { name, id, SVs, actions, callAction } = useDoenetRender(props, false);
+ let { name, id, SVs, actions, callAction } = useDoenetRenderer(props, false);
ButtonComponent.ignoreActionsWithoutCore = (actionName) =>
actionName === "moveButton";
diff --git a/src/Viewer/renderers/c.jsx b/src/Viewer/renderers/c.jsx
index 98627501a8..b5e3968074 100644
--- a/src/Viewer/renderers/c.jsx
+++ b/src/Viewer/renderers/c.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function C(props) {
- let { name, id, SVs, children } = useDoenetRender(props);
+ let { name, id, SVs, children } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/cell.jsx b/src/Viewer/renderers/cell.jsx
index 8f5221ba3f..7dee83a721 100644
--- a/src/Viewer/renderers/cell.jsx
+++ b/src/Viewer/renderers/cell.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Cell(props) {
- let { name, id, SVs, children } = useDoenetRender(props);
+ let { name, id, SVs, children } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/chart.jsx b/src/Viewer/renderers/chart.jsx
index 42c8f77f97..bf0d02775e 100644
--- a/src/Viewer/renderers/chart.jsx
+++ b/src/Viewer/renderers/chart.jsx
@@ -1,13 +1,13 @@
import React, { useEffect, useState, useRef, createContext } from "react";
import { sizeToCSS } from "./utils/css";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import Plotly from "plotly.js-dist-min";
import VisibilitySensor from "react-visibility-sensor-v2";
export const BoardContext = createContext();
export default React.memo(function Chart(props) {
- let { name, id, SVs, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, actions, callAction } = useDoenetRenderer(props);
// console.log({ name, SVs })
let onChangeVisibility = (isVisible) => {
diff --git a/src/Viewer/renderers/choiceInput.jsx b/src/Viewer/renderers/choiceInput.jsx
index 42366867ca..22284d8ecd 100644
--- a/src/Viewer/renderers/choiceInput.jsx
+++ b/src/Viewer/renderers/choiceInput.jsx
@@ -1,5 +1,5 @@
import React, { useRef, useState } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
@@ -46,7 +46,7 @@ export default React.memo(function ChoiceInput(props) {
ignoreUpdate,
rendererName,
callAction,
- } = useDoenetRender(props);
+ } = useDoenetRenderer(props);
ChoiceInput.baseStateVariable = "selectedIndices";
diff --git a/src/Viewer/renderers/circle.jsx b/src/Viewer/renderers/circle.jsx
index 70d2f91f57..20daf7394d 100644
--- a/src/Viewer/renderers/circle.jsx
+++ b/src/Viewer/renderers/circle.jsx
@@ -1,8 +1,6 @@
import React, { useContext, useEffect, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { BoardContext, LINE_LAYER_OFFSET, POINT_LAYER_OFFSET } from "./graph";
-import { useRecoilValue } from "recoil";
-import { darkModeAtom } from "../../Tools/_framework/DarkmodeController";
import {
characterizeOffGraphCircleArc,
characterizeOffGraphPoint,
@@ -15,9 +13,10 @@ import {
normalizePointSize,
normalizePointStyle,
} from "./utils/graph";
+import { PageContext } from "../PageViewer";
export default React.memo(function Circle(props) {
- let { name, id, SVs, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, actions, callAction } = useDoenetRenderer(props);
Circle.ignoreActionsWithoutCore = () => true;
@@ -53,7 +52,7 @@ export default React.memo(function Circle(props) {
fixed.current = SVs.fixed;
fixLocation.current = !SVs.draggable || SVs.fixLocation || SVs.fixed;
- const darkMode = useRecoilValue(darkModeAtom);
+ const { darkMode } = useContext(PageContext) || {};
useEffect(() => {
//On unmount
diff --git a/src/Viewer/renderers/cobwebPolyline.jsx b/src/Viewer/renderers/cobwebPolyline.jsx
index 0cbc52a715..175e091b24 100644
--- a/src/Viewer/renderers/cobwebPolyline.jsx
+++ b/src/Viewer/renderers/cobwebPolyline.jsx
@@ -1,11 +1,11 @@
import React, { useContext, useEffect, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { BoardContext, LINE_LAYER_OFFSET, VERTEX_LAYER_OFFSET } from "./graph";
import { createFunctionFromDefinition } from "../../Core/utils/function";
export default React.memo(function CobwebPolyline(props) {
let { name, id, SVs, actions, sourceOfUpdate, callAction } =
- useDoenetRender(props);
+ useDoenetRenderer(props);
CobwebPolyline.ignoreActionsWithoutCore = () => true;
diff --git a/src/Viewer/renderers/codeEditor.jsx b/src/Viewer/renderers/codeEditor.jsx
index 8348526d6e..4a88aa1ec9 100644
--- a/src/Viewer/renderers/codeEditor.jsx
+++ b/src/Viewer/renderers/codeEditor.jsx
@@ -4,6 +4,8 @@ import { sizeToCSS } from "./utils/css";
import CodeMirror from "../../Tools/_framework/CodeMirror";
import VisibilitySensor from "react-visibility-sensor-v2";
import { useSetRecoilState } from "recoil";
+import { Box, Flex } from "@chakra-ui/react";
+import ErrorWarningPopovers from "../../Tools/_framework/ChakraBasedComponents/ErrorWarningPopovers";
export default React.memo(function CodeEditor(props) {
let {
@@ -59,6 +61,40 @@ export default React.memo(function CodeEditor(props) {
return null;
}
+ useEffect(() => {
+ let platform = "Linux";
+ if (navigator.platform.indexOf("Win") != -1) {
+ platform = "Win";
+ } else if (navigator.platform.indexOf("Mac") != -1) {
+ platform = "Mac";
+ }
+ const handleEditorKeyDown = (event) => {
+ if (
+ (platform == "Mac" && event.metaKey && event.code === "KeyS") ||
+ (platform != "Mac" && event.ctrlKey && event.code === "KeyS")
+ ) {
+ event.preventDefault();
+ event.stopPropagation();
+ clearTimeout(updateValueTimer.current);
+ callAction({
+ action: actions.updateValue,
+ baseVariableValue: currentValue.current,
+ });
+ updateValueTimer.current = null;
+ callAction({ action: actions.updateComponents });
+ }
+ };
+
+ let codeEditorContainer = document.getElementById(id);
+ if (SVs.showResults) {
+ codeEditorContainer.addEventListener("keydown", handleEditorKeyDown);
+ }
+
+ return () => {
+ codeEditorContainer.removeEventListener("keydown", handleEditorKeyDown);
+ };
+ }, [SVs.showResults]);
+
const editorKey = id + "_editor";
const viewerKey = id + "_viewer";
@@ -88,7 +124,7 @@ export default React.memo(function CodeEditor(props) {
maxWidth: "100%",
padding: "0px",
overflowX: "hidden",
- overflowY: "scroll",
+ overflowY: "hidden",
};
if (SVs.showResults) {
@@ -108,56 +144,88 @@ export default React.memo(function CodeEditor(props) {
paddingBottom.size /= 2;
paddingBottom = sizeToCSS(paddingBottom);
+ let errorsAndWarnings = null;
+ let errorsAndWarningsHeight = 0;
+
+ if (SVs.errorsAndWarnings) {
+ errorsAndWarningsHeight = 32;
+
+ const warningsLevel = 1; //TODO: eventually give user ability adjust warning level filter
+ const warningsObjs = SVs.errorsAndWarnings.warnings.filter(
+ (w) => w.level <= warningsLevel,
+ );
+ const errorsObjs = [...SVs.errorsAndWarnings.errors];
+
+ errorsAndWarnings = (
+
+
+
+ );
+ }
+
let editor = (
-
- readOnly={SVs.disabled}
- onBlur={() => {
- clearTimeout(updateValueTimer.current);
- callAction({
- action: actions.updateValue,
- baseVariableValue: currentValue.current,
- });
- updateValueTimer.current = null;
- }}
- onFocus={() => {
- // console.log(">>codeEditor FOCUS!!!!!")
- }}
- onBeforeChange={(value) => {
- if (currentValue.current !== value) {
- currentValue.current = value;
-
- setRendererState((was) => {
- let newObj = { ...was };
- newObj.ignoreUpdate = true;
- return newObj;
- });
-
+
+
+ readOnly={SVs.disabled}
+ onBlur={() => {
+ clearTimeout(updateValueTimer.current);
callAction({
- action: actions.updateImmediateValue,
- args: { text: value },
- baseVariableValue: value,
+ action: actions.updateValue,
+ baseVariableValue: currentValue.current,
});
+ updateValueTimer.current = null;
+ }}
+ onFocus={() => {
+ // console.log(">>codeEditor FOCUS!!!!!")
+ }}
+ onBeforeChange={(value) => {
+ if (currentValue.current !== value) {
+ currentValue.current = value;
- // Debounce update value at 3 seconds
- clearTimeout(updateValueTimer.current);
+ setRendererState((was) => {
+ let newObj = { ...was };
+ newObj.ignoreUpdate = true;
+ return newObj;
+ });
- //TODO: when you try to leave the page before it saved you will lose work
- //so prompt the user on page leave
- updateValueTimer.current = setTimeout(function () {
callAction({
- action: actions.updateValue,
- baseVariableValue: currentValue.current,
+ action: actions.updateImmediateValue,
+ args: { text: value },
+ baseVariableValue: value,
});
- updateValueTimer.current = null;
- }, 3000); //3 seconds
- }
- }}
- paddingBottom={paddingBottom}
- />
+
+ // Debounce update value at 3 seconds
+ clearTimeout(updateValueTimer.current);
+
+ //TODO: when you try to leave the page before it saved you will lose work
+ //so prompt the user on page leave
+ updateValueTimer.current = setTimeout(function () {
+ callAction({
+ action: actions.updateValue,
+ baseVariableValue: currentValue.current,
+ });
+ updateValueTimer.current = null;
+ }, 3000); //3 seconds
+ }
+ }}
+ paddingBottom={paddingBottom}
+ />
+
+ {errorsAndWarnings}
);
diff --git a/src/Viewer/renderers/codeViewer.jsx b/src/Viewer/renderers/codeViewer.jsx
index d0f8561603..acdb3da89a 100644
--- a/src/Viewer/renderers/codeViewer.jsx
+++ b/src/Viewer/renderers/codeViewer.jsx
@@ -1,8 +1,12 @@
import React, { useState, useRef, useEffect } from "react";
import useDoenetRenderer from "../useDoenetRenderer";
import { sizeToCSS } from "./utils/css";
-import Button from "../../_reactComponents/PanelHeaderComponents/Button";
import VisibilitySensor from "react-visibility-sensor-v2";
+import { DoenetML } from "../DoenetML";
+import { Box, HStack, Button, Tooltip } from "@chakra-ui/react";
+import VariantSelect from "../../Tools/_framework/ChakraBasedComponents/VariantSelect";
+import { WarningTwoIcon } from "@chakra-ui/icons";
+import { RxUpdate } from "react-icons/rx";
export default React.memo(function CodeViewer(props) {
let { name, id, SVs, children, actions, callAction } = useDoenetRenderer(
@@ -10,6 +14,12 @@ export default React.memo(function CodeViewer(props) {
false,
);
+ const [variants, setVariants] = useState({
+ index: 1,
+ numVariants: 1,
+ allPossibleVariants: ["a"],
+ });
+
let onChangeVisibility = (isVisible) => {
callAction({
action: actions.recordVisibilityChange,
@@ -59,12 +69,49 @@ export default React.memo(function CodeViewer(props) {
}}
>
-
+
+
+
+ }
+ rightIcon={
+ SVs.codeChanged ? (
+
+ ) : (
+ ""
+ )
+ }
+ isDisabled={!SVs.codeChanged}
+ onClick={() => {
+ callAction({ action: actions.updateComponents });
+ }}
+ >
+ Update
+
+
+
+ {variants.numVariants > 1 && (
+
+
+ setVariants((prev) => {
+ let next = { ...prev };
+ next.index = index + 1;
+ return next;
+ })
+ }
+ />
+
+ )}
+
- {children}
+ {
+ callAction({
+ action: actions.setErrorsAndWarnings,
+ args: { errorsAndWarnings },
+ });
+ }}
+ />
);
diff --git a/src/Viewer/renderers/containerInline.jsx b/src/Viewer/renderers/containerInline.jsx
index fae9d4e6da..4c13b18201 100644
--- a/src/Viewer/renderers/containerInline.jsx
+++ b/src/Viewer/renderers/containerInline.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function ContainerInline(props) {
- let { name, id, SVs, children } = useDoenetRender(props);
+ let { name, id, SVs, children } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/curve.jsx b/src/Viewer/renderers/curve.jsx
index f9066f89ee..756ce2b589 100644
--- a/src/Viewer/renderers/curve.jsx
+++ b/src/Viewer/renderers/curve.jsx
@@ -1,18 +1,17 @@
import React, { useContext, useEffect, useRef } from "react";
import { createFunctionFromDefinition } from "../../Core/utils/function";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import {
BoardContext,
CONTROL_POINT_LAYER_OFFSET,
LINE_LAYER_OFFSET,
VERTEX_LAYER_OFFSET,
} from "./graph";
-import { useRecoilValue } from "recoil";
-import { darkModeAtom } from "../../Tools/_framework/DarkmodeController";
+import { PageContext } from "../PageViewer";
export default React.memo(function Curve(props) {
let { name, id, SVs, actions, sourceOfUpdate, callAction } =
- useDoenetRender(props);
+ useDoenetRenderer(props);
Curve.ignoreActionsWithoutCore = () => true;
@@ -59,7 +58,7 @@ export default React.memo(function Curve(props) {
let lastControlPointPositionsFromCore = useRef(null);
lastControlPointPositionsFromCore.current = SVs.numericalControlPoints;
- const darkMode = useRecoilValue(darkModeAtom);
+ const { darkMode } = useContext(PageContext) || {};
useEffect(() => {
//On unmount
diff --git a/src/Viewer/renderers/ellipsis.jsx b/src/Viewer/renderers/ellipsis.jsx
index c4259ce93f..5f80934d72 100644
--- a/src/Viewer/renderers/ellipsis.jsx
+++ b/src/Viewer/renderers/ellipsis.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Ellipsis(props) {
- let { name, id, SVs } = useDoenetRender(props);
+ let { name, id, SVs } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/em.jsx b/src/Viewer/renderers/em.jsx
index 3b5fa09c69..63521aca8f 100644
--- a/src/Viewer/renderers/em.jsx
+++ b/src/Viewer/renderers/em.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Em(props) {
- let { name, id, SVs, children } = useDoenetRender(props);
+ let { name, id, SVs, children } = useDoenetRenderer(props);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/embed.jsx b/src/Viewer/renderers/embed.jsx
index 7402df61cb..debee4a809 100644
--- a/src/Viewer/renderers/embed.jsx
+++ b/src/Viewer/renderers/embed.jsx
@@ -1,10 +1,10 @@
import React, { useEffect } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { sizeToCSS } from "./utils/css";
import VisibilitySensor from "react-visibility-sensor-v2";
export default React.memo(function Figure(props) {
- let { name, id, SVs, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, actions, callAction } = useDoenetRenderer(props);
let onChangeVisibility = (isVisible) => {
callAction({
diff --git a/src/Viewer/renderers/feedback.jsx b/src/Viewer/renderers/feedback.jsx
index df7ca45ef8..4fc9c26cf7 100644
--- a/src/Viewer/renderers/feedback.jsx
+++ b/src/Viewer/renderers/feedback.jsx
@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faComment as thoughtBubble } from "@fortawesome/free-regular-svg-icons";
import VisibilitySensor from "react-visibility-sensor-v2";
@@ -31,7 +31,8 @@ const SpanStyling = styled.span`
}
`;
export default React.memo(function Feedback(props) {
- let { name, id, SVs, children, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, children, actions, callAction } =
+ useDoenetRenderer(props);
let onChangeVisibility = (isVisible) => {
callAction({
diff --git a/src/Viewer/renderers/figure.jsx b/src/Viewer/renderers/figure.jsx
index 19d7e5b3cb..c938f2feef 100644
--- a/src/Viewer/renderers/figure.jsx
+++ b/src/Viewer/renderers/figure.jsx
@@ -1,10 +1,11 @@
import React, { useState, useEffect } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import VisibilitySensor from "react-visibility-sensor-v2";
import Measure from "react-measure";
export default React.memo(function Figure(props) {
- let { name, id, SVs, children, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, children, actions, callAction } =
+ useDoenetRenderer(props);
let onChangeVisibility = (isVisible) => {
callAction({
diff --git a/src/Viewer/renderers/footnote.jsx b/src/Viewer/renderers/footnote.jsx
index 2bc8bf41ab..953f9f1746 100644
--- a/src/Viewer/renderers/footnote.jsx
+++ b/src/Viewer/renderers/footnote.jsx
@@ -1,8 +1,8 @@
import React, { useState } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Footnote(props) {
- let { name, id, SVs } = useDoenetRender(props, false);
+ let { name, id, SVs } = useDoenetRenderer(props, false);
let [isVisible, setIsVisible] = useState(false);
if (SVs.hidden) {
diff --git a/src/Viewer/renderers/graph.jsx b/src/Viewer/renderers/graph.jsx
index 9ee49bce5a..ea2a58f40b 100644
--- a/src/Viewer/renderers/graph.jsx
+++ b/src/Viewer/renderers/graph.jsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef, createContext } from "react";
import { sizeToCSS } from "./utils/css";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import me from "math-expressions";
import VisibilitySensor from "react-visibility-sensor-v2";
import JXG from "./jsxgraph-distrib/jsxgraphcore.mjs";
@@ -10,7 +10,8 @@ import { cesc } from "../../_utils/url";
export const BoardContext = createContext();
export default React.memo(function Graph(props) {
- let { name, id, SVs, children, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, children, actions, callAction } =
+ useDoenetRenderer(props);
// console.log({ name, id, SVs, children, actions })
const [board, setBoard] = useState(null);
diff --git a/src/Viewer/renderers/hint.jsx b/src/Viewer/renderers/hint.jsx
index 6648565608..46928aa031 100644
--- a/src/Viewer/renderers/hint.jsx
+++ b/src/Viewer/renderers/hint.jsx
@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faLightbulb as lightOff } from "@fortawesome/free-solid-svg-icons";
import { faLightbulb as lightOn } from "@fortawesome/free-regular-svg-icons";
@@ -17,7 +17,8 @@ const SpanStyling = styled.span`
`;
export default React.memo(function Hint(props) {
- let { name, id, SVs, children, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, children, actions, callAction } =
+ useDoenetRenderer(props);
let onChangeVisibility = (isVisible) => {
callAction({
diff --git a/src/Viewer/renderers/image.jsx b/src/Viewer/renderers/image.jsx
index 4e4a156b2e..17343e3a77 100644
--- a/src/Viewer/renderers/image.jsx
+++ b/src/Viewer/renderers/image.jsx
@@ -1,13 +1,13 @@
import React, { useEffect, useState, useContext, useRef } from "react";
import { BoardContext, IMAGE_LAYER_OFFSET } from "./graph";
import { retrieveMediaForCid } from "../../Core/utils/retrieveMedia";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { sizeToCSS } from "./utils/css";
import VisibilitySensor from "react-visibility-sensor-v2";
import me from "math-expressions";
export default React.memo(function Image(props) {
- let { name, id, SVs, actions, callAction } = useDoenetRender(props, false);
+ let { name, id, SVs, actions, callAction } = useDoenetRenderer(props, false);
let [url, setUrl] = useState(null);
Image.ignoreActionsWithoutCore = () => true;
diff --git a/src/Viewer/renderers/label.jsx b/src/Viewer/renderers/label.jsx
index 9058254cfe..eb3c263c95 100644
--- a/src/Viewer/renderers/label.jsx
+++ b/src/Viewer/renderers/label.jsx
@@ -1,15 +1,15 @@
import React, { useContext, useEffect, useRef } from "react";
import { BoardContext, TEXT_LAYER_OFFSET } from "./graph";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { MathJax } from "better-react-mathjax";
import me from "math-expressions";
-import { useRecoilValue } from "recoil";
-import { darkModeAtom } from "../../Tools/_framework/DarkmodeController";
import { textRendererStyle } from "../../Core/utils/style";
import { getPositionFromAnchorByCoordinate } from "../../Core/utils/graphical";
+import { PageContext } from "../PageViewer";
export default React.memo(function Label(props) {
- let { name, id, SVs, children, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, children, actions, callAction } =
+ useDoenetRenderer(props);
Label.ignoreActionsWithoutCore = () => true;
@@ -37,7 +37,7 @@ export default React.memo(function Label(props) {
fixed.current = SVs.fixed;
fixLocation.current = !SVs.draggable || SVs.fixLocation || SVs.fixed;
- const darkMode = useRecoilValue(darkModeAtom);
+ const { darkMode } = useContext(PageContext) || {};
useEffect(() => {
//On unmount
diff --git a/src/Viewer/renderers/legend.jsx b/src/Viewer/renderers/legend.jsx
index 5c8e433077..4f62616a56 100644
--- a/src/Viewer/renderers/legend.jsx
+++ b/src/Viewer/renderers/legend.jsx
@@ -1,10 +1,10 @@
import React, { useContext, useEffect, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { BoardContext } from "./graph";
import { deepCompare } from "../../Core/utils/deepFunctions";
export default React.memo(function Legend(props) {
- let { name, id, SVs } = useDoenetRender(props);
+ let { name, id, SVs } = useDoenetRenderer(props);
const board = useContext(BoardContext);
diff --git a/src/Viewer/renderers/line.jsx b/src/Viewer/renderers/line.jsx
index b2dc56370b..ed7e0e726a 100644
--- a/src/Viewer/renderers/line.jsx
+++ b/src/Viewer/renderers/line.jsx
@@ -1,14 +1,13 @@
import React, { useContext, useEffect, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { BoardContext, LINE_LAYER_OFFSET } from "./graph";
import me from "math-expressions";
import { MathJax } from "better-react-mathjax";
-import { useRecoilValue } from "recoil";
-import { darkModeAtom } from "../../Tools/_framework/DarkmodeController";
import { textRendererStyle } from "../../Core/utils/style";
+import { PageContext } from "../PageViewer";
export default React.memo(function Line(props) {
- let { name, id, SVs, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, actions, callAction } = useDoenetRenderer(props);
Line.ignoreActionsWithoutCore = () => true;
@@ -35,7 +34,7 @@ export default React.memo(function Line(props) {
fixLocation.current = !SVs.draggable || SVs.fixLocation || SVs.fixed;
switchable.current = SVs.switchable && !SVs.fixed;
- const darkMode = useRecoilValue(darkModeAtom);
+ const { darkMode } = useContext(PageContext) || {};
useEffect(() => {
//On unmount
diff --git a/src/Viewer/renderers/lineSegment.jsx b/src/Viewer/renderers/lineSegment.jsx
index ff8ce9977e..70cd923a16 100644
--- a/src/Viewer/renderers/lineSegment.jsx
+++ b/src/Viewer/renderers/lineSegment.jsx
@@ -1,12 +1,11 @@
import React, { useContext, useEffect, useRef } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { BoardContext, LINE_LAYER_OFFSET, VERTEX_LAYER_OFFSET } from "./graph";
-import { useRecoilValue } from "recoil";
-import { darkModeAtom } from "../../Tools/_framework/DarkmodeController";
+import { PageContext } from "../PageViewer";
export default React.memo(function LineSegment(props) {
let { name, id, SVs, actions, sourceOfUpdate, callAction } =
- useDoenetRender(props);
+ useDoenetRenderer(props);
LineSegment.ignoreActionsWithoutCore = () => true;
@@ -37,7 +36,7 @@ export default React.memo(function LineSegment(props) {
endpointsFixed.current =
!SVs.endpointsDraggable || SVs.fixed || SVs.fixLocation;
- const darkMode = useRecoilValue(darkModeAtom);
+ const { darkMode } = useContext(PageContext) || {};
useEffect(() => {
//On unmount
diff --git a/src/Viewer/renderers/list.jsx b/src/Viewer/renderers/list.jsx
index 9e0207509c..762c81673c 100644
--- a/src/Viewer/renderers/list.jsx
+++ b/src/Viewer/renderers/list.jsx
@@ -1,9 +1,10 @@
import React, { useEffect } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import VisibilitySensor from "react-visibility-sensor-v2";
export default React.memo(function List(props) {
- let { name, id, SVs, children, actions, callAction } = useDoenetRender(props);
+ let { name, id, SVs, children, actions, callAction } =
+ useDoenetRenderer(props);
let onChangeVisibility = (isVisible) => {
callAction({
diff --git a/src/Viewer/renderers/lq.jsx b/src/Viewer/renderers/lq.jsx
index e1847f2be0..fb21b66668 100644
--- a/src/Viewer/renderers/lq.jsx
+++ b/src/Viewer/renderers/lq.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Lq(props) {
- let { SVs } = useDoenetRender(props, false);
+ let { SVs } = useDoenetRenderer(props, false);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/lsq.jsx b/src/Viewer/renderers/lsq.jsx
index b30ca67bdb..f2bc25db49 100644
--- a/src/Viewer/renderers/lsq.jsx
+++ b/src/Viewer/renderers/lsq.jsx
@@ -1,8 +1,8 @@
import React from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
export default React.memo(function Lsq(props) {
- let { SVs } = useDoenetRender(props, false);
+ let { SVs } = useDoenetRenderer(props, false);
if (SVs.hidden) {
return null;
diff --git a/src/Viewer/renderers/math.jsx b/src/Viewer/renderers/math.jsx
index 3c33734494..1cea81ee22 100644
--- a/src/Viewer/renderers/math.jsx
+++ b/src/Viewer/renderers/math.jsx
@@ -1,17 +1,16 @@
import React, { useContext, useEffect, useRef } from "react";
import { BoardContext, TEXT_LAYER_OFFSET } from "./graph";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { MathJax } from "better-react-mathjax";
import me from "math-expressions";
-import { useRecoilValue } from "recoil";
-import { darkModeAtom } from "../../Tools/_framework/DarkmodeController";
import { textRendererStyle } from "../../Core/utils/style";
import { getPositionFromAnchorByCoordinate } from "../../Core/utils/graphical";
import { cesc } from "../../_utils/url";
+import { PageContext } from "../PageViewer";
export default React.memo(function MathComponent(props) {
let { name, id, SVs, actions, sourceOfUpdate, callAction } =
- useDoenetRender(props);
+ useDoenetRenderer(props);
MathComponent.ignoreActionsWithoutCore = () => true;
@@ -39,7 +38,7 @@ export default React.memo(function MathComponent(props) {
fixed.current = SVs.fixed;
fixLocation.current = !SVs.draggable || SVs.fixLocation || SVs.fixed;
- const darkMode = useRecoilValue(darkModeAtom);
+ const { darkMode } = useContext(PageContext) || {};
useEffect(() => {
//On unmount
diff --git a/src/Viewer/renderers/mathInput.jsx b/src/Viewer/renderers/mathInput.jsx
index 7a9e4148bd..cb37026719 100644
--- a/src/Viewer/renderers/mathInput.jsx
+++ b/src/Viewer/renderers/mathInput.jsx
@@ -1,5 +1,5 @@
import React, { useRef, useState, useEffect } from "react";
-import useDoenetRender from "../useDoenetRenderer";
+import useDoenetRenderer from "../useDoenetRenderer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import styled from "styled-components";
import {
@@ -54,7 +54,7 @@ export default function MathInput(props) {
ignoreUpdate,
rendererName,
callAction,
- } = useDoenetRender(props);
+ } = useDoenetRenderer(props);
MathInput.baseStateVariable = "rawRendererValue";
@@ -358,9 +358,9 @@ export default function MathInput(props) {