+
+
+ A graph with point $P that is movable
+ (0,0)
+
+
+
+ $P.x < 0 and $P.y > 0
+
+
+
+
+
+
+ Maxima of a function
+
+
+
+ A graph of a function that increases until x=-7,
+ then decreases until x=-2
+ then increases until x = 4
+ and then decreases.
+ Two movable points $A and $B are shown.
+
+
+
+
+ (4,7)
+
+ $f.maxima
+ $f.minima
+
+
+
+
+ (6,7)
+
+ $f.maxima
+ $f.minima
+
+
+
+
+
+
Move the $A.styleDescriptionWithNouns to the maxima of the function
+
+
+ $A $B = $f.maxima
+
+
+
+
+ Draw function with prescribed maxima
+
+
+ Move the points so that the below function has a local maximum at
+ x = -2,
+ a local minimum of y= -5,
+ and no other local extrema.
+
+
+ A graph of a function through five movable points
+
+ (-8,-5)
+
+ x=$maxLocation
+ y=$minValue
+
+
+
+ (-4,-1)
+
+
+
+ (-1,1)
+
+
+
+ (3,3)
+
+
+
+ (7,4)
+
+
+
+
+
+
+
+
+
+
+
+ $f.numMaxima = 1 and $f.maximumLocations[1]=$maxLocation
+ and
+ $f.numMinima = 1 and $f.minimumValues[1] = $minValue
+
+
+
+
+
The function needs to have a local maximum
+
+
+
The function must have only one local maximum
+
+
+
The function correctly has one local maximum, but it is not in the correct location.
+
+
+
Good job. The function correctly has one local maximum in the correct location.
+
+
+
+
+
The function needs to have a local minimum
+
+
+
The function must have only one local minimum
+
+
+
The function correctly has one local minimum, but it does not have the correct value.
+
+
+
Looks good. The function correctly has one local minimum with the correct value.
Let y(t) be the solution to the differential equation
+
+ \frac{dy}{dt} &= $a - y
+ y(0) &= 1
+
+
+
+
Move the points and adjust the slopes so that the $C.styleDescription curve is a qualitative sketch of the solution y(t).
+
+
+
+ A graph of a function through two movable points,
+ where the slope of the function at each point can be adjusted
+ by changing a line segment at the point
+
+
+ t
+ y
+
+
+ (0, $y0)
+
+
+
+
+
+
+
+
+
+
+ y=$a
+
+
+
+
+
+ y=0
+
+
+
+
+
+ (0.5 $P1slope.x,
+ 0.5 $P1slope.y)
+
+ $P2slope
+
+
+
+
+
+
+
+
+ $P1.y = $y0 and
+ $P2.y = $a and
+ $P2slope.y = 0 and
+ $P1slope.y/$P1slope.x > 2
+
+
+
+
+ The initial condition is not correct.
+
+
+
+ The solution should start out increasing.
+
+
+
+ The solution should start by increasing more rapidly.
+
+
+
+ The solution's behavior for large values of t is incorrect.
+
+
+
+
Good job! The $C.styleDescription curve qualitatively represents the solution y(t).
+
+
It starts at the initial condition y(0)=$y0.
+
It begins by increasing rapidly.
+
It asymptotes to y=$a as t increases.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/assets/mathAnswerExamples.doenet b/client/src/assets/mathAnswerExamples.doenet
new file mode 100644
index 000000000..490b7eba2
--- /dev/null
+++ b/client/src/assets/mathAnswerExamples.doenet
@@ -0,0 +1,183 @@
+
+
+ Illustrations of mathematical answers
+
+
+ Examples showing different types of answer tags involving math
+
+
+
+ The simplest answer
+
+
What is ? 2x
+
+
+
+
+ Adding a short description or label
+
+
The simplest answer triggers a warning, as there is no information for screen readers to give about the answer blank. You can fix that by adding either a shortDescription (accessed only by screen readers) or a label (both visible and accessed by screen readers).
+
+
+
+
+ 2x
+
+
+
+
What is ?
+
+ x+x
+ 2x
+
+
+
+
This is equivalent to the last one, but just adds an award for clarity.
+
+
What is ?
+
+ x+x
+ 2x
+
+
+
+
+
+
+
+
+ Symbolic equality
+
+
The above answers actually accept
+ as a correct answer, as it is mathematical equivalent to . To require exact answers, specify the symbolicEquality attribute.
+
+
What is ?
+
+ x+x
+ 2x
+
+
+
+
+
+
+ Specifying partial credit options
+
+
+ Compute the integral \int x^2\, dx
+
+
+
+ integral of x-squared
+ x^3/3 + c
+ x^3/3 + C
+ x^3/3
+
+
+
+
+
+
+ Partial credit for unordered list
+
+
Let f(x) = (x-2)(x+5). Solve . If more than one answer, separate by commas.
+
+
x=
+
+ solve f(x)=0
+ 2, -5
+
+
+
+
+
+
+ Allowing answers in a range
+
+
+
+
+
+ 2 <= $n <= 7
+
+
+
+
+
+ Specifying an interval
+
+
+
+ A graph of a line segment on the x-axis from the point -2 to the point 5.
+ At the point -2 is an open circle.
+ At the point 5 is a closed circle.
+
+ x
+
+ (-2,0)
+ (5,0)
+
+
+
What is the interval shown above?
+
+
+ Enter interval
+
+
+ $interval =
+ (-2,5]
+
+
+
+
+
The above answer will accept different ways to express the interval, including:
+
+
(-2,5]
+
-2 < x \le 5
+
x \in (-2,5]
+
+
+
+
+
+
+
+ Dynamic number of answers
+
+
Let f(x) = (x-2)(x+5). Solve .
+
+
How many solutions are there?
+
+ number of solutions
+
+
+
+
The solutions are
+
+ x =
+
+ answer number $i
+
+ .
+
+
+
+
+
+
+
+
+
+
+ $values = 2 -5
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/assets/multipleChoiceExamples.doenet b/client/src/assets/multipleChoiceExamples.doenet
new file mode 100644
index 000000000..6b731d74e
--- /dev/null
+++ b/client/src/assets/multipleChoiceExamples.doenet
@@ -0,0 +1,120 @@
+
+
+ Illustrations of multiple-choice answers
+
+
+ Examples showing different types of multiple choice answer tags
+ available in Doenet
+
+
+
+ Basic example
+
+
What's the heaviest dog breed?
+
+ heaviest dog breed
+ English Mastiff
+ Irish Wolfhound
+ Great Dane
+ Saint Bernard
+
+
+
+
+ Shuffle order
+
+
What's the heaviest dog breed?
+
+ heaviest dog breed
+ English Mastiff
+ Irish Wolfhound
+ Great Dane
+ Saint Bernard
+
+
+
+
+
+ Inline
+
+
+ A mammal is a
+
+ Describe a mammal
+ warm-blooded
+ cold-blooded
+
+ animal.
+
+
+
+
+
+ Select multiple
+
+
+
+ 10
+ 92
+ 0
+ -29
+ 5.6
+
+
+
+
+
+ Select multiple with partial credit
+
+
+
+ 10
+ 92
+ 0
+ -29
+ 5.6
+
+
+
+
+
+
+ Limit number of attempts
+
+
+
+ Mozart
+ Verdi
+ Puccini
+ Wagner
+
+
+
+
+
+ Disable wrong choices, reduce credit with wrong choices
+
+
+
+ Archduke Franz Ferdinand
+ Gavrilo Princip
+ Danilo Ilić
+ Nedeljko Čabrinović
+
+
+
+
+
+ Disable after correct
+
+
+
+ table salt
+ Sodium acetate
+ sugar
+ vinegar
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/assets/scratchPadDefault.doenet b/client/src/assets/scratchPadDefault.doenet
new file mode 100644
index 000000000..b6d02573d
--- /dev/null
+++ b/client/src/assets/scratchPadDefault.doenet
@@ -0,0 +1,61 @@
+Welcome to the DoenetML scratch pad
+
+
+ You can use this space to experiment with writing DoenetML.
+
+
+ Your scratch pad might get overwritten, so be sure to save elsewhere anything you want to keep.
+
+
+
+ Examples
+
+
+ Auto-graded answers
+
+
1+1 = 2
+
+
The point is in the
+
+ first
+ second
+ third
+ fourth
+
+ quadrant.
+
+
+
+
+ References
+
+
What is your name?
+
+
Hello, $yourName!
+
+
+
+ A graph
+
+
Move the point. The change will be reflected here. The point is $P.
+
+
+ (3,4)
+
+
+
Change the point by typing: $P
+
+
+
+
+
+
+ Need help?
+
+
For support, you can:
+
+
Peruse the documentation.
+
Check out our community discussions
+ or our Discord server.
+
+
\ No newline at end of file
diff --git a/client/src/index.tsx b/client/src/index.tsx
index 5a80a13e1..ed7d5fea8 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -123,6 +123,7 @@ import {
loader as sharedWithMeLoader,
} from "./paths/SharedWithMe";
import { editorUrl } from "./utils/url";
+import { ScratchPad, loader as scratchPadLoader } from "./paths/ScratchPad";
const theme = extendTheme({
fonts: {
@@ -437,6 +438,13 @@ const router = createBrowserRouter([
path: "loadShareStatus/:contentId",
loader: loadShareStatus,
},
+ {
+ path: "scratchPad",
+ loader: scratchPadLoader,
+ action: genericAction,
+ errorElement: ,
+ element: ,
+ },
],
},
]);
diff --git a/client/src/paths/Home.tsx b/client/src/paths/Home.tsx
index 0036e17bf..e799a9d35 100644
--- a/client/src/paths/Home.tsx
+++ b/client/src/paths/Home.tsx
@@ -249,6 +249,14 @@ export function Home() {
>
How to get involved with Doenet
+ .
+
+
+ To experiment with writing Doenet activities, visit the{" "}
+
+ Scratch Pad
+
+ .
Events
diff --git a/client/src/paths/ScratchPad.tsx b/client/src/paths/ScratchPad.tsx
new file mode 100644
index 000000000..f1890a950
--- /dev/null
+++ b/client/src/paths/ScratchPad.tsx
@@ -0,0 +1,307 @@
+import { useCallback, useEffect, useRef, useState } from "react";
+import { redirect, useLoaderData, useOutletContext } from "react-router";
+import { DoenetmlVersion } from "../types";
+import { DoenetEditor } from "@doenet/doenetml-iframe";
+import axios from "axios";
+import defaultSource from "../assets/scratchPadDefault.doenet?raw";
+import multipleChoice from "../assets/multipleChoiceExamples.doenet?raw";
+import mathAnswers from "../assets/mathAnswerExamples.doenet?raw";
+import graphicalAnswers from "../assets/graphicalAnswerExamples.doenet?raw";
+
+import {
+ Alert,
+ AlertIcon,
+ AlertTitle,
+ AlertDescription,
+ Box,
+ Flex,
+ Button,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Text,
+ useDisclosure,
+} from "@chakra-ui/react";
+import { SiteContext } from "./SiteHeader";
+import { SaveDoenetmlAndReportFinish } from "../popups/SaveDoenetmlAndReportFinish";
+
+export async function loader({ request }: { request: Request }) {
+ const url = new URL(request.url);
+
+ const doenetML = url.searchParams.get("doenetml");
+ if (doenetML) {
+ try {
+ // Save requested DoenetML in localStorage
+ // and then reload page without doenetml param
+ // so that reloading the page won't reset to the original DoenetML
+ localStorage.setItem("scratchPad", doenetML);
+ return redirect(`/scratchPad`);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ const contentId = url.searchParams.get("contentId");
+ if (contentId) {
+ try {
+ const { data } = await axios.get(
+ `/api/activityEditView/getContentSource/${contentId}`,
+ );
+
+ // Save DoenetML source from the contentId in localStorage
+ // and then reload page without contentId param
+ // so that reloading the page won't reset to the original DoenetML
+ localStorage.setItem("scratchPad", data.source);
+ return redirect(`/scratchPad`);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ const {
+ data: { defaultDoenetmlVersion },
+ } = await axios.get(`/api/info/getDefaultDoenetmlVersion`);
+
+ let source = "";
+ try {
+ source = localStorage.getItem("scratchPad") || defaultSource;
+ } catch (e) {
+ console.error(e);
+ }
+
+ return {
+ doenetmlVersion: defaultDoenetmlVersion,
+ source,
+ };
+}
+
+/**
+ * This page allows you edit a scratch pad of DoenetML to explore the features of DoenetML.
+ */
+export function ScratchPad() {
+ const { doenetmlVersion, source } = useLoaderData() as {
+ doenetmlVersion: DoenetmlVersion;
+ source: string;
+ };
+
+ useEffect(() => {
+ document.title = `Scratch pad - Doenet`;
+ }, []);
+
+ const [initialSource, setInitialSource] = useState(source);
+ const [resetNum, setResetNum] = useState(0);
+
+ const { user } = useOutletContext();
+
+ const {
+ isOpen: saveDialogIsOpen,
+ onOpen: saveDialogOnOpen,
+ onClose: saveDialogOnClose,
+ } = useDisclosure();
+
+ const saveDocumentDialog = (
+
+ );
+
+ const loadButton = (
+
+ );
+
+ const saveScratchPad = user && (
+
+ );
+
+ const scratchPadMessage = (
+
+
+ Scratch Pad
+
+
+ Your changes are not permanently saved.
+
+
+
+ );
+
+ return (
+ <>
+ {saveDocumentDialog}
+
+
+ {scratchPadMessage}
+
+ {loadButton}
+ {saveScratchPad}
+
+
+
+
+
+ >
+ );
+}
+
+function DocumentEditor({
+ source,
+ doenetmlVersion,
+}: {
+ source: string;
+ doenetmlVersion: DoenetmlVersion;
+}) {
+ const textEditorDoenetML = useRef(source);
+ const savedDoenetML = useRef(source);
+
+ const handleSaveDoc = useCallback(async () => {
+ if (savedDoenetML.current === textEditorDoenetML.current) {
+ return;
+ }
+
+ const newDoenetML = textEditorDoenetML.current;
+
+ try {
+ //Save in localStorage
+ localStorage.setItem("scratchPad", newDoenetML);
+
+ savedDoenetML.current = newDoenetML;
+ } catch (e) {
+ console.error(e);
+ }
+ }, []);
+
+ // save draft when leave page
+ useEffect(() => {
+ return () => {
+ handleSaveDoc();
+ };
+ }, [handleSaveDoc]);
+
+ const baseUrl = window.location.protocol + "//" + window.location.host;
+ const doenetViewerUrl = `${baseUrl}/activityViewer`;
+
+ return (
+ {
+ handleSaveDoc();
+ }}
+ immediateDoenetmlChangeCallback={(newDoenetML: string) => {
+ textEditorDoenetML.current = newDoenetML;
+ }}
+ doenetmlVersion={doenetmlVersion.fullVersion}
+ border="none"
+ doenetViewerUrl={doenetViewerUrl}
+ />
+ );
+}
diff --git a/client/src/paths/SiteHeader.tsx b/client/src/paths/SiteHeader.tsx
index 21f8a8716..89a8ace20 100644
--- a/client/src/paths/SiteHeader.tsx
+++ b/client/src/paths/SiteHeader.tsx
@@ -399,7 +399,13 @@ export function SiteHeader() {
>
Update {user.isAnonymous ? "pseudonym" : "name"}
-