@doenet/doenetml 0.6.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (549) hide show
  1. package/.prettierrc +3 -0
  2. package/LICENSE +661 -0
  3. package/README.md +146 -0
  4. package/cypress/e2e/ActivityViewer/activityVariants.cy.js +1770 -0
  5. package/cypress/e2e/ActivityViewer/compiledActivity.cy.js +83 -0
  6. package/cypress/e2e/ActivityViewer/relationshipsAmongPages.cy.js +697 -0
  7. package/cypress/e2e/answerValidation/errorinnumbers.cy.js +2125 -0
  8. package/cypress/e2e/answerValidation/factoring.cy.js +1945 -0
  9. package/cypress/e2e/answerValidation/factoringOldAlgorithm.cy.js +892 -0
  10. package/cypress/e2e/answerValidation/functionanswers.cy.js +314 -0
  11. package/cypress/e2e/answerValidation/matchingpatterns.cy.js +287 -0
  12. package/cypress/e2e/answerValidation/matchpartial.cy.js +6711 -0
  13. package/cypress/e2e/answerValidation/pointlocation.cy.js +3989 -0
  14. package/cypress/e2e/answerValidation/symbolicequality.cy.js +1893 -0
  15. package/cypress/e2e/answerValidation/videoProgress.cy.js +210 -0
  16. package/cypress/e2e/assignNames/basiccopy.cy.js +2376 -0
  17. package/cypress/e2e/assignNames/collections.cy.js +9247 -0
  18. package/cypress/e2e/assignNames/selects.cy.js +105 -0
  19. package/cypress/e2e/assignNames/sequences.cy.js +1964 -0
  20. package/cypress/e2e/baseComponent/basecomponentproperties.cy.js +999 -0
  21. package/cypress/e2e/baseComponent/doenetMLtext.cy.js +427 -0
  22. package/cypress/e2e/chemistry/atom.cy.js +201 -0
  23. package/cypress/e2e/chemistry/ion.cy.js +608 -0
  24. package/cypress/e2e/chemistry/ioniccompound.cy.js +133 -0
  25. package/cypress/e2e/dynamicalsystem/cobwebpolyline.cy.js +2653 -0
  26. package/cypress/e2e/dynamicalsystem/equilibriumcurve.cy.js +311 -0
  27. package/cypress/e2e/dynamicalsystem/equilibriumline.cy.js +279 -0
  28. package/cypress/e2e/dynamicalsystem/equilibriumpoint.cy.js +283 -0
  29. package/cypress/e2e/dynamicalsystem/odesystem.cy.js +1834 -0
  30. package/cypress/e2e/equality/mathexpressions.cy.js +948 -0
  31. package/cypress/e2e/graphing/graphreferences.cy.js +978 -0
  32. package/cypress/e2e/graphing/graphreferences2.cy.js +615 -0
  33. package/cypress/e2e/linearAlgebra/eigenDecomposition.cy.js +401 -0
  34. package/cypress/e2e/tagSpecific/angle.cy.js +3898 -0
  35. package/cypress/e2e/tagSpecific/animatefromsequence.cy.js +2306 -0
  36. package/cypress/e2e/tagSpecific/answer.cy.js +31647 -0
  37. package/cypress/e2e/tagSpecific/bestfitline.cy.js +612 -0
  38. package/cypress/e2e/tagSpecific/blockquote.cy.js +30 -0
  39. package/cypress/e2e/tagSpecific/boolean.cy.js +742 -0
  40. package/cypress/e2e/tagSpecific/booleaninput.cy.js +1283 -0
  41. package/cypress/e2e/tagSpecific/booleanlist.cy.js +588 -0
  42. package/cypress/e2e/tagSpecific/booleanoperators.cy.js +596 -0
  43. package/cypress/e2e/tagSpecific/booleanoperatorsonmath.cy.js +498 -0
  44. package/cypress/e2e/tagSpecific/callaction.cy.js +2835 -0
  45. package/cypress/e2e/tagSpecific/choiceinput.cy.js +3205 -0
  46. package/cypress/e2e/tagSpecific/circle.cy.js +22036 -0
  47. package/cypress/e2e/tagSpecific/codeeditor.cy.js +1995 -0
  48. package/cypress/e2e/tagSpecific/collect.cy.js +5035 -0
  49. package/cypress/e2e/tagSpecific/componentsize.cy.js +502 -0
  50. package/cypress/e2e/tagSpecific/conditionalcontent.cy.js +3495 -0
  51. package/cypress/e2e/tagSpecific/contentBrowser.cy.js +335 -0
  52. package/cypress/e2e/tagSpecific/contentpicker.cy.js +261 -0
  53. package/cypress/e2e/tagSpecific/copy.cy.js +12627 -0
  54. package/cypress/e2e/tagSpecific/copy2.cy.js +5698 -0
  55. package/cypress/e2e/tagSpecific/curve.bezier.cy.js +12440 -0
  56. package/cypress/e2e/tagSpecific/curve.cy.js +1716 -0
  57. package/cypress/e2e/tagSpecific/curve.function.cy.js +1471 -0
  58. package/cypress/e2e/tagSpecific/curve.parametrized.cy.js +920 -0
  59. package/cypress/e2e/tagSpecific/document.cy.js +234 -0
  60. package/cypress/e2e/tagSpecific/endpoint.cy.js +197 -0
  61. package/cypress/e2e/tagSpecific/evaluate.cy.js +8895 -0
  62. package/cypress/e2e/tagSpecific/extract.cy.js +2282 -0
  63. package/cypress/e2e/tagSpecific/feedback.cy.js +2941 -0
  64. package/cypress/e2e/tagSpecific/function.cy.js +9450 -0
  65. package/cypress/e2e/tagSpecific/functioniterates.cy.js +1178 -0
  66. package/cypress/e2e/tagSpecific/functionoperators.cy.js +4047 -0
  67. package/cypress/e2e/tagSpecific/graph.cy.js +2491 -0
  68. package/cypress/e2e/tagSpecific/group.cy.js +683 -0
  69. package/cypress/e2e/tagSpecific/hint.cy.js +204 -0
  70. package/cypress/e2e/tagSpecific/image.cy.js +770 -0
  71. package/cypress/e2e/tagSpecific/integer.cy.js +206 -0
  72. package/cypress/e2e/tagSpecific/label.cy.js +800 -0
  73. package/cypress/e2e/tagSpecific/legend.cy.js +1001 -0
  74. package/cypress/e2e/tagSpecific/line.cy.js +12167 -0
  75. package/cypress/e2e/tagSpecific/linesegment.cy.js +4749 -0
  76. package/cypress/e2e/tagSpecific/lorem.cy.js +289 -0
  77. package/cypress/e2e/tagSpecific/map.cy.js +4476 -0
  78. package/cypress/e2e/tagSpecific/matchespattern.cy.js +693 -0
  79. package/cypress/e2e/tagSpecific/math.cy.js +10990 -0
  80. package/cypress/e2e/tagSpecific/mathdisplay.cy.js +2689 -0
  81. package/cypress/e2e/tagSpecific/mathinput.cy.js +15628 -0
  82. package/cypress/e2e/tagSpecific/mathinputgraph.cy.js +566 -0
  83. package/cypress/e2e/tagSpecific/mathlist.cy.js +4073 -0
  84. package/cypress/e2e/tagSpecific/mathoperators.cy.js +13851 -0
  85. package/cypress/e2e/tagSpecific/matrix.cy.js +8825 -0
  86. package/cypress/e2e/tagSpecific/matrixinput.cy.js +16277 -0
  87. package/cypress/e2e/tagSpecific/module.cy.js +1771 -0
  88. package/cypress/e2e/tagSpecific/number.cy.js +2221 -0
  89. package/cypress/e2e/tagSpecific/numberlist.cy.js +1285 -0
  90. package/cypress/e2e/tagSpecific/p.cy.js +72 -0
  91. package/cypress/e2e/tagSpecific/paginator.cy.js +2983 -0
  92. package/cypress/e2e/tagSpecific/parabola.cy.js +14331 -0
  93. package/cypress/e2e/tagSpecific/paragraphmarkup.cy.js +104 -0
  94. package/cypress/e2e/tagSpecific/periodicset.cy.js +1439 -0
  95. package/cypress/e2e/tagSpecific/piecewisefunction.cy.js +1055 -0
  96. package/cypress/e2e/tagSpecific/pluralize.cy.js +274 -0
  97. package/cypress/e2e/tagSpecific/point.cy.js +8895 -0
  98. package/cypress/e2e/tagSpecific/point2.cy.js +10259 -0
  99. package/cypress/e2e/tagSpecific/polygon.cy.js +5039 -0
  100. package/cypress/e2e/tagSpecific/polyline.cy.js +4704 -0
  101. package/cypress/e2e/tagSpecific/problem.cy.js +2768 -0
  102. package/cypress/e2e/tagSpecific/ray.cy.js +10770 -0
  103. package/cypress/e2e/tagSpecific/rectangle.cy.js +2143 -0
  104. package/cypress/e2e/tagSpecific/ref.cy.js +420 -0
  105. package/cypress/e2e/tagSpecific/regularPolygon.cy.js +1006 -0
  106. package/cypress/e2e/tagSpecific/regularPolygon2.cy.js +100 -0
  107. package/cypress/e2e/tagSpecific/regularPolygon3.cy.js +777 -0
  108. package/cypress/e2e/tagSpecific/samplerandomnumbers.cy.js +3619 -0
  109. package/cypress/e2e/tagSpecific/sectioning.cy.js +3530 -0
  110. package/cypress/e2e/tagSpecific/select.cy.js +5376 -0
  111. package/cypress/e2e/tagSpecific/selectfromsequence.cy.js +3846 -0
  112. package/cypress/e2e/tagSpecific/selectrandomnumbers.cy.js +2914 -0
  113. package/cypress/e2e/tagSpecific/sequence.cy.js +2093 -0
  114. package/cypress/e2e/tagSpecific/shuffle.cy.js +490 -0
  115. package/cypress/e2e/tagSpecific/sidebyside.cy.js +8057 -0
  116. package/cypress/e2e/tagSpecific/singlecharactercomponents.cy.js +72 -0
  117. package/cypress/e2e/tagSpecific/slider.cy.js +914 -0
  118. package/cypress/e2e/tagSpecific/solution.cy.js +109 -0
  119. package/cypress/e2e/tagSpecific/solveequations.cy.js +1026 -0
  120. package/cypress/e2e/tagSpecific/sort.cy.js +1685 -0
  121. package/cypress/e2e/tagSpecific/spreadsheet.cy.js +5971 -0
  122. package/cypress/e2e/tagSpecific/subsetofreals.cy.js +2725 -0
  123. package/cypress/e2e/tagSpecific/substitute.cy.js +2646 -0
  124. package/cypress/e2e/tagSpecific/tabular.cy.js +36 -0
  125. package/cypress/e2e/tagSpecific/text.cy.js +975 -0
  126. package/cypress/e2e/tagSpecific/textinput.cy.js +2177 -0
  127. package/cypress/e2e/tagSpecific/textlist.cy.js +369 -0
  128. package/cypress/e2e/tagSpecific/triangle.cy.js +1936 -0
  129. package/cypress/e2e/tagSpecific/triggerset.cy.js +2023 -0
  130. package/cypress/e2e/tagSpecific/updatevalue.cy.js +3288 -0
  131. package/cypress/e2e/tagSpecific/vector.cy.js +20183 -0
  132. package/cypress/e2e/tagSpecific/video.cy.js +612 -0
  133. package/cypress/e2e/tagSpecific/when.cy.js +202 -0
  134. package/cypress/e2e/variants/specifysinglevariant.cy.js +6726 -0
  135. package/cypress/e2e/variants/uniquevariants.cy.js +4846 -0
  136. package/cypress/fixtures/example.json +5 -0
  137. package/cypress/support/commands.js +32 -0
  138. package/cypress/support/e2e.js +31 -0
  139. package/cypress.config.js +18 -0
  140. package/docs/codeSnippet.jsx +11 -0
  141. package/docs/index.html +133 -0
  142. package/docs/index.jsx +138 -0
  143. package/docs/prism.css +3 -0
  144. package/index.html +14 -0
  145. package/index.js +21 -0
  146. package/media/answer_example.png +0 -0
  147. package/media/graph_example.png +0 -0
  148. package/media/graph_markup_example.png +0 -0
  149. package/package.json +83 -0
  150. package/public/favicon.ico +0 -0
  151. package/public/fonts/files/open-sans-v18-latin-700.woff +0 -0
  152. package/public/fonts/files/open-sans-v18-latin-700.woff2 +0 -0
  153. package/public/fonts/files/open-sans-v18-latin-700italic.woff +0 -0
  154. package/public/fonts/files/open-sans-v18-latin-700italic.woff2 +0 -0
  155. package/public/fonts/files/open-sans-v18-latin-italic.woff +0 -0
  156. package/public/fonts/files/open-sans-v18-latin-italic.woff2 +0 -0
  157. package/public/fonts/files/open-sans-v18-latin-light-italic.woff +0 -0
  158. package/public/fonts/files/open-sans-v18-latin-light-italic.woff2 +0 -0
  159. package/public/fonts/files/open-sans-v18-latin-light.woff +0 -0
  160. package/public/fonts/files/open-sans-v18-latin-light.woff2 +0 -0
  161. package/public/fonts/files/open-sans-v18-latin-regular.woff +0 -0
  162. package/public/fonts/files/open-sans-v18-latin-regular.woff2 +0 -0
  163. package/src/Core/ComponentTypes.js +426 -0
  164. package/src/Core/Core.js +11852 -0
  165. package/src/Core/CoreWorker.js +127 -0
  166. package/src/Core/Dependencies.js +8226 -0
  167. package/src/Core/Numerics.js +473 -0
  168. package/src/Core/ParameterStack.js +36 -0
  169. package/src/Core/ReadOnlyProxyHandler.js +41 -0
  170. package/src/Core/StateProxyHandler.js +88 -0
  171. package/src/Core/components/Aliases.js +67 -0
  172. package/src/Core/components/Angle.js +758 -0
  173. package/src/Core/components/AnimateFromSequence.js +922 -0
  174. package/src/Core/components/Answer.js +2087 -0
  175. package/src/Core/components/AsList.js +83 -0
  176. package/src/Core/components/AttractTo.js +245 -0
  177. package/src/Core/components/AttractToAngles.js +262 -0
  178. package/src/Core/components/AttractToConstraint.js +104 -0
  179. package/src/Core/components/AttractToGrid.js +315 -0
  180. package/src/Core/components/Award.js +906 -0
  181. package/src/Core/components/BestFitLine.js +318 -0
  182. package/src/Core/components/BezierControls.js +719 -0
  183. package/src/Core/components/BlockQuote.js +35 -0
  184. package/src/Core/components/Boolean.js +500 -0
  185. package/src/Core/components/BooleanInput.js +330 -0
  186. package/src/Core/components/BooleanList.js +396 -0
  187. package/src/Core/components/BooleanOperators.js +35 -0
  188. package/src/Core/components/BooleanOperatorsOfMath.js +148 -0
  189. package/src/Core/components/CallAction.js +261 -0
  190. package/src/Core/components/Caption.js +73 -0
  191. package/src/Core/components/Case.js +56 -0
  192. package/src/Core/components/Cell.js +439 -0
  193. package/src/Core/components/CellBlock.js +64 -0
  194. package/src/Core/components/Chart.js +795 -0
  195. package/src/Core/components/Choice.js +266 -0
  196. package/src/Core/components/ChoiceInput.js +1407 -0
  197. package/src/Core/components/Circle.js +2884 -0
  198. package/src/Core/components/CodeEditor.js +647 -0
  199. package/src/Core/components/CodeViewer.js +294 -0
  200. package/src/Core/components/CollaborateGroupSetup.js +46 -0
  201. package/src/Core/components/CollaborateGroups.js +119 -0
  202. package/src/Core/components/Collect.js +850 -0
  203. package/src/Core/components/Column.js +608 -0
  204. package/src/Core/components/ConditionalContent.js +468 -0
  205. package/src/Core/components/ConsiderAsResponses.js +49 -0
  206. package/src/Core/components/ConstrainTo.js +161 -0
  207. package/src/Core/components/ConstrainToAngles.js +244 -0
  208. package/src/Core/components/ConstrainToGraph.js +142 -0
  209. package/src/Core/components/ConstrainToGrid.js +175 -0
  210. package/src/Core/components/ConstraintUnion.js +119 -0
  211. package/src/Core/components/Constraints.js +497 -0
  212. package/src/Core/components/ContentBrowser.js +441 -0
  213. package/src/Core/components/ContentPicker.js +263 -0
  214. package/src/Core/components/ControlVectors.js +25 -0
  215. package/src/Core/components/Coords.js +63 -0
  216. package/src/Core/components/Copy.js +3412 -0
  217. package/src/Core/components/Curve.js +4130 -0
  218. package/src/Core/components/CustomAttribute.js +175 -0
  219. package/src/Core/components/DataFrame.js +357 -0
  220. package/src/Core/components/DiscreteSimulationResultList.js +342 -0
  221. package/src/Core/components/DiscreteSimulationResultPolyline.js +581 -0
  222. package/src/Core/components/Divisions.js +55 -0
  223. package/src/Core/components/Document.js +888 -0
  224. package/src/Core/components/Embed.js +65 -0
  225. package/src/Core/components/Endpoint.js +62 -0
  226. package/src/Core/components/Evaluate.js +321 -0
  227. package/src/Core/components/Extract.js +656 -0
  228. package/src/Core/components/Extrema.js +556 -0
  229. package/src/Core/components/Feedback.js +200 -0
  230. package/src/Core/components/FeedbackDefinitions.js +97 -0
  231. package/src/Core/components/Figure.js +148 -0
  232. package/src/Core/components/Footnote.js +73 -0
  233. package/src/Core/components/Function.js +5344 -0
  234. package/src/Core/components/FunctionIterates.js +306 -0
  235. package/src/Core/components/FunctionOperators.js +702 -0
  236. package/src/Core/components/Graph.js +1679 -0
  237. package/src/Core/components/Group.js +7 -0
  238. package/src/Core/components/HasSameFactoring.js +407 -0
  239. package/src/Core/components/Hint.js +241 -0
  240. package/src/Core/components/Image.js +524 -0
  241. package/src/Core/components/Indexing.js +79 -0
  242. package/src/Core/components/IntComma.js +64 -0
  243. package/src/Core/components/Integer.js +81 -0
  244. package/src/Core/components/Intersection.js +328 -0
  245. package/src/Core/components/Interval.js +29 -0
  246. package/src/Core/components/Label.js +492 -0
  247. package/src/Core/components/Latex.js +104 -0
  248. package/src/Core/components/Legend.js +329 -0
  249. package/src/Core/components/Line.js +2040 -0
  250. package/src/Core/components/LineSegment.js +882 -0
  251. package/src/Core/components/Lists.js +180 -0
  252. package/src/Core/components/Lorem.js +249 -0
  253. package/src/Core/components/MMeMen.js +377 -0
  254. package/src/Core/components/Map.js +873 -0
  255. package/src/Core/components/Markers.js +101 -0
  256. package/src/Core/components/MatchesPattern.js +339 -0
  257. package/src/Core/components/Math.js +2552 -0
  258. package/src/Core/components/MathInput.js +948 -0
  259. package/src/Core/components/MathList.js +828 -0
  260. package/src/Core/components/MathOperators.js +1286 -0
  261. package/src/Core/components/Matrix.js +497 -0
  262. package/src/Core/components/MatrixInput.js +3157 -0
  263. package/src/Core/components/MdMdnMrow.js +394 -0
  264. package/src/Core/components/Module.js +16 -0
  265. package/src/Core/components/Number.js +1031 -0
  266. package/src/Core/components/NumberList.js +550 -0
  267. package/src/Core/components/Option.js +24 -0
  268. package/src/Core/components/P.js +71 -0
  269. package/src/Core/components/Paginator.js +338 -0
  270. package/src/Core/components/Panel.js +126 -0
  271. package/src/Core/components/Parabola.js +1561 -0
  272. package/src/Core/components/ParagraphMarkup.js +59 -0
  273. package/src/Core/components/Pegboard.js +43 -0
  274. package/src/Core/components/PeriodicSet.js +291 -0
  275. package/src/Core/components/PiecewiseFunction.js +832 -0
  276. package/src/Core/components/Pluralize.js +198 -0
  277. package/src/Core/components/Point.js +1295 -0
  278. package/src/Core/components/Polygon.js +408 -0
  279. package/src/Core/components/Polyline.js +841 -0
  280. package/src/Core/components/RandomizedTextList.js +225 -0
  281. package/src/Core/components/Ray.js +1737 -0
  282. package/src/Core/components/Rectangle.js +1535 -0
  283. package/src/Core/components/Ref.js +350 -0
  284. package/src/Core/components/RegionBetweenCurveXAxis.js +124 -0
  285. package/src/Core/components/RegionHalfPlane.js +107 -0
  286. package/src/Core/components/RegularPolygon.js +2118 -0
  287. package/src/Core/components/RenderDoenetML.js +181 -0
  288. package/src/Core/components/Row.js +780 -0
  289. package/src/Core/components/SampleRandomNumbers.js +653 -0
  290. package/src/Core/components/Sectioning.js +303 -0
  291. package/src/Core/components/Select.js +947 -0
  292. package/src/Core/components/SelectFromSequence.js +1242 -0
  293. package/src/Core/components/SelectRandomNumbers.js +225 -0
  294. package/src/Core/components/Sequence.js +444 -0
  295. package/src/Core/components/Setup.js +53 -0
  296. package/src/Core/components/Shuffle.js +470 -0
  297. package/src/Core/components/SideBySide.js +2130 -0
  298. package/src/Core/components/SingleCharacterComponents.js +41 -0
  299. package/src/Core/components/Slider.js +819 -0
  300. package/src/Core/components/SolutionContainer.js +67 -0
  301. package/src/Core/components/Solutions.js +334 -0
  302. package/src/Core/components/SolveEquations.js +568 -0
  303. package/src/Core/components/Sort.js +398 -0
  304. package/src/Core/components/Sources.js +108 -0
  305. package/src/Core/components/Split.js +205 -0
  306. package/src/Core/components/Spreadsheet.js +1507 -0
  307. package/src/Core/components/StyleDefinitions.js +111 -0
  308. package/src/Core/components/SubsetOfReals.js +348 -0
  309. package/src/Core/components/SubsetOfRealsInput.js +1474 -0
  310. package/src/Core/components/Substitute.js +496 -0
  311. package/src/Core/components/SummaryStatistics.js +652 -0
  312. package/src/Core/components/Table.js +145 -0
  313. package/src/Core/components/Tabular.js +384 -0
  314. package/src/Core/components/Template.js +360 -0
  315. package/src/Core/components/Text.js +341 -0
  316. package/src/Core/components/TextInput.js +566 -0
  317. package/src/Core/components/TextList.js +442 -0
  318. package/src/Core/components/TextListFromString.js +137 -0
  319. package/src/Core/components/TextOperatorsOfMath.js +21 -0
  320. package/src/Core/components/Triangle.js +280 -0
  321. package/src/Core/components/TriggerSet.js +189 -0
  322. package/src/Core/components/TupleList.js +43 -0
  323. package/src/Core/components/UpdateValue.js +435 -0
  324. package/src/Core/components/VariantControl.js +36 -0
  325. package/src/Core/components/Vector.js +2478 -0
  326. package/src/Core/components/Verbatim.js +125 -0
  327. package/src/Core/components/Video.js +673 -0
  328. package/src/Core/components/When.js +198 -0
  329. package/src/Core/components/abstract/AngleListComponent.js +140 -0
  330. package/src/Core/components/abstract/BaseComponent.js +1496 -0
  331. package/src/Core/components/abstract/BlockComponent.js +5 -0
  332. package/src/Core/components/abstract/BooleanBaseOperator.js +88 -0
  333. package/src/Core/components/abstract/BooleanBaseOperatorOfMath.js +100 -0
  334. package/src/Core/components/abstract/BooleanBaseOperatorOneInput.js +44 -0
  335. package/src/Core/components/abstract/ComponentSize.js +789 -0
  336. package/src/Core/components/abstract/ComponentWithSelectableType.js +537 -0
  337. package/src/Core/components/abstract/CompositeComponent.js +142 -0
  338. package/src/Core/components/abstract/ConstraintComponent.js +19 -0
  339. package/src/Core/components/abstract/FunctionBaseOperator.js +680 -0
  340. package/src/Core/components/abstract/GraphicalComponent.js +56 -0
  341. package/src/Core/components/abstract/InlineComponent.js +5 -0
  342. package/src/Core/components/abstract/InlineRenderInlineChildren.js +63 -0
  343. package/src/Core/components/abstract/Input.js +192 -0
  344. package/src/Core/components/abstract/IntervalListComponent.js +218 -0
  345. package/src/Core/components/abstract/LineListComponent.js +114 -0
  346. package/src/Core/components/abstract/MathBaseOperator.js +631 -0
  347. package/src/Core/components/abstract/MathBaseOperatorOneInput.js +112 -0
  348. package/src/Core/components/abstract/PointListComponent.js +238 -0
  349. package/src/Core/components/abstract/SectioningComponent.js +1262 -0
  350. package/src/Core/components/abstract/SingleCharacterInline.js +23 -0
  351. package/src/Core/components/abstract/TextBaseOperatorOfMath.js +47 -0
  352. package/src/Core/components/abstract/TextOrInline.js +66 -0
  353. package/src/Core/components/abstract/VariableName.js +31 -0
  354. package/src/Core/components/abstract/VariableNameList.js +83 -0
  355. package/src/Core/components/abstract/VectorListComponent.js +235 -0
  356. package/src/Core/components/chemistry/Atom.js +910 -0
  357. package/src/Core/components/chemistry/ElectronConfiguration.js +36 -0
  358. package/src/Core/components/chemistry/Ion.js +684 -0
  359. package/src/Core/components/chemistry/IonicCompound.js +189 -0
  360. package/src/Core/components/chemistry/OrbitalDiagram.js +175 -0
  361. package/src/Core/components/chemistry/OrbitalDiagramInput.js +753 -0
  362. package/src/Core/components/chemistry/index.js +6 -0
  363. package/src/Core/components/commonsugar/breakstrings.js +627 -0
  364. package/src/Core/components/commonsugar/lists.js +177 -0
  365. package/src/Core/components/dynamicalSystems/CobwebPolyline.js +913 -0
  366. package/src/Core/components/dynamicalSystems/EquilibriumCurve.js +95 -0
  367. package/src/Core/components/dynamicalSystems/EquilibriumLine.js +93 -0
  368. package/src/Core/components/dynamicalSystems/EquilibriumPoint.js +93 -0
  369. package/src/Core/components/dynamicalSystems/ODESystem.js +943 -0
  370. package/src/Core/components/dynamicalSystems/index.js +5 -0
  371. package/src/Core/components/linearAlgebra/EigenDecomposition.js +294 -0
  372. package/src/Core/utils/array.js +30 -0
  373. package/src/Core/utils/booleanLogic.js +965 -0
  374. package/src/Core/utils/checkEquality.js +818 -0
  375. package/src/Core/utils/cid.js +29 -0
  376. package/src/Core/utils/componentInfoObjects.js +100 -0
  377. package/src/Core/utils/constraints.js +23 -0
  378. package/src/Core/utils/copy.js +572 -0
  379. package/src/Core/utils/deepFunctions.js +173 -0
  380. package/src/Core/utils/descendants.js +252 -0
  381. package/src/Core/utils/enumeration.js +234 -0
  382. package/src/Core/utils/feedback.js +84 -0
  383. package/src/Core/utils/function.js +1343 -0
  384. package/src/Core/utils/graphical.js +196 -0
  385. package/src/Core/utils/label.js +396 -0
  386. package/src/Core/utils/math.js +1056 -0
  387. package/src/Core/utils/naming.js +45 -0
  388. package/src/Core/utils/periodicSetEquality.js +403 -0
  389. package/src/Core/utils/randomNumbers.js +70 -0
  390. package/src/Core/utils/retrieveMedia.js +98 -0
  391. package/src/Core/utils/retrieveTextFile.js +140 -0
  392. package/src/Core/utils/returnAllPossibleVariants.js +73 -0
  393. package/src/Core/utils/rounding.js +316 -0
  394. package/src/Core/utils/sequence.js +754 -0
  395. package/src/Core/utils/serializedStateProcessing.js +4049 -0
  396. package/src/Core/utils/size.js +22 -0
  397. package/src/Core/utils/stateVariables.js +138 -0
  398. package/src/Core/utils/style.js +535 -0
  399. package/src/Core/utils/subset-of-reals.js +796 -0
  400. package/src/Core/utils/table.js +41 -0
  401. package/src/Core/utils/text.js +16 -0
  402. package/src/Core/utils/triggering.js +167 -0
  403. package/src/Core/utils/variants.js +477 -0
  404. package/src/DoenetML.css +308 -0
  405. package/src/DoenetML.jsx +201 -0
  406. package/src/Parser/doenet.grammar +90 -0
  407. package/src/Parser/doenet.js +33 -0
  408. package/src/Parser/doenet.terms.js +20 -0
  409. package/src/Parser/parser.js +266 -0
  410. package/src/Parser/tokens.js +129 -0
  411. package/src/Tools/CodeMirror.jsx +440 -0
  412. package/src/Tools/DarkmodeController.jsx +21 -0
  413. package/src/Tools/Footers/MathInputSelector.jsx +34 -0
  414. package/src/Tools/Footers/VirtualKeyboard.jsx +751 -0
  415. package/src/Tools/cypressTest/CypressTest.jsx +341 -0
  416. package/src/Tools/cypressTest/index.html +102 -0
  417. package/src/Tools/cypressTest/index.jsx +40 -0
  418. package/src/Viewer/ActivityViewer.jsx +1461 -0
  419. package/src/Viewer/PageViewer.jsx +1329 -0
  420. package/src/Viewer/renderers/alert.jsx +17 -0
  421. package/src/Viewer/renderers/angle.jsx +209 -0
  422. package/src/Viewer/renderers/answer.jsx +206 -0
  423. package/src/Viewer/renderers/asList.jsx +25 -0
  424. package/src/Viewer/renderers/blockQuote.jsx +41 -0
  425. package/src/Viewer/renderers/boolean.jsx +17 -0
  426. package/src/Viewer/renderers/booleanInput.css +105 -0
  427. package/src/Viewer/renderers/booleanInput.jsx +636 -0
  428. package/src/Viewer/renderers/button.jsx +369 -0
  429. package/src/Viewer/renderers/c.jsx +17 -0
  430. package/src/Viewer/renderers/callAction.jsx +18 -0
  431. package/src/Viewer/renderers/cell.jsx +59 -0
  432. package/src/Viewer/renderers/chart.jsx +83 -0
  433. package/src/Viewer/renderers/choiceInput.css +223 -0
  434. package/src/Viewer/renderers/choiceInput.jsx +535 -0
  435. package/src/Viewer/renderers/circle.jsx +990 -0
  436. package/src/Viewer/renderers/cobwebPolyline.jsx +442 -0
  437. package/src/Viewer/renderers/codeEditor.jsx +248 -0
  438. package/src/Viewer/renderers/codeViewer.jsx +105 -0
  439. package/src/Viewer/renderers/containerBlock.jsx +41 -0
  440. package/src/Viewer/renderers/containerInline.jsx +17 -0
  441. package/src/Viewer/renderers/contentBrowser.jsx +159 -0
  442. package/src/Viewer/renderers/contentPicker.jsx +160 -0
  443. package/src/Viewer/renderers/curve.jsx +1072 -0
  444. package/src/Viewer/renderers/ellipsis.jsx +17 -0
  445. package/src/Viewer/renderers/em.jsx +17 -0
  446. package/src/Viewer/renderers/embed.jsx +110 -0
  447. package/src/Viewer/renderers/feedback.jsx +74 -0
  448. package/src/Viewer/renderers/figure.jsx +131 -0
  449. package/src/Viewer/renderers/footnote.jsx +52 -0
  450. package/src/Viewer/renderers/graph.jsx +925 -0
  451. package/src/Viewer/renderers/hint.jsx +142 -0
  452. package/src/Viewer/renderers/image.jsx +581 -0
  453. package/src/Viewer/renderers/jsxgraph-distrib/jsxgraphcore.mjs +2 -0
  454. package/src/Viewer/renderers/jsxgraph-distrib/jsxgraphcore.mjs.map +1 -0
  455. package/src/Viewer/renderers/label.jsx +470 -0
  456. package/src/Viewer/renderers/legend.jsx +306 -0
  457. package/src/Viewer/renderers/line.jsx +511 -0
  458. package/src/Viewer/renderers/lineSegment.jsx +754 -0
  459. package/src/Viewer/renderers/list.jsx +111 -0
  460. package/src/Viewer/renderers/lq.jsx +12 -0
  461. package/src/Viewer/renderers/lsq.jsx +12 -0
  462. package/src/Viewer/renderers/math.jsx +582 -0
  463. package/src/Viewer/renderers/mathInput.css +10 -0
  464. package/src/Viewer/renderers/mathInput.jsx +425 -0
  465. package/src/Viewer/renderers/mathInputog.jsx +534 -0
  466. package/src/Viewer/renderers/mathList.jsx +39 -0
  467. package/src/Viewer/renderers/matrixInput.jsx +317 -0
  468. package/src/Viewer/renderers/mdash.jsx +12 -0
  469. package/src/Viewer/renderers/nbsp.jsx +12 -0
  470. package/src/Viewer/renderers/ndash.jsx +12 -0
  471. package/src/Viewer/renderers/number.jsx +454 -0
  472. package/src/Viewer/renderers/numberList.jsx +35 -0
  473. package/src/Viewer/renderers/orbitalDiagram.jsx +247 -0
  474. package/src/Viewer/renderers/orbitalDiagramInput.jsx +450 -0
  475. package/src/Viewer/renderers/p.jsx +38 -0
  476. package/src/Viewer/renderers/paginatorControls.jsx +41 -0
  477. package/src/Viewer/renderers/pegboard.jsx +239 -0
  478. package/src/Viewer/renderers/point.jsx +649 -0
  479. package/src/Viewer/renderers/polygon.jsx +612 -0
  480. package/src/Viewer/renderers/polyline.jsx +608 -0
  481. package/src/Viewer/renderers/pre.jsx +34 -0
  482. package/src/Viewer/renderers/q.jsx +17 -0
  483. package/src/Viewer/renderers/ray.jsx +410 -0
  484. package/src/Viewer/renderers/ref.jsx +149 -0
  485. package/src/Viewer/renderers/regionBetweenCurveXAxis.jsx +182 -0
  486. package/src/Viewer/renderers/renderDoenetML.jsx +56 -0
  487. package/src/Viewer/renderers/row.jsx +31 -0
  488. package/src/Viewer/renderers/rq.jsx +12 -0
  489. package/src/Viewer/renderers/rsq.jsx +12 -0
  490. package/src/Viewer/renderers/section.jsx +427 -0
  491. package/src/Viewer/renderers/sideBySide.jsx +80 -0
  492. package/src/Viewer/renderers/slider.jsx +800 -0
  493. package/src/Viewer/renderers/solution.jsx +134 -0
  494. package/src/Viewer/renderers/spreadsheet.jsx +83 -0
  495. package/src/Viewer/renderers/sq.jsx +17 -0
  496. package/src/Viewer/renderers/styles/global.css +14 -0
  497. package/src/Viewer/renderers/subsetOfRealsInput.jsx +392 -0
  498. package/src/Viewer/renderers/summaryStatistics.jsx +83 -0
  499. package/src/Viewer/renderers/table.jsx +78 -0
  500. package/src/Viewer/renderers/tabular.jsx +58 -0
  501. package/src/Viewer/renderers/tag.jsx +26 -0
  502. package/src/Viewer/renderers/text.jsx +439 -0
  503. package/src/Viewer/renderers/textInput.jsx +774 -0
  504. package/src/Viewer/renderers/textList.jsx +30 -0
  505. package/src/Viewer/renderers/triggerSet.jsx +52 -0
  506. package/src/Viewer/renderers/updateValue.jsx +30 -0
  507. package/src/Viewer/renderers/utils/css.js +13 -0
  508. package/src/Viewer/renderers/utils/graph.js +159 -0
  509. package/src/Viewer/renderers/utils/offGraphIndicators.js +91 -0
  510. package/src/Viewer/renderers/vector.jsx +678 -0
  511. package/src/Viewer/renderers/video.jsx +494 -0
  512. package/src/Viewer/useDoenetRenderer.jsx +128 -0
  513. package/src/main.jsx +16 -0
  514. package/src/media/fonts/files/open-sans-v18-latin-700.woff +0 -0
  515. package/src/media/fonts/files/open-sans-v18-latin-700.woff2 +0 -0
  516. package/src/media/fonts/files/open-sans-v18-latin-700italic.woff +0 -0
  517. package/src/media/fonts/files/open-sans-v18-latin-700italic.woff2 +0 -0
  518. package/src/media/fonts/files/open-sans-v18-latin-italic.woff +0 -0
  519. package/src/media/fonts/files/open-sans-v18-latin-italic.woff2 +0 -0
  520. package/src/media/fonts/files/open-sans-v18-latin-light-italic.woff +0 -0
  521. package/src/media/fonts/files/open-sans-v18-latin-light-italic.woff2 +0 -0
  522. package/src/media/fonts/files/open-sans-v18-latin-light.woff +0 -0
  523. package/src/media/fonts/files/open-sans-v18-latin-light.woff2 +0 -0
  524. package/src/media/fonts/files/open-sans-v18-latin-regular.woff +0 -0
  525. package/src/media/fonts/files/open-sans-v18-latin-regular.woff2 +0 -0
  526. package/src/test/testCode.doenet +26 -0
  527. package/src/test/testViewer.jsx +158 -0
  528. package/src/uiComponents/ActionButton.jsx +157 -0
  529. package/src/uiComponents/ActionButtonGroup.jsx +93 -0
  530. package/src/uiComponents/Button.jsx +160 -0
  531. package/src/uiComponents/ButtonGroup.jsx +56 -0
  532. package/src/uiComponents/ToggleButton.jsx +194 -0
  533. package/src/uiComponents/ToggleButtonGroup.jsx +77 -0
  534. package/src/utils/activityUtils.js +713 -0
  535. package/src/utils/array.js +17 -0
  536. package/src/utils/cid.js +34 -0
  537. package/src/utils/componentInfoObjects.js +89 -0
  538. package/src/utils/deepFunctions.js +165 -0
  539. package/src/utils/enumeration.js +226 -0
  540. package/src/utils/math.js +624 -0
  541. package/src/utils/naming.js +44 -0
  542. package/src/utils/retrieveTextFile.js +156 -0
  543. package/src/utils/returnAllPossibleVariants.js +81 -0
  544. package/src/utils/sequence.js +715 -0
  545. package/src/utils/serialize.js +29 -0
  546. package/src/utils/serializedStateProcessing.js +2587 -0
  547. package/src/utils/subset-of-reals.js +783 -0
  548. package/src/utils/url.js +19 -0
  549. package/vite.config.js +14 -0
@@ -0,0 +1,1461 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import { retrieveTextFileForCid } from "../Core/utils/retrieveTextFile";
3
+ import PageViewer, { scrollableContainerAtom } from "./PageViewer";
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
6
+ import axios from "axios";
7
+ import { get as idb_get, set as idb_set } from "idb-keyval";
8
+ import { cidFromText } from "../Core/utils/cid";
9
+ import { nanoid } from "nanoid";
10
+ import {
11
+ calculateOrderAndVariants,
12
+ parseActivityDefinition,
13
+ } from "../utils/activityUtils";
14
+ import VisibilitySensor from "react-visibility-sensor-v2";
15
+ import { useRecoilState } from "recoil";
16
+ import Button from "../uiComponents/Button";
17
+ import ButtonGroup from "../uiComponents/ButtonGroup";
18
+ import ActionButton from "../uiComponents/ActionButton";
19
+ import { cesc } from "../utils/url";
20
+
21
+ const sendAlert = (msg, type) => console.log(msg);
22
+
23
+ export default function ActivityViewer(props) {
24
+ const [errMsg, setErrMsg] = useState(null);
25
+
26
+ const [
27
+ {
28
+ cidFromProps,
29
+ activityDefinitionFromProps,
30
+ attemptNumber,
31
+ requestedVariantIndex,
32
+ },
33
+ setInfoFromProps,
34
+ ] = useState({
35
+ cidFromProps: null,
36
+ activityDefinitionFromProps: null,
37
+ attemptNumber: null,
38
+ requestedVariantIndex: null,
39
+ });
40
+
41
+ const attemptNumberRef = useRef(null);
42
+ attemptNumberRef.current = attemptNumber;
43
+
44
+ const [cid, setCid] = useState(null);
45
+ const cidRef = useRef(null);
46
+ cidRef.current = cid;
47
+
48
+ const activityDefinitionDoenetML = useRef(null);
49
+
50
+ const [activityDefinition, setActivityDefinition] = useState(null);
51
+
52
+ const [variantIndex, setVariantIndex] = useState(null);
53
+ const variantIndexRef = useRef(null);
54
+ variantIndexRef.current = variantIndex;
55
+
56
+ const [stage, setStage] = useState("initial");
57
+ const stageRef = useRef(null);
58
+ stageRef.current = stage;
59
+
60
+ const settingUp = useRef(true);
61
+
62
+ const [activityContentChanged, setActivityContentChanged] = useState(false);
63
+
64
+ const [order, setOrder] = useState(null);
65
+
66
+ const [flags, setFlags] = useState(props.flags);
67
+
68
+ const [currentPage, setCurrentPage] = useState(0);
69
+ const currentPageRef = useRef(currentPage); // so that event listener can get new current page
70
+ currentPageRef.current = currentPage; // so updates on every refresh
71
+
72
+ const [activityAttemptNumberSetUp, setActivityAttemptNumberSetUp] =
73
+ useState(0);
74
+
75
+ const [nPages, setNPages] = useState(0);
76
+
77
+ const [variantsByPage, setVariantsByPage] = useState(null);
78
+ const [itemWeights, setItemWeights] = useState([]);
79
+ const previousComponentTypeCountsByPage = useRef([]);
80
+
81
+ const serverSaveId = useRef(null);
82
+
83
+ const activityStateToBeSavedToDatabase = useRef(null);
84
+ const changesToBeSaved = useRef(false);
85
+
86
+ const saveStateToDBTimerId = useRef(null);
87
+ const [scrollableContainer, setScrollableContainer] = useRecoilState(
88
+ scrollableContainerAtom,
89
+ );
90
+
91
+ const activityInfo = useRef(null);
92
+ const activityInfoString = useRef(null);
93
+ const pageAtPreviousSave = useRef(null);
94
+ const pageAtPreviousSaveToDatabase = useRef(null);
95
+
96
+ const [pageInfo, setPageInfo] = useState({
97
+ pageIsVisible: [],
98
+ pageIsActive: [],
99
+ pageCoreWorker: [],
100
+ waitingForPagesCore: null,
101
+ });
102
+
103
+ const [renderedPages, setRenderedPages] = useState([]);
104
+ const allPagesRendered = useRef(false);
105
+
106
+ const nodeRef = useRef(null);
107
+ const ignoreNextScroll = useRef(false);
108
+ const stillNeedToScrollTo = useRef(null);
109
+
110
+ let location = props.location || {};
111
+ let hash = location.hash;
112
+ const previousLocations = useRef({});
113
+ const currentLocationKey = useRef(null);
114
+ const viewerWasUnmounted = useRef(false);
115
+
116
+ const [finishAssessmentMessageOpen, setFinishAssessmentMessageOpen] =
117
+ useState(false);
118
+ const [processingSubmitAll, setProcessingSubmitAll] = useState(false);
119
+
120
+ const updateActivityStatusCallback = props.updateActivityStatusCallback;
121
+
122
+ let navigate = props.navigate;
123
+
124
+ useEffect(() => {
125
+ return () => {
126
+ saveState({ overrideThrottle: true });
127
+ viewerWasUnmounted.current = true;
128
+ };
129
+ }, []);
130
+
131
+ useEffect(() => {
132
+ updateActivityStatusCallback?.({
133
+ itemWeights,
134
+ currentPage,
135
+ activityAttemptNumberSetUp,
136
+ });
137
+ }, [
138
+ updateActivityStatusCallback,
139
+ itemWeights,
140
+ currentPage,
141
+ activityAttemptNumberSetUp,
142
+ ]);
143
+
144
+ useEffect(() => {
145
+ let newFlags = { ...props.flags };
146
+ if (props.userId) {
147
+ newFlags.allowLocalState = false;
148
+ newFlags.allowSaveState = false;
149
+ } else if (newFlags.allowSaveState) {
150
+ // allowSaveState implies allowLoadState
151
+ newFlags.allowLoadState = true;
152
+ }
153
+
154
+ setFlags(newFlags);
155
+ }, [props.userId, props.flags]);
156
+
157
+ useEffect(() => {
158
+ window.returnActivityData = function () {
159
+ return {
160
+ activityDefinition,
161
+ requestedVariantIndex,
162
+ variantIndex,
163
+ cid,
164
+ order,
165
+ currentPage,
166
+ nPages,
167
+ variantsByPage,
168
+ itemWeights,
169
+ };
170
+ };
171
+ }, [
172
+ activityDefinition,
173
+ requestedVariantIndex,
174
+ variantIndex,
175
+ cid,
176
+ order,
177
+ currentPage,
178
+ nPages,
179
+ variantsByPage,
180
+ itemWeights,
181
+ ]);
182
+
183
+ useEffect(() => {
184
+ // for non-paginated activity
185
+ // set the current page to be the page at the top of the screen
186
+ // will be used to set the url hash
187
+
188
+ if (nodeRef.current) {
189
+ let newScrollableContainer =
190
+ nodeRef.current.parentNode.id === "mainPanel"
191
+ ? nodeRef.current.parentNode
192
+ : window;
193
+
194
+ setScrollableContainer(newScrollableContainer);
195
+
196
+ if (!props.paginate && nPages > 1) {
197
+ newScrollableContainer.addEventListener("scroll", (event) => {
198
+ // find page that is at the top
199
+
200
+ if (ignoreNextScroll.current) {
201
+ ignoreNextScroll.current = false;
202
+ } else {
203
+ let topPage;
204
+ for (let ind = 0; ind < nPages - 1; ind++) {
205
+ let thePage = document.getElementById(`page${ind + 1}`);
206
+ if (thePage) {
207
+ let { bottom } = thePage.getBoundingClientRect();
208
+ if (bottom < 50) {
209
+ topPage = ind + 2;
210
+ } else if (!topPage) {
211
+ topPage = 1;
212
+ }
213
+ }
214
+ }
215
+
216
+ if (topPage && topPage !== currentPageRef.current) {
217
+ setCurrentPage(topPage);
218
+ }
219
+ }
220
+ });
221
+ }
222
+ }
223
+ }, [nodeRef.current, nPages]);
224
+
225
+ useEffect(() => {
226
+ props.pageChangedCallback?.(currentPage);
227
+ }, [currentPage]);
228
+
229
+ useEffect(() => {
230
+ if (hash && nPages) {
231
+ let match = hash.match(/^#page(\d+)/);
232
+ if (match) {
233
+ let newPage = Math.max(1, Math.min(nPages, match[1]));
234
+ if (newPage !== currentPage) {
235
+ setCurrentPage(newPage);
236
+ }
237
+ }
238
+ }
239
+ }, [hash, nPages]);
240
+
241
+ useEffect(() => {
242
+ if (currentPage > 0 && nPages > 1) {
243
+ let hashPage = Number(hash?.match(/^#page(\d+)/)?.[1]);
244
+ if (hashPage !== currentPage) {
245
+ let pageAnchor = `#page${currentPage}`;
246
+ // if we have moved to a page that does not correspond to the hash
247
+ // modify the hash to match the page.
248
+ let navigateAttrs = { replace: true };
249
+ if (!props.paginate) {
250
+ // If not paginated, then do not scroll to the top of the page,
251
+ // as the page change could be triggered by scrolling
252
+
253
+ navigateAttrs.state = { doNotScroll: true };
254
+ }
255
+ navigate?.(location.search + pageAnchor, navigateAttrs);
256
+ }
257
+ if (stillNeedToScrollTo.current) {
258
+ document.getElementById(stillNeedToScrollTo.current)?.scrollIntoView();
259
+ stillNeedToScrollTo.current = null;
260
+ }
261
+ }
262
+ }, [currentPage, nPages]);
263
+
264
+ useEffect(() => {
265
+ if (
266
+ allPagesRendered.current &&
267
+ !props.paginate &&
268
+ hash?.match(/^#page(\d+)$/)
269
+ ) {
270
+ ignoreNextScroll.current = true;
271
+ document.getElementById(hash.slice(1))?.scrollIntoView();
272
+ }
273
+ }, [allPagesRendered.current]);
274
+
275
+ useEffect(() => {
276
+ // Keep track of scroll position when clicked on a link
277
+ // If navigate back to that location (i.e., hit back button)
278
+ // then scroll back to the location when clicked
279
+
280
+ // console.log({
281
+ // currentLocationKey: currentLocationKey.current,
282
+ // newLocationKey: location.key,
283
+ // scrollPositionFromLink: location.state?.previousScrollPosition,
284
+ // newScrollPosition: previousLocations.current[location.key]?.lastScrollPosition
285
+ // })
286
+
287
+ let foundNewInPrevious = false;
288
+
289
+ if (currentLocationKey.current !== location.key) {
290
+ if (
291
+ location.state?.previousScrollPosition !== undefined &&
292
+ currentLocationKey.current
293
+ ) {
294
+ previousLocations.current[
295
+ currentLocationKey.current
296
+ ].lastScrollPosition = location.state.previousScrollPosition;
297
+ }
298
+
299
+ if (previousLocations.current[location.key]) {
300
+ foundNewInPrevious = true;
301
+
302
+ if (
303
+ previousLocations.current[location.key]?.lastScrollPosition !==
304
+ undefined
305
+ ) {
306
+ scrollableContainer.scroll({
307
+ top: previousLocations.current[location.key].lastScrollPosition,
308
+ });
309
+ }
310
+ }
311
+
312
+ previousLocations.current[location.key] = { ...location };
313
+ currentLocationKey.current = location.key;
314
+ }
315
+
316
+ stillNeedToScrollTo.current = null;
317
+
318
+ // since the <Link> from react router doesn't seem to scroll into hashes
319
+ // always scroll to the hash the first time we get a location from a <Link>
320
+ if (
321
+ hash &&
322
+ !location.state?.doNotScroll &&
323
+ (location.key === "default" || !foundNewInPrevious)
324
+ ) {
325
+ let scrollTo = hash.slice(1);
326
+ if (props.paginate && hash.match(/^#page(\d+)$/)) {
327
+ // if paginate, want to scroll to top of activity so can still see page controls
328
+ scrollTo = `${activityPrefix}top`;
329
+ }
330
+ if (
331
+ props.paginate &&
332
+ Number(hash.match(/^#page(\d+)/)?.[1]) !== currentPage
333
+ ) {
334
+ stillNeedToScrollTo.current = scrollTo;
335
+ } else {
336
+ document.getElementById(scrollTo)?.scrollIntoView();
337
+ }
338
+ }
339
+ }, [location]);
340
+
341
+ function resetActivity({ changedOnDevice, newCid, newAttemptNumber }) {
342
+ console.log("resetActivity", changedOnDevice, newCid, newAttemptNumber);
343
+
344
+ if (newAttemptNumber !== attemptNumber) {
345
+ if (props.updateAttemptNumber) {
346
+ sendAlert(
347
+ `Reverted activity as attempt number changed on other device`,
348
+ "info",
349
+ );
350
+ props.updateAttemptNumber(newAttemptNumber);
351
+ } else {
352
+ // what do we do in this case?
353
+ if (props.setIsInErrorState) {
354
+ props.setIsInErrorState(true);
355
+ }
356
+ setErrMsg(
357
+ "how to reset attempt number when not given updateAttemptNumber function?",
358
+ );
359
+ }
360
+ } else if (newCid !== cid) {
361
+ if (props.setIsInErrorState) {
362
+ props.setIsInErrorState(true);
363
+ }
364
+ setErrMsg("Content changed unexpectedly!");
365
+ } else {
366
+ // since, at least for now, only activity state is page number,
367
+ // we ignore the change
368
+ }
369
+
370
+ // sendAlert(`Reverted page to state saved on device ${changedOnDevice}`, "error");
371
+
372
+ // if (cid && newCid !== cid) {
373
+ // if (props.setIsInErrorState) {
374
+ // props.setIsInErrorState(true)
375
+ // }
376
+ // console.log(`cid: ${cid}, newCid ${newCid}`)
377
+ // setErrMsg("Have not implemented handling change in activity content from other device. Please reload page");
378
+ // } else if (newAttemptNumber !== attemptNumber) {
379
+ // if (props.setIsInErrorState) {
380
+ // props.setIsInErrorState(true)
381
+ // }
382
+ // setErrMsg("Have not implemented handling creating new attempt from other device. Please reload page");
383
+ // } else {
384
+ // // What here?
385
+ // }
386
+ }
387
+
388
+ function calculateCidDefinition() {
389
+ if (activityDefinitionFromProps) {
390
+ if (cidFromProps) {
391
+ // check to see if activityDefinition matches cid
392
+ cidFromText(JSON.stringify(activityDefinitionFromProps)).then(
393
+ (calcCid) => {
394
+ if (calcCid === cidFromProps) {
395
+ setCid(cidFromProps);
396
+ activityDefinitionDoenetML.current = activityDefinitionFromProps;
397
+ let result = parseActivityDefinition(activityDefinitionFromProps);
398
+ if (result.success) {
399
+ setActivityDefinition(result.activityJSON);
400
+ setStage("continue");
401
+ } else {
402
+ if (props.setIsInErrorState) {
403
+ props.setIsInErrorState(true);
404
+ }
405
+ setErrMsg(result.message);
406
+ }
407
+ } else {
408
+ if (props.setIsInErrorState) {
409
+ props.setIsInErrorState(true);
410
+ }
411
+ setErrMsg(
412
+ `activity definition did not match specified cid: ${cidFromProps}`,
413
+ );
414
+ }
415
+ },
416
+ );
417
+ } else {
418
+ // if have activityDefinition and no cid, then calculate cid
419
+ cidFromText(JSON.stringify(activityDefinitionFromProps)).then((cid) => {
420
+ setCid(cid);
421
+ activityDefinitionDoenetML.current = activityDefinitionFromProps;
422
+ let result = parseActivityDefinition(activityDefinitionFromProps);
423
+ if (result.success) {
424
+ setActivityDefinition(result.activityJSON);
425
+ setStage("continue");
426
+ } else {
427
+ if (props.setIsInErrorState) {
428
+ props.setIsInErrorState(true);
429
+ }
430
+ setErrMsg(result.message);
431
+ }
432
+ });
433
+ }
434
+ } else {
435
+ // if don't have activityDefinition, then retrieve activityDefinition from cid
436
+
437
+ retrieveTextFileForCid(cidFromProps, "doenet")
438
+ .then((retrievedActivityDefinition) => {
439
+ setCid(cidFromProps);
440
+ activityDefinitionDoenetML.current = retrievedActivityDefinition;
441
+ let result = parseActivityDefinition(retrievedActivityDefinition);
442
+ if (result.success) {
443
+ setActivityDefinition(result.activityJSON);
444
+ setStage("continue");
445
+ } else {
446
+ if (props.setIsInErrorState) {
447
+ props.setIsInErrorState(true);
448
+ }
449
+ setErrMsg(result.message);
450
+ }
451
+ })
452
+ .catch((e) => {
453
+ if (props.setIsInErrorState) {
454
+ props.setIsInErrorState(true);
455
+ }
456
+ setErrMsg(`activity definition not found for cid: ${cidFromProps}`);
457
+ });
458
+ }
459
+ }
460
+
461
+ async function loadState() {
462
+ let loadedState = false;
463
+ let newItemWeights;
464
+ let newVariantIndex;
465
+ let loadedFromInitialState = false;
466
+
467
+ if (flags.allowLocalState) {
468
+ let localInfo;
469
+
470
+ try {
471
+ localInfo = await idb_get(
472
+ `${props.activityId}|${attemptNumber}|${cid}`,
473
+ );
474
+ } catch (e) {
475
+ // ignore error
476
+ }
477
+
478
+ if (localInfo) {
479
+ if (flags.allowSaveState) {
480
+ // attempt to save local info to database,
481
+ // reseting data to that from database if it has changed since last save
482
+
483
+ let result = await saveLoadedLocalStateToDatabase(localInfo);
484
+
485
+ if (result.changedOnDevice) {
486
+ if (Number(result.newAttemptNumber) !== attemptNumber) {
487
+ resetActivity({
488
+ changedOnDevice: result.changedOnDevice,
489
+ newCid: result.newCid,
490
+ newAttemptNumber: Number(result.newAttemptNumber),
491
+ });
492
+ return;
493
+ } else if (result.newCid !== cid) {
494
+ // if cid changes for the same attempt number, then something went wrong
495
+ if (props.setIsInErrorState) {
496
+ props.setIsInErrorState(true);
497
+ }
498
+ setErrMsg(`content changed unexpectedly!`);
499
+ }
500
+
501
+ // if just the localInfo changed, use that instead
502
+ localInfo = result.newLocalInfo;
503
+
504
+ // no need to send sendAlert, as state is just page number
505
+ }
506
+ }
507
+
508
+ serverSaveId.current = localInfo.saveId;
509
+
510
+ // activityState is just currentPage
511
+ // if hash doesn't already specify a page, set page from activityState
512
+ if (!hash?.match(/^#page(\d+)/)) {
513
+ setCurrentPage(localInfo.activityState.currentPage);
514
+ }
515
+
516
+ // activityInfo is orderWithCids, variantsByPage, itemWeights, and numberOfVariants
517
+ let newActivityInfo = localInfo.activityInfo;
518
+ newVariantIndex = localInfo.variantIndex;
519
+ setVariantIndex(newVariantIndex);
520
+ setNPages(newActivityInfo.orderWithCids.length);
521
+ setOrder(newActivityInfo.orderWithCids);
522
+ setVariantsByPage(newActivityInfo.variantsByPage);
523
+ setItemWeights(newActivityInfo.itemWeights);
524
+ newItemWeights = newActivityInfo.itemWeights;
525
+ previousComponentTypeCountsByPage.current =
526
+ newActivityInfo.previousComponentTypeCounts || [];
527
+
528
+ activityInfo.current = newActivityInfo;
529
+ activityInfoString.current = JSON.stringify(activityInfo.current);
530
+
531
+ loadedState = true;
532
+ }
533
+ }
534
+
535
+ if (!loadedState) {
536
+ // if didn't load core state from local storage, try to load from database
537
+
538
+ // even if allowLoadState is false,
539
+ // still call loadActivityState, in which case it will only retrieve the initial activity state
540
+
541
+ const payload = {
542
+ params: {
543
+ cid,
544
+ attemptNumber,
545
+ activityId: props.activityId,
546
+ userId: props.userId,
547
+ allowLoadState: flags.allowLoadState,
548
+ },
549
+ };
550
+
551
+ let resp;
552
+
553
+ if (props.apiURLs?.loadActivityState) {
554
+ try {
555
+ resp = await axios.get(props.apiURLs.loadActivityState, payload);
556
+
557
+ if (!resp.data.success) {
558
+ if (flags.allowLoadState) {
559
+ if (props.setIsInErrorState) {
560
+ props.setIsInErrorState(true);
561
+ }
562
+ setErrMsg(`Error loading activity state: ${resp.data.message}`);
563
+ return;
564
+ } else {
565
+ // ignore this error if didn't allow loading of page state
566
+ }
567
+ }
568
+ } catch (e) {
569
+ if (flags.allowLoadState) {
570
+ if (props.setIsInErrorState) {
571
+ props.setIsInErrorState(true);
572
+ }
573
+ setErrMsg(`Error loading activity state: ${e.message}`);
574
+ return;
575
+ } else {
576
+ // ignore this error if didn't allow loading of page state
577
+ }
578
+ }
579
+ }
580
+
581
+ if (resp?.data.loadedState) {
582
+ let newActivityInfo = JSON.parse(resp.data.activityInfo);
583
+ let activityState = JSON.parse(resp.data.activityState);
584
+
585
+ // activityState is just currentPage
586
+ // if hash doesn't already specify a page, set page from activityState
587
+ if (!hash?.match(/^#page(\d+)/)) {
588
+ setCurrentPage(activityState.currentPage);
589
+ }
590
+
591
+ // activityInfo is orderWithCids, variantsByPage, itemWeights, and numberOfVariants
592
+ newVariantIndex = resp.data.variantIndex;
593
+ setVariantIndex(newVariantIndex);
594
+ setNPages(newActivityInfo.orderWithCids.length);
595
+ setOrder(newActivityInfo.orderWithCids);
596
+ setVariantsByPage(newActivityInfo.variantsByPage);
597
+ setItemWeights(newActivityInfo.itemWeights);
598
+ newItemWeights = newActivityInfo.itemWeights;
599
+ previousComponentTypeCountsByPage.current =
600
+ newActivityInfo.previousComponentTypeCounts || [];
601
+
602
+ activityInfo.current = newActivityInfo;
603
+ activityInfoString.current = JSON.stringify(activityInfo.current);
604
+ } else {
605
+ // get initial state and info
606
+
607
+ loadedFromInitialState = true;
608
+
609
+ // start at page 1
610
+ // if hash doesn't already specify a page, set page to 1
611
+ if (!hash?.match(/^#page(\d+)/)) {
612
+ setCurrentPage(1);
613
+ }
614
+
615
+ let results;
616
+ results = await calculateOrderAndVariants({
617
+ activityDefinition,
618
+ requestedVariantIndex,
619
+ });
620
+ if (!results.success) {
621
+ if (props.setIsInErrorState) {
622
+ props.setIsInErrorState(true);
623
+ }
624
+ setErrMsg(`Error initializing activity state: ${results.message}`);
625
+ return;
626
+ }
627
+
628
+ newVariantIndex = results.variantIndex;
629
+ setVariantIndex(newVariantIndex);
630
+ setNPages(results.order.length);
631
+ setOrder(results.order);
632
+ setVariantsByPage(results.variantsByPage);
633
+ setItemWeights(results.itemWeights);
634
+ newItemWeights = results.itemWeights;
635
+ previousComponentTypeCountsByPage.current =
636
+ results.previousComponentTypeCounts || [];
637
+
638
+ activityInfo.current = results.activityInfo;
639
+ activityInfoString.current = JSON.stringify(activityInfo.current);
640
+ }
641
+ }
642
+
643
+ return { newItemWeights, newVariantIndex, loadedFromInitialState };
644
+ }
645
+
646
+ async function saveLoadedLocalStateToDatabase(localInfo) {
647
+ if (!flags.allowSaveState || !props.apiURLs?.saveActivityState) {
648
+ return;
649
+ }
650
+
651
+ let serverSaveId = await idb_get(
652
+ `${props.activityId}|${attemptNumber}|${cid}|ServerSaveId`,
653
+ );
654
+
655
+ let activityStateToBeSavedToDatabase = {
656
+ cid,
657
+ activityInfo: JSON.stringify(localInfo.activityInfo),
658
+ activityState: JSON.stringify(localInfo.activityState),
659
+ variantIndex: localInfo.variantIndex,
660
+ attemptNumber,
661
+ activityId: props.activityId,
662
+ saveId: localInfo.saveId,
663
+ serverSaveId,
664
+ updateDataOnContentChange: props.updateDataOnContentChange,
665
+ };
666
+
667
+ let resp;
668
+
669
+ try {
670
+ console.log(
671
+ "first one saveActivityState activityStateToBeSavedToDatabase",
672
+ activityStateToBeSavedToDatabase,
673
+ );
674
+ resp = await axios.post(
675
+ props.apiURLs.saveActivityState,
676
+ activityStateToBeSavedToDatabase,
677
+ );
678
+ } catch (e) {
679
+ // since this is initial load, don't show error message
680
+ return { localInfo, cid, attemptNumber };
681
+ }
682
+
683
+ if (resp.data.cidChanged === true) {
684
+ props?.cidChangedCallback();
685
+ }
686
+
687
+ let data = resp.data;
688
+
689
+ if (!data.success) {
690
+ // since this is initial load, don't show error message
691
+ return { localInfo, cid, attemptNumber };
692
+ }
693
+
694
+ await idb_set(
695
+ `${props.activityId}|${attemptNumber}|${cid}|ServerSaveId`,
696
+ data.saveId,
697
+ );
698
+
699
+ if (data.stateOverwritten) {
700
+ let newLocalInfo = {
701
+ activityState: JSON.parse(data.activityState),
702
+ activityInfo: JSON.parse(data.activityInfo),
703
+ saveId: data.saveId,
704
+ variantIndex: data.variantIndex,
705
+ };
706
+
707
+ await idb_set(
708
+ `${props.activityId}|${data.attemptNumber}|${data.cid}`,
709
+ newLocalInfo,
710
+ );
711
+
712
+ return {
713
+ changedOnDevice: data.device,
714
+ newLocalInfo,
715
+ newCid: data.cid,
716
+ newAttemptNumber: data.attemptNumber,
717
+ };
718
+ }
719
+
720
+ return { localInfo, cid, attemptNumber };
721
+ }
722
+
723
+ async function saveState({
724
+ overrideThrottle = false,
725
+ overrideStage = false,
726
+ } = {}) {
727
+ if (!flags.allowSaveState && !flags.allowLocalState) {
728
+ return;
729
+ }
730
+
731
+ if (
732
+ (stageRef.current !== "saving" && !overrideStage) ||
733
+ (!overrideThrottle &&
734
+ currentPageRef.current === pageAtPreviousSave.current) ||
735
+ (overrideThrottle &&
736
+ currentPageRef.current === pageAtPreviousSaveToDatabase.current)
737
+ ) {
738
+ // haven't gotten a save event from page or no change to be saved
739
+ return;
740
+ }
741
+
742
+ pageAtPreviousSave.current = currentPageRef.current;
743
+
744
+ let saveId = nanoid();
745
+
746
+ if (flags.allowLocalState) {
747
+ await idb_set(
748
+ `${props.activityId}|${attemptNumberRef.current}|${cidRef.current}`,
749
+ {
750
+ activityInfo: activityInfo.current,
751
+ activityState: { currentPage: currentPageRef.current },
752
+ saveId,
753
+ variantIndex: variantIndexRef.current,
754
+ },
755
+ );
756
+ }
757
+
758
+ if (!flags.allowSaveState) {
759
+ return;
760
+ }
761
+
762
+ activityStateToBeSavedToDatabase.current = {
763
+ cid: cidRef.current,
764
+ activityInfo: activityInfoString.current,
765
+ activityState: JSON.stringify({ currentPage: currentPageRef.current }),
766
+ variantIndex: variantIndexRef.current,
767
+ attemptNumber: attemptNumberRef.current,
768
+ activityId: props.activityId,
769
+ saveId,
770
+ serverSaveId: serverSaveId.current,
771
+ updateDataOnContentChange: props.updateDataOnContentChange,
772
+ };
773
+
774
+ // mark presence of changes
775
+ // so that next call to saveChangesToDatabase will save changes
776
+ changesToBeSaved.current = true;
777
+
778
+ // if not currently in throttle, save changes to database
779
+ await saveChangesToDatabase(overrideThrottle);
780
+ }
781
+
782
+ async function saveChangesToDatabase(overrideThrottle) {
783
+ // throttle save to database at 60 seconds
784
+
785
+ if (
786
+ !changesToBeSaved.current ||
787
+ !flags.allowSaveState ||
788
+ !props.apiURLs?.saveActivityState
789
+ ) {
790
+ return;
791
+ }
792
+
793
+ // get the up-to-date value here without a refresh!!
794
+
795
+ // just use the ref
796
+
797
+ let oldTimeoutId = saveStateToDBTimerId.current;
798
+
799
+ if (oldTimeoutId !== null) {
800
+ if (overrideThrottle) {
801
+ clearTimeout(oldTimeoutId);
802
+ } else {
803
+ return;
804
+ }
805
+ }
806
+
807
+ changesToBeSaved.current = false;
808
+ pageAtPreviousSaveToDatabase.current = currentPageRef.current;
809
+
810
+ // check for changes again after 60 seconds
811
+ saveStateToDBTimerId.current = setTimeout(() => {
812
+ saveStateToDBTimerId.current = null;
813
+ saveChangesToDatabase();
814
+ }, 60000);
815
+
816
+ // TODO: find out how to test if not online
817
+ // and send this sendAlert if not online:
818
+
819
+ let resp;
820
+
821
+ try {
822
+ console.log(
823
+ "activity state params",
824
+ activityStateToBeSavedToDatabase.current,
825
+ );
826
+ resp = await axios.post(
827
+ props.apiURLs.saveActivityState,
828
+ activityStateToBeSavedToDatabase.current,
829
+ );
830
+ } catch (e) {
831
+ console.log(
832
+ `sending sendAlert: Error synchronizing data. Changes not saved to the server.`,
833
+ );
834
+ sendAlert(
835
+ "Error synchronizing data. Changes not saved to the server.",
836
+ "error",
837
+ );
838
+ return;
839
+ }
840
+
841
+ console.log("result from saving activity to database:", resp.data);
842
+
843
+ if (resp.status === null) {
844
+ console.log(
845
+ `sending sendAlert: Error synchronizing data. Changes not saved to the server. Are you connected to the internet?`,
846
+ );
847
+ sendAlert(
848
+ "Error synchronizing data. Changes not saved to the server. Are you connected to the internet?",
849
+ "error",
850
+ );
851
+ return;
852
+ }
853
+
854
+ let data = resp.data;
855
+
856
+ if (!data.success) {
857
+ console.log(`sending sendAlert: ${data.message}`);
858
+ sendAlert(data.message, "error");
859
+ return;
860
+ }
861
+
862
+ serverSaveId.current = data.saveId;
863
+
864
+ if (flags.allowLocalState) {
865
+ await idb_set(
866
+ `${props.activityId}|${attemptNumberRef.current}|${cidRef.current}|ServerSaveId`,
867
+ data.saveId,
868
+ );
869
+ }
870
+
871
+ if (data.stateOverwritten) {
872
+ // if a new attempt number was generated,
873
+ // then we reset the activity to the new state
874
+ if (attemptNumberRef.current !== Number(data.attemptNumber)) {
875
+ resetActivity({
876
+ changedOnDevice: data.device,
877
+ newCid: data.cid,
878
+ newAttemptNumber: Number(data.attemptNumber),
879
+ });
880
+ } else if (cidRef.current !== data.cid) {
881
+ // if the cid changed without the attemptNumber changing, something went wrong
882
+ if (props.setIsInErrorState) {
883
+ props.setIsInErrorState(true);
884
+ }
885
+ setErrMsg("Content changed unexpectedly!");
886
+ return;
887
+ }
888
+
889
+ // if only the activity state changed,
890
+ // just ignore it as it is only changing the page and we can leave it at the old page
891
+ }
892
+
893
+ // TODO: send message so that UI can show changes have been synchronized
894
+ }
895
+
896
+ async function initializeUserAssignmentTables(newItemWeights) {
897
+ //Initialize user_assignment tables
898
+ if (flags.allowSaveSubmissions && props.apiURLs?.initAssignmentAttempt) {
899
+ try {
900
+ let resp = await axios.post(props.apiURLs.initAssignmentAttempt, {
901
+ activityId: props.activityId,
902
+ weights: newItemWeights,
903
+ attemptNumber,
904
+ });
905
+
906
+ if (resp.status === null) {
907
+ sendAlert(
908
+ `Could not initialize assignment tables. Are you connected to the internet?`,
909
+ "error",
910
+ );
911
+ } else if (!resp.data.success) {
912
+ sendAlert(
913
+ `Could not initialize assignment tables: ${resp.data.message}.`,
914
+ "error",
915
+ );
916
+ }
917
+ } catch (e) {
918
+ sendAlert(
919
+ `Could not initialize assignment tables: ${e.message}.`,
920
+ "error",
921
+ );
922
+ }
923
+ }
924
+ }
925
+
926
+ async function receivedSaveFromPage() {
927
+ // activity state isn't saved until a first save from a page
928
+ if (stage !== "saving" && !settingUp.current) {
929
+ setStage("saving");
930
+ }
931
+
932
+ props.checkIfCidChanged?.(cid);
933
+
934
+ if (viewerWasUnmounted.current) {
935
+ await saveState({ overrideThrottle: true, overrideStage: true });
936
+ }
937
+ }
938
+
939
+ function clickNext() {
940
+ setCurrentPage((was) => Math.min(nPages, was + 1));
941
+
942
+ let event = {
943
+ verb: "interacted",
944
+ object: { objectType: "button", objectname: "next page button" },
945
+ result: { newPage: Math.min(nPages, currentPage + 1) },
946
+ context: { oldPage: currentPage },
947
+ };
948
+
949
+ recordEvent(event);
950
+ }
951
+
952
+ function clickPrevious() {
953
+ setCurrentPage((was) => Math.max(1, was - 1));
954
+
955
+ let event = {
956
+ verb: "interacted",
957
+ object: { objectType: "button", objectname: "previous page button" },
958
+ result: { newPage: Math.max(1, currentPage - 1) },
959
+ context: { oldPage: currentPage },
960
+ };
961
+
962
+ recordEvent(event);
963
+ }
964
+
965
+ function recordEvent(event) {
966
+ if (!flags.allowSaveEvents || !props.apiURLs?.recordEvent) {
967
+ return;
968
+ }
969
+
970
+ const payload = {
971
+ activityId: props.activityId,
972
+ activityCid: cid,
973
+ attemptNumber,
974
+ activityVariantIndex: variantIndex,
975
+ timestamp: new Date().toISOString().slice(0, 19).replace("T", " "),
976
+ version: "0.1.1",
977
+ verb: event.verb,
978
+ object: JSON.stringify(event.object),
979
+ result: JSON.stringify(event.result),
980
+ context: JSON.stringify(event.context),
981
+ };
982
+
983
+ axios
984
+ .post(props.apiURLs.recordEvent, payload)
985
+ .then((resp) => {
986
+ // console.log(">>>>Activity Viewer resp",resp.data)
987
+ })
988
+ .catch((e) => {
989
+ console.error(`Error saving event: ${e.message}`);
990
+ });
991
+ }
992
+
993
+ function onChangeVisibility(isVisible, pageInd) {
994
+ if (!props.paginate) {
995
+ setPageInfo((was) => {
996
+ let newObj = { ...was };
997
+ let newVisible = [...newObj.pageIsVisible];
998
+ newVisible[pageInd] = isVisible;
999
+ newObj.pageIsVisible = newVisible;
1000
+
1001
+ if (!isVisible && newObj.pageIsActive[pageInd]) {
1002
+ let newActive = [...newObj.pageIsActive];
1003
+ newActive[pageInd] = false;
1004
+ newObj.pageIsActive = newActive;
1005
+
1006
+ if (newObj.waitingForPagesCore === pageInd) {
1007
+ newObj.waitingForPagesCore = null;
1008
+ }
1009
+ }
1010
+
1011
+ return newObj;
1012
+ });
1013
+ }
1014
+ }
1015
+
1016
+ function coreCreatedCallback(pageInd, coreWorker) {
1017
+ setPageInfo((was) => {
1018
+ let newObj = { ...was };
1019
+ if (newObj.waitingForPagesCore === pageInd) {
1020
+ newObj.waitingForPagesCore = null;
1021
+ }
1022
+ let newPageCoreWorker = [...newObj.pageCoreWorker];
1023
+ newPageCoreWorker[pageInd] = coreWorker;
1024
+ newObj.pageCoreWorker = newPageCoreWorker;
1025
+
1026
+ return newObj;
1027
+ });
1028
+ }
1029
+
1030
+ function pageRenderedCallback(pageInd) {
1031
+ let newRenderedPages;
1032
+ setRenderedPages((was) => {
1033
+ newRenderedPages = [...was];
1034
+ newRenderedPages[pageInd] = true;
1035
+ return newRenderedPages;
1036
+ });
1037
+
1038
+ if (
1039
+ newRenderedPages?.length === nPages &&
1040
+ newRenderedPages.every((x) => x)
1041
+ ) {
1042
+ allPagesRendered.current = true;
1043
+ }
1044
+ }
1045
+
1046
+ async function submitAllAndFinishAssessment() {
1047
+ setProcessingSubmitAll(true);
1048
+
1049
+ let terminatePromises = [];
1050
+
1051
+ for (let coreWorker of pageInfo.pageCoreWorker) {
1052
+ if (coreWorker) {
1053
+ let actionId = nanoid();
1054
+ let resolveTerminatePromise;
1055
+
1056
+ terminatePromises.push(
1057
+ new Promise((resolve, reject) => {
1058
+ resolveTerminatePromise = resolve;
1059
+ }),
1060
+ );
1061
+
1062
+ coreWorker.onmessage = function (e) {
1063
+ if (
1064
+ e.data.messageType === "resolveAction" &&
1065
+ e.data.args.actionId === actionId
1066
+ ) {
1067
+ // posting terminate will make sure page state gets saved
1068
+ // (as navigating to another URL will not initiate a state save)
1069
+ coreWorker.postMessage({
1070
+ messageType: "terminate",
1071
+ });
1072
+ } else if (e.data.messageType === "terminated") {
1073
+ // resolve promise
1074
+ resolveTerminatePromise();
1075
+ }
1076
+ };
1077
+
1078
+ coreWorker.postMessage({
1079
+ messageType: "submitAllAnswers",
1080
+ args: { actionId },
1081
+ });
1082
+ }
1083
+ }
1084
+
1085
+ await Promise.all(terminatePromises);
1086
+
1087
+ await saveState({ overrideThrottle: true });
1088
+
1089
+ props.setActivityAsCompleted?.(itemWeights);
1090
+ }
1091
+
1092
+ if (errMsg !== null) {
1093
+ let errorIcon = (
1094
+ <span style={{ fontSize: "1em", color: "#C1292E" }}>
1095
+ <FontAwesomeIcon icon={faExclamationCircle} />
1096
+ </span>
1097
+ );
1098
+ return (
1099
+ <div style={{ fontSize: "1.3em", marginLeft: "20px", marginTop: "20px" }}>
1100
+ {errorIcon} {errMsg}
1101
+ </div>
1102
+ );
1103
+ }
1104
+
1105
+ if (pageInfo.waitingForPagesCore === null) {
1106
+ // check current page first
1107
+ if (currentPage) {
1108
+ for (let pageInd of [currentPage - 1, ...Array(nPages).keys()]) {
1109
+ let isVisible = pageInfo.pageIsVisible[pageInd];
1110
+ if (
1111
+ (isVisible || currentPage === pageInd + 1) &&
1112
+ !pageInfo.pageIsActive[pageInd]
1113
+ ) {
1114
+ // activate either the current page or an inactive page that is visible
1115
+
1116
+ // if we need to create core
1117
+ // then stop here to not create multiple cores at once
1118
+ let waitingAgain = !pageInfo.pageCoreWorker[pageInd];
1119
+
1120
+ setPageInfo((was) => {
1121
+ let newObj = { ...was };
1122
+ let newActive = [...newObj.pageIsActive];
1123
+ newActive[pageInd] = true;
1124
+ newObj.pageIsActive = newActive;
1125
+ if (!newObj.pageCoreWorker[pageInd]) {
1126
+ newObj.waitingForPagesCore = pageInd;
1127
+ }
1128
+ return newObj;
1129
+ });
1130
+
1131
+ if (waitingAgain) {
1132
+ break;
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ //If no attemptNumber prop then set to 1
1140
+ let propAttemptNumber = props.attemptNumber;
1141
+ if (propAttemptNumber === undefined) {
1142
+ propAttemptNumber = 1;
1143
+ }
1144
+
1145
+ // attemptNumber is used for requestedVariantIndex if not specified
1146
+ let adjustedRequestedVariantIndex = props.requestedVariantIndex;
1147
+ if (adjustedRequestedVariantIndex === undefined) {
1148
+ adjustedRequestedVariantIndex = propAttemptNumber;
1149
+ }
1150
+
1151
+ if (
1152
+ activityDefinitionFromProps !== props.activityDefinition ||
1153
+ cidFromProps !== props.cid ||
1154
+ propAttemptNumber !== attemptNumber ||
1155
+ requestedVariantIndex !== adjustedRequestedVariantIndex
1156
+ ) {
1157
+ settingUp.current = true;
1158
+
1159
+ setInfoFromProps({
1160
+ activityDefinitionFromProps: props.activityDefinition,
1161
+ cidFromProps: props.cid,
1162
+ attemptNumber: propAttemptNumber,
1163
+ requestedVariantIndex: adjustedRequestedVariantIndex,
1164
+ });
1165
+
1166
+ setStage("recalcParams");
1167
+ setActivityContentChanged(true);
1168
+ return null;
1169
+ }
1170
+
1171
+ if (stage === "wait") {
1172
+ return null;
1173
+ }
1174
+
1175
+ if (stage === "recalcParams") {
1176
+ setStage("wait");
1177
+ calculateCidDefinition();
1178
+ return null;
1179
+ }
1180
+
1181
+ // at this point, we have
1182
+ // attemptNumber, requestedVariantIndex, cid, activityDefinition
1183
+
1184
+ if (activityDefinition?.type?.toLowerCase() !== "activity") {
1185
+ if (props.setIsInErrorState) {
1186
+ props.setIsInErrorState(true);
1187
+ }
1188
+ setErrMsg("Invalid activity definition: type is not activity");
1189
+ return null;
1190
+ }
1191
+
1192
+ if (activityContentChanged) {
1193
+ setActivityContentChanged(false);
1194
+ setActivityAttemptNumberSetUp(0);
1195
+
1196
+ previousComponentTypeCountsByPage.current = [];
1197
+
1198
+ setStage("wait");
1199
+
1200
+ loadState().then(async (results) => {
1201
+ if (results) {
1202
+ if (results.loadedFromInitialState) {
1203
+ await initializeUserAssignmentTables(results.newItemWeights);
1204
+ }
1205
+ setStage("continue");
1206
+ setActivityAttemptNumberSetUp(attemptNumber);
1207
+ props.generatedVariantCallback?.({
1208
+ activityVariant: {
1209
+ variantIndex: results.newVariantIndex,
1210
+ numberOfVariants: activityInfo.current.numberOfVariants,
1211
+ },
1212
+ });
1213
+ }
1214
+ settingUp.current = false;
1215
+ });
1216
+
1217
+ return null;
1218
+ }
1219
+
1220
+ saveState();
1221
+
1222
+ let activityPrefix = "";
1223
+ if (props.idsIncludeActivityId) {
1224
+ activityPrefix = cesc(props.activityId);
1225
+ }
1226
+
1227
+ let title = <h1>{activityDefinition.title}</h1>;
1228
+
1229
+ let pages = [];
1230
+
1231
+ if (order && variantsByPage) {
1232
+ for (let [ind, page] of order.entries()) {
1233
+ let thisPageIsActive = false;
1234
+ if (props.paginate) {
1235
+ if (ind === currentPage - 1) {
1236
+ // the current page is always active
1237
+ thisPageIsActive = true;
1238
+ } else if (
1239
+ pageInfo.pageCoreWorker[currentPage - 1] &&
1240
+ ind === currentPage
1241
+ ) {
1242
+ // if the current page already has core created, activate next page
1243
+ thisPageIsActive = true;
1244
+ } else if (
1245
+ pageInfo.pageCoreWorker[currentPage - 1] &&
1246
+ (currentPage === nPages || pageInfo.pageCoreWorker[currentPage]) &&
1247
+ ind === currentPage - 2
1248
+ ) {
1249
+ // if current page and page after current page (if exists) already have current page
1250
+ // activate previous page
1251
+ thisPageIsActive = true;
1252
+ }
1253
+ } else {
1254
+ // pageIsActive is used only if not paginated
1255
+ thisPageIsActive = pageInfo.pageIsActive[ind];
1256
+ }
1257
+
1258
+ let prefixForIds = activityPrefix + (nPages > 1 ? `page${ind + 1}` : "");
1259
+
1260
+ let pageViewer = (
1261
+ <PageViewer
1262
+ userId={props.userId}
1263
+ activityId={props.activityId}
1264
+ activityCid={cid}
1265
+ cid={page.cid}
1266
+ doenetML={page.doenetML}
1267
+ pageNumber={(ind + 1).toString()}
1268
+ previousComponentTypeCounts={
1269
+ previousComponentTypeCountsByPage.current[ind]
1270
+ }
1271
+ pageIsActive={thisPageIsActive}
1272
+ pageIsCurrent={ind === currentPage - 1}
1273
+ itemNumber={ind + 1}
1274
+ attemptNumber={attemptNumber}
1275
+ forceDisable={props.forceDisable}
1276
+ forceShowCorrectness={props.forceShowCorrectness}
1277
+ forceShowSolution={props.forceShowSolution}
1278
+ forceUnsuppressCheckwork={props.forceUnsuppressCheckwork}
1279
+ generatedVariantCallback={props.generatedVariantCallback}
1280
+ flags={flags}
1281
+ activityVariantIndex={variantIndex}
1282
+ requestedVariantIndex={variantsByPage[ind]}
1283
+ updateCreditAchievedCallback={props.updateCreditAchievedCallback}
1284
+ setIsInErrorState={props.setIsInErrorState}
1285
+ updateAttemptNumber={props.updateAttemptNumber}
1286
+ saveStateCallback={receivedSaveFromPage}
1287
+ updateDataOnContentChange={props.updateDataOnContentChange}
1288
+ coreCreatedCallback={(coreWorker) =>
1289
+ coreCreatedCallback(ind, coreWorker)
1290
+ }
1291
+ renderersInitializedCallback={() => pageRenderedCallback(ind)}
1292
+ hideWhenNotCurrent={props.paginate}
1293
+ prefixForIds={prefixForIds}
1294
+ apiURLs={props.apiURLs}
1295
+ location={location}
1296
+ navigate={navigate}
1297
+ />
1298
+ );
1299
+
1300
+ if (!props.paginate) {
1301
+ pageViewer = (
1302
+ <VisibilitySensor
1303
+ partialVisibility={true}
1304
+ offset={{ top: -200, bottom: -200 }}
1305
+ requireContentsSize={false}
1306
+ onChange={(isVisible) => onChangeVisibility(isVisible, ind)}
1307
+ >
1308
+ <div>{pageViewer}</div>
1309
+ </VisibilitySensor>
1310
+ );
1311
+ }
1312
+
1313
+ pages.push(
1314
+ <div key={`page${ind + 1}`} id={`page${ind + 1}`}>
1315
+ {pageViewer}
1316
+ </div>,
1317
+ );
1318
+ }
1319
+ }
1320
+
1321
+ let pageControlsTop = null;
1322
+ let pageControlsBottom = null;
1323
+ if (props.paginate && nPages > 1) {
1324
+ pageControlsTop = (
1325
+ <div style={{ display: "flex", alignItems: "center", marginLeft: "5px" }}>
1326
+ <Button
1327
+ dataTest={"previous"}
1328
+ disabled={currentPage === 1}
1329
+ onClick={clickPrevious}
1330
+ value="Previous page"
1331
+ ></Button>
1332
+ <p style={{ margin: "5px" }}>
1333
+ {} Page {currentPage} of {nPages} {}
1334
+ </p>
1335
+ <Button
1336
+ dataTest={"next"}
1337
+ disabled={currentPage === nPages}
1338
+ onClick={clickNext}
1339
+ value="Next page"
1340
+ ></Button>
1341
+ </div>
1342
+ );
1343
+
1344
+ if (renderedPages[currentPage - 1]) {
1345
+ pageControlsBottom = (
1346
+ <div
1347
+ style={{ display: "flex", alignItems: "center", marginLeft: "5px" }}
1348
+ >
1349
+ <Button
1350
+ dataTest={"previous-bottom"}
1351
+ disabled={currentPage === 1}
1352
+ onClick={clickPrevious}
1353
+ value="Previous page"
1354
+ ></Button>
1355
+ <p style={{ margin: "5px" }}>
1356
+ {} Page {currentPage} of {nPages} {}
1357
+ </p>
1358
+ <Button
1359
+ dataTest={"next-bottom"}
1360
+ disabled={currentPage === nPages}
1361
+ onClick={clickNext}
1362
+ value="Next page"
1363
+ ></Button>
1364
+ </div>
1365
+ );
1366
+ }
1367
+ }
1368
+
1369
+ let finishAssessmentPrompt = null;
1370
+
1371
+ if (props.showFinishButton) {
1372
+ if (finishAssessmentMessageOpen) {
1373
+ finishAssessmentPrompt = (
1374
+ <div
1375
+ style={{
1376
+ marginLeft: "1px",
1377
+ marginRight: "5px",
1378
+ marginBottom: "5px",
1379
+ marginTop: "80px",
1380
+ border: "var(--mainBorder)",
1381
+ borderRadius: "var(--mainBorderRadius)",
1382
+ padding: "5px",
1383
+ display: "flex",
1384
+ flexFlow: "column wrap",
1385
+ }}
1386
+ >
1387
+ <div
1388
+ style={{
1389
+ display: "flex",
1390
+ justifyContent: "center",
1391
+ padding: "5px",
1392
+ }}
1393
+ >
1394
+ Are you sure you want to finish this assessment?
1395
+ </div>
1396
+ <div
1397
+ style={{
1398
+ display: "flex",
1399
+ justifyContent: "center",
1400
+ padding: "5px",
1401
+ }}
1402
+ >
1403
+ <ButtonGroup>
1404
+ <Button
1405
+ onClick={submitAllAndFinishAssessment}
1406
+ dataTest="ConfirmFinishAssessment"
1407
+ value="Yes"
1408
+ disabled={processingSubmitAll}
1409
+ ></Button>
1410
+ <Button
1411
+ onClick={() => setFinishAssessmentMessageOpen(false)}
1412
+ dataTest="CancelFinishAssessment"
1413
+ value="No"
1414
+ alert
1415
+ disabled={processingSubmitAll}
1416
+ ></Button>
1417
+ </ButtonGroup>
1418
+ </div>
1419
+ </div>
1420
+ );
1421
+ } else {
1422
+ finishAssessmentPrompt = (
1423
+ <div
1424
+ style={{
1425
+ marginLeft: "1px",
1426
+ marginRight: "5px",
1427
+ marginBottom: "5px",
1428
+ marginTop: "80px",
1429
+ }}
1430
+ >
1431
+ <div
1432
+ data-test="centerone"
1433
+ style={{ display: "flex", justifyContent: "center" }}
1434
+ >
1435
+ <div style={{ width: "240px" }}>
1436
+ <ActionButton
1437
+ onClick={() => setFinishAssessmentMessageOpen(true)}
1438
+ dataTest="FinishAssessmentPrompt"
1439
+ value="Finish assessment"
1440
+ ></ActionButton>
1441
+ </div>
1442
+ </div>
1443
+ </div>
1444
+ );
1445
+ }
1446
+ }
1447
+
1448
+ return (
1449
+ <div
1450
+ style={{ paddingBottom: "50vh" }}
1451
+ id={`${activityPrefix}top`}
1452
+ ref={nodeRef}
1453
+ >
1454
+ {pageControlsTop}
1455
+ {title}
1456
+ {pages}
1457
+ {pageControlsBottom}
1458
+ {finishAssessmentPrompt}
1459
+ </div>
1460
+ );
1461
+ }