@olinfo/quizms-training 5.0.1 → 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.
- package/dist/entry/iframe.js +99 -0
- package/dist/entry/iframe.js.map +1 -0
- package/dist/entry/provider.js +143 -0
- package/dist/entry/provider.js.map +1 -0
- package/dist/entry/statement.js +20 -0
- package/dist/entry/statement.js.map +1 -0
- package/dist/entry.d.ts +22 -0
- package/dist/entry.js +8 -0
- package/dist/entry.js.map +1 -0
- package/dist/vite/entry.js +43 -0
- package/dist/vite/entry.js.map +1 -0
- package/dist/vite/preset.js +44 -0
- package/dist/vite/preset.js.map +1 -0
- package/dist/vite.d.ts +4 -0
- package/dist/vite.js +7 -0
- package/dist/vite.js.map +1 -0
- package/package.json +4 -1
- package/src/entry/iframe.ts +0 -131
- package/src/entry/index.tsx +0 -2
- package/src/entry/provider.tsx +0 -213
- package/src/entry/statement.tsx +0 -30
- package/src/vite/entry.ts +0 -47
- package/src/vite/index.ts +0 -4
- package/src/vite/preset.ts +0 -45
- package/tailwind.config.mjs +0 -7
- package/tsconfig.json +0 -28
- package/vite.config.mjs +0 -26
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { studentSchema as i, calcScore as f } from "@olinfo/quizms/models";
|
|
2
|
+
import { validate as l } from "@olinfo/quizms/utils";
|
|
3
|
+
import { sumBy as p, cloneDeepWith as g, isDate as I } from "lodash-es";
|
|
4
|
+
import n from "zod";
|
|
5
|
+
async function D() {
|
|
6
|
+
if (window.parent === window) return "Quizms";
|
|
7
|
+
for (let e = 0; e < 50; e++)
|
|
8
|
+
try {
|
|
9
|
+
return await r({ type: "getTitle" }, n.string(), 200);
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
throw new Error("Failed to connect");
|
|
13
|
+
}
|
|
14
|
+
async function _([e, t]) {
|
|
15
|
+
return window.parent === window ? null : await r({ type: "getStudent", contestId: t }, i.nullable());
|
|
16
|
+
}
|
|
17
|
+
function j(e) {
|
|
18
|
+
return async (t, s) => window.parent === window ? null : await r(
|
|
19
|
+
{
|
|
20
|
+
type: "setAnswers",
|
|
21
|
+
contestId: s.id,
|
|
22
|
+
answers: t.answers,
|
|
23
|
+
score: f(t, e.schema),
|
|
24
|
+
maxScore: p(Object.values(e.schema).map((a) => a.pointsCorrect))
|
|
25
|
+
},
|
|
26
|
+
i
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function x(e) {
|
|
30
|
+
return async (t, s) => window.parent === window ? null : await r(
|
|
31
|
+
{
|
|
32
|
+
type: "start",
|
|
33
|
+
contestId: s.id,
|
|
34
|
+
variantId: t.variantId,
|
|
35
|
+
duration: s.onlineSettings?.duration ?? 0,
|
|
36
|
+
score: f(t, e.schema),
|
|
37
|
+
maxScore: p(Object.values(e.schema).map((a) => a.pointsCorrect))
|
|
38
|
+
},
|
|
39
|
+
i
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
async function A(e, t) {
|
|
43
|
+
return window.parent === window ? null : await r({ type: "submit", contestId: t.id }, i);
|
|
44
|
+
}
|
|
45
|
+
async function C(e, t) {
|
|
46
|
+
return window.parent === window ? null : await r({ type: "reset", contestId: t.id }, i);
|
|
47
|
+
}
|
|
48
|
+
async function E() {
|
|
49
|
+
return window.parent === window ? null : await r({ type: "logout" }, n.null());
|
|
50
|
+
}
|
|
51
|
+
async function r(e, t, s = 1e4) {
|
|
52
|
+
const a = crypto.randomUUID(), d = new AbortController(), y = new Promise((c, u) => {
|
|
53
|
+
window.addEventListener(
|
|
54
|
+
"message",
|
|
55
|
+
(w) => {
|
|
56
|
+
try {
|
|
57
|
+
const o = l(
|
|
58
|
+
h,
|
|
59
|
+
g(w.data, (m) => I(m) ? new Date(m) : void 0)
|
|
60
|
+
);
|
|
61
|
+
if (o.messageId !== a) return;
|
|
62
|
+
o.success ? c(l(t, o.data)) : u(new Error(o.error));
|
|
63
|
+
} catch (o) {
|
|
64
|
+
u(o);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{ signal: d.signal }
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
window.parent.postMessage({ ...e, messageId: a }, "*");
|
|
71
|
+
try {
|
|
72
|
+
const c = new Promise((u, w) => setTimeout(() => w(), s));
|
|
73
|
+
return await Promise.race([y, c]);
|
|
74
|
+
} finally {
|
|
75
|
+
d.abort();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const h = n.discriminatedUnion("success", [
|
|
79
|
+
n.object({
|
|
80
|
+
success: n.literal(!0),
|
|
81
|
+
messageId: n.string(),
|
|
82
|
+
data: n.any()
|
|
83
|
+
}),
|
|
84
|
+
n.object({
|
|
85
|
+
success: n.literal(!1),
|
|
86
|
+
messageId: n.string(),
|
|
87
|
+
error: n.string()
|
|
88
|
+
})
|
|
89
|
+
]);
|
|
90
|
+
export {
|
|
91
|
+
_ as getStudentIframe,
|
|
92
|
+
D as getTitleIframe,
|
|
93
|
+
E as logoutIframe,
|
|
94
|
+
C as resetIframe,
|
|
95
|
+
j as setAnswersIframe,
|
|
96
|
+
x as startIframe,
|
|
97
|
+
A as submitIframe
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=iframe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframe.js","sources":["../../src/entry/iframe.ts"],"sourcesContent":["import {\n type Contest,\n calcScore,\n type Student,\n studentSchema,\n type Variant,\n} from \"@olinfo/quizms/models\";\nimport { validate } from \"@olinfo/quizms/utils\";\nimport { cloneDeepWith, isDate, sumBy } from \"lodash-es\";\nimport z from \"zod\";\n\nexport async function getTitleIframe(): Promise<string> {\n if (window.parent === window) return \"Quizms\";\n\n for (let i = 0; i < 50; i++) {\n try {\n return await getResponse({ type: \"getTitle\" }, z.string(), 200);\n } catch {}\n }\n throw new Error(\"Failed to connect\");\n}\n\nexport async function getStudentIframe([_, contestId]: [string, string]): Promise<Student | null> {\n if (window.parent === window) return null;\n return await getResponse({ type: \"getStudent\", contestId }, studentSchema.nullable());\n}\n\nexport function setAnswersIframe(variant: Variant) {\n return async (student: Student, contest: Contest): Promise<Student | null> => {\n if (window.parent === window) return null;\n\n return await getResponse(\n {\n type: \"setAnswers\",\n contestId: contest.id,\n answers: student.answers,\n score: calcScore(student, variant.schema),\n maxScore: sumBy(Object.values(variant.schema).map((problem) => problem.pointsCorrect)),\n },\n studentSchema,\n );\n };\n}\n\nexport function startIframe(variant: Variant) {\n return async (student: Student, contest: Contest): Promise<Student | null> => {\n if (window.parent === window) return null;\n\n return await getResponse(\n {\n type: \"start\",\n contestId: contest.id,\n variantId: student.variantId,\n duration: contest.onlineSettings?.duration ?? 0,\n score: calcScore(student, variant.schema),\n maxScore: sumBy(Object.values(variant.schema).map((problem) => problem.pointsCorrect)),\n },\n studentSchema,\n );\n };\n}\n\nexport async function submitIframe(_student: Student, contest: Contest): Promise<Student | null> {\n if (window.parent === window) return null;\n return await getResponse({ type: \"submit\", contestId: contest.id }, studentSchema);\n}\n\nexport async function resetIframe(_student: Student, contest: Contest): Promise<Student | null> {\n if (window.parent === window) return null;\n return await getResponse({ type: \"reset\", contestId: contest.id }, studentSchema);\n}\n\nexport async function logoutIframe(): Promise<null> {\n if (window.parent === window) return null;\n return await getResponse({ type: \"logout\" }, z.null());\n}\n\nasync function getResponse<Message extends { type: string }, Data>(\n message: Message,\n schema: z.ZodType<Data>,\n timeout = 10_000,\n): Promise<Data> {\n const messageId = crypto.randomUUID();\n\n const controller = new AbortController();\n const responsePromise = new Promise<Data>((resolve, reject) => {\n window.addEventListener(\n \"message\",\n (ev) => {\n try {\n const response = validate(\n messageSchema,\n cloneDeepWith(ev.data, (value) => (isDate(value) ? new Date(value) : undefined)),\n );\n if (response.messageId !== messageId) return;\n\n if (response.success) {\n resolve(validate(schema, response.data));\n } else {\n reject(new Error(response.error));\n }\n } catch (err) {\n reject(err);\n }\n },\n { signal: controller.signal },\n );\n });\n\n window.parent.postMessage({ ...message, messageId }, \"*\");\n\n try {\n const timeoutPromise = new Promise<never>((_, reject) => setTimeout(() => reject(), timeout));\n return await Promise.race([responsePromise, timeoutPromise]);\n } finally {\n controller.abort();\n }\n}\n\nconst messageSchema = z.discriminatedUnion(\"success\", [\n z.object({\n success: z.literal(true),\n messageId: z.string(),\n data: z.any(),\n }),\n z.object({\n success: z.literal(false),\n messageId: z.string(),\n error: z.string(),\n }),\n]);\n"],"names":["getTitleIframe","i","getResponse","z","getStudentIframe","_","contestId","studentSchema","setAnswersIframe","variant","student","contest","calcScore","sumBy","problem","startIframe","submitIframe","_student","resetIframe","logoutIframe","message","schema","timeout","messageId","controller","responsePromise","resolve","reject","ev","response","validate","messageSchema","cloneDeepWith","value","isDate","err","timeoutPromise"],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
}) {
|
|
15
|
+
const { data: d } = l("getTitle", $, { suspense: !0 }), { data: s, mutate: p } = l(["student", t.id], z, {
|
|
16
|
+
suspense: !0
|
|
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,
|
|
37
|
+
{
|
|
38
|
+
revalidateOnFocus: !1,
|
|
39
|
+
revalidateIfStale: !1
|
|
40
|
+
}
|
|
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
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
[u, t, p]
|
|
48
|
+
), I = v(
|
|
49
|
+
() => ({
|
|
50
|
+
id: "",
|
|
51
|
+
schoolId: "",
|
|
52
|
+
contestId: t.id,
|
|
53
|
+
name: "",
|
|
54
|
+
participationWindow: r.participationWindow,
|
|
55
|
+
finalized: !1,
|
|
56
|
+
disabled: !1,
|
|
57
|
+
pdfVariants: []
|
|
58
|
+
}),
|
|
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 } })
|
|
66
|
+
);
|
|
67
|
+
},
|
|
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() }
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
}, [e]), W = o(async () => {
|
|
78
|
+
await e(
|
|
79
|
+
O,
|
|
80
|
+
(n) => ({
|
|
81
|
+
...n,
|
|
82
|
+
answers: {},
|
|
83
|
+
participationWindow: void 0
|
|
84
|
+
})
|
|
85
|
+
);
|
|
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,
|
|
94
|
+
variantId: i,
|
|
95
|
+
participationWindow: {
|
|
96
|
+
start: F(n, 2),
|
|
97
|
+
end: C(n, t.onlineSettings?.duration ?? 0)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
}, [t, e]);
|
|
102
|
+
return /* @__PURE__ */ f(R, { title: d, children: /* @__PURE__ */ f(B.Provider, { value: { start: T }, children: /* @__PURE__ */ f(
|
|
103
|
+
j,
|
|
104
|
+
{
|
|
105
|
+
contest: t,
|
|
106
|
+
venue: I,
|
|
107
|
+
student: r,
|
|
108
|
+
setAnswer: S,
|
|
109
|
+
submit: h,
|
|
110
|
+
reset: W,
|
|
111
|
+
logout: b,
|
|
112
|
+
enforceFullscreen: !1,
|
|
113
|
+
schema: c?.schema,
|
|
114
|
+
children: m
|
|
115
|
+
}
|
|
116
|
+
) }) });
|
|
117
|
+
}
|
|
118
|
+
G.displayName = "TrainingProvider";
|
|
119
|
+
function H(t) {
|
|
120
|
+
return {
|
|
121
|
+
id: "",
|
|
122
|
+
userData: {
|
|
123
|
+
name: "Utente",
|
|
124
|
+
surname: "anonimo"
|
|
125
|
+
},
|
|
126
|
+
answers: {},
|
|
127
|
+
contestId: t.id
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const J = new E();
|
|
131
|
+
function K(t) {
|
|
132
|
+
return J.choice(t.variantIds);
|
|
133
|
+
}
|
|
134
|
+
async function g([, t, m]) {
|
|
135
|
+
const d = await fetch(`/variants/${t}/${m}/answers.json`);
|
|
136
|
+
if (!d.ok) throw new Error("Failed to fetch schema");
|
|
137
|
+
const s = await d.json();
|
|
138
|
+
return k(V, s);
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
G as TrainingProvider
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as a, useContext as o } from "react";
|
|
3
|
+
import { useStudent as s, RemoteStatement as m } from "@olinfo/quizms/student";
|
|
4
|
+
import { Button as c } from "@olinfo/react-components";
|
|
5
|
+
const l = a({});
|
|
6
|
+
function p() {
|
|
7
|
+
const { student: t, contest: n } = s(), { start: i } = o(l);
|
|
8
|
+
return t.participationWindow ? /* @__PURE__ */ e(
|
|
9
|
+
m,
|
|
10
|
+
{
|
|
11
|
+
statementUrl: () => `/variants/${n.id}/${t.variantId}/statement.txt`,
|
|
12
|
+
moduleUrl: (r) => `/variants/${n.id}/${t.variantId}/${r}`
|
|
13
|
+
}
|
|
14
|
+
) : /* @__PURE__ */ e("div", { className: "flex h-[50vh] flex-col items-center justify-center", children: /* @__PURE__ */ e(c, { className: "btn-success btn-lg", onClick: i, children: "Inizia" }) });
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
p as TrainingStatement,
|
|
18
|
+
l as TrainingStatementContext
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=statement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statement.js","sources":["../../src/entry/statement.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\n\nimport { RemoteStatement, useStudent } from \"@olinfo/quizms/student\";\nimport { Button } from \"@olinfo/react-components\";\n\nexport const TrainingStatementContext = createContext<{\n start?: () => Promise<void> | void;\n}>({});\n\nexport function TrainingStatement() {\n const { student, contest } = useStudent();\n const { start } = useContext(TrainingStatementContext);\n\n if (!student.participationWindow) {\n return (\n <div className=\"flex h-[50vh] flex-col items-center justify-center\">\n <Button className=\"btn-success btn-lg\" onClick={start}>\n Inizia\n </Button>\n </div>\n );\n }\n\n return (\n <RemoteStatement\n statementUrl={() => `/variants/${contest.id}/${student.variantId}/statement.txt`}\n moduleUrl={(id) => `/variants/${contest.id}/${student.variantId}/${id}`}\n />\n );\n}\n"],"names":["TrainingStatementContext","createContext","TrainingStatement","student","contest","useStudent","start","useContext","jsx","RemoteStatement","id","Button"],"mappings":";;;;AAKO,MAAMA,IAA2BC,EAErC,CAAA,CAAE;AAEE,SAASC,IAAoB;AAClC,QAAM,EAAE,SAAAC,GAAS,SAAAC,EAAA,IAAYC,EAAA,GACvB,EAAE,OAAAC,EAAA,IAAUC,EAAWP,CAAwB;AAErD,SAAKG,EAAQ,sBAWX,gBAAAK;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,cAAc,MAAM,aAAaL,EAAQ,EAAE,IAAID,EAAQ,SAAS;AAAA,MAChE,WAAW,CAACO,MAAO,aAAaN,EAAQ,EAAE,IAAID,EAAQ,SAAS,IAAIO,CAAE;AAAA,IAAA;AAAA,EAAA,IAXrE,gBAAAF,EAAC,OAAA,EAAI,WAAU,sDACb,UAAA,gBAAAA,EAACG,GAAA,EAAO,WAAU,sBAAqB,SAASL,GAAO,UAAA,SAAA,CAEvD,GACF;AAUN;"}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Contest } from '@olinfo/quizms/models';
|
|
2
|
+
import { Context } from 'react';
|
|
3
|
+
import { JSX } from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
import { VariantsConfig } from '@olinfo/quizms/models';
|
|
6
|
+
|
|
7
|
+
export declare function TrainingProvider({ contest, children, }: {
|
|
8
|
+
contest: Contest & VariantsConfig;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): JSX.Element;
|
|
11
|
+
|
|
12
|
+
export declare namespace TrainingProvider {
|
|
13
|
+
var displayName: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare function TrainingStatement(): JSX.Element;
|
|
17
|
+
|
|
18
|
+
export declare const TrainingStatementContext: Context< {
|
|
19
|
+
start?: () => Promise<void> | void;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export { }
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TrainingProvider as r } from "./entry/provider.js";
|
|
2
|
+
import { TrainingStatement as i, TrainingStatementContext as o } from "./entry/statement.js";
|
|
3
|
+
export {
|
|
4
|
+
r as TrainingProvider,
|
|
5
|
+
i as TrainingStatement,
|
|
6
|
+
o as TrainingStatementContext
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { reactComponentCase as n } from "@olinfo/quizms/utils";
|
|
2
|
+
import { loadContests as u } from "@olinfo/quizms/utils-node";
|
|
3
|
+
import { transformWithEsbuild as p } from "vite";
|
|
4
|
+
async function y() {
|
|
5
|
+
const o = await u();
|
|
6
|
+
return {
|
|
7
|
+
name: "quizms:training-entry",
|
|
8
|
+
resolveId(r) {
|
|
9
|
+
const [i] = r.split("?");
|
|
10
|
+
if (i === "virtual:quizms-training-entry")
|
|
11
|
+
return `\0${r}`;
|
|
12
|
+
},
|
|
13
|
+
load(r) {
|
|
14
|
+
const [i, s] = r.split("?");
|
|
15
|
+
if (i === "\0virtual:quizms-training-entry") {
|
|
16
|
+
const e = new URLSearchParams(s), m = e.get("id"), t = o.find((d) => d.id === m), a = e.has("dev"), c = `
|
|
17
|
+
import { createApp } from "@olinfo/quizms/entry";
|
|
18
|
+
import { TrainingProvider, TrainingStatement } from "@olinfo/quizms-training/entry";
|
|
19
|
+
import { Route, Switch } from "wouter";
|
|
20
|
+
|
|
21
|
+
${a ? `import ${n(t.id)} from "/${t.entry}";` : ""}
|
|
22
|
+
${t.header ? `import ${n(t.id)}Header from "/${t.header}";` : ""}
|
|
23
|
+
|
|
24
|
+
export default function createTrainingEntry() {
|
|
25
|
+
return createApp(
|
|
26
|
+
<TrainingProvider contest={${JSON.stringify(t)}}>
|
|
27
|
+
${t.header ? `<${n(t.id)}Header />` : ""}
|
|
28
|
+
${a ? `<${n(t.id)} />` : "<TrainingStatement />"}
|
|
29
|
+
</TrainingProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
return p(c, "virtual-quizms-training-entry.jsx", {
|
|
34
|
+
jsx: "automatic"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
y as default
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sources":["../../src/vite/entry.ts"],"sourcesContent":["import { reactComponentCase } from \"@olinfo/quizms/utils\";\nimport { loadContests } from \"@olinfo/quizms/utils-node\";\nimport { type PluginOption, transformWithEsbuild } from \"vite\";\n\nexport default async function trainingEntry(): Promise<PluginOption> {\n const contests = await loadContests();\n\n return {\n name: \"quizms:training-entry\",\n resolveId(id) {\n const [pathname] = id.split(\"?\");\n if (pathname === \"virtual:quizms-training-entry\") {\n return `\\0${id}`;\n }\n },\n load(id) {\n const [pathname, query] = id.split(\"?\");\n if (pathname === \"\\0virtual:quizms-training-entry\") {\n const params = new URLSearchParams(query);\n const contestId = params.get(\"id\");\n const contest = contests.find((c) => c.id === contestId)!;\n const isDev = params.has(\"dev\");\n\n const entry = `\nimport { createApp } from \"@olinfo/quizms/entry\";\nimport { TrainingProvider, TrainingStatement } from \"@olinfo/quizms-training/entry\";\nimport { Route, Switch } from \"wouter\";\n\n${isDev ? `import ${reactComponentCase(contest.id)} from \"/${contest.entry}\";` : \"\"}\n${contest.header ? `import ${reactComponentCase(contest.id)}Header from \"/${contest.header}\";` : \"\"}\n\nexport default function createTrainingEntry() {\n return createApp(\n <TrainingProvider contest={${JSON.stringify(contest)}}>\n ${contest.header ? `<${reactComponentCase(contest.id)}Header />` : \"\"}\n ${isDev ? `<${reactComponentCase(contest.id)} />` : \"<TrainingStatement />\"}\n </TrainingProvider>\n );\n}\n`;\n return transformWithEsbuild(entry, \"virtual-quizms-training-entry.jsx\", {\n jsx: \"automatic\",\n });\n }\n },\n };\n}\n"],"names":["trainingEntry","contests","loadContests","id","pathname","query","params","contestId","contest","c","isDev","entry","reactComponentCase","transformWithEsbuild"],"mappings":";;;AAIA,eAA8BA,IAAuC;AACnE,QAAMC,IAAW,MAAMC,EAAA;AAEvB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAUC,GAAI;AACZ,YAAM,CAACC,CAAQ,IAAID,EAAG,MAAM,GAAG;AAC/B,UAAIC,MAAa;AACf,eAAO,KAAKD,CAAE;AAAA,IAElB;AAAA,IACA,KAAKA,GAAI;AACP,YAAM,CAACC,GAAUC,CAAK,IAAIF,EAAG,MAAM,GAAG;AACtC,UAAIC,MAAa,mCAAmC;AAClD,cAAME,IAAS,IAAI,gBAAgBD,CAAK,GAClCE,IAAYD,EAAO,IAAI,IAAI,GAC3BE,IAAUP,EAAS,KAAK,CAACQ,MAAMA,EAAE,OAAOF,CAAS,GACjDG,IAAQJ,EAAO,IAAI,KAAK,GAExBK,IAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpBD,IAAQ,UAAUE,EAAmBJ,EAAQ,EAAE,CAAC,WAAWA,EAAQ,KAAK,OAAO,EAAE;AAAA,EACjFA,EAAQ,SAAS,UAAUI,EAAmBJ,EAAQ,EAAE,CAAC,iBAAiBA,EAAQ,MAAM,OAAO,EAAE;AAAA;AAAA;AAAA;AAAA,iCAIlE,KAAK,UAAUA,CAAO,CAAC;AAAA,QAChDA,EAAQ,SAAS,IAAII,EAAmBJ,EAAQ,EAAE,CAAC,cAAc,EAAE;AAAA,QACnEE,IAAQ,IAAIE,EAAmBJ,EAAQ,EAAE,CAAC,QAAQ,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAKzE,eAAOK,EAAqBF,GAAO,qCAAqC;AAAA,UACtE,KAAK;AAAA,QAAA,CACN;AAAA,MACH;AAAA,IACF;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { loadContests as n } from "@olinfo/quizms/utils-node";
|
|
2
|
+
import { defaultClientMainFields as e, defaultClientConditions as r } from "vite";
|
|
3
|
+
async function a() {
|
|
4
|
+
const i = await n();
|
|
5
|
+
return {
|
|
6
|
+
name: "quizms:training-preset",
|
|
7
|
+
apply: "build",
|
|
8
|
+
config() {
|
|
9
|
+
return {
|
|
10
|
+
environments: {
|
|
11
|
+
training: {
|
|
12
|
+
resolve: {
|
|
13
|
+
conditions: [...r],
|
|
14
|
+
mainFields: [...e],
|
|
15
|
+
noExternal: !0
|
|
16
|
+
},
|
|
17
|
+
build: {
|
|
18
|
+
rollupOptions: {
|
|
19
|
+
input: Object.fromEntries(
|
|
20
|
+
i.map((t) => [
|
|
21
|
+
t.id,
|
|
22
|
+
`virtual:quizms-entry?id=${encodeURIComponent(`virtual:quizms-training-entry?id=${t.id}`)}`
|
|
23
|
+
])
|
|
24
|
+
)
|
|
25
|
+
},
|
|
26
|
+
sourcemap: !0
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
buildStart() {
|
|
33
|
+
this.environment.name === "training" && (process.env.QUIZMS_SHOW_SOLUTIONS = "true", this.emitFile({
|
|
34
|
+
type: "asset",
|
|
35
|
+
fileName: "contests.json",
|
|
36
|
+
source: JSON.stringify(i)
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
a as default
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=preset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.js","sources":["../../src/vite/preset.ts"],"sourcesContent":["import { loadContests } from \"@olinfo/quizms/utils-node\";\nimport { defaultClientConditions, defaultClientMainFields, type PluginOption } from \"vite\";\n\nexport default async function trainingPreset(): Promise<PluginOption> {\n const contests = await loadContests();\n\n return {\n name: \"quizms:training-preset\",\n apply: \"build\",\n config() {\n return {\n environments: {\n training: {\n resolve: {\n conditions: [...defaultClientConditions],\n mainFields: [...defaultClientMainFields],\n noExternal: true,\n },\n build: {\n rollupOptions: {\n input: Object.fromEntries(\n contests.map((contest) => [\n contest.id,\n `virtual:quizms-entry?id=${encodeURIComponent(`virtual:quizms-training-entry?id=${contest.id}`)}`,\n ]),\n ),\n },\n sourcemap: true,\n },\n },\n },\n };\n },\n buildStart() {\n if (this.environment.name === \"training\") {\n process.env.QUIZMS_SHOW_SOLUTIONS = \"true\";\n this.emitFile({\n type: \"asset\",\n fileName: \"contests.json\",\n source: JSON.stringify(contests),\n });\n }\n },\n };\n}\n"],"names":["trainingPreset","contests","loadContests","defaultClientConditions","defaultClientMainFields","contest"],"mappings":";;AAGA,eAA8BA,IAAwC;AACpE,QAAMC,IAAW,MAAMC,EAAA;AAEvB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AACP,aAAO;AAAA,QACL,cAAc;AAAA,UACZ,UAAU;AAAA,YACR,SAAS;AAAA,cACP,YAAY,CAAC,GAAGC,CAAuB;AAAA,cACvC,YAAY,CAAC,GAAGC,CAAuB;AAAA,cACvC,YAAY;AAAA,YAAA;AAAA,YAEd,OAAO;AAAA,cACL,eAAe;AAAA,gBACb,OAAO,OAAO;AAAA,kBACZH,EAAS,IAAI,CAACI,MAAY;AAAA,oBACxBA,EAAQ;AAAA,oBACR,2BAA2B,mBAAmB,oCAAoCA,EAAQ,EAAE,EAAE,CAAC;AAAA,kBAAA,CAChG;AAAA,gBAAA;AAAA,cACH;AAAA,cAEF,WAAW;AAAA,YAAA;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IAEJ;AAAA,IACA,aAAa;AACX,MAAI,KAAK,YAAY,SAAS,eAC5B,QAAQ,IAAI,wBAAwB,QACpC,KAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,KAAK,UAAUJ,CAAQ;AAAA,MAAA,CAChC;AAAA,IAEL;AAAA,EAAA;AAEJ;"}
|
package/dist/vite.d.ts
ADDED
package/dist/vite.js
ADDED
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sources":["../src/vite/index.ts"],"sourcesContent":["import trainingEntry from \"./entry\";\nimport trainingPreset from \"./preset\";\n\nexport default [trainingEntry(), trainingPreset()] as any;\n"],"names":["index","trainingEntry","trainingPreset"],"mappings":";;AAGA,MAAAA,IAAe,CAACC,EAAA,GAAiBC,GAAgB;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olinfo/quizms-training",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Alessandro Bortolin <bortolin.alessandro@outlook.it>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"default": "./dist/vite.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
17
20
|
"dependencies": {
|
|
18
21
|
"@olinfo/react-components": "^0.12.3",
|
|
19
22
|
"@types/lodash-es": "^4.17.12",
|
package/src/entry/iframe.ts
DELETED
|
@@ -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
|
-
]);
|
package/src/entry/index.tsx
DELETED
package/src/entry/provider.tsx
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { type ReactNode, useCallback, useEffect, 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, isFuture, 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
|
-
useEffect(() => {
|
|
54
|
-
if (type !== "anonymous") return;
|
|
55
|
-
|
|
56
|
-
const isRunning = student.participationWindow && isFuture(student.participationWindow.end);
|
|
57
|
-
|
|
58
|
-
if (!isRunning) return;
|
|
59
|
-
|
|
60
|
-
const controller = new AbortController();
|
|
61
|
-
|
|
62
|
-
window.addEventListener(
|
|
63
|
-
"beforeunload",
|
|
64
|
-
(event) => {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
},
|
|
67
|
-
{ signal: controller.signal },
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
return () => controller.abort();
|
|
71
|
-
}, [type, student.participationWindow]);
|
|
72
|
-
|
|
73
|
-
const { data: variant } = useSWR(
|
|
74
|
-
student.variantId && ["variant", contest.id, student.variantId],
|
|
75
|
-
getVariant,
|
|
76
|
-
{
|
|
77
|
-
revalidateOnFocus: false,
|
|
78
|
-
revalidateIfStale: false,
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const updateStudent = useCallback(
|
|
83
|
-
async (
|
|
84
|
-
iframeFn: (student: Student, contest: Contest) => Promise<Student | null>,
|
|
85
|
-
updateFn: (student: Student) => Student,
|
|
86
|
-
) => {
|
|
87
|
-
if (type === "anonymous") {
|
|
88
|
-
setAnonymousStudent(updateFn);
|
|
89
|
-
} else {
|
|
90
|
-
await mutate((student) => (student ? iframeFn(updateFn(student), contest) : null), {
|
|
91
|
-
optimisticData: (student) => (student ? updateFn(student) : null),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
[type, contest, mutate],
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const mockVenue: Venue = useMemo(
|
|
99
|
-
() => ({
|
|
100
|
-
id: "",
|
|
101
|
-
schoolId: "",
|
|
102
|
-
contestId: contest.id,
|
|
103
|
-
name: "",
|
|
104
|
-
participationWindow: student.participationWindow,
|
|
105
|
-
finalized: false,
|
|
106
|
-
disabled: false,
|
|
107
|
-
pdfVariants: [],
|
|
108
|
-
}),
|
|
109
|
-
[contest.id, student.participationWindow],
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const setAnswer = useCallback(
|
|
113
|
-
async (problemId: string, answer: Answer) => {
|
|
114
|
-
if (!variant) throw new Error("Variant non loaded");
|
|
115
|
-
await updateStudent(
|
|
116
|
-
setAnswersIframe(variant),
|
|
117
|
-
(student) =>
|
|
118
|
-
({ ...student, answers: { ...student.answers, [problemId]: answer } }) satisfies Student,
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
[updateStudent, variant],
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const submit = useCallback(async () => {
|
|
125
|
-
await updateStudent(
|
|
126
|
-
submitIframe,
|
|
127
|
-
(student) =>
|
|
128
|
-
({
|
|
129
|
-
...student,
|
|
130
|
-
participationWindow: { start: student.participationWindow!.start, end: new Date() },
|
|
131
|
-
}) satisfies Student,
|
|
132
|
-
);
|
|
133
|
-
}, [updateStudent]);
|
|
134
|
-
|
|
135
|
-
const reset = useCallback(async () => {
|
|
136
|
-
await updateStudent(
|
|
137
|
-
resetIframe,
|
|
138
|
-
(student) =>
|
|
139
|
-
({
|
|
140
|
-
...student,
|
|
141
|
-
answers: {},
|
|
142
|
-
participationWindow: undefined,
|
|
143
|
-
}) satisfies Student,
|
|
144
|
-
);
|
|
145
|
-
}, [updateStudent]);
|
|
146
|
-
|
|
147
|
-
const logout = useCallback(async () => {
|
|
148
|
-
await updateStudent(logoutIframe, (student) => student);
|
|
149
|
-
}, [updateStudent]);
|
|
150
|
-
|
|
151
|
-
const start = useCallback(async () => {
|
|
152
|
-
const now = new Date();
|
|
153
|
-
const variantId = getRandomVariant(contest);
|
|
154
|
-
const variant = await preload(["variant", contest.id, variantId], getVariant);
|
|
155
|
-
await updateStudent(
|
|
156
|
-
startIframe(variant),
|
|
157
|
-
(student) =>
|
|
158
|
-
({
|
|
159
|
-
...student,
|
|
160
|
-
variantId,
|
|
161
|
-
participationWindow: {
|
|
162
|
-
start: subSeconds(now, 2),
|
|
163
|
-
end: addMinutes(now, contest.onlineSettings?.duration ?? 0),
|
|
164
|
-
},
|
|
165
|
-
}) satisfies Student,
|
|
166
|
-
);
|
|
167
|
-
}, [contest, updateStudent]);
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<TitleProvider title={title}>
|
|
171
|
-
<TrainingStatementContext.Provider value={{ start }}>
|
|
172
|
-
<StudentProvider
|
|
173
|
-
contest={contest}
|
|
174
|
-
venue={mockVenue}
|
|
175
|
-
student={student}
|
|
176
|
-
setAnswer={setAnswer}
|
|
177
|
-
submit={submit}
|
|
178
|
-
reset={reset}
|
|
179
|
-
logout={logout}
|
|
180
|
-
enforceFullscreen={false}
|
|
181
|
-
schema={variant?.schema}>
|
|
182
|
-
{children}
|
|
183
|
-
</StudentProvider>
|
|
184
|
-
</TrainingStatementContext.Provider>
|
|
185
|
-
</TitleProvider>
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
TrainingProvider.displayName = "TrainingProvider";
|
|
189
|
-
|
|
190
|
-
function createAnonymousStudent(contest: VariantsConfig): Student {
|
|
191
|
-
return {
|
|
192
|
-
id: "",
|
|
193
|
-
userData: {
|
|
194
|
-
name: "Utente",
|
|
195
|
-
surname: "anonimo",
|
|
196
|
-
},
|
|
197
|
-
answers: {},
|
|
198
|
-
contestId: contest.id,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const rng = new Rng();
|
|
203
|
-
|
|
204
|
-
function getRandomVariant(contest: VariantsConfig): string {
|
|
205
|
-
return rng.choice(contest.variantIds);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function getVariant([, contestId, variantId]: [string, string, string]): Promise<Variant> {
|
|
209
|
-
const resp = await fetch(`/variants/${contestId}/${variantId}/answers.json`);
|
|
210
|
-
if (!resp.ok) throw new Error("Failed to fetch schema");
|
|
211
|
-
const json: unknown = await resp.json();
|
|
212
|
-
return validate(variantSchema, json);
|
|
213
|
-
}
|
package/src/entry/statement.tsx
DELETED
|
@@ -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
package/src/vite/preset.ts
DELETED
|
@@ -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
|
-
}
|
package/tailwind.config.mjs
DELETED
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
|
-
});
|