react-cheminfo 0.64.0 → 0.65.0
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.
- package/README.md +282 -0
- package/lib/card/core/cardAddress.d.ts +70 -0
- package/lib/card/core/cardAddress.d.ts.map +1 -0
- package/lib/card/core/cardAddress.js +59 -0
- package/lib/card/core/cardAddress.js.map +1 -0
- package/lib/card/core/cardPageUrl.d.ts +49 -0
- package/lib/card/core/cardPageUrl.d.ts.map +1 -0
- package/lib/card/core/cardPageUrl.js +59 -0
- package/lib/card/core/cardPageUrl.js.map +1 -0
- package/lib/card/core/index.d.ts +7 -0
- package/lib/card/core/index.d.ts.map +1 -0
- package/lib/card/core/index.js +4 -0
- package/lib/card/core/index.js.map +1 -0
- package/lib/card/core/panelRoom.d.ts +40 -0
- package/lib/card/core/panelRoom.d.ts.map +1 -0
- package/lib/card/core/panelRoom.js +40 -0
- package/lib/card/core/panelRoom.js.map +1 -0
- package/lib/card/ui/CardDetailsButton.d.ts +38 -0
- package/lib/card/ui/CardDetailsButton.d.ts.map +1 -0
- package/lib/card/ui/CardDetailsButton.js +50 -0
- package/lib/card/ui/CardDetailsButton.js.map +1 -0
- package/lib/card/ui/CardFigure.d.ts +51 -0
- package/lib/card/ui/CardFigure.d.ts.map +1 -0
- package/lib/card/ui/CardFigure.js +27 -0
- package/lib/card/ui/CardFigure.js.map +1 -0
- package/lib/card/ui/CardFrame.d.ts +30 -0
- package/lib/card/ui/CardFrame.d.ts.map +1 -0
- package/lib/card/ui/CardFrame.js +20 -0
- package/lib/card/ui/CardFrame.js.map +1 -0
- package/lib/card/ui/CopyTableButton.d.ts +37 -0
- package/lib/card/ui/CopyTableButton.d.ts.map +1 -0
- package/lib/card/ui/CopyTableButton.js +19 -0
- package/lib/card/ui/CopyTableButton.js.map +1 -0
- package/lib/card/ui/cardTooltip.d.ts +14 -0
- package/lib/card/ui/cardTooltip.d.ts.map +1 -0
- package/lib/card/ui/cardTooltip.js +15 -0
- package/lib/card/ui/cardTooltip.js.map +1 -0
- package/lib/card/ui/embed.d.ts +15 -0
- package/lib/card/ui/embed.d.ts.map +1 -0
- package/lib/card/ui/embed.js +24 -0
- package/lib/card/ui/embed.js.map +1 -0
- package/lib/card/ui/fitCardToFrame.d.ts +31 -0
- package/lib/card/ui/fitCardToFrame.d.ts.map +1 -0
- package/lib/card/ui/fitCardToFrame.js +66 -0
- package/lib/card/ui/fitCardToFrame.js.map +1 -0
- package/lib/card/ui/holdRoomForPanels.d.ts +16 -0
- package/lib/card/ui/holdRoomForPanels.d.ts.map +1 -0
- package/lib/card/ui/holdRoomForPanels.js +76 -0
- package/lib/card/ui/holdRoomForPanels.js.map +1 -0
- package/lib/card/ui/index.d.ts +11 -0
- package/lib/card/ui/index.d.ts.map +1 -0
- package/lib/card/ui/index.js +7 -0
- package/lib/card/ui/index.js.map +1 -0
- package/lib/card/ui/openPanels.d.ts +25 -0
- package/lib/card/ui/openPanels.d.ts.map +1 -0
- package/lib/card/ui/openPanels.js +85 -0
- package/lib/card/ui/openPanels.js.map +1 -0
- package/lib/card.d.ts +13 -0
- package/lib/card.d.ts.map +1 -0
- package/lib/card.js +13 -0
- package/lib/card.js.map +1 -0
- package/lib/conformers.d.ts +1 -0
- package/lib/conformers.d.ts.map +1 -1
- package/lib/conformers.js +1 -0
- package/lib/conformers.js.map +1 -1
- package/lib/core.d.ts +4 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +5 -0
- package/lib/core.js.map +1 -1
- package/lib/fetch/core/apiError.d.ts +56 -0
- package/lib/fetch/core/apiError.d.ts.map +1 -0
- package/lib/fetch/core/apiError.js +43 -0
- package/lib/fetch/core/apiError.js.map +1 -0
- package/lib/fetch/core/fetchJson.d.ts +47 -0
- package/lib/fetch/core/fetchJson.d.ts.map +1 -0
- package/lib/fetch/core/fetchJson.js +109 -0
- package/lib/fetch/core/fetchJson.js.map +1 -0
- package/lib/fetch/core/index.d.ts +5 -0
- package/lib/fetch/core/index.d.ts.map +1 -0
- package/lib/fetch/core/index.js +3 -0
- package/lib/fetch/core/index.js.map +1 -0
- package/lib/formula/core/formulaText.d.ts +46 -0
- package/lib/formula/core/formulaText.d.ts.map +1 -0
- package/lib/formula/core/formulaText.js +74 -0
- package/lib/formula/core/formulaText.js.map +1 -0
- package/lib/formula/core/index.d.ts +6 -0
- package/lib/formula/core/index.d.ts.map +1 -0
- package/lib/formula/core/index.js +4 -0
- package/lib/formula/core/index.js.map +1 -0
- package/lib/formula/core/normalizeFormula.d.ts +22 -0
- package/lib/formula/core/normalizeFormula.d.ts.map +1 -0
- package/lib/formula/core/normalizeFormula.js +46 -0
- package/lib/formula/core/normalizeFormula.js.map +1 -0
- package/lib/formula/core/readFormula.d.ts +73 -0
- package/lib/formula/core/readFormula.d.ts.map +1 -0
- package/lib/formula/core/readFormula.js +132 -0
- package/lib/formula/core/readFormula.js.map +1 -0
- package/lib/formula/ui/CopyableFormula.d.ts +31 -0
- package/lib/formula/ui/CopyableFormula.d.ts.map +1 -0
- package/lib/formula/ui/CopyableFormula.js +19 -0
- package/lib/formula/ui/CopyableFormula.js.map +1 -0
- package/lib/formula/ui/FormulaInput.d.ts +29 -0
- package/lib/formula/ui/FormulaInput.d.ts.map +1 -0
- package/lib/formula/ui/FormulaInput.js +16 -0
- package/lib/formula/ui/FormulaInput.js.map +1 -0
- package/lib/formula/ui/QuestionFormula.d.ts +14 -0
- package/lib/formula/ui/QuestionFormula.d.ts.map +1 -0
- package/lib/formula/ui/QuestionFormula.js +13 -0
- package/lib/formula/ui/QuestionFormula.js.map +1 -0
- package/lib/formula/ui/index.d.ts +7 -0
- package/lib/formula/ui/index.d.ts.map +1 -0
- package/lib/formula/ui/index.js +4 -0
- package/lib/formula/ui/index.js.map +1 -0
- package/lib/formula.d.ts +11 -0
- package/lib/formula.d.ts.map +1 -0
- package/lib/formula.js +11 -0
- package/lib/formula.js.map +1 -0
- package/lib/hooks/ui/index.d.ts +1 -0
- package/lib/hooks/ui/index.d.ts.map +1 -1
- package/lib/hooks/ui/index.js.map +1 -1
- package/lib/hooks/ui/keyTargets.d.ts +4 -2
- package/lib/hooks/ui/keyTargets.d.ts.map +1 -1
- package/lib/hooks/ui/keyTargets.js +5 -3
- package/lib/hooks/ui/keyTargets.js.map +1 -1
- package/lib/hooks/ui/useAsync.d.ts +20 -5
- package/lib/hooks/ui/useAsync.d.ts.map +1 -1
- package/lib/hooks/ui/useAsync.js +29 -14
- package/lib/hooks/ui/useAsync.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +37 -0
- package/lib/locales/en.json +37 -0
- package/lib/locales/es.json +37 -0
- package/lib/locales/fr.json +37 -0
- package/lib/locales/it.json +37 -0
- package/lib/molecule3d/core/highlight.d.ts.map +1 -1
- package/lib/molecule3d/core/highlight.js +2 -8
- package/lib/molecule3d/core/highlight.js.map +1 -1
- package/lib/molecule3d/core/index.d.ts +1 -1
- package/lib/molecule3d/core/index.d.ts.map +1 -1
- package/lib/molecule3d/core/index.js +1 -1
- package/lib/molecule3d/core/index.js.map +1 -1
- package/lib/molecule3d/core/measurement.d.ts +17 -0
- package/lib/molecule3d/core/measurement.d.ts.map +1 -1
- package/lib/molecule3d/core/measurement.js +43 -0
- package/lib/molecule3d/core/measurement.js.map +1 -1
- package/lib/molecule3d/ui/drawScene.d.ts +4 -2
- package/lib/molecule3d/ui/drawScene.d.ts.map +1 -1
- package/lib/molecule3d/ui/drawScene.js +1 -1
- package/lib/molecule3d/ui/drawScene.js.map +1 -1
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +3 -1
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
- package/lib/molecule3d/ui/sceneDrawer.d.ts +47 -0
- package/lib/molecule3d/ui/sceneDrawer.d.ts.map +1 -0
- package/lib/molecule3d/ui/sceneDrawer.js +103 -0
- package/lib/molecule3d/ui/sceneDrawer.js.map +1 -0
- package/lib/molecule3d/ui/useMoleculeScene.d.ts +2 -1
- package/lib/molecule3d/ui/useMoleculeScene.d.ts.map +1 -1
- package/lib/molecule3d/ui/useMoleculeScene.js +13 -22
- package/lib/molecule3d/ui/useMoleculeScene.js.map +1 -1
- package/lib/molecule3d/ui/useSharedCamera.d.ts +1 -3
- package/lib/molecule3d/ui/useSharedCamera.d.ts.map +1 -1
- package/lib/molecule3d/ui/useSharedCamera.js.map +1 -1
- package/lib/moleculesClient/core/index.d.ts +5 -0
- package/lib/moleculesClient/core/index.d.ts.map +1 -0
- package/lib/moleculesClient/core/index.js +3 -0
- package/lib/moleculesClient/core/index.js.map +1 -0
- package/lib/moleculesClient/core/moleculesClient.d.ts +12 -0
- package/lib/moleculesClient/core/moleculesClient.d.ts.map +1 -0
- package/lib/moleculesClient/core/moleculesClient.js +105 -0
- package/lib/moleculesClient/core/moleculesClient.js.map +1 -0
- package/lib/moleculesClient/core/moleculesClientApi.d.ts +100 -0
- package/lib/moleculesClient/core/moleculesClientApi.d.ts.map +1 -0
- package/lib/moleculesClient/core/moleculesClientApi.js +20 -0
- package/lib/moleculesClient/core/moleculesClientApi.js.map +1 -0
- package/lib/moleculesClient/core/moleculesTypes.d.ts +154 -0
- package/lib/moleculesClient/core/moleculesTypes.d.ts.map +1 -0
- package/lib/moleculesClient/core/moleculesTypes.js +2 -0
- package/lib/moleculesClient/core/moleculesTypes.js.map +1 -0
- package/lib/moleculesClient/core/requestRetry.d.ts +50 -0
- package/lib/moleculesClient/core/requestRetry.d.ts.map +1 -0
- package/lib/moleculesClient/core/requestRetry.js +108 -0
- package/lib/moleculesClient/core/requestRetry.js.map +1 -0
- package/lib/molstar/core/camera.d.ts +2 -1
- package/lib/molstar/core/camera.d.ts.map +1 -1
- package/lib/molstar/core/camera.js +3 -2
- package/lib/molstar/core/camera.js.map +1 -1
- package/lib/molstar/core/framing.d.ts +102 -0
- package/lib/molstar/core/framing.d.ts.map +1 -0
- package/lib/molstar/core/framing.js +120 -0
- package/lib/molstar/core/framing.js.map +1 -0
- package/lib/molstar/core/hover.d.ts +16 -0
- package/lib/molstar/core/hover.d.ts.map +1 -1
- package/lib/molstar/core/hover.js +14 -0
- package/lib/molstar/core/hover.js.map +1 -1
- package/lib/molstar/core/index.d.ts +4 -1
- package/lib/molstar/core/index.d.ts.map +1 -1
- package/lib/molstar/core/index.js +2 -1
- package/lib/molstar/core/index.js.map +1 -1
- package/lib/pedagogy/core/answers.d.ts +46 -0
- package/lib/pedagogy/core/answers.d.ts.map +1 -0
- package/lib/pedagogy/core/answers.js +77 -0
- package/lib/pedagogy/core/answers.js.map +1 -0
- package/lib/pedagogy/core/attempts.d.ts +86 -0
- package/lib/pedagogy/core/attempts.d.ts.map +1 -0
- package/lib/pedagogy/core/attempts.js +117 -0
- package/lib/pedagogy/core/attempts.js.map +1 -0
- package/lib/pedagogy/core/index.d.ts +8 -0
- package/lib/pedagogy/core/index.d.ts.map +1 -1
- package/lib/pedagogy/core/index.js +4 -0
- package/lib/pedagogy/core/index.js.map +1 -1
- package/lib/pedagogy/core/progressController.d.ts +64 -0
- package/lib/pedagogy/core/progressController.d.ts.map +1 -0
- package/lib/pedagogy/core/progressController.js +56 -0
- package/lib/pedagogy/core/progressController.js.map +1 -0
- package/lib/pedagogy/core/seed.d.ts +96 -0
- package/lib/pedagogy/core/seed.d.ts.map +1 -0
- package/lib/pedagogy/core/seed.js +128 -0
- package/lib/pedagogy/core/seed.js.map +1 -0
- package/lib/pedagogy/core/series.d.ts +77 -0
- package/lib/pedagogy/core/series.d.ts.map +1 -0
- package/lib/pedagogy/core/series.js +10 -0
- package/lib/pedagogy/core/series.js.map +1 -0
- package/lib/pedagogy/ui/AnswerFields.d.ts +21 -0
- package/lib/pedagogy/ui/AnswerFields.d.ts.map +1 -0
- package/lib/pedagogy/ui/AnswerFields.js +24 -0
- package/lib/pedagogy/ui/AnswerFields.js.map +1 -0
- package/lib/pedagogy/ui/ExerciseList.d.ts +82 -0
- package/lib/pedagogy/ui/ExerciseList.d.ts.map +1 -0
- package/lib/pedagogy/ui/ExerciseList.js +53 -0
- package/lib/pedagogy/ui/ExerciseList.js.map +1 -0
- package/lib/pedagogy/ui/ExerciseSeries.d.ts +95 -0
- package/lib/pedagogy/ui/ExerciseSeries.d.ts.map +1 -0
- package/lib/pedagogy/ui/ExerciseSeries.js +74 -0
- package/lib/pedagogy/ui/ExerciseSeries.js.map +1 -0
- package/lib/pedagogy/ui/QuestionCard.d.ts +64 -0
- package/lib/pedagogy/ui/QuestionCard.d.ts.map +1 -0
- package/lib/pedagogy/ui/QuestionCard.js +73 -0
- package/lib/pedagogy/ui/QuestionCard.js.map +1 -0
- package/lib/pedagogy/ui/index.d.ts +10 -0
- package/lib/pedagogy/ui/index.d.ts.map +1 -1
- package/lib/pedagogy/ui/index.js +6 -0
- package/lib/pedagogy/ui/index.js.map +1 -1
- package/lib/pedagogy/ui/seriesCounts.d.ts +17 -0
- package/lib/pedagogy/ui/seriesCounts.d.ts.map +1 -0
- package/lib/pedagogy/ui/seriesCounts.js +27 -0
- package/lib/pedagogy/ui/seriesCounts.js.map +1 -0
- package/lib/pedagogy/ui/seriesParts.d.ts +55 -0
- package/lib/pedagogy/ui/seriesParts.d.ts.map +1 -0
- package/lib/pedagogy/ui/seriesParts.js +38 -0
- package/lib/pedagogy/ui/seriesParts.js.map +1 -0
- package/lib/pedagogy/ui/useAttemptRecords.d.ts +10 -0
- package/lib/pedagogy/ui/useAttemptRecords.d.ts.map +1 -0
- package/lib/pedagogy/ui/useAttemptRecords.js +11 -0
- package/lib/pedagogy/ui/useAttemptRecords.js.map +1 -0
- package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTable.js +6 -3
- package/lib/periodic/ui/PeriodicTable.js.map +1 -1
- package/lib/property/core/copyableText.d.ts +14 -0
- package/lib/property/core/copyableText.d.ts.map +1 -0
- package/lib/property/core/copyableText.js +21 -0
- package/lib/property/core/copyableText.js.map +1 -0
- package/lib/property/core/index.d.ts +2 -0
- package/lib/property/core/index.d.ts.map +1 -0
- package/lib/property/core/index.js +2 -0
- package/lib/property/core/index.js.map +1 -0
- package/lib/property/ui/PropertyTable.d.ts +43 -0
- package/lib/property/ui/PropertyTable.d.ts.map +1 -0
- package/lib/property/ui/PropertyTable.js +19 -0
- package/lib/property/ui/PropertyTable.js.map +1 -0
- package/lib/property/ui/index.d.ts +3 -0
- package/lib/property/ui/index.d.ts.map +1 -0
- package/lib/property/ui/index.js +2 -0
- package/lib/property/ui/index.js.map +1 -0
- package/lib/router/core/history.d.ts +24 -3
- package/lib/router/core/history.d.ts.map +1 -1
- package/lib/router/core/history.js +55 -7
- package/lib/router/core/history.js.map +1 -1
- package/lib/router/core/index.d.ts +3 -3
- package/lib/router/core/index.d.ts.map +1 -1
- package/lib/router/core/index.js +2 -2
- package/lib/router/core/index.js.map +1 -1
- package/lib/router/core/languageRoute.d.ts +61 -0
- package/lib/router/core/languageRoute.d.ts.map +1 -0
- package/lib/router/core/languageRoute.js +96 -0
- package/lib/router/core/languageRoute.js.map +1 -0
- package/lib/router/core/siteAddresses.d.ts +23 -0
- package/lib/router/core/siteAddresses.d.ts.map +1 -1
- package/lib/router/core/siteAddresses.js +26 -0
- package/lib/router/core/siteAddresses.js.map +1 -1
- package/lib/router/core/tabRouter.d.ts +2 -108
- package/lib/router/core/tabRouter.d.ts.map +1 -1
- package/lib/router/core/tabRouter.js +38 -5
- package/lib/router/core/tabRouter.js.map +1 -1
- package/lib/router/core/tabRouterTypes.d.ts +154 -0
- package/lib/router/core/tabRouterTypes.d.ts.map +1 -0
- package/lib/router/core/tabRouterTypes.js +2 -0
- package/lib/router/core/tabRouterTypes.js.map +1 -0
- package/lib/seo/core/fitDescription.d.ts +40 -0
- package/lib/seo/core/fitDescription.d.ts.map +1 -0
- package/lib/seo/core/fitDescription.js +45 -0
- package/lib/seo/core/fitDescription.js.map +1 -0
- package/lib/seo/core/index.d.ts +7 -0
- package/lib/seo/core/index.d.ts.map +1 -1
- package/lib/seo/core/index.js +4 -0
- package/lib/seo/core/index.js.map +1 -1
- package/lib/seo/core/limits.d.ts +24 -0
- package/lib/seo/core/limits.d.ts.map +1 -0
- package/lib/seo/core/limits.js +24 -0
- package/lib/seo/core/limits.js.map +1 -0
- package/lib/seo/core/pageContent.d.ts +25 -0
- package/lib/seo/core/pageContent.d.ts.map +1 -0
- package/lib/seo/core/pageContent.js +25 -0
- package/lib/seo/core/pageContent.js.map +1 -0
- package/lib/seo/core/plainDescription.d.ts.map +1 -1
- package/lib/seo/core/plainDescription.js +3 -2
- package/lib/seo/core/plainDescription.js.map +1 -1
- package/lib/seo/core/routeProblems.d.ts.map +1 -1
- package/lib/seo/core/routeProblems.js +1 -6
- package/lib/seo/core/routeProblems.js.map +1 -1
- package/lib/seo/core/routeTable.d.ts +31 -0
- package/lib/seo/core/routeTable.d.ts.map +1 -0
- package/lib/seo/core/routeTable.js +96 -0
- package/lib/seo/core/routeTable.js.map +1 -0
- package/lib/seo/vite/index.d.ts +2 -0
- package/lib/seo/vite/index.d.ts.map +1 -1
- package/lib/seo/vite/index.js +1 -0
- package/lib/seo/vite/index.js.map +1 -1
- package/lib/seo/vite/prerender.d.ts +10 -0
- package/lib/seo/vite/prerender.d.ts.map +1 -1
- package/lib/seo/vite/prerender.js +12 -1
- package/lib/seo/vite/prerender.js.map +1 -1
- package/lib/seo/vite/routeTable.d.ts +40 -0
- package/lib/seo/vite/routeTable.d.ts.map +1 -0
- package/lib/seo/vite/routeTable.js +44 -0
- package/lib/seo/vite/routeTable.js.map +1 -0
- package/lib/server/answerAddresses.d.ts +51 -0
- package/lib/server/answerAddresses.d.ts.map +1 -0
- package/lib/server/answerAddresses.js +72 -0
- package/lib/server/answerAddresses.js.map +1 -0
- package/lib/server/cardMeta.d.ts +57 -0
- package/lib/server/cardMeta.d.ts.map +1 -0
- package/lib/server/cardMeta.js +52 -0
- package/lib/server/cardMeta.js.map +1 -0
- package/lib/server/config.d.ts +72 -0
- package/lib/server/config.d.ts.map +1 -0
- package/lib/server/config.js +67 -0
- package/lib/server/config.js.map +1 -0
- package/lib/server/envNumber.d.ts +35 -0
- package/lib/server/envNumber.d.ts.map +1 -0
- package/lib/server/envNumber.js +42 -0
- package/lib/server/envNumber.js.map +1 -0
- package/lib/server/healthRoute.d.ts +26 -0
- package/lib/server/healthRoute.d.ts.map +1 -0
- package/lib/server/healthRoute.js +22 -0
- package/lib/server/healthRoute.js.map +1 -0
- package/lib/server/listenSite.d.ts +38 -0
- package/lib/server/listenSite.d.ts.map +1 -0
- package/lib/server/listenSite.js +60 -0
- package/lib/server/listenSite.js.map +1 -0
- package/lib/server/mcpServer.d.ts +8 -0
- package/lib/server/mcpServer.d.ts.map +1 -0
- package/lib/server/mcpServer.js +11 -0
- package/lib/server/mcpServer.js.map +1 -0
- package/lib/server/repositoryVersion.d.ts +16 -0
- package/lib/server/repositoryVersion.d.ts.map +1 -0
- package/lib/server/repositoryVersion.js +33 -0
- package/lib/server/repositoryVersion.js.map +1 -0
- package/lib/server/routeOptions.d.ts +23 -0
- package/lib/server/routeOptions.d.ts.map +1 -0
- package/lib/server/routeOptions.js +20 -0
- package/lib/server/routeOptions.js.map +1 -0
- package/lib/server/routeTable.d.ts +14 -0
- package/lib/server/routeTable.d.ts.map +1 -0
- package/lib/server/routeTable.js +32 -0
- package/lib/server/routeTable.js.map +1 -0
- package/lib/server/siteView.d.ts +29 -0
- package/lib/server/siteView.d.ts.map +1 -0
- package/lib/server/siteView.js +21 -0
- package/lib/server/siteView.js.map +1 -0
- package/lib/server/spaPages.d.ts +32 -0
- package/lib/server/spaPages.d.ts.map +1 -0
- package/lib/server/spaPages.js +126 -0
- package/lib/server/spaPages.js.map +1 -0
- package/lib/server/spaSite.d.ts +30 -0
- package/lib/server/spaSite.d.ts.map +1 -0
- package/lib/server/spaSite.js +170 -0
- package/lib/server/spaSite.js.map +1 -0
- package/lib/server/spaSiteOptions.d.ts +126 -0
- package/lib/server/spaSiteOptions.d.ts.map +1 -0
- package/lib/server/spaSiteOptions.js +2 -0
- package/lib/server/spaSiteOptions.js.map +1 -0
- package/lib/server/staticSite.d.ts +9 -0
- package/lib/server/staticSite.d.ts.map +1 -1
- package/lib/server/staticSite.js +41 -19
- package/lib/server/staticSite.js.map +1 -1
- package/lib/server/toolSiteServer.d.ts +77 -0
- package/lib/server/toolSiteServer.d.ts.map +1 -0
- package/lib/server/toolSiteServer.js +58 -0
- package/lib/server/toolSiteServer.js.map +1 -0
- package/lib/server/trustProxy.d.ts +20 -0
- package/lib/server/trustProxy.d.ts.map +1 -0
- package/lib/server/trustProxy.js +32 -0
- package/lib/server/trustProxy.js.map +1 -0
- package/lib/server/types.d.ts +13 -0
- package/lib/server/types.d.ts.map +1 -0
- package/lib/server/types.js +2 -0
- package/lib/server/types.js.map +1 -0
- package/lib/server.d.ts +22 -0
- package/lib/server.d.ts.map +1 -1
- package/lib/server.js +12 -0
- package/lib/server.js.map +1 -1
- package/lib/share/core/config.d.ts +18 -0
- package/lib/share/core/config.d.ts.map +1 -1
- package/lib/share/core/config.js +33 -2
- package/lib/share/core/config.js.map +1 -1
- package/lib/share/core/index.d.ts +3 -1
- package/lib/share/core/index.d.ts.map +1 -1
- package/lib/share/core/index.js +2 -1
- package/lib/share/core/index.js.map +1 -1
- package/lib/share/core/keySetParam.d.ts +40 -0
- package/lib/share/core/keySetParam.d.ts.map +1 -0
- package/lib/share/core/keySetParam.js +60 -0
- package/lib/share/core/keySetParam.js.map +1 -0
- package/lib/share/core/vocabulary.d.ts +8 -0
- package/lib/share/core/vocabulary.d.ts.map +1 -1
- package/lib/shell/ui/ShareControl.d.ts +75 -0
- package/lib/shell/ui/ShareControl.d.ts.map +1 -0
- package/lib/shell/ui/ShareControl.js +40 -0
- package/lib/shell/ui/ShareControl.js.map +1 -0
- package/lib/shell/ui/ShellFrame.d.ts +14 -0
- package/lib/shell/ui/ShellFrame.d.ts.map +1 -0
- package/lib/shell/ui/ShellFrame.js +45 -0
- package/lib/shell/ui/ShellFrame.js.map +1 -0
- package/lib/shell/ui/SiteShell.d.ts +251 -0
- package/lib/shell/ui/SiteShell.d.ts.map +1 -0
- package/lib/shell/ui/SiteShell.js +39 -0
- package/lib/shell/ui/SiteShell.js.map +1 -0
- package/lib/shell/ui/index.d.ts +5 -0
- package/lib/shell/ui/index.d.ts.map +1 -0
- package/lib/shell/ui/index.js +3 -0
- package/lib/shell/ui/index.js.map +1 -0
- package/lib/state/core/index.d.ts +1 -0
- package/lib/state/core/index.d.ts.map +1 -1
- package/lib/state/core/index.js.map +1 -1
- package/lib/state/core/legacyEntries.d.ts +30 -0
- package/lib/state/core/legacyEntries.d.ts.map +1 -0
- package/lib/state/core/legacyEntries.js +51 -0
- package/lib/state/core/legacyEntries.js.map +1 -0
- package/lib/state/core/persistBucket.d.ts +26 -2
- package/lib/state/core/persistBucket.d.ts.map +1 -1
- package/lib/state/core/persistBucket.js +26 -9
- package/lib/state/core/persistBucket.js.map +1 -1
- package/lib/state/core/persistSignalBucket.d.ts +26 -0
- package/lib/state/core/persistSignalBucket.d.ts.map +1 -1
- package/lib/state/core/persistSignalBucket.js +31 -0
- package/lib/state/core/persistSignalBucket.js.map +1 -1
- package/lib/state/core/storageEntry.d.ts +15 -0
- package/lib/state/core/storageEntry.d.ts.map +1 -1
- package/lib/state/core/storageEntry.js +33 -0
- package/lib/state/core/storageEntry.js.map +1 -1
- package/lib/structure/ui/StructureExportButton.d.ts +27 -0
- package/lib/structure/ui/StructureExportButton.d.ts.map +1 -0
- package/lib/structure/ui/StructureExportButton.js +21 -0
- package/lib/structure/ui/StructureExportButton.js.map +1 -0
- package/lib/structure/ui/index.d.ts +4 -0
- package/lib/structure/ui/index.d.ts.map +1 -1
- package/lib/structure/ui/index.js +2 -0
- package/lib/structure/ui/index.js.map +1 -1
- package/lib/structure/ui/useStructureDraft.d.ts +32 -0
- package/lib/structure/ui/useStructureDraft.d.ts.map +1 -0
- package/lib/structure/ui/useStructureDraft.js +20 -0
- package/lib/structure/ui/useStructureDraft.js.map +1 -0
- package/lib/ui.d.ts +2 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +2 -0
- package/lib/ui.js.map +1 -1
- package/lib/worker/core/index.d.ts +1 -1
- package/lib/worker/core/index.d.ts.map +1 -1
- package/lib/worker/core/messages.d.ts +4 -2
- package/lib/worker/core/messages.d.ts.map +1 -1
- package/lib/worker/core/messages.js.map +1 -1
- package/lib/worker/core/oclResourcesUrl.d.ts +16 -0
- package/lib/worker/core/oclResourcesUrl.d.ts.map +1 -0
- package/lib/worker/core/oclResourcesUrl.js +19 -0
- package/lib/worker/core/oclResourcesUrl.js.map +1 -0
- package/lib/worker/core/serveWorkerRequests.d.ts +8 -0
- package/lib/worker/core/serveWorkerRequests.d.ts.map +1 -1
- package/lib/worker/core/serveWorkerRequests.js +15 -2
- package/lib/worker/core/serveWorkerRequests.js.map +1 -1
- package/lib/worker/core/workerChannel.d.ts +3 -4
- package/lib/worker/core/workerChannel.d.ts.map +1 -1
- package/lib/worker/core/workerChannel.js +72 -67
- package/lib/worker/core/workerChannel.js.map +1 -1
- package/lib/worker/core/workerChannelParts.d.ts +17 -6
- package/lib/worker/core/workerChannelParts.d.ts.map +1 -1
- package/lib/worker/core/workerChannelParts.js +20 -4
- package/lib/worker/core/workerChannelParts.js.map +1 -1
- package/lib/worker/core/workerChannelTypes.d.ts +46 -2
- package/lib/worker/core/workerChannelTypes.d.ts.map +1 -1
- package/lib/worker/core/workerSlot.d.ts +66 -0
- package/lib/worker/core/workerSlot.d.ts.map +1 -0
- package/lib/worker/core/workerSlot.js +107 -0
- package/lib/worker/core/workerSlot.js.map +1 -0
- package/package.json +16 -1
- package/src/card/core/cardAddress.ts +100 -0
- package/src/card/core/cardPageUrl.ts +97 -0
- package/src/card/core/index.ts +6 -0
- package/src/card/core/panelRoom.ts +66 -0
- package/src/card/ui/CardDetailsButton.tsx +104 -0
- package/src/card/ui/CardFigure.tsx +86 -0
- package/src/card/ui/CardFrame.tsx +46 -0
- package/src/card/ui/CopyTableButton.tsx +81 -0
- package/src/card/ui/cardTooltip.ts +18 -0
- package/src/card/ui/embed.ts +27 -0
- package/src/card/ui/fitCardToFrame.ts +76 -0
- package/src/card/ui/holdRoomForPanels.ts +83 -0
- package/src/card/ui/index.ts +14 -0
- package/src/card/ui/openPanels.ts +106 -0
- package/src/card.ts +13 -0
- package/src/conformers.ts +4 -0
- package/src/core.ts +5 -0
- package/src/fetch/core/apiError.ts +65 -0
- package/src/fetch/core/fetchJson.ts +129 -0
- package/src/fetch/core/index.ts +4 -0
- package/src/formula/core/formulaText.ts +96 -0
- package/src/formula/core/index.ts +20 -0
- package/src/formula/core/normalizeFormula.ts +50 -0
- package/src/formula/core/readFormula.ts +211 -0
- package/src/formula/ui/CopyableFormula.tsx +51 -0
- package/src/formula/ui/FormulaInput.tsx +50 -0
- package/src/formula/ui/QuestionFormula.tsx +20 -0
- package/src/formula/ui/index.ts +6 -0
- package/src/formula.ts +11 -0
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/keyTargets.ts +5 -3
- package/src/hooks/ui/useAsync.ts +50 -14
- package/src/locales/de.json +37 -0
- package/src/locales/en.json +37 -0
- package/src/locales/es.json +37 -0
- package/src/locales/fr.json +37 -0
- package/src/locales/it.json +37 -0
- package/src/molecule3d/core/highlight.ts +2 -8
- package/src/molecule3d/core/index.ts +1 -0
- package/src/molecule3d/core/measurement.ts +47 -0
- package/src/molecule3d/ui/drawScene.ts +13 -2
- package/src/molecule3d/ui/moleculeViewer3DProps.ts +3 -1
- package/src/molecule3d/ui/sceneDrawer.ts +132 -0
- package/src/molecule3d/ui/useMoleculeScene.ts +16 -26
- package/src/molecule3d/ui/useSharedCamera.ts +1 -3
- package/src/moleculesClient/core/index.ts +19 -0
- package/src/moleculesClient/core/moleculesClient.ts +175 -0
- package/src/moleculesClient/core/moleculesClientApi.ts +120 -0
- package/src/moleculesClient/core/moleculesTypes.ts +163 -0
- package/src/moleculesClient/core/requestRetry.ts +118 -0
- package/src/molstar/core/camera.ts +3 -2
- package/src/molstar/core/framing.ts +200 -0
- package/src/molstar/core/hover.ts +26 -0
- package/src/molstar/core/index.ts +21 -1
- package/src/pedagogy/core/answers.ts +94 -0
- package/src/pedagogy/core/attempts.ts +205 -0
- package/src/pedagogy/core/index.ts +36 -0
- package/src/pedagogy/core/progressController.ts +116 -0
- package/src/pedagogy/core/seed.ts +177 -0
- package/src/pedagogy/core/series.ts +82 -0
- package/src/pedagogy/ui/AnswerFields.tsx +63 -0
- package/src/pedagogy/ui/ExerciseList.tsx +202 -0
- package/src/pedagogy/ui/ExerciseSeries.tsx +250 -0
- package/src/pedagogy/ui/QuestionCard.tsx +231 -0
- package/src/pedagogy/ui/index.ts +18 -0
- package/src/pedagogy/ui/seriesCounts.ts +34 -0
- package/src/pedagogy/ui/seriesParts.tsx +127 -0
- package/src/pedagogy/ui/useAttemptRecords.ts +16 -0
- package/src/periodic/ui/PeriodicTable.tsx +6 -3
- package/src/property/core/copyableText.ts +20 -0
- package/src/property/core/index.ts +1 -0
- package/src/property/ui/PropertyTable.tsx +83 -0
- package/src/property/ui/index.ts +2 -0
- package/src/router/core/history.ts +61 -7
- package/src/router/core/index.ts +3 -2
- package/src/router/core/languageRoute.ts +136 -0
- package/src/router/core/siteAddresses.ts +38 -0
- package/src/router/core/tabRouter.ts +59 -117
- package/src/router/core/tabRouterTypes.ts +158 -0
- package/src/seo/core/fitDescription.ts +74 -0
- package/src/seo/core/index.ts +17 -0
- package/src/seo/core/limits.ts +27 -0
- package/src/seo/core/pageContent.ts +42 -0
- package/src/seo/core/plainDescription.ts +4 -2
- package/src/seo/core/routeProblems.ts +1 -7
- package/src/seo/core/routeTable.ts +103 -0
- package/src/seo/vite/index.ts +2 -0
- package/src/seo/vite/prerender.ts +26 -1
- package/src/seo/vite/routeTable.ts +79 -0
- package/src/server/answerAddresses.ts +92 -0
- package/src/server/cardMeta.ts +88 -0
- package/src/server/config.ts +125 -0
- package/src/server/envNumber.ts +65 -0
- package/src/server/healthRoute.ts +42 -0
- package/src/server/listenSite.ts +90 -0
- package/src/server/mcpServer.ts +14 -0
- package/src/server/repositoryVersion.ts +33 -0
- package/src/server/routeOptions.ts +26 -0
- package/src/server/routeTable.ts +35 -0
- package/src/server/siteView.ts +42 -0
- package/src/server/spaPages.ts +179 -0
- package/src/server/spaSite.ts +216 -0
- package/src/server/spaSiteOptions.ts +133 -0
- package/src/server/staticSite.ts +65 -35
- package/src/server/toolSiteServer.ts +127 -0
- package/src/server/trustProxy.ts +32 -0
- package/src/server/types.ts +25 -0
- package/src/server.ts +40 -0
- package/src/share/core/config.ts +45 -2
- package/src/share/core/index.ts +4 -0
- package/src/share/core/keySetParam.ts +92 -0
- package/src/share/core/vocabulary.ts +8 -0
- package/src/shell/ui/ShareControl.tsx +150 -0
- package/src/shell/ui/ShellFrame.tsx +180 -0
- package/src/shell/ui/SiteShell.tsx +297 -0
- package/src/shell/ui/index.ts +9 -0
- package/src/state/core/index.ts +1 -0
- package/src/state/core/legacyEntries.ts +76 -0
- package/src/state/core/persistBucket.ts +50 -10
- package/src/state/core/persistSignalBucket.ts +52 -0
- package/src/state/core/storageEntry.ts +32 -0
- package/src/structure/ui/StructureExportButton.tsx +57 -0
- package/src/structure/ui/index.ts +4 -0
- package/src/structure/ui/useStructureDraft.ts +47 -0
- package/src/ui.ts +2 -0
- package/src/worker/core/index.ts +1 -0
- package/src/worker/core/messages.ts +4 -3
- package/src/worker/core/oclResourcesUrl.ts +21 -0
- package/src/worker/core/serveWorkerRequests.ts +30 -2
- package/src/worker/core/workerChannel.ts +74 -74
- package/src/worker/core/workerChannelParts.ts +33 -13
- package/src/worker/core/workerChannelTypes.ts +47 -2
- package/src/worker/core/workerSlot.ts +181 -0
- package/styles/card.css +145 -0
- package/styles/chrome.css +248 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { Tag } from '@blueprintjs/core';
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useDocumentListNavigation } from '../../hooks/ui/useDocumentListNavigation.ts';
|
|
5
|
+
import type { ChromeKey } from '../../i18n/core/chromeCatalog.ts';
|
|
6
|
+
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
7
|
+
import { joinClassNames } from '../../shared/ui/joinClassNames.ts';
|
|
8
|
+
import type { ProgressRecords } from '../core/progress.ts';
|
|
9
|
+
import { progressSummary } from '../core/progress.ts';
|
|
10
|
+
import type { ExerciseLevel, ExerciseStatus } from '../core/types.ts';
|
|
11
|
+
|
|
12
|
+
import { ExerciseProgressHeader } from './ExerciseProgressHeader.tsx';
|
|
13
|
+
import { ExerciseLevelTag, ExerciseStatusIcon } from './ExerciseTags.tsx';
|
|
14
|
+
import { InlineText } from './InlineText.tsx';
|
|
15
|
+
|
|
16
|
+
/** What a row of the list reads off an exercise. */
|
|
17
|
+
export interface ExerciseListEntry {
|
|
18
|
+
/** Stable id, which is what is selected. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** How the exercise is named; may carry the shared prose marks. */
|
|
21
|
+
title: string;
|
|
22
|
+
/**
|
|
23
|
+
* How hard it is, drawn as a coloured tag.
|
|
24
|
+
* @default undefined — no level tag
|
|
25
|
+
*/
|
|
26
|
+
level?: ExerciseLevel;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What the list reads of the progress kept on one exercise. */
|
|
30
|
+
export interface ExerciseListProgress {
|
|
31
|
+
/** Where the student stands. */
|
|
32
|
+
status: ExerciseStatus;
|
|
33
|
+
/** How many hints were opened. */
|
|
34
|
+
hintsRevealed: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** What {@link ExerciseList} needs. */
|
|
38
|
+
export interface ExerciseListProps<TExercise extends ExerciseListEntry> {
|
|
39
|
+
/** The exercises, in the order they are worked through. */
|
|
40
|
+
exercises: readonly TExercise[];
|
|
41
|
+
/** The progress kept, by exercise id; an absent one reads as untouched. */
|
|
42
|
+
progress: ProgressRecords<ExerciseListProgress>;
|
|
43
|
+
/** The exercise on screen, or `null` for none. */
|
|
44
|
+
activeId: string | null;
|
|
45
|
+
/** Called with the id of the exercise to open. */
|
|
46
|
+
onSelect: (id: string) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Tags of the tool's own after the level — the kind of question, say.
|
|
49
|
+
* @default undefined
|
|
50
|
+
*/
|
|
51
|
+
tags?: (exercise: TExercise) => ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* Draws the title the tool's own way.
|
|
54
|
+
* @default the title as inline prose, its marks drawn
|
|
55
|
+
*/
|
|
56
|
+
renderTitle?: (exercise: TExercise) => ReactNode;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the bar of solved exercises heads the list.
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
progressHeader?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Throws away every answer, behind a dialog. Without it the header only
|
|
64
|
+
* reports.
|
|
65
|
+
* @default undefined
|
|
66
|
+
*/
|
|
67
|
+
onClearAll?: () => void;
|
|
68
|
+
/**
|
|
69
|
+
* What the dialog says before the work is thrown away.
|
|
70
|
+
* @default the header's own sentence
|
|
71
|
+
*/
|
|
72
|
+
clearWarning?: ReactNode;
|
|
73
|
+
/**
|
|
74
|
+
* What a screen reader calls the list.
|
|
75
|
+
* @default the chrome's own word for it, in the language of the page
|
|
76
|
+
*/
|
|
77
|
+
label?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Class the list carries, in addition to `exercise-list`.
|
|
80
|
+
* @default undefined
|
|
81
|
+
*/
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const STATUS_TITLE: Record<ExerciseStatus, ChromeKey> = {
|
|
86
|
+
idle: 'pedagogy.statusIdle',
|
|
87
|
+
attempted: 'pedagogy.statusAttempted',
|
|
88
|
+
solved: 'pedagogy.statusSolved',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The exercises of a set, each with where the student stands on it, under the
|
|
93
|
+
* bar of how many are solved.
|
|
94
|
+
*
|
|
95
|
+
* The hint count of a solved exercise is shown rather than hidden: it is the
|
|
96
|
+
* honest record of which ones were hard, which is what a student coming back a
|
|
97
|
+
* week later wants to know. `ArrowUp` and `ArrowDown` walk the list from
|
|
98
|
+
* anywhere on the page, unless a text field has the focus.
|
|
99
|
+
* @param props - The exercises, their progress, and the open one.
|
|
100
|
+
* @returns The list.
|
|
101
|
+
*/
|
|
102
|
+
export function ExerciseList<TExercise extends ExerciseListEntry>(
|
|
103
|
+
props: ExerciseListProps<TExercise>,
|
|
104
|
+
): ReactElement {
|
|
105
|
+
const {
|
|
106
|
+
exercises,
|
|
107
|
+
progress,
|
|
108
|
+
activeId,
|
|
109
|
+
onSelect,
|
|
110
|
+
tags,
|
|
111
|
+
renderTitle,
|
|
112
|
+
progressHeader = true,
|
|
113
|
+
onClearAll,
|
|
114
|
+
clearWarning,
|
|
115
|
+
label,
|
|
116
|
+
className,
|
|
117
|
+
} = props;
|
|
118
|
+
const t = useChromeT();
|
|
119
|
+
const activeIndex = exercises.findIndex(
|
|
120
|
+
(exercise) => exercise.id === activeId,
|
|
121
|
+
);
|
|
122
|
+
const listRef = useDocumentListNavigation<HTMLDivElement>({
|
|
123
|
+
length: exercises.length,
|
|
124
|
+
selectedIndex: activeIndex,
|
|
125
|
+
onSelect: (index) => {
|
|
126
|
+
const next = exercises[index];
|
|
127
|
+
if (next !== undefined) onSelect(next.id);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className={joinClassNames('exercise-list', className)}>
|
|
133
|
+
{progressHeader ? (
|
|
134
|
+
<ExerciseProgressHeader
|
|
135
|
+
summary={progressSummary(
|
|
136
|
+
progress,
|
|
137
|
+
exercises.map((exercise) => exercise.id),
|
|
138
|
+
)}
|
|
139
|
+
onClearAll={onClearAll}
|
|
140
|
+
clearWarning={clearWarning}
|
|
141
|
+
clearDisabled={Object.keys(progress).length === 0}
|
|
142
|
+
/>
|
|
143
|
+
) : null}
|
|
144
|
+
<div
|
|
145
|
+
ref={listRef}
|
|
146
|
+
className="exercise-list__rows"
|
|
147
|
+
role="listbox"
|
|
148
|
+
aria-label={label ?? t('pedagogy.exercises')}
|
|
149
|
+
data-testid="exercise-list"
|
|
150
|
+
>
|
|
151
|
+
{exercises.map((exercise) => {
|
|
152
|
+
const record = progress[exercise.id];
|
|
153
|
+
const status = record?.status ?? 'idle';
|
|
154
|
+
const hints = record?.hintsRevealed ?? 0;
|
|
155
|
+
const active = exercise.id === activeId;
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
key={exercise.id}
|
|
159
|
+
type="button"
|
|
160
|
+
role="option"
|
|
161
|
+
aria-selected={active}
|
|
162
|
+
data-selected={active ? 'true' : undefined}
|
|
163
|
+
className={
|
|
164
|
+
active
|
|
165
|
+
? 'exercise-list__item exercise-list__item--active'
|
|
166
|
+
: 'exercise-list__item'
|
|
167
|
+
}
|
|
168
|
+
onClick={() => {
|
|
169
|
+
onSelect(exercise.id);
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<ExerciseStatusIcon
|
|
173
|
+
status={status}
|
|
174
|
+
title={t(STATUS_TITLE[status])}
|
|
175
|
+
/>
|
|
176
|
+
<span className="exercise-list__title">
|
|
177
|
+
{renderTitle === undefined ? (
|
|
178
|
+
<InlineText text={exercise.title} />
|
|
179
|
+
) : (
|
|
180
|
+
renderTitle(exercise)
|
|
181
|
+
)}
|
|
182
|
+
</span>
|
|
183
|
+
<span className="exercise-list__tags">
|
|
184
|
+
{exercise.level === undefined ? null : (
|
|
185
|
+
<ExerciseLevelTag level={exercise.level} active={active} />
|
|
186
|
+
)}
|
|
187
|
+
{tags?.(exercise)}
|
|
188
|
+
{status === 'solved' && hints > 0 ? (
|
|
189
|
+
<Tag minimal round intent="success">
|
|
190
|
+
{hints === 1
|
|
191
|
+
? t('pedagogy.solvedWithOneHint')
|
|
192
|
+
: t('pedagogy.solvedWithHints', { count: hints })}
|
|
193
|
+
</Tag>
|
|
194
|
+
) : null}
|
|
195
|
+
</span>
|
|
196
|
+
</button>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { useEffect, useEffectEvent, useMemo, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useDocumentListNavigation } from '../../hooks/ui/useDocumentListNavigation.ts';
|
|
5
|
+
import type { SplitRange } from '../../split/core/split.ts';
|
|
6
|
+
import { clampSplit } from '../../split/core/split.ts';
|
|
7
|
+
import { SplitRow } from '../../split/ui/SplitRow.tsx';
|
|
8
|
+
import type { AttemptStore } from '../core/attempts.ts';
|
|
9
|
+
import { attemptKey } from '../core/attempts.ts';
|
|
10
|
+
import { progressSummary } from '../core/progress.ts';
|
|
11
|
+
import type { PickSeededOptions, SeedRange } from '../core/seed.ts';
|
|
12
|
+
import { freshSeed, pickSeeded } from '../core/seed.ts';
|
|
13
|
+
import type { SeriesTool } from '../core/series.ts';
|
|
14
|
+
|
|
15
|
+
import { ExerciseProgressHeader } from './ExerciseProgressHeader.tsx';
|
|
16
|
+
import type { QuestionRendering } from './QuestionCard.tsx';
|
|
17
|
+
import { QuestionCard } from './QuestionCard.tsx';
|
|
18
|
+
import { SeriesHead, SeriesQuestionList } from './seriesParts.tsx';
|
|
19
|
+
import { useAttemptRecords } from './useAttemptRecords.ts';
|
|
20
|
+
|
|
21
|
+
/** What {@link ExerciseSeries} needs. */
|
|
22
|
+
export interface ExerciseSeriesProps<
|
|
23
|
+
Question,
|
|
24
|
+
> extends QuestionRendering<Question> {
|
|
25
|
+
/** The tool the series is drawn from. */
|
|
26
|
+
tool: SeriesTool<Question>;
|
|
27
|
+
/** Where the attempts, and the series each tool was last on, are kept. */
|
|
28
|
+
store: AttemptStore;
|
|
29
|
+
/** How a question is named in the list beside the card. */
|
|
30
|
+
label: (question: Question) => ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* The seed the address names, read with `parseSeed`, or `null` when it names
|
|
33
|
+
* none — the series this browser was last on is then written into it.
|
|
34
|
+
*/
|
|
35
|
+
seed: number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Write a seed into the address: `replace` when the page adopts one it was
|
|
38
|
+
* opened without, `push` when the student draws a new series, so Back
|
|
39
|
+
* returns to the previous one.
|
|
40
|
+
*/
|
|
41
|
+
onSeed: (seed: number, history: 'replace' | 'push') => void;
|
|
42
|
+
/** How many questions the student asked for; the pool caps it. */
|
|
43
|
+
count: number;
|
|
44
|
+
/** Change how many questions the series holds. */
|
|
45
|
+
onCount: (count: number) => void;
|
|
46
|
+
/**
|
|
47
|
+
* The lengths offered, beside the whole pool.
|
|
48
|
+
* @default [5, 10, 15, 20]
|
|
49
|
+
*/
|
|
50
|
+
counts?: readonly number[];
|
|
51
|
+
/**
|
|
52
|
+
* The share of the row the list takes, as the address names it, or `null`
|
|
53
|
+
* for {@link ExerciseSeriesProps.defaultSplit}.
|
|
54
|
+
*/
|
|
55
|
+
split: number | null;
|
|
56
|
+
/** Write the share a drag came to rest at, or `null` for the default. */
|
|
57
|
+
onSplit: (split: number | null) => void;
|
|
58
|
+
/**
|
|
59
|
+
* The share the list opens at: the card is the figure, so the list opens
|
|
60
|
+
* narrow.
|
|
61
|
+
* @default 22
|
|
62
|
+
*/
|
|
63
|
+
defaultSplit?: number;
|
|
64
|
+
/**
|
|
65
|
+
* How far the list may be dragged, measured on the labels of the pool.
|
|
66
|
+
* @default { min: 15, max: 26 }
|
|
67
|
+
*/
|
|
68
|
+
splitRange?: SplitRange;
|
|
69
|
+
/**
|
|
70
|
+
* The narrowest row still divided, in pixels; under it the list stacks under
|
|
71
|
+
* the card's row, as on a tablet held upright.
|
|
72
|
+
* @default SplitRow's own, 860
|
|
73
|
+
*/
|
|
74
|
+
stackBelow?: number;
|
|
75
|
+
/**
|
|
76
|
+
* How the series is drawn from the pool, for a site whose links were written
|
|
77
|
+
* with the other generator call.
|
|
78
|
+
* @default { draw: 'getFloat', wrap: false }
|
|
79
|
+
*/
|
|
80
|
+
pick?: PickSeededOptions;
|
|
81
|
+
/**
|
|
82
|
+
* The seeds a new series is drawn from.
|
|
83
|
+
* @default every unsigned 32-bit integer
|
|
84
|
+
*/
|
|
85
|
+
seedRange?: SeedRange;
|
|
86
|
+
/**
|
|
87
|
+
* The heading over the series.
|
|
88
|
+
* @default the chrome's own word for it, in the language of the page
|
|
89
|
+
*/
|
|
90
|
+
title?: ReactNode;
|
|
91
|
+
/**
|
|
92
|
+
* Id of the heading, for a section labelled by it.
|
|
93
|
+
* @default 'questions-heading'
|
|
94
|
+
*/
|
|
95
|
+
headingId?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const DEFAULT_RANGE: SplitRange = { min: 15, max: 26 };
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A graded series drawn from a tool's pool: the list of its questions, the one
|
|
102
|
+
* open, and the progress over the series.
|
|
103
|
+
*
|
|
104
|
+
* The series is its seed, and the seed is in the address, so the link on
|
|
105
|
+
* screen reopens the same questions for anyone — a teacher hands one series to
|
|
106
|
+
* a whole class. How the row is divided between the list and the open question
|
|
107
|
+
* travels in the address too. The component reads neither: the page hands it
|
|
108
|
+
* the seed, the length and the share, and is told when one changes.
|
|
109
|
+
* @param props - The tool, where its attempts are kept, the address values,
|
|
110
|
+
* and how to draw a question.
|
|
111
|
+
* @returns The series.
|
|
112
|
+
*/
|
|
113
|
+
export function ExerciseSeries<Question>(
|
|
114
|
+
props: ExerciseSeriesProps<Question>,
|
|
115
|
+
): ReactElement {
|
|
116
|
+
const {
|
|
117
|
+
tool,
|
|
118
|
+
store,
|
|
119
|
+
label,
|
|
120
|
+
seed: addressSeed,
|
|
121
|
+
onSeed,
|
|
122
|
+
count: wanted,
|
|
123
|
+
onCount,
|
|
124
|
+
counts,
|
|
125
|
+
split,
|
|
126
|
+
onSplit,
|
|
127
|
+
defaultSplit = 22,
|
|
128
|
+
splitRange = DEFAULT_RANGE,
|
|
129
|
+
stackBelow,
|
|
130
|
+
pick,
|
|
131
|
+
seedRange,
|
|
132
|
+
title,
|
|
133
|
+
headingId = 'questions-heading',
|
|
134
|
+
...rendering
|
|
135
|
+
} = props;
|
|
136
|
+
const records = useAttemptRecords(store);
|
|
137
|
+
const [fallbackSeed] = useState(
|
|
138
|
+
() => store.storedSeed(tool.id) ?? freshSeed(seedRange),
|
|
139
|
+
);
|
|
140
|
+
const seed = addressSeed ?? fallbackSeed;
|
|
141
|
+
const poolSize = tool.maxCount ?? tool.pool.length;
|
|
142
|
+
const count = Math.min(wanted, poolSize);
|
|
143
|
+
|
|
144
|
+
// The address is what the series is read from and handed out as, so a page
|
|
145
|
+
// opened without a seed writes the one it fell back on into it; and the
|
|
146
|
+
// series on screen is the one this tool reopens on the next visit.
|
|
147
|
+
const followSeed = useEffectEvent((): void => {
|
|
148
|
+
if (addressSeed === null) onSeed(fallbackSeed, 'replace');
|
|
149
|
+
store.storeSeed(tool.id, addressSeed ?? fallbackSeed);
|
|
150
|
+
});
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
followSeed();
|
|
153
|
+
}, [seed, addressSeed, tool.id]);
|
|
154
|
+
|
|
155
|
+
const series = useMemo(
|
|
156
|
+
() =>
|
|
157
|
+
tool.draw === undefined
|
|
158
|
+
? pickSeeded(tool.pool, count, seed, pick)
|
|
159
|
+
: tool.draw(count, seed),
|
|
160
|
+
[tool, count, seed, pick],
|
|
161
|
+
);
|
|
162
|
+
const keys = useMemo(
|
|
163
|
+
() =>
|
|
164
|
+
series.map((question) => attemptKey(tool.id, seed, tool.key(question))),
|
|
165
|
+
[series, tool, seed],
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const [opened, setOpened] = useState({ seed, index: 0 });
|
|
169
|
+
const index =
|
|
170
|
+
opened.seed === seed ? Math.min(opened.index, series.length - 1) : 0;
|
|
171
|
+
function open(next: number): void {
|
|
172
|
+
setOpened({ seed, index: next });
|
|
173
|
+
}
|
|
174
|
+
const listRef = useDocumentListNavigation<HTMLElement>({
|
|
175
|
+
length: series.length,
|
|
176
|
+
selectedIndex: index,
|
|
177
|
+
onSelect: open,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const summary = progressSummary(records, keys);
|
|
181
|
+
const question = series[index];
|
|
182
|
+
const storageKey = keys[index];
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div className="exercise-series">
|
|
186
|
+
<SeriesHead
|
|
187
|
+
title={title}
|
|
188
|
+
headingId={headingId}
|
|
189
|
+
count={count}
|
|
190
|
+
poolSize={poolSize}
|
|
191
|
+
counts={counts}
|
|
192
|
+
onCount={onCount}
|
|
193
|
+
onNewSeries={() => {
|
|
194
|
+
const next = freshSeed(seedRange);
|
|
195
|
+
store.forgetOtherSeries(tool.id, next);
|
|
196
|
+
onSeed(next, 'push');
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
|
|
200
|
+
<ExerciseProgressHeader
|
|
201
|
+
summary={summary}
|
|
202
|
+
onClearAll={() => {
|
|
203
|
+
store.clearAttempts(keys);
|
|
204
|
+
}}
|
|
205
|
+
clearDisabled={!keys.some((key) => records[key] !== undefined)}
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
<SplitRow
|
|
209
|
+
ratio={split === null ? null : clampSplit(split, splitRange)}
|
|
210
|
+
defaultRatio={defaultSplit}
|
|
211
|
+
min={splitRange.min}
|
|
212
|
+
max={splitRange.max}
|
|
213
|
+
stackBelow={stackBelow}
|
|
214
|
+
onRatio={onSplit}
|
|
215
|
+
start={
|
|
216
|
+
<SeriesQuestionList
|
|
217
|
+
series={series}
|
|
218
|
+
keys={keys}
|
|
219
|
+
records={records}
|
|
220
|
+
index={index}
|
|
221
|
+
onOpen={open}
|
|
222
|
+
label={label}
|
|
223
|
+
listRef={listRef}
|
|
224
|
+
/>
|
|
225
|
+
}
|
|
226
|
+
end={
|
|
227
|
+
question === undefined || storageKey === undefined ? null : (
|
|
228
|
+
<QuestionCard
|
|
229
|
+
key={storageKey}
|
|
230
|
+
tool={tool}
|
|
231
|
+
question={question}
|
|
232
|
+
store={store}
|
|
233
|
+
storageKey={storageKey}
|
|
234
|
+
number={index + 1}
|
|
235
|
+
total={series.length}
|
|
236
|
+
onNext={
|
|
237
|
+
index + 1 < series.length
|
|
238
|
+
? () => {
|
|
239
|
+
open(index + 1);
|
|
240
|
+
}
|
|
241
|
+
: undefined
|
|
242
|
+
}
|
|
243
|
+
{...rendering}
|
|
244
|
+
/>
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Button, Callout, Tag } from '@blueprintjs/core';
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
6
|
+
import { useIsHidden } from '../../share/ui/hiddenParts.ts';
|
|
7
|
+
import type { AttemptStore } from '../core/attempts.ts';
|
|
8
|
+
import { emptyAttempt } from '../core/attempts.ts';
|
|
9
|
+
import type { Answers, FieldCase, SeriesTool } from '../core/series.ts';
|
|
10
|
+
|
|
11
|
+
import { AnswerFields } from './AnswerFields.tsx';
|
|
12
|
+
import { ExerciseActions } from './ExerciseActions.tsx';
|
|
13
|
+
import { GlossaryText } from './GlossaryText.tsx';
|
|
14
|
+
import { HintLadder } from './HintLadder.tsx';
|
|
15
|
+
import { TestCaseList } from './TestCaseList.tsx';
|
|
16
|
+
import { LEVEL_INTENT } from './exerciseMeta.ts';
|
|
17
|
+
import { useAttemptRecords } from './useAttemptRecords.ts';
|
|
18
|
+
|
|
19
|
+
/** What a tool's own answer input is handed. */
|
|
20
|
+
export interface AnswerInputProps<Question> {
|
|
21
|
+
/** The question being answered. */
|
|
22
|
+
question: Question;
|
|
23
|
+
/** What is typed so far, field by field. */
|
|
24
|
+
answers: Answers;
|
|
25
|
+
/** Change one field. */
|
|
26
|
+
onChange: (key: string, value: string) => void;
|
|
27
|
+
/** Check the answer, as Enter does in a box. */
|
|
28
|
+
onSubmit: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** How one tool draws the parts of a question the card does not know. */
|
|
32
|
+
export interface QuestionRendering<Question> {
|
|
33
|
+
/** The statement of the question. */
|
|
34
|
+
prompt: (question: Question) => ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* An input of the tool's own — a drawing canvas, a reaction to balance — in
|
|
37
|
+
* place of the typed boxes.
|
|
38
|
+
* @default undefined — one box per field of the tool
|
|
39
|
+
*/
|
|
40
|
+
answer?: (props: AnswerInputProps<Question>) => ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* What the solution shows beyond its text, such as a drawn structure.
|
|
43
|
+
* @default undefined
|
|
44
|
+
*/
|
|
45
|
+
solution?: (question: Question) => ReactNode;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** What {@link QuestionCard} needs. */
|
|
49
|
+
export interface QuestionCardProps<
|
|
50
|
+
Question,
|
|
51
|
+
> extends QuestionRendering<Question> {
|
|
52
|
+
/** The tool the question belongs to. */
|
|
53
|
+
tool: SeriesTool<Question>;
|
|
54
|
+
/** The question on the card. */
|
|
55
|
+
question: Question;
|
|
56
|
+
/** Where the attempts are kept. */
|
|
57
|
+
store: AttemptStore;
|
|
58
|
+
/** The key this question's attempt is kept under, from `attemptKey`. */
|
|
59
|
+
storageKey: string;
|
|
60
|
+
/** Its position in the series, counted from 1. */
|
|
61
|
+
number: number;
|
|
62
|
+
/** How many questions the series holds. */
|
|
63
|
+
total: number;
|
|
64
|
+
/**
|
|
65
|
+
* Open the next question; left out on the last one.
|
|
66
|
+
* @default undefined
|
|
67
|
+
*/
|
|
68
|
+
onNext?: () => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* One question: its statement, the answer, the four actions, and — once
|
|
73
|
+
* checked — the verdict case by case, the hints taken and the solution.
|
|
74
|
+
*
|
|
75
|
+
* The answer is graded on every change, but the verdict is shown only for the
|
|
76
|
+
* answer as it was checked: a numeric answer that turned green as it was typed
|
|
77
|
+
* could be found by trying numbers. A link that hides the `solutions` part
|
|
78
|
+
* takes the solution button away.
|
|
79
|
+
* @param props - The tool, the question, where its attempt is kept, and how to
|
|
80
|
+
* draw it.
|
|
81
|
+
* @returns The card.
|
|
82
|
+
*/
|
|
83
|
+
export function QuestionCard<Question>(
|
|
84
|
+
props: QuestionCardProps<Question>,
|
|
85
|
+
): ReactElement {
|
|
86
|
+
const {
|
|
87
|
+
tool,
|
|
88
|
+
question,
|
|
89
|
+
store,
|
|
90
|
+
storageKey,
|
|
91
|
+
number,
|
|
92
|
+
total,
|
|
93
|
+
onNext,
|
|
94
|
+
prompt,
|
|
95
|
+
answer,
|
|
96
|
+
solution,
|
|
97
|
+
} = props;
|
|
98
|
+
const t = useChromeT();
|
|
99
|
+
const isHidden = useIsHidden();
|
|
100
|
+
const records = useAttemptRecords(store);
|
|
101
|
+
const {
|
|
102
|
+
answers,
|
|
103
|
+
checked: checkedAnswers,
|
|
104
|
+
hintsRevealed,
|
|
105
|
+
showSolution,
|
|
106
|
+
} = records[storageKey] ?? emptyAttempt();
|
|
107
|
+
const check = useMemo(
|
|
108
|
+
() => tool.grade(question, answers),
|
|
109
|
+
[tool, question, answers],
|
|
110
|
+
);
|
|
111
|
+
const hints = useMemo(() => tool.hints(question), [tool, question]);
|
|
112
|
+
const snapshot = JSON.stringify(answers);
|
|
113
|
+
const checked = checkedAnswers === snapshot;
|
|
114
|
+
const solutionsHidden = isHidden('solutions');
|
|
115
|
+
const level = tool.level?.(question);
|
|
116
|
+
|
|
117
|
+
function setAnswer(key: string, value: string): void {
|
|
118
|
+
store.updateAttempt(storageKey, { answers: { ...answers, [key]: value } });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function commit(): void {
|
|
122
|
+
store.updateAttempt(storageKey, {
|
|
123
|
+
status: check.passed ? 'solved' : 'attempted',
|
|
124
|
+
checked: snapshot,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<article
|
|
130
|
+
className="series-question"
|
|
131
|
+
aria-label={t('pedagogy.questionNumber', { number })}
|
|
132
|
+
>
|
|
133
|
+
<div className="series-question__meta">
|
|
134
|
+
<span>{t('pedagogy.questionOf', { number, total })}</span>
|
|
135
|
+
{level === undefined ? null : (
|
|
136
|
+
<Tag minimal intent={LEVEL_INTENT[level]}>
|
|
137
|
+
{level}
|
|
138
|
+
</Tag>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
<div className="series-question__prompt">{prompt(question)}</div>
|
|
142
|
+
|
|
143
|
+
{answer ? (
|
|
144
|
+
answer({ question, answers, onChange: setAnswer, onSubmit: commit })
|
|
145
|
+
) : (
|
|
146
|
+
<AnswerFields
|
|
147
|
+
fields={tool.fields(question)}
|
|
148
|
+
answers={answers}
|
|
149
|
+
onChange={setAnswer}
|
|
150
|
+
onSubmit={commit}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
<ExerciseActions
|
|
155
|
+
onCheck={commit}
|
|
156
|
+
checkDisabled={isBlank(answers)}
|
|
157
|
+
onRevealHint={() => {
|
|
158
|
+
store.updateAttempt(storageKey, {
|
|
159
|
+
hintsRevealed: Math.min(hintsRevealed + 1, hints.length),
|
|
160
|
+
});
|
|
161
|
+
}}
|
|
162
|
+
hintsRevealed={hintsRevealed}
|
|
163
|
+
hintCount={hints.length}
|
|
164
|
+
onToggleSolution={
|
|
165
|
+
solutionsHidden
|
|
166
|
+
? undefined
|
|
167
|
+
: () => {
|
|
168
|
+
store.updateAttempt(storageKey, {
|
|
169
|
+
showSolution: !showSolution,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
showSolution={showSolution}
|
|
174
|
+
onReset={() => {
|
|
175
|
+
store.updateAttempt(storageKey, emptyAttempt());
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
{checked ? (
|
|
180
|
+
<TestCaseList<FieldCase>
|
|
181
|
+
results={check.cases}
|
|
182
|
+
label={(verdict) => verdict.label}
|
|
183
|
+
/>
|
|
184
|
+
) : null}
|
|
185
|
+
|
|
186
|
+
{checked && check.passed ? (
|
|
187
|
+
<Callout
|
|
188
|
+
intent="success"
|
|
189
|
+
icon="tick-circle"
|
|
190
|
+
title={t('pedagogy.solved')}
|
|
191
|
+
>
|
|
192
|
+
{hintsRevealed > 0
|
|
193
|
+
? t('pedagogy.solvedWithHintsOf', {
|
|
194
|
+
revealed: hintsRevealed,
|
|
195
|
+
total: hints.length,
|
|
196
|
+
})
|
|
197
|
+
: t('pedagogy.solvedWithoutHint')}
|
|
198
|
+
{onNext ? (
|
|
199
|
+
<Button
|
|
200
|
+
className="series-question__next"
|
|
201
|
+
intent="success"
|
|
202
|
+
endIcon="arrow-right"
|
|
203
|
+
text={t('pedagogy.nextQuestion')}
|
|
204
|
+
onClick={onNext}
|
|
205
|
+
/>
|
|
206
|
+
) : null}
|
|
207
|
+
</Callout>
|
|
208
|
+
) : null}
|
|
209
|
+
|
|
210
|
+
{hintsRevealed > 0 ? (
|
|
211
|
+
<HintLadder hints={hints} revealed={hintsRevealed} />
|
|
212
|
+
) : null}
|
|
213
|
+
|
|
214
|
+
{showSolution && !solutionsHidden ? (
|
|
215
|
+
<Callout intent="warning" icon="key" title={t('pedagogy.solution')}>
|
|
216
|
+
<p className="series-question__solution">
|
|
217
|
+
<GlossaryText text={tool.solution(question)} />
|
|
218
|
+
</p>
|
|
219
|
+
{solution?.(question)}
|
|
220
|
+
</Callout>
|
|
221
|
+
) : null}
|
|
222
|
+
</article>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isBlank(answers: Answers): boolean {
|
|
227
|
+
for (const value of Object.values(answers)) {
|
|
228
|
+
if (value.trim() !== '') return false;
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
package/src/pedagogy/ui/index.ts
CHANGED
|
@@ -51,3 +51,21 @@ export type {
|
|
|
51
51
|
export { TutorialStepStrip } from './TutorialStepStrip.tsx';
|
|
52
52
|
export type { TutorialLevelColours } from './tutorialLevels.ts';
|
|
53
53
|
export { TUTORIAL_LEVEL_COLOURS } from './tutorialLevels.ts';
|
|
54
|
+
export type { AnswerFieldsProps } from './AnswerFields.tsx';
|
|
55
|
+
export { AnswerFields } from './AnswerFields.tsx';
|
|
56
|
+
export type {
|
|
57
|
+
ExerciseListEntry,
|
|
58
|
+
ExerciseListProgress,
|
|
59
|
+
ExerciseListProps,
|
|
60
|
+
} from './ExerciseList.tsx';
|
|
61
|
+
export { ExerciseList } from './ExerciseList.tsx';
|
|
62
|
+
export type { ExerciseSeriesProps } from './ExerciseSeries.tsx';
|
|
63
|
+
export { ExerciseSeries } from './ExerciseSeries.tsx';
|
|
64
|
+
export type {
|
|
65
|
+
AnswerInputProps,
|
|
66
|
+
QuestionCardProps,
|
|
67
|
+
QuestionRendering,
|
|
68
|
+
} from './QuestionCard.tsx';
|
|
69
|
+
export { QuestionCard } from './QuestionCard.tsx';
|
|
70
|
+
export { SERIES_COUNTS } from './seriesCounts.ts';
|
|
71
|
+
export { useAttemptRecords } from './useAttemptRecords.ts';
|