@olinfo/quizms-training 5.0.0 → 5.0.2

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.
@@ -1,110 +1,122 @@
1
- import { jsx as u } from "react/jsx-runtime";
2
- import { useState as V, useMemo as v, useCallback as r } from "react";
3
- import { TitleProvider as j } from "@olinfo/quizms/components";
4
- import { variantSchema as A } from "@olinfo/quizms/models";
5
- import { StudentProvider as D } from "@olinfo/quizms/student";
6
- import { Rng as k, validate as x } from "@olinfo/quizms/utils";
7
- import { addMinutes as R, subSeconds as C } from "date-fns";
8
- import f, { preload as E } from "swr";
9
- import { getTitleIframe as M, getStudentIframe as $, setAnswersIframe as z, submitIframe as F, resetIframe as N, logoutIframe as O, startIframe as U } from "./iframe.js";
10
- import { TrainingStatementContext as q } from "./statement.js";
11
- function B({
12
- contest: a,
13
- children: c
1
+ import { jsx as f } from "react/jsx-runtime";
2
+ import { useState as D, useMemo as v, useEffect as P, useCallback as o } from "react";
3
+ import { TitleProvider as R } from "@olinfo/quizms/components";
4
+ import { variantSchema as V } from "@olinfo/quizms/models";
5
+ import { StudentProvider as j } from "@olinfo/quizms/student";
6
+ import { Rng as E, validate as k } from "@olinfo/quizms/utils";
7
+ import { isFuture as x, addMinutes as C, subSeconds as F } from "date-fns";
8
+ import l, { preload as M } from "swr";
9
+ import { getTitleIframe as $, getStudentIframe as z, setAnswersIframe as L, submitIframe as N, resetIframe as O, logoutIframe as U, startIframe as q } from "./iframe.js";
10
+ import { TrainingStatementContext as B } from "./statement.js";
11
+ function G({
12
+ contest: t,
13
+ children: m
14
14
  }) {
15
- const { data: d } = f("getTitle", M, { suspense: !0 }), { data: o, mutate: l } = f(["student", a.id], $, {
15
+ const { data: d } = l("getTitle", $, { suspense: !0 }), { data: s, mutate: p } = l(["student", t.id], z, {
16
16
  suspense: !0
17
- }), [p, I] = V(
18
- G(a)
19
- ), { student: s, type: w } = v(
20
- () => o ? { type: "iframe", student: o } : { type: "anonymous", student: p },
21
- [o, p]
22
- ), { data: m } = f(
23
- s.variantId && ["variant", a.id, s.variantId],
24
- y,
17
+ }), [w, y] = D(
18
+ H(t)
19
+ ), { student: r, type: u } = v(
20
+ () => s ? { type: "iframe", student: s } : { type: "anonymous", student: w },
21
+ [s, w]
22
+ );
23
+ P(() => {
24
+ if (u !== "anonymous" || !(r.participationWindow && x(r.participationWindow.end))) return;
25
+ const i = new AbortController();
26
+ return window.addEventListener(
27
+ "beforeunload",
28
+ (a) => {
29
+ a.preventDefault();
30
+ },
31
+ { signal: i.signal }
32
+ ), () => i.abort();
33
+ }, [u, r.participationWindow]);
34
+ const { data: c } = l(
35
+ r.variantId && ["variant", t.id, r.variantId],
36
+ g,
25
37
  {
26
38
  revalidateOnFocus: !1,
27
39
  revalidateIfStale: !1
28
40
  }
29
- ), n = r(
30
- async (t, i) => {
31
- w === "anonymous" ? I(i) : await l((e) => e ? t(i(e), a) : null, {
32
- optimisticData: (e) => e ? i(e) : null
41
+ ), e = o(
42
+ async (n, i) => {
43
+ u === "anonymous" ? y(i) : await p((a) => a ? n(i(a), t) : null, {
44
+ optimisticData: (a) => a ? i(a) : null
33
45
  });
34
46
  },
35
- [w, a, l]
36
- ), S = v(
47
+ [u, t, p]
48
+ ), I = v(
37
49
  () => ({
38
50
  id: "",
39
51
  schoolId: "",
40
- contestId: a.id,
52
+ contestId: t.id,
41
53
  name: "",
42
- participationWindow: s.participationWindow,
54
+ participationWindow: r.participationWindow,
43
55
  finalized: !1,
44
56
  disabled: !1,
45
57
  pdfVariants: []
46
58
  }),
47
- [a.id, s.participationWindow]
48
- ), g = r(
49
- async (t, i) => {
50
- if (!m) throw new Error("Variant non loaded");
51
- await n(
52
- z(m),
53
- (e) => ({ ...e, answers: { ...e.answers, [t]: i } })
59
+ [t.id, r.participationWindow]
60
+ ), S = o(
61
+ async (n, i) => {
62
+ if (!c) throw new Error("Variant non loaded");
63
+ await e(
64
+ L(c),
65
+ (a) => ({ ...a, answers: { ...a.answers, [n]: i } })
54
66
  );
55
67
  },
56
- [n, m]
57
- ), h = r(async () => {
58
- await n(
59
- F,
60
- (t) => ({
61
- ...t,
62
- participationWindow: { start: t.participationWindow.start, end: /* @__PURE__ */ new Date() }
68
+ [e, c]
69
+ ), h = o(async () => {
70
+ await e(
71
+ N,
72
+ (n) => ({
73
+ ...n,
74
+ participationWindow: { start: n.participationWindow.start, end: /* @__PURE__ */ new Date() }
63
75
  })
64
76
  );
65
- }, [n]), W = r(async () => {
66
- await n(
67
- N,
68
- (t) => ({
69
- ...t,
77
+ }, [e]), W = o(async () => {
78
+ await e(
79
+ O,
80
+ (n) => ({
81
+ ...n,
70
82
  answers: {},
71
83
  participationWindow: void 0
72
84
  })
73
85
  );
74
- }, [n]), T = r(async () => {
75
- await n(O, (t) => t);
76
- }, [n]), b = r(async () => {
77
- const t = /* @__PURE__ */ new Date(), i = J(a), e = await E(["variant", a.id, i], y);
78
- await n(
79
- U(e),
80
- (P) => ({
81
- ...P,
86
+ }, [e]), b = o(async () => {
87
+ await e(U, (n) => n);
88
+ }, [e]), T = o(async () => {
89
+ const n = /* @__PURE__ */ new Date(), i = K(t), a = await M(["variant", t.id, i], g);
90
+ await e(
91
+ q(a),
92
+ (A) => ({
93
+ ...A,
82
94
  variantId: i,
83
95
  participationWindow: {
84
- start: C(t, 2),
85
- end: R(t, a.onlineSettings?.duration ?? 0)
96
+ start: F(n, 2),
97
+ end: C(n, t.onlineSettings?.duration ?? 0)
86
98
  }
87
99
  })
88
100
  );
89
- }, [a, n]);
90
- return /* @__PURE__ */ u(j, { title: d, children: /* @__PURE__ */ u(q.Provider, { value: { start: b }, children: /* @__PURE__ */ u(
91
- D,
101
+ }, [t, e]);
102
+ return /* @__PURE__ */ f(R, { title: d, children: /* @__PURE__ */ f(B.Provider, { value: { start: T }, children: /* @__PURE__ */ f(
103
+ j,
92
104
  {
93
- contest: a,
94
- venue: S,
95
- student: s,
96
- setAnswer: g,
105
+ contest: t,
106
+ venue: I,
107
+ student: r,
108
+ setAnswer: S,
97
109
  submit: h,
98
110
  reset: W,
99
- logout: T,
111
+ logout: b,
100
112
  enforceFullscreen: !1,
101
- schema: m?.schema,
102
- children: c
113
+ schema: c?.schema,
114
+ children: m
103
115
  }
104
116
  ) }) });
105
117
  }
106
- B.displayName = "TrainingProvider";
107
- function G(a) {
118
+ G.displayName = "TrainingProvider";
119
+ function H(t) {
108
120
  return {
109
121
  id: "",
110
122
  userData: {
@@ -112,20 +124,20 @@ function G(a) {
112
124
  surname: "anonimo"
113
125
  },
114
126
  answers: {},
115
- contestId: a.id
127
+ contestId: t.id
116
128
  };
117
129
  }
118
- const H = new k();
119
- function J(a) {
120
- return H.choice(a.variantIds);
130
+ const J = new E();
131
+ function K(t) {
132
+ return J.choice(t.variantIds);
121
133
  }
122
- async function y([, a, c]) {
123
- const d = await fetch(`/variants/${a}/${c}/answers.json`);
134
+ async function g([, t, m]) {
135
+ const d = await fetch(`/variants/${t}/${m}/answers.json`);
124
136
  if (!d.ok) throw new Error("Failed to fetch schema");
125
- const o = await d.json();
126
- return x(A, o);
137
+ const s = await d.json();
138
+ return k(V, s);
127
139
  }
128
140
  export {
129
- B as TrainingProvider
141
+ G as TrainingProvider
130
142
  };
131
143
  //# sourceMappingURL=provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sources":["../../src/entry/provider.tsx"],"sourcesContent":["import { type ReactNode, useCallback, useMemo, useState } from \"react\";\n\nimport { TitleProvider } from \"@olinfo/quizms/components\";\nimport {\n type Answer,\n type Contest,\n type Student,\n type Variant,\n type VariantsConfig,\n type Venue,\n variantSchema,\n} from \"@olinfo/quizms/models\";\nimport { StudentProvider } from \"@olinfo/quizms/student\";\nimport { Rng, validate } from \"@olinfo/quizms/utils\";\nimport { addMinutes, subSeconds } from \"date-fns\";\nimport useSWR, { preload } from \"swr\";\n\nimport {\n getStudentIframe,\n getTitleIframe,\n logoutIframe,\n resetIframe,\n setAnswersIframe,\n startIframe,\n submitIframe,\n} from \"./iframe\";\nimport { TrainingStatementContext } from \"./statement\";\n\nexport function TrainingProvider({\n contest,\n children,\n}: {\n contest: Contest & VariantsConfig;\n children: ReactNode;\n}) {\n const { data: title } = useSWR(\"getTitle\", getTitleIframe, { suspense: true });\n\n const { data: iframeStudent, mutate } = useSWR([\"student\", contest.id], getStudentIframe, {\n suspense: true,\n });\n const [anonymousStudent, setAnonymousStudent] = useState<Student>(\n createAnonymousStudent(contest),\n );\n\n const { student, type } = useMemo(\n () =>\n iframeStudent\n ? { type: \"iframe\", student: iframeStudent }\n : { type: \"anonymous\", student: anonymousStudent },\n [iframeStudent, anonymousStudent],\n );\n\n const { data: variant } = useSWR(\n student.variantId && [\"variant\", contest.id, student.variantId],\n getVariant,\n {\n revalidateOnFocus: false,\n revalidateIfStale: false,\n },\n );\n\n const updateStudent = useCallback(\n async (\n iframeFn: (student: Student, contest: Contest) => Promise<Student | null>,\n updateFn: (student: Student) => Student,\n ) => {\n if (type === \"anonymous\") {\n setAnonymousStudent(updateFn);\n } else {\n await mutate((student) => (student ? iframeFn(updateFn(student), contest) : null), {\n optimisticData: (student) => (student ? updateFn(student) : null),\n });\n }\n },\n [type, contest, mutate],\n );\n\n const mockVenue: Venue = useMemo(\n () => ({\n id: \"\",\n schoolId: \"\",\n contestId: contest.id,\n name: \"\",\n participationWindow: student.participationWindow,\n finalized: false,\n disabled: false,\n pdfVariants: [],\n }),\n [contest.id, student.participationWindow],\n );\n\n const setAnswer = useCallback(\n async (problemId: string, answer: Answer) => {\n if (!variant) throw new Error(\"Variant non loaded\");\n await updateStudent(\n setAnswersIframe(variant),\n (student) =>\n ({ ...student, answers: { ...student.answers, [problemId]: answer } }) satisfies Student,\n );\n },\n [updateStudent, variant],\n );\n\n const submit = useCallback(async () => {\n await updateStudent(\n submitIframe,\n (student) =>\n ({\n ...student,\n participationWindow: { start: student.participationWindow!.start, end: new Date() },\n }) satisfies Student,\n );\n }, [updateStudent]);\n\n const reset = useCallback(async () => {\n await updateStudent(\n resetIframe,\n (student) =>\n ({\n ...student,\n answers: {},\n participationWindow: undefined,\n }) satisfies Student,\n );\n }, [updateStudent]);\n\n const logout = useCallback(async () => {\n await updateStudent(logoutIframe, (student) => student);\n }, [updateStudent]);\n\n const start = useCallback(async () => {\n const now = new Date();\n const variantId = getRandomVariant(contest);\n const variant = await preload([\"variant\", contest.id, variantId], getVariant);\n await updateStudent(\n startIframe(variant),\n (student) =>\n ({\n ...student,\n variantId,\n participationWindow: {\n start: subSeconds(now, 2),\n end: addMinutes(now, contest.onlineSettings?.duration ?? 0),\n },\n }) satisfies Student,\n );\n }, [contest, updateStudent]);\n\n return (\n <TitleProvider title={title}>\n <TrainingStatementContext.Provider value={{ start }}>\n <StudentProvider\n contest={contest}\n venue={mockVenue}\n student={student}\n setAnswer={setAnswer}\n submit={submit}\n reset={reset}\n logout={logout}\n enforceFullscreen={false}\n schema={variant?.schema}>\n {children}\n </StudentProvider>\n </TrainingStatementContext.Provider>\n </TitleProvider>\n );\n}\nTrainingProvider.displayName = \"TrainingProvider\";\n\nfunction createAnonymousStudent(contest: VariantsConfig): Student {\n return {\n id: \"\",\n userData: {\n name: \"Utente\",\n surname: \"anonimo\",\n },\n answers: {},\n contestId: contest.id,\n };\n}\n\nconst rng = new Rng();\n\nfunction getRandomVariant(contest: VariantsConfig): string {\n return rng.choice(contest.variantIds);\n}\n\nasync function getVariant([, contestId, variantId]: [string, string, string]): Promise<Variant> {\n const resp = await fetch(`/variants/${contestId}/${variantId}/answers.json`);\n if (!resp.ok) throw new Error(\"Failed to fetch schema\");\n const json: unknown = await resp.json();\n return validate(variantSchema, json);\n}\n"],"names":["TrainingProvider","contest","children","title","useSWR","getTitleIframe","iframeStudent","mutate","getStudentIframe","anonymousStudent","setAnonymousStudent","useState","createAnonymousStudent","student","type","useMemo","variant","getVariant","updateStudent","useCallback","iframeFn","updateFn","mockVenue","setAnswer","problemId","answer","setAnswersIframe","submit","submitIframe","reset","resetIframe","logout","logoutIframe","start","now","variantId","getRandomVariant","preload","startIframe","subSeconds","addMinutes","jsx","TitleProvider","TrainingStatementContext","StudentProvider","rng","Rng","contestId","resp","json","validate","variantSchema"],"mappings":";;;;;;;;;;AA4BO,SAASA,EAAiB;AAAA,EAC/B,SAAAC;AAAA,EACA,UAAAC;AACF,GAGG;AACD,QAAM,EAAE,MAAMC,EAAA,IAAUC,EAAO,YAAYC,GAAgB,EAAE,UAAU,IAAM,GAEvE,EAAE,MAAMC,GAAe,QAAAC,MAAWH,EAAO,CAAC,WAAWH,EAAQ,EAAE,GAAGO,GAAkB;AAAA,IACxF,UAAU;AAAA,EAAA,CACX,GACK,CAACC,GAAkBC,CAAmB,IAAIC;AAAA,IAC9CC,EAAuBX,CAAO;AAAA,EAAA,GAG1B,EAAE,SAAAY,GAAS,MAAAC,EAAA,IAASC;AAAA,IACxB,MACET,IACI,EAAE,MAAM,UAAU,SAASA,EAAA,IAC3B,EAAE,MAAM,aAAa,SAASG,EAAA;AAAA,IACpC,CAACH,GAAeG,CAAgB;AAAA,EAAA,GAG5B,EAAE,MAAMO,EAAA,IAAYZ;AAAA,IACxBS,EAAQ,aAAa,CAAC,WAAWZ,EAAQ,IAAIY,EAAQ,SAAS;AAAA,IAC9DI;AAAA,IACA;AAAA,MACE,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,IAAA;AAAA,EACrB,GAGIC,IAAgBC;AAAA,IACpB,OACEC,GACAC,MACG;AACH,MAAIP,MAAS,cACXJ,EAAoBW,CAAQ,IAE5B,MAAMd,EAAO,CAACM,MAAaA,IAAUO,EAASC,EAASR,CAAO,GAAGZ,CAAO,IAAI,MAAO;AAAA,QACjF,gBAAgB,CAACY,MAAaA,IAAUQ,EAASR,CAAO,IAAI;AAAA,MAAA,CAC7D;AAAA,IAEL;AAAA,IACA,CAACC,GAAMb,GAASM,CAAM;AAAA,EAAA,GAGlBe,IAAmBP;AAAA,IACvB,OAAO;AAAA,MACL,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,WAAWd,EAAQ;AAAA,MACnB,MAAM;AAAA,MACN,qBAAqBY,EAAQ;AAAA,MAC7B,WAAW;AAAA,MACX,UAAU;AAAA,MACV,aAAa,CAAA;AAAA,IAAC;AAAA,IAEhB,CAACZ,EAAQ,IAAIY,EAAQ,mBAAmB;AAAA,EAAA,GAGpCU,IAAYJ;AAAA,IAChB,OAAOK,GAAmBC,MAAmB;AAC3C,UAAI,CAACT,EAAS,OAAM,IAAI,MAAM,oBAAoB;AAClD,YAAME;AAAA,QACJQ,EAAiBV,CAAO;AAAA,QACxB,CAACH,OACE,EAAE,GAAGA,GAAS,SAAS,EAAE,GAAGA,EAAQ,SAAS,CAACW,CAAS,GAAGC,IAAO;AAAA,MAAE;AAAA,IAE1E;AAAA,IACA,CAACP,GAAeF,CAAO;AAAA,EAAA,GAGnBW,IAASR,EAAY,YAAY;AACrC,UAAMD;AAAA,MACJU;AAAA,MACA,CAACf,OACE;AAAA,QACC,GAAGA;AAAAA,QACH,qBAAqB,EAAE,OAAOA,EAAQ,oBAAqB,OAAO,KAAK,oBAAI,KAAA,EAAK;AAAA,MAAE;AAAA,IACpF;AAAA,EAEN,GAAG,CAACK,CAAa,CAAC,GAEZW,IAAQV,EAAY,YAAY;AACpC,UAAMD;AAAA,MACJY;AAAA,MACA,CAACjB,OACE;AAAA,QACC,GAAGA;AAAAA,QACH,SAAS,CAAA;AAAA,QACT,qBAAqB;AAAA,MAAA;AAAA,IACvB;AAAA,EAEN,GAAG,CAACK,CAAa,CAAC,GAEZa,IAASZ,EAAY,YAAY;AACrC,UAAMD,EAAcc,GAAc,CAACnB,MAAYA,CAAO;AAAA,EACxD,GAAG,CAACK,CAAa,CAAC,GAEZe,IAAQd,EAAY,YAAY;AACpC,UAAMe,wBAAU,KAAA,GACVC,IAAYC,EAAiBnC,CAAO,GACpCe,IAAU,MAAMqB,EAAQ,CAAC,WAAWpC,EAAQ,IAAIkC,CAAS,GAAGlB,CAAU;AAC5E,UAAMC;AAAA,MACJoB,EAAYtB,CAAO;AAAA,MACnB,CAACH,OACE;AAAA,QACC,GAAGA;AAAAA,QACH,WAAAsB;AAAA,QACA,qBAAqB;AAAA,UACnB,OAAOI,EAAWL,GAAK,CAAC;AAAA,UACxB,KAAKM,EAAWN,GAAKjC,EAAQ,gBAAgB,YAAY,CAAC;AAAA,QAAA;AAAA,MAC5D;AAAA,IACF;AAAA,EAEN,GAAG,CAACA,GAASiB,CAAa,CAAC;AAE3B,SACE,gBAAAuB,EAACC,GAAA,EAAc,OAAAvC,GACb,UAAA,gBAAAsC,EAACE,EAAyB,UAAzB,EAAkC,OAAO,EAAE,OAAAV,EAAA,GAC1C,UAAA,gBAAAQ;AAAA,IAACG;AAAA,IAAA;AAAA,MACC,SAAA3C;AAAA,MACA,OAAOqB;AAAA,MACP,SAAAT;AAAA,MACA,WAAAU;AAAA,MACA,QAAAI;AAAA,MACA,OAAAE;AAAA,MACA,QAAAE;AAAA,MACA,mBAAmB;AAAA,MACnB,QAAQf,GAAS;AAAA,MAChB,UAAAd;AAAA,IAAA;AAAA,EAAA,GAEL,EAAA,CACF;AAEJ;AACAF,EAAiB,cAAc;AAE/B,SAASY,EAAuBX,GAAkC;AAChE,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,IAAA;AAAA,IAEX,SAAS,CAAA;AAAA,IACT,WAAWA,EAAQ;AAAA,EAAA;AAEvB;AAEA,MAAM4C,IAAM,IAAIC,EAAA;AAEhB,SAASV,EAAiBnC,GAAiC;AACzD,SAAO4C,EAAI,OAAO5C,EAAQ,UAAU;AACtC;AAEA,eAAegB,EAAW,CAAA,EAAG8B,GAAWZ,CAAS,GAA+C;AAC9F,QAAMa,IAAO,MAAM,MAAM,aAAaD,CAAS,IAAIZ,CAAS,eAAe;AAC3E,MAAI,CAACa,EAAK,GAAI,OAAM,IAAI,MAAM,wBAAwB;AACtD,QAAMC,IAAgB,MAAMD,EAAK,KAAA;AACjC,SAAOE,EAASC,GAAeF,CAAI;AACrC;"}
1
+ {"version":3,"file":"provider.js","sources":["../../src/entry/provider.tsx"],"sourcesContent":["import { type ReactNode, useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { TitleProvider } from \"@olinfo/quizms/components\";\nimport {\n type Answer,\n type Contest,\n type Student,\n type Variant,\n type VariantsConfig,\n type Venue,\n variantSchema,\n} from \"@olinfo/quizms/models\";\nimport { StudentProvider } from \"@olinfo/quizms/student\";\nimport { Rng, validate } from \"@olinfo/quizms/utils\";\nimport { addMinutes, isFuture, subSeconds } from \"date-fns\";\nimport useSWR, { preload } from \"swr\";\n\nimport {\n getStudentIframe,\n getTitleIframe,\n logoutIframe,\n resetIframe,\n setAnswersIframe,\n startIframe,\n submitIframe,\n} from \"./iframe\";\nimport { TrainingStatementContext } from \"./statement\";\n\nexport function TrainingProvider({\n contest,\n children,\n}: {\n contest: Contest & VariantsConfig;\n children: ReactNode;\n}) {\n const { data: title } = useSWR(\"getTitle\", getTitleIframe, { suspense: true });\n\n const { data: iframeStudent, mutate } = useSWR([\"student\", contest.id], getStudentIframe, {\n suspense: true,\n });\n const [anonymousStudent, setAnonymousStudent] = useState<Student>(\n createAnonymousStudent(contest),\n );\n\n const { student, type } = useMemo(\n () =>\n iframeStudent\n ? { type: \"iframe\", student: iframeStudent }\n : { type: \"anonymous\", student: anonymousStudent },\n [iframeStudent, anonymousStudent],\n );\n\n useEffect(() => {\n if (type !== \"anonymous\") return;\n\n const isRunning = student.participationWindow && isFuture(student.participationWindow.end);\n\n if (!isRunning) return;\n\n const controller = new AbortController();\n\n window.addEventListener(\n \"beforeunload\",\n (event) => {\n event.preventDefault();\n },\n { signal: controller.signal },\n );\n\n return () => controller.abort();\n }, [type, student.participationWindow]);\n\n const { data: variant } = useSWR(\n student.variantId && [\"variant\", contest.id, student.variantId],\n getVariant,\n {\n revalidateOnFocus: false,\n revalidateIfStale: false,\n },\n );\n\n const updateStudent = useCallback(\n async (\n iframeFn: (student: Student, contest: Contest) => Promise<Student | null>,\n updateFn: (student: Student) => Student,\n ) => {\n if (type === \"anonymous\") {\n setAnonymousStudent(updateFn);\n } else {\n await mutate((student) => (student ? iframeFn(updateFn(student), contest) : null), {\n optimisticData: (student) => (student ? updateFn(student) : null),\n });\n }\n },\n [type, contest, mutate],\n );\n\n const mockVenue: Venue = useMemo(\n () => ({\n id: \"\",\n schoolId: \"\",\n contestId: contest.id,\n name: \"\",\n participationWindow: student.participationWindow,\n finalized: false,\n disabled: false,\n pdfVariants: [],\n }),\n [contest.id, student.participationWindow],\n );\n\n const setAnswer = useCallback(\n async (problemId: string, answer: Answer) => {\n if (!variant) throw new Error(\"Variant non loaded\");\n await updateStudent(\n setAnswersIframe(variant),\n (student) =>\n ({ ...student, answers: { ...student.answers, [problemId]: answer } }) satisfies Student,\n );\n },\n [updateStudent, variant],\n );\n\n const submit = useCallback(async () => {\n await updateStudent(\n submitIframe,\n (student) =>\n ({\n ...student,\n participationWindow: { start: student.participationWindow!.start, end: new Date() },\n }) satisfies Student,\n );\n }, [updateStudent]);\n\n const reset = useCallback(async () => {\n await updateStudent(\n resetIframe,\n (student) =>\n ({\n ...student,\n answers: {},\n participationWindow: undefined,\n }) satisfies Student,\n );\n }, [updateStudent]);\n\n const logout = useCallback(async () => {\n await updateStudent(logoutIframe, (student) => student);\n }, [updateStudent]);\n\n const start = useCallback(async () => {\n const now = new Date();\n const variantId = getRandomVariant(contest);\n const variant = await preload([\"variant\", contest.id, variantId], getVariant);\n await updateStudent(\n startIframe(variant),\n (student) =>\n ({\n ...student,\n variantId,\n participationWindow: {\n start: subSeconds(now, 2),\n end: addMinutes(now, contest.onlineSettings?.duration ?? 0),\n },\n }) satisfies Student,\n );\n }, [contest, updateStudent]);\n\n return (\n <TitleProvider title={title}>\n <TrainingStatementContext.Provider value={{ start }}>\n <StudentProvider\n contest={contest}\n venue={mockVenue}\n student={student}\n setAnswer={setAnswer}\n submit={submit}\n reset={reset}\n logout={logout}\n enforceFullscreen={false}\n schema={variant?.schema}>\n {children}\n </StudentProvider>\n </TrainingStatementContext.Provider>\n </TitleProvider>\n );\n}\nTrainingProvider.displayName = \"TrainingProvider\";\n\nfunction createAnonymousStudent(contest: VariantsConfig): Student {\n return {\n id: \"\",\n userData: {\n name: \"Utente\",\n surname: \"anonimo\",\n },\n answers: {},\n contestId: contest.id,\n };\n}\n\nconst rng = new Rng();\n\nfunction getRandomVariant(contest: VariantsConfig): string {\n return rng.choice(contest.variantIds);\n}\n\nasync function getVariant([, contestId, variantId]: [string, string, string]): Promise<Variant> {\n const resp = await fetch(`/variants/${contestId}/${variantId}/answers.json`);\n if (!resp.ok) throw new Error(\"Failed to fetch schema\");\n const json: unknown = await resp.json();\n return validate(variantSchema, json);\n}\n"],"names":["TrainingProvider","contest","children","title","useSWR","getTitleIframe","iframeStudent","mutate","getStudentIframe","anonymousStudent","setAnonymousStudent","useState","createAnonymousStudent","student","type","useMemo","useEffect","isFuture","controller","event","variant","getVariant","updateStudent","useCallback","iframeFn","updateFn","mockVenue","setAnswer","problemId","answer","setAnswersIframe","submit","submitIframe","reset","resetIframe","logout","logoutIframe","start","now","variantId","getRandomVariant","preload","startIframe","subSeconds","addMinutes","jsx","TitleProvider","TrainingStatementContext","StudentProvider","rng","Rng","contestId","resp","json","validate","variantSchema"],"mappings":"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"}
package/dist/entry.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Contest } from '@olinfo/quizms/models';
2
2
  import { Context } from 'react';
3
- import { JSX } from 'react/jsx-runtime';
3
+ import { JSX } from 'react';
4
4
  import { ReactNode } from 'react';
5
5
  import { VariantsConfig } from '@olinfo/quizms/models';
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olinfo/quizms-training",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "type": "module",
5
5
  "author": "Alessandro Bortolin <bortolin.alessandro@outlook.it>",
6
6
  "license": "MIT",
@@ -14,15 +14,18 @@
14
14
  "default": "./dist/vite.js"
15
15
  }
16
16
  },
17
+ "files": [
18
+ "dist"
19
+ ],
17
20
  "dependencies": {
18
- "@olinfo/react-components": "^0.12.2",
21
+ "@olinfo/react-components": "^0.12.3",
19
22
  "@types/lodash-es": "^4.17.12",
20
- "date-fns": "^4.1.0",
21
- "lodash-es": "^4.17.23",
22
- "swr": "^2.4.1",
23
- "vite": "^7.3.1",
24
- "zod": "^4.3.6",
25
- "@olinfo/quizms": "^5.0.2"
23
+ "date-fns": "^4.4.0",
24
+ "lodash-es": "^4.18.1",
25
+ "swr": "^2.4.2",
26
+ "vite": "^7.3.6",
27
+ "zod": "^4.4.3",
28
+ "@olinfo/quizms": "^5.0.4"
26
29
  },
27
30
  "peerDependencies": {
28
31
  "react": "^19.2.4"
@@ -33,12 +36,12 @@
33
36
  }
34
37
  },
35
38
  "devDependencies": {
36
- "@biomejs/biome": "^2.4.6",
37
- "@types/node": "^25.3.5",
38
- "@types/react": "^19.2.14",
39
+ "@biomejs/biome": "^2.5.6",
40
+ "@types/node": "^25.9.5",
41
+ "@types/react": "^19.2.18",
39
42
  "rollup-plugin-node-externals": "^8.1.2",
40
43
  "rollup-preserve-directives": "^1.1.3",
41
- "typescript": "5.8.2",
44
+ "typescript": "5.9.3",
42
45
  "vite-plugin-dts": "^4.5.4",
43
46
  "vite-tsconfig-paths": "^6.1.1"
44
47
  },
@@ -1,131 +0,0 @@
1
- import {
2
- type Contest,
3
- calcScore,
4
- type Student,
5
- studentSchema,
6
- type Variant,
7
- } from "@olinfo/quizms/models";
8
- import { validate } from "@olinfo/quizms/utils";
9
- import { cloneDeepWith, isDate, sumBy } from "lodash-es";
10
- import z from "zod";
11
-
12
- export async function getTitleIframe(): Promise<string> {
13
- if (window.parent === window) return "Quizms";
14
-
15
- for (let i = 0; i < 50; i++) {
16
- try {
17
- return await getResponse({ type: "getTitle" }, z.string(), 200);
18
- } catch {}
19
- }
20
- throw new Error("Failed to connect");
21
- }
22
-
23
- export async function getStudentIframe([_, contestId]: [string, string]): Promise<Student | null> {
24
- if (window.parent === window) return null;
25
- return await getResponse({ type: "getStudent", contestId }, studentSchema.nullable());
26
- }
27
-
28
- export function setAnswersIframe(variant: Variant) {
29
- return async (student: Student, contest: Contest): Promise<Student | null> => {
30
- if (window.parent === window) return null;
31
-
32
- return await getResponse(
33
- {
34
- type: "setAnswers",
35
- contestId: contest.id,
36
- answers: student.answers,
37
- score: calcScore(student, variant.schema),
38
- maxScore: sumBy(Object.values(variant.schema).map((problem) => problem.pointsCorrect)),
39
- },
40
- studentSchema,
41
- );
42
- };
43
- }
44
-
45
- export function startIframe(variant: Variant) {
46
- return async (student: Student, contest: Contest): Promise<Student | null> => {
47
- if (window.parent === window) return null;
48
-
49
- return await getResponse(
50
- {
51
- type: "start",
52
- contestId: contest.id,
53
- variantId: student.variantId,
54
- duration: contest.onlineSettings?.duration ?? 0,
55
- score: calcScore(student, variant.schema),
56
- maxScore: sumBy(Object.values(variant.schema).map((problem) => problem.pointsCorrect)),
57
- },
58
- studentSchema,
59
- );
60
- };
61
- }
62
-
63
- export async function submitIframe(_student: Student, contest: Contest): Promise<Student | null> {
64
- if (window.parent === window) return null;
65
- return await getResponse({ type: "submit", contestId: contest.id }, studentSchema);
66
- }
67
-
68
- export async function resetIframe(_student: Student, contest: Contest): Promise<Student | null> {
69
- if (window.parent === window) return null;
70
- return await getResponse({ type: "reset", contestId: contest.id }, studentSchema);
71
- }
72
-
73
- export async function logoutIframe(): Promise<null> {
74
- if (window.parent === window) return null;
75
- return await getResponse({ type: "logout" }, z.null());
76
- }
77
-
78
- async function getResponse<Message extends { type: string }, Data>(
79
- message: Message,
80
- schema: z.ZodType<Data>,
81
- timeout = 10_000,
82
- ): Promise<Data> {
83
- const messageId = crypto.randomUUID();
84
-
85
- const controller = new AbortController();
86
- const responsePromise = new Promise<Data>((resolve, reject) => {
87
- window.addEventListener(
88
- "message",
89
- (ev) => {
90
- try {
91
- const response = validate(
92
- messageSchema,
93
- cloneDeepWith(ev.data, (value) => (isDate(value) ? new Date(value) : undefined)),
94
- );
95
- if (response.messageId !== messageId) return;
96
-
97
- if (response.success) {
98
- resolve(validate(schema, response.data));
99
- } else {
100
- reject(new Error(response.error));
101
- }
102
- } catch (err) {
103
- reject(err);
104
- }
105
- },
106
- { signal: controller.signal },
107
- );
108
- });
109
-
110
- window.parent.postMessage({ ...message, messageId }, "*");
111
-
112
- try {
113
- const timeoutPromise = new Promise<never>((_, reject) => setTimeout(() => reject(), timeout));
114
- return await Promise.race([responsePromise, timeoutPromise]);
115
- } finally {
116
- controller.abort();
117
- }
118
- }
119
-
120
- const messageSchema = z.discriminatedUnion("success", [
121
- z.object({
122
- success: z.literal(true),
123
- messageId: z.string(),
124
- data: z.any(),
125
- }),
126
- z.object({
127
- success: z.literal(false),
128
- messageId: z.string(),
129
- error: z.string(),
130
- }),
131
- ]);
@@ -1,2 +0,0 @@
1
- export * from "./provider";
2
- export * from "./statement";
@@ -1,193 +0,0 @@
1
- import { type ReactNode, useCallback, useMemo, useState } from "react";
2
-
3
- import { TitleProvider } from "@olinfo/quizms/components";
4
- import {
5
- type Answer,
6
- type Contest,
7
- type Student,
8
- type Variant,
9
- type VariantsConfig,
10
- type Venue,
11
- variantSchema,
12
- } from "@olinfo/quizms/models";
13
- import { StudentProvider } from "@olinfo/quizms/student";
14
- import { Rng, validate } from "@olinfo/quizms/utils";
15
- import { addMinutes, subSeconds } from "date-fns";
16
- import useSWR, { preload } from "swr";
17
-
18
- import {
19
- getStudentIframe,
20
- getTitleIframe,
21
- logoutIframe,
22
- resetIframe,
23
- setAnswersIframe,
24
- startIframe,
25
- submitIframe,
26
- } from "./iframe";
27
- import { TrainingStatementContext } from "./statement";
28
-
29
- export function TrainingProvider({
30
- contest,
31
- children,
32
- }: {
33
- contest: Contest & VariantsConfig;
34
- children: ReactNode;
35
- }) {
36
- const { data: title } = useSWR("getTitle", getTitleIframe, { suspense: true });
37
-
38
- const { data: iframeStudent, mutate } = useSWR(["student", contest.id], getStudentIframe, {
39
- suspense: true,
40
- });
41
- const [anonymousStudent, setAnonymousStudent] = useState<Student>(
42
- createAnonymousStudent(contest),
43
- );
44
-
45
- const { student, type } = useMemo(
46
- () =>
47
- iframeStudent
48
- ? { type: "iframe", student: iframeStudent }
49
- : { type: "anonymous", student: anonymousStudent },
50
- [iframeStudent, anonymousStudent],
51
- );
52
-
53
- const { data: variant } = useSWR(
54
- student.variantId && ["variant", contest.id, student.variantId],
55
- getVariant,
56
- {
57
- revalidateOnFocus: false,
58
- revalidateIfStale: false,
59
- },
60
- );
61
-
62
- const updateStudent = useCallback(
63
- async (
64
- iframeFn: (student: Student, contest: Contest) => Promise<Student | null>,
65
- updateFn: (student: Student) => Student,
66
- ) => {
67
- if (type === "anonymous") {
68
- setAnonymousStudent(updateFn);
69
- } else {
70
- await mutate((student) => (student ? iframeFn(updateFn(student), contest) : null), {
71
- optimisticData: (student) => (student ? updateFn(student) : null),
72
- });
73
- }
74
- },
75
- [type, contest, mutate],
76
- );
77
-
78
- const mockVenue: Venue = useMemo(
79
- () => ({
80
- id: "",
81
- schoolId: "",
82
- contestId: contest.id,
83
- name: "",
84
- participationWindow: student.participationWindow,
85
- finalized: false,
86
- disabled: false,
87
- pdfVariants: [],
88
- }),
89
- [contest.id, student.participationWindow],
90
- );
91
-
92
- const setAnswer = useCallback(
93
- async (problemId: string, answer: Answer) => {
94
- if (!variant) throw new Error("Variant non loaded");
95
- await updateStudent(
96
- setAnswersIframe(variant),
97
- (student) =>
98
- ({ ...student, answers: { ...student.answers, [problemId]: answer } }) satisfies Student,
99
- );
100
- },
101
- [updateStudent, variant],
102
- );
103
-
104
- const submit = useCallback(async () => {
105
- await updateStudent(
106
- submitIframe,
107
- (student) =>
108
- ({
109
- ...student,
110
- participationWindow: { start: student.participationWindow!.start, end: new Date() },
111
- }) satisfies Student,
112
- );
113
- }, [updateStudent]);
114
-
115
- const reset = useCallback(async () => {
116
- await updateStudent(
117
- resetIframe,
118
- (student) =>
119
- ({
120
- ...student,
121
- answers: {},
122
- participationWindow: undefined,
123
- }) satisfies Student,
124
- );
125
- }, [updateStudent]);
126
-
127
- const logout = useCallback(async () => {
128
- await updateStudent(logoutIframe, (student) => student);
129
- }, [updateStudent]);
130
-
131
- const start = useCallback(async () => {
132
- const now = new Date();
133
- const variantId = getRandomVariant(contest);
134
- const variant = await preload(["variant", contest.id, variantId], getVariant);
135
- await updateStudent(
136
- startIframe(variant),
137
- (student) =>
138
- ({
139
- ...student,
140
- variantId,
141
- participationWindow: {
142
- start: subSeconds(now, 2),
143
- end: addMinutes(now, contest.onlineSettings?.duration ?? 0),
144
- },
145
- }) satisfies Student,
146
- );
147
- }, [contest, updateStudent]);
148
-
149
- return (
150
- <TitleProvider title={title}>
151
- <TrainingStatementContext.Provider value={{ start }}>
152
- <StudentProvider
153
- contest={contest}
154
- venue={mockVenue}
155
- student={student}
156
- setAnswer={setAnswer}
157
- submit={submit}
158
- reset={reset}
159
- logout={logout}
160
- enforceFullscreen={false}
161
- schema={variant?.schema}>
162
- {children}
163
- </StudentProvider>
164
- </TrainingStatementContext.Provider>
165
- </TitleProvider>
166
- );
167
- }
168
- TrainingProvider.displayName = "TrainingProvider";
169
-
170
- function createAnonymousStudent(contest: VariantsConfig): Student {
171
- return {
172
- id: "",
173
- userData: {
174
- name: "Utente",
175
- surname: "anonimo",
176
- },
177
- answers: {},
178
- contestId: contest.id,
179
- };
180
- }
181
-
182
- const rng = new Rng();
183
-
184
- function getRandomVariant(contest: VariantsConfig): string {
185
- return rng.choice(contest.variantIds);
186
- }
187
-
188
- async function getVariant([, contestId, variantId]: [string, string, string]): Promise<Variant> {
189
- const resp = await fetch(`/variants/${contestId}/${variantId}/answers.json`);
190
- if (!resp.ok) throw new Error("Failed to fetch schema");
191
- const json: unknown = await resp.json();
192
- return validate(variantSchema, json);
193
- }
@@ -1,30 +0,0 @@
1
- import { createContext, useContext } from "react";
2
-
3
- import { RemoteStatement, useStudent } from "@olinfo/quizms/student";
4
- import { Button } from "@olinfo/react-components";
5
-
6
- export const TrainingStatementContext = createContext<{
7
- start?: () => Promise<void> | void;
8
- }>({});
9
-
10
- export function TrainingStatement() {
11
- const { student, contest } = useStudent();
12
- const { start } = useContext(TrainingStatementContext);
13
-
14
- if (!student.participationWindow) {
15
- return (
16
- <div className="flex h-[50vh] flex-col items-center justify-center">
17
- <Button className="btn-success btn-lg" onClick={start}>
18
- Inizia
19
- </Button>
20
- </div>
21
- );
22
- }
23
-
24
- return (
25
- <RemoteStatement
26
- statementUrl={() => `/variants/${contest.id}/${student.variantId}/statement.txt`}
27
- moduleUrl={(id) => `/variants/${contest.id}/${student.variantId}/${id}`}
28
- />
29
- );
30
- }
package/src/vite/entry.ts DELETED
@@ -1,47 +0,0 @@
1
- import { reactComponentCase } from "@olinfo/quizms/utils";
2
- import { loadContests } from "@olinfo/quizms/utils-node";
3
- import { type PluginOption, transformWithEsbuild } from "vite";
4
-
5
- export default async function trainingEntry(): Promise<PluginOption> {
6
- const contests = await loadContests();
7
-
8
- return {
9
- name: "quizms:training-entry",
10
- resolveId(id) {
11
- const [pathname] = id.split("?");
12
- if (pathname === "virtual:quizms-training-entry") {
13
- return `\0${id}`;
14
- }
15
- },
16
- load(id) {
17
- const [pathname, query] = id.split("?");
18
- if (pathname === "\0virtual:quizms-training-entry") {
19
- const params = new URLSearchParams(query);
20
- const contestId = params.get("id");
21
- const contest = contests.find((c) => c.id === contestId)!;
22
- const isDev = params.has("dev");
23
-
24
- const entry = `
25
- import { createApp } from "@olinfo/quizms/entry";
26
- import { TrainingProvider, TrainingStatement } from "@olinfo/quizms-training/entry";
27
- import { Route, Switch } from "wouter";
28
-
29
- ${isDev ? `import ${reactComponentCase(contest.id)} from "/${contest.entry}";` : ""}
30
- ${contest.header ? `import ${reactComponentCase(contest.id)}Header from "/${contest.header}";` : ""}
31
-
32
- export default function createTrainingEntry() {
33
- return createApp(
34
- <TrainingProvider contest={${JSON.stringify(contest)}}>
35
- ${contest.header ? `<${reactComponentCase(contest.id)}Header />` : ""}
36
- ${isDev ? `<${reactComponentCase(contest.id)} />` : "<TrainingStatement />"}
37
- </TrainingProvider>
38
- );
39
- }
40
- `;
41
- return transformWithEsbuild(entry, "virtual-quizms-training-entry.jsx", {
42
- jsx: "automatic",
43
- });
44
- }
45
- },
46
- };
47
- }
package/src/vite/index.ts DELETED
@@ -1,4 +0,0 @@
1
- import trainingEntry from "./entry";
2
- import trainingPreset from "./preset";
3
-
4
- export default [trainingEntry(), trainingPreset()] as any;
@@ -1,45 +0,0 @@
1
- import { loadContests } from "@olinfo/quizms/utils-node";
2
- import { defaultClientConditions, defaultClientMainFields, type PluginOption } from "vite";
3
-
4
- export default async function trainingPreset(): Promise<PluginOption> {
5
- const contests = await loadContests();
6
-
7
- return {
8
- name: "quizms:training-preset",
9
- apply: "build",
10
- config() {
11
- return {
12
- environments: {
13
- training: {
14
- resolve: {
15
- conditions: [...defaultClientConditions],
16
- mainFields: [...defaultClientMainFields],
17
- noExternal: true,
18
- },
19
- build: {
20
- rollupOptions: {
21
- input: Object.fromEntries(
22
- contests.map((contest) => [
23
- contest.id,
24
- `virtual:quizms-entry?id=${encodeURIComponent(`virtual:quizms-training-entry?id=${contest.id}`)}`,
25
- ]),
26
- ),
27
- },
28
- sourcemap: true,
29
- },
30
- },
31
- },
32
- };
33
- },
34
- buildStart() {
35
- if (this.environment.name === "training") {
36
- process.env.QUIZMS_SHOW_SOLUTIONS = "true";
37
- this.emitFile({
38
- type: "asset",
39
- fileName: "contests.json",
40
- source: JSON.stringify(contests),
41
- });
42
- }
43
- },
44
- };
45
- }
@@ -1,7 +0,0 @@
1
- import olinfoPresets from "@olinfo/tailwind";
2
-
3
- /** @type {import("tailwindcss").Config} */
4
- export default {
5
- presets: [olinfoPresets],
6
- content: ["./src/**/*.{ts,tsx}", "./node_modules/@olinfo/react-components/dist/**/*.js"],
7
- };
package/tsconfig.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "esnext",
4
- "moduleResolution": "bundler",
5
- "target": "es2022",
6
- "declaration": true,
7
- "declarationDir": "dist",
8
- "jsx": "react-jsx",
9
- "types": ["vite/client"],
10
- "strict": true,
11
- "noImplicitAny": true,
12
- "allowJs": true,
13
- "skipLibCheck": true,
14
- "esModuleInterop": true,
15
- "allowSyntheticDefaultImports": true,
16
- "forceConsistentCasingInFileNames": true,
17
- "noFallthroughCasesInSwitch": true,
18
- "resolveJsonModule": true,
19
- "isolatedModules": true,
20
- "baseUrl": "./",
21
- "paths": {
22
- "~/*": ["./src/*"]
23
- },
24
- "rootDir": "src",
25
- "outDir": "dist"
26
- },
27
- "include": ["src/"]
28
- }
package/vite.config.mjs DELETED
@@ -1,26 +0,0 @@
1
- import nodeExternals from "rollup-plugin-node-externals";
2
- import preserveDirectives from "rollup-preserve-directives";
3
- import { defineConfig } from "vite";
4
- import dts from "vite-plugin-dts";
5
- import tsconfigPaths from "vite-tsconfig-paths";
6
-
7
- export default defineConfig({
8
- build: {
9
- lib: {
10
- entry: {
11
- entry: "src/entry/index.tsx",
12
- vite: "src/vite/index.ts",
13
- },
14
- formats: ["es"],
15
- },
16
- target: "esnext",
17
- sourcemap: true,
18
- rollupOptions: {
19
- output: {
20
- preserveModules: true,
21
- hoistTransitiveImports: false,
22
- },
23
- },
24
- },
25
- plugins: [dts({ rollupTypes: true }), nodeExternals(), preserveDirectives(), tsconfigPaths()],
26
- });