@olinfo/quizms-firebase 4.0.0 → 4.1.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.
Files changed (65) hide show
  1. package/dist/functions.d.ts +2 -11
  2. package/dist/functions.js +2 -2
  3. package/dist/package.json.js +1 -1
  4. package/dist/src/cli/files/functions-index.js +4 -4
  5. package/dist/src/cli/files/functions-index.js.map +1 -1
  6. package/dist/src/cli/init.js +8 -8
  7. package/dist/src/cli/init.js.map +1 -1
  8. package/dist/src/cli/update-scores.js +21 -32
  9. package/dist/src/cli/update-scores.js.map +1 -1
  10. package/dist/src/cli/utils/collection.js +20 -18
  11. package/dist/src/cli/utils/collection.js.map +1 -1
  12. package/dist/src/cli/utils/converters-admin.js +31 -32
  13. package/dist/src/cli/utils/converters-admin.js.map +1 -1
  14. package/dist/src/cli/utils/region-finder.js +6 -5
  15. package/dist/src/cli/utils/region-finder.js.map +1 -1
  16. package/dist/src/cli/utils/storage.js +26 -35
  17. package/dist/src/cli/utils/storage.js.map +1 -1
  18. package/dist/src/cli/utils/users.js +13 -12
  19. package/dist/src/cli/utils/users.js.map +1 -1
  20. package/dist/src/functions/api/admin.js +28 -0
  21. package/dist/src/functions/api/admin.js.map +1 -0
  22. package/dist/src/functions/api/index.js +26 -34
  23. package/dist/src/functions/api/index.js.map +1 -1
  24. package/dist/src/functions/api/queries.js +5 -5
  25. package/dist/src/functions/api/student.js +62 -72
  26. package/dist/src/functions/api/student.js.map +1 -1
  27. package/dist/src/functions/api/teacher.js +104 -111
  28. package/dist/src/functions/api/teacher.js.map +1 -1
  29. package/dist/src/functions/api/trpc.js +18 -0
  30. package/dist/src/functions/api/trpc.js.map +1 -0
  31. package/dist/src/functions/submissions.js +1 -1
  32. package/dist/src/functions/submissions.js.map +1 -1
  33. package/dist/src/models/user.js +2 -2
  34. package/dist/src/models/user.js.map +1 -1
  35. package/dist/src/models/website.js +1 -1
  36. package/dist/src/models/website.js.map +1 -1
  37. package/dist/src/vite/preset.js +3 -4
  38. package/dist/src/vite/preset.js.map +1 -1
  39. package/dist/src/web/admin/announcements.js +9 -10
  40. package/dist/src/web/admin/announcements.js.map +1 -1
  41. package/dist/src/web/admin/school-table.js +7 -8
  42. package/dist/src/web/admin/school-table.js.map +1 -1
  43. package/dist/src/web/common/api.js +32 -35
  44. package/dist/src/web/common/api.js.map +1 -1
  45. package/dist/src/web/common/converters.js +35 -36
  46. package/dist/src/web/common/converters.js.map +1 -1
  47. package/dist/src/web/common/token-login.js +11 -11
  48. package/dist/src/web/common/token-login.js.map +1 -1
  49. package/dist/src/web/entry/admin.js +5 -7
  50. package/dist/src/web/entry/admin.js.map +1 -1
  51. package/dist/src/web/entry/student-restoring.js +18 -20
  52. package/dist/src/web/entry/student-restoring.js.map +1 -1
  53. package/dist/src/web/entry/student.js +70 -76
  54. package/dist/src/web/entry/student.js.map +1 -1
  55. package/dist/src/web/entry/teacher.js +68 -68
  56. package/dist/src/web/entry/teacher.js.map +1 -1
  57. package/dist/src/web/hooks/collection.js +29 -30
  58. package/dist/src/web/hooks/collection.js.map +1 -1
  59. package/dist/src/web/hooks/document.js +35 -36
  60. package/dist/src/web/hooks/document.js.map +1 -1
  61. package/dist/src/web/hooks/subscription.js +26 -22
  62. package/dist/src/web/hooks/subscription.js.map +1 -1
  63. package/package.json +23 -19
  64. package/dist/src/functions/api/schema.js +0 -42
  65. package/dist/src/functions/api/schema.js.map +0 -1
@@ -1,47 +1,44 @@
1
- import { jsx as t, jsxs as u, Fragment as b } from "react/jsx-runtime";
1
+ import { jsx as t, jsxs as u, Fragment as f } from "react/jsx-runtime";
2
2
  import { useMemo as w, useCallback as p, useEffect as v } from "react";
3
- import { Title as x, StudentFormField as F } from "@olinfo/quizms/components";
3
+ import { Title as F, StudentFormField as x } from "@olinfo/quizms/components";
4
4
  import { StudentProvider as I } from "@olinfo/quizms/student";
5
- import { Navbar as k, NavbarBrand as A, Form as C, SelectField as D, TextField as W, SubmitButton as y } from "@olinfo/react-components";
6
- import { signOut as E, getAuth as g, signInWithCustomToken as H } from "firebase/auth";
7
- import { waitForPendingWrites as f } from "firebase/firestore";
8
- import { isEqual as N } from "lodash-es";
5
+ import { useUserAgent as k } from "@olinfo/quizms/utils";
6
+ import { Navbar as A, NavbarBrand as C, Form as W, SelectField as y, TextField as E, SubmitButton as H } from "@olinfo/react-components";
7
+ import { signOut as N, getAuth as g, signInWithCustomToken as D } from "firebase/auth";
8
+ import { waitForPendingWrites as h } from "firebase/firestore";
9
+ import { isEqual as T } from "lodash-es";
9
10
  import { studentLogin as j } from "../common/api.js";
10
11
  import { useDb as S } from "../common/base-login.js";
11
12
  import { contestConverter as M, studentConverter as P, participationConverter as R } from "../common/converters.js";
12
- import { useWebsite as T } from "../common/website.js";
13
- import { useAuth as q } from "../hooks/auth.js";
14
- import { useCollection as z } from "../hooks/collection.js";
15
- import { useDocument as h } from "../hooks/document.js";
16
- import { StudentRestoring as B } from "./student-restoring.js";
17
- import { FirebaseStatement as O } from "./student-statement.js";
18
- import G from "virtual:quizms-firebase-header";
19
- function ut() {
20
- const e = q("student"), o = T(), [a] = z("contests", M, {
21
- constraints: { id: o.contests },
22
- subscribe: !0
13
+ import { useWebsite as q } from "../common/website.js";
14
+ import { useAuth as z } from "../hooks/auth.js";
15
+ import { useCollection as B } from "../hooks/collection.js";
16
+ import { useDocument as b } from "../hooks/document.js";
17
+ import { StudentRestoring as O } from "./student-restoring.js";
18
+ import { FirebaseStatement as G } from "./student-statement.js";
19
+ import L from "virtual:quizms-firebase-header";
20
+ function lt() {
21
+ const e = z("student"), s = q(), [o] = B("contests", M, {
22
+ constraints: { id: s.contests }
23
23
  });
24
- return e ? /* @__PURE__ */ t(B, { user: e.user, children: /* @__PURE__ */ t(
24
+ return e ? /* @__PURE__ */ t(O, { user: e.user, children: /* @__PURE__ */ t(
25
25
  V,
26
26
  {
27
- contests: a,
27
+ contests: o,
28
28
  participationId: e.claims.participationId,
29
29
  studentId: e.claims.studentId
30
30
  }
31
- ) }) : /* @__PURE__ */ t(L, { contests: a });
31
+ ) }) : /* @__PURE__ */ t(U, { contests: o });
32
32
  }
33
- function L({ contests: e }) {
34
- const o = S(), a = p(
35
- async ({ contestId: n, token: s, ...i }) => {
36
- const d = await j(o, {
37
- contestId: n,
38
- token: s,
39
- userData: {
40
- ...i,
41
- birthDate: i.birthDate ? new Date(i.birthDate) : void 0
42
- },
33
+ function U({ contests: e }) {
34
+ const s = S(), o = k(), d = p(
35
+ async ({ contestId: r, token: a, ...n }) => {
36
+ const l = await j({
37
+ contestId: r,
38
+ token: a,
39
+ userData: n,
43
40
  extraData: {
44
- userAgent: navigator.userAgent,
41
+ userAgent: o,
45
42
  windowWidth: window.innerWidth,
46
43
  windowHeight: window.innerHeight,
47
44
  screenWidth: window.screen.availWidth,
@@ -49,36 +46,36 @@ function L({ contests: e }) {
49
46
  pixelRatio: window.devicePixelRatio,
50
47
  darkMode: window.matchMedia("(prefers-color-scheme: dark)").matches
51
48
  }
52
- }), m = g(o.app);
53
- await H(m, d.token), await document.documentElement.requestFullscreen();
49
+ }), c = g(s.app);
50
+ await D(c, l), o.hasFullscreen && await document.documentElement.requestFullscreen?.();
54
51
  },
55
- [o]
56
- ), r = w(
52
+ [s, o]
53
+ ), i = w(
57
54
  () => ({
58
55
  contestId: e.length === 1 ? e[0].id : void 0
59
56
  }),
60
57
  [e]
61
58
  );
62
- return /* @__PURE__ */ u(b, { children: [
63
- /* @__PURE__ */ t(k, { color: "bg-base-300 text-base-content", children: /* @__PURE__ */ t(A, { children: /* @__PURE__ */ t("div", { className: "flex items-center h-full font-bold", children: /* @__PURE__ */ t(x, {}) }) }) }),
64
- /* @__PURE__ */ u(C, { defaultValue: r, onSubmit: a, className: "p-4 pb-8", children: [
59
+ return /* @__PURE__ */ u(f, { children: [
60
+ /* @__PURE__ */ t(A, { color: "bg-base-300 text-base-content", children: /* @__PURE__ */ t(C, { children: /* @__PURE__ */ t("div", { className: "flex items-center h-full font-bold", children: /* @__PURE__ */ t(F, {}) }) }) }),
61
+ /* @__PURE__ */ u(W, { defaultValue: i, onSubmit: d, className: "p-4 pb-8", children: [
65
62
  /* @__PURE__ */ t("h1", { className: "mb-2 text-xl font-bold", children: "Accedi alla gara" }),
66
63
  /* @__PURE__ */ t(
67
- D,
64
+ y,
68
65
  {
69
66
  field: "contestId",
70
67
  label: "Gara",
71
- options: Object.fromEntries(e.map((n) => [n.id, n.name])),
68
+ options: Object.fromEntries(e.map((r) => [r.id, r.name])),
72
69
  placeholder: "Seleziona una gara"
73
70
  }
74
71
  ),
75
- ({ contestId: n }) => {
76
- const s = e.find((i) => i.id === n);
77
- if (s)
78
- return /* @__PURE__ */ u(b, { children: [
79
- s.userData.map((i) => /* @__PURE__ */ t(F, { field: i }, i.name)),
80
- /* @__PURE__ */ t(W, { field: "token", label: "Codice prova", placeholder: "aaaaa-bbbbb-ccccc" }),
81
- /* @__PURE__ */ t(y, { children: "Inizia la prova" })
72
+ ({ contestId: r }) => {
73
+ const a = e.find((n) => n.id === r);
74
+ if (a)
75
+ return /* @__PURE__ */ u(f, { children: [
76
+ a.userData.map((n) => /* @__PURE__ */ t(x, { field: n }, n.name)),
77
+ /* @__PURE__ */ t(E, { field: "token", label: "Codice prova", placeholder: "aaaaa-bbbbb-ccccc" }),
78
+ /* @__PURE__ */ t(H, { children: "Inizia la prova" })
82
79
  ] });
83
80
  }
84
81
  ] })
@@ -86,46 +83,43 @@ function L({ contests: e }) {
86
83
  }
87
84
  function V({
88
85
  contests: e,
89
- participationId: o,
90
- studentId: a
86
+ participationId: s,
87
+ studentId: o
91
88
  }) {
92
- const r = S(), [n, s] = h(
93
- `participations/${o}/students`,
94
- a,
95
- P,
96
- { subscribe: !0 }
97
- ), [i] = h("participations", o, R, {
98
- subscribe: !0
99
- }), d = w(
100
- () => e.find((c) => c.id === n.contestId),
101
- [e, n.contestId]
102
- ), m = p(async () => {
103
- await f(r);
104
- }, [r]), l = p(async () => {
105
- await f(r), await E(g(r.app)), window.location.reload();
106
- }, [r]);
89
+ const d = S(), [i, r] = b(
90
+ `participations/${s}/students`,
91
+ o,
92
+ P
93
+ ), [a] = b("participations", s, R), n = w(
94
+ () => e.find((m) => m.id === i.contestId),
95
+ [e, i.contestId]
96
+ ), l = p(async () => {
97
+ await h(d);
98
+ }, [d]), c = p(async () => {
99
+ await h(d), await N(g(d.app)), window.location.reload();
100
+ }, [d]);
107
101
  return v(() => {
108
- i.token !== n.token && l();
109
- }, [i.token, n.token, l]), /* @__PURE__ */ u(
102
+ a.token !== i.token && c();
103
+ }, [a.token, i.token, c]), /* @__PURE__ */ u(
110
104
  I,
111
105
  {
112
- contest: d,
113
- participation: i,
114
- student: n,
115
- setStudent: async (c) => {
116
- N(n, c) || await s({ ...c });
106
+ contest: n,
107
+ participation: a,
108
+ student: i,
109
+ setStudent: async (m) => {
110
+ T(i, m) || await r({ ...m });
117
111
  },
118
- logout: l,
119
- onSubmit: m,
112
+ logout: c,
113
+ onSubmit: l,
120
114
  enforceFullscreen: !0,
121
115
  children: [
122
- /* @__PURE__ */ t(G, { contestId: d.id }),
123
- /* @__PURE__ */ t(O, {})
116
+ /* @__PURE__ */ t(L, { contestId: n.id }),
117
+ /* @__PURE__ */ t(G, {})
124
118
  ]
125
119
  }
126
120
  );
127
121
  }
128
122
  export {
129
- ut as default
123
+ lt as default
130
124
  };
131
125
  //# sourceMappingURL=student.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"student.js","sources":["../../../../src/web/entry/student.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo } from \"react\";\n\nimport { StudentFormField, Title } from \"@olinfo/quizms/components\";\nimport type { Contest, Student } from \"@olinfo/quizms/models\";\nimport { StudentProvider } from \"@olinfo/quizms/student\";\nimport {\n Form,\n Navbar,\n NavbarBrand,\n SelectField,\n SubmitButton,\n TextField,\n} from \"@olinfo/react-components\";\nimport { getAuth, signInWithCustomToken, signOut } from \"firebase/auth\";\nimport { waitForPendingWrites } from \"firebase/firestore\";\nimport { isEqual } from \"lodash-es\";\n\nimport { studentLogin } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n} from \"~/web/common/converters\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useAuth, useCollection, useDocument } from \"~/web/hooks\";\n\nimport { StudentRestoring } from \"./student-restoring\";\nimport { FirebaseStatement } from \"./student-statement\";\n// @ts-expect-error\nimport Header from \"virtual:quizms-firebase-header\";\n\nexport default function StudentEntry() {\n const auth = useAuth(\"student\");\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n subscribe: true,\n });\n\n if (auth) {\n return (\n <StudentRestoring user={auth.user}>\n <StudentInner\n contests={contests}\n participationId={auth.claims.participationId}\n studentId={auth.claims.studentId}\n />\n </StudentRestoring>\n );\n }\n\n return <StudentForm contests={contests} />;\n}\n\ntype FormStudent = {\n contestId: string;\n token: string;\n} & Student[\"userData\"];\n\nfunction StudentForm({ contests }: { contests: Contest[] }) {\n const db = useDb();\n\n const submit = useCallback(\n async ({ contestId, token, ...userData }: FormStudent) => {\n const data = await studentLogin(db, {\n contestId,\n token,\n userData: {\n ...userData,\n birthDate: userData.birthDate ? new Date(userData.birthDate) : undefined,\n },\n extraData: {\n userAgent: navigator.userAgent,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight,\n screenWidth: window.screen.availWidth,\n screenHeight: window.screen.availHeight,\n pixelRatio: window.devicePixelRatio,\n darkMode: window.matchMedia(\"(prefers-color-scheme: dark)\").matches,\n },\n });\n\n const auth = getAuth(db.app);\n await signInWithCustomToken(auth, data.token);\n await document.documentElement.requestFullscreen();\n },\n [db],\n );\n\n const defaultValue = useMemo(\n () => ({\n contestId: contests.length === 1 ? contests[0].id : undefined,\n }),\n [contests],\n );\n\n return (\n <>\n <Navbar color=\"bg-base-300 text-base-content\">\n <NavbarBrand>\n <div className=\"flex items-center h-full font-bold\">\n <Title />\n </div>\n </NavbarBrand>\n </Navbar>\n <Form defaultValue={defaultValue} onSubmit={submit} className=\"p-4 pb-8\">\n <h1 className=\"mb-2 text-xl font-bold\">Accedi alla gara</h1>\n <SelectField\n field=\"contestId\"\n label=\"Gara\"\n options={Object.fromEntries(contests.map((c) => [c.id, c.name]))}\n placeholder=\"Seleziona una gara\"\n />\n {({ contestId }) => {\n const contest = contests.find((c) => c.id === contestId);\n if (!contest) return;\n return (\n <>\n {contest.userData.map((field) => (\n <StudentFormField key={field.name} field={field} />\n ))}\n <TextField field=\"token\" label=\"Codice prova\" placeholder=\"aaaaa-bbbbb-ccccc\" />\n <SubmitButton>Inizia la prova</SubmitButton>\n </>\n );\n }}\n </Form>\n </>\n );\n}\n\nfunction StudentInner({\n contests,\n participationId,\n studentId,\n}: {\n contests: Contest[];\n participationId: string;\n studentId: string;\n}) {\n const db = useDb();\n\n const [student, setStudent] = useDocument(\n `participations/${participationId}/students`,\n studentId,\n studentConverter,\n { subscribe: true },\n );\n const [participation] = useDocument(\"participations\", participationId, participationConverter, {\n subscribe: true,\n });\n\n const contest = useMemo(\n () => contests.find((c) => c.id === student.contestId)!,\n [contests, student.contestId],\n );\n\n const onSubmit = useCallback(async () => {\n await waitForPendingWrites(db);\n }, [db]);\n\n const logout = useCallback(async () => {\n await waitForPendingWrites(db);\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n useEffect(() => {\n if (participation.token !== student.token) {\n void logout();\n }\n }, [participation.token, student.token, logout]);\n\n const setStudentAndSubmission = async (newStudent: Student) => {\n if (!isEqual(student, newStudent)) {\n await setStudent({ ...newStudent });\n }\n };\n\n return (\n <StudentProvider\n contest={contest}\n participation={participation}\n student={student}\n setStudent={setStudentAndSubmission}\n logout={logout}\n onSubmit={onSubmit}\n enforceFullscreen={true}>\n <Header contestId={contest.id} />\n <FirebaseStatement />\n </StudentProvider>\n );\n}\n"],"names":["StudentEntry","auth","useAuth","website","useWebsite","contests","useCollection","contestConverter","jsx","StudentRestoring","StudentInner","StudentForm","db","useDb","submit","useCallback","contestId","token","userData","data","studentLogin","getAuth","signInWithCustomToken","defaultValue","useMemo","jsxs","Fragment","Navbar","NavbarBrand","Title","Form","SelectField","c","contest","field","StudentFormField","TextField","SubmitButton","participationId","studentId","student","setStudent","useDocument","studentConverter","participation","participationConverter","onSubmit","waitForPendingWrites","logout","signOut","useEffect","StudentProvider","newStudent","isEqual","Header","FirebaseStatement"],"mappings":"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"}
1
+ {"version":3,"file":"student.js","sources":["../../../../src/web/entry/student.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo } from \"react\";\n\nimport { StudentFormField, Title } from \"@olinfo/quizms/components\";\nimport type { Contest, Student } from \"@olinfo/quizms/models\";\nimport { StudentProvider } from \"@olinfo/quizms/student\";\nimport { useUserAgent } from \"@olinfo/quizms/utils\";\nimport {\n Form,\n Navbar,\n NavbarBrand,\n SelectField,\n SubmitButton,\n TextField,\n} from \"@olinfo/react-components\";\nimport { getAuth, signInWithCustomToken, signOut } from \"firebase/auth\";\nimport { waitForPendingWrites } from \"firebase/firestore\";\nimport { isEqual } from \"lodash-es\";\n\nimport { studentLogin } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n} from \"~/web/common/converters\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useAuth, useCollection, useDocument } from \"~/web/hooks\";\n\nimport { StudentRestoring } from \"./student-restoring\";\nimport { FirebaseStatement } from \"./student-statement\";\n// @ts-expect-error\nimport Header from \"virtual:quizms-firebase-header\";\n\nexport default function StudentEntry() {\n const auth = useAuth(\"student\");\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n });\n\n if (auth) {\n return (\n <StudentRestoring user={auth.user}>\n <StudentInner\n contests={contests}\n participationId={auth.claims.participationId}\n studentId={auth.claims.studentId}\n />\n </StudentRestoring>\n );\n }\n\n return <StudentForm contests={contests} />;\n}\n\ntype FormStudent = {\n contestId: string;\n token: string;\n} & Required<NonNullable<Student[\"userData\"]>>;\n\nfunction StudentForm({ contests }: { contests: Contest[] }) {\n const db = useDb();\n const ua = useUserAgent();\n\n const submit = useCallback(\n async ({ contestId, token, ...userData }: FormStudent) => {\n const authToken = await studentLogin({\n contestId,\n token,\n userData,\n extraData: {\n userAgent: ua,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight,\n screenWidth: window.screen.availWidth,\n screenHeight: window.screen.availHeight,\n pixelRatio: window.devicePixelRatio,\n darkMode: window.matchMedia(\"(prefers-color-scheme: dark)\").matches,\n },\n });\n\n const auth = getAuth(db.app);\n await signInWithCustomToken(auth, authToken);\n if (ua.hasFullscreen) {\n await document.documentElement.requestFullscreen?.();\n }\n },\n [db, ua],\n );\n\n const defaultValue = useMemo(\n () => ({\n contestId: contests.length === 1 ? contests[0].id : undefined,\n }),\n [contests],\n );\n\n return (\n <>\n <Navbar color=\"bg-base-300 text-base-content\">\n <NavbarBrand>\n <div className=\"flex items-center h-full font-bold\">\n <Title />\n </div>\n </NavbarBrand>\n </Navbar>\n <Form defaultValue={defaultValue} onSubmit={submit} className=\"p-4 pb-8\">\n <h1 className=\"mb-2 text-xl font-bold\">Accedi alla gara</h1>\n <SelectField\n field=\"contestId\"\n label=\"Gara\"\n options={Object.fromEntries(contests.map((c) => [c.id, c.name]))}\n placeholder=\"Seleziona una gara\"\n />\n {({ contestId }) => {\n const contest = contests.find((c) => c.id === contestId);\n if (!contest) return;\n return (\n <>\n {contest.userData.map((field) => (\n <StudentFormField key={field.name} field={field} />\n ))}\n <TextField field=\"token\" label=\"Codice prova\" placeholder=\"aaaaa-bbbbb-ccccc\" />\n <SubmitButton>Inizia la prova</SubmitButton>\n </>\n );\n }}\n </Form>\n </>\n );\n}\n\nfunction StudentInner({\n contests,\n participationId,\n studentId,\n}: {\n contests: Contest[];\n participationId: string;\n studentId: string;\n}) {\n const db = useDb();\n\n const [student, setStudent] = useDocument(\n `participations/${participationId}/students`,\n studentId,\n studentConverter,\n );\n const [participation] = useDocument(\"participations\", participationId, participationConverter);\n\n const contest = useMemo(\n () => contests.find((c) => c.id === student.contestId)!,\n [contests, student.contestId],\n );\n\n const onSubmit = useCallback(async () => {\n await waitForPendingWrites(db);\n }, [db]);\n\n const logout = useCallback(async () => {\n await waitForPendingWrites(db);\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n useEffect(() => {\n if (participation.token !== student.token) {\n void logout();\n }\n }, [participation.token, student.token, logout]);\n\n const setStudentAndSubmission = async (newStudent: Student) => {\n if (!isEqual(student, newStudent)) {\n await setStudent({ ...newStudent });\n }\n };\n\n return (\n <StudentProvider\n contest={contest}\n participation={participation}\n student={student}\n setStudent={setStudentAndSubmission}\n logout={logout}\n onSubmit={onSubmit}\n enforceFullscreen={true}>\n <Header contestId={contest.id} />\n <FirebaseStatement />\n </StudentProvider>\n );\n}\n"],"names":["StudentEntry","auth","useAuth","website","useWebsite","contests","useCollection","contestConverter","jsx","StudentRestoring","StudentInner","StudentForm","db","useDb","ua","useUserAgent","submit","useCallback","contestId","token","userData","authToken","studentLogin","getAuth","signInWithCustomToken","defaultValue","useMemo","jsxs","Fragment","Navbar","NavbarBrand","Title","Form","SelectField","c","contest","field","StudentFormField","TextField","SubmitButton","participationId","studentId","student","setStudent","useDocument","studentConverter","participation","participationConverter","onSubmit","waitForPendingWrites","logout","signOut","useEffect","StudentProvider","newStudent","isEqual","Header","FirebaseStatement"],"mappings":"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"}
@@ -1,88 +1,88 @@
1
- import { jsx as u } from "react/jsx-runtime";
2
- import { useCallback as v } from "react";
3
- import { TeacherProvider as g } from "@olinfo/quizms/teacher";
4
- import { signOut as h, getAuth as w } from "firebase/auth";
5
- import { writeBatch as f, doc as d, serverTimestamp as P } from "firebase/firestore";
6
- import { getStorage as y, getBytes as C, ref as R } from "firebase/storage";
7
- import { finalizeParticipation as I, stopParticipation as S, startParticipation as k } from "../common/api.js";
8
- import { useDb as b } from "../common/base-login.js";
9
- import { contestConverter as A, participationConverter as l, studentRestoreConvert as T, studentConverter as B, variantConverter as $ } from "../common/converters.js";
10
- import j from "../common/token-login.js";
11
- import { useWebsite as x } from "../common/website.js";
1
+ import { jsx as d } from "react/jsx-runtime";
2
+ import { useCallback as g } from "react";
3
+ import { TeacherProvider as h } from "@olinfo/quizms/teacher";
4
+ import { signOut as w, getAuth as y } from "firebase/auth";
5
+ import { writeBatch as f, doc as u, serverTimestamp as b } from "firebase/firestore";
6
+ import { getStorage as C, getBytes as P, ref as R } from "firebase/storage";
7
+ import { finalizeParticipation as A, stopParticipation as I, startParticipation as S } from "../common/api.js";
8
+ import { useDb as l } from "../common/base-login.js";
9
+ import { contestConverter as k, participationConverter as v, studentRestoreConvert as B, studentConverter as T, announcementConverter as $, variantConverter as j } from "../common/converters.js";
10
+ import x from "../common/token-login.js";
11
+ import { useWebsite as z } from "../common/website.js";
12
12
  import "lodash-es";
13
13
  import "react-error-boundary";
14
14
  import "swr";
15
15
  import { useCollection as c } from "../hooks/collection.js";
16
- import { useDocument as z } from "../hooks/document.js";
17
- import { FirebaseStatement as D } from "./student-statement.js";
16
+ import { useDocument as D } from "../hooks/document.js";
17
+ import { FirebaseStatement as E } from "./student-statement.js";
18
18
  function rt() {
19
- return /* @__PURE__ */ u(j, { allowedRole: "teacher", children: ({ claims: o }) => /* @__PURE__ */ u(E, { schoolId: o.schoolId }) });
19
+ return /* @__PURE__ */ d(x, { allowedRole: "teacher", children: ({ user: t, claims: e }) => /* @__PURE__ */ d(O, { user: t, schoolId: e.schoolId }) });
20
20
  }
21
- function E({ schoolId: o }) {
22
- const t = b(), n = x(), [s] = c("contests", A, {
23
- constraints: { id: n.contests },
24
- subscribe: !0
25
- }), [i] = c("participations", l, {
26
- constraints: { contestId: n.contests, schoolId: o },
27
- subscribe: !0
28
- }), [a] = c("variants", $), r = v(async () => {
29
- await h(w(t.app)), window.location.reload();
30
- }, [t]);
31
- return /* @__PURE__ */ u(
32
- g,
21
+ function O({ user: t, schoolId: e }) {
22
+ const r = l(), s = z(), [a] = c("contests", k, {
23
+ constraints: { id: s.contests }
24
+ }), [m] = c("participations", v, {
25
+ constraints: { contestId: s.contests, schoolId: e }
26
+ }), [n] = c("variants", j), i = g(async () => {
27
+ await w(y(r.app)), window.location.reload();
28
+ }, [r]);
29
+ return /* @__PURE__ */ d(
30
+ h,
33
31
  {
34
- participations: i,
35
- contests: s,
36
- startParticipation: (...e) => k(t, ...e),
37
- stopParticipation: (...e) => S(t, ...e),
38
- finalizeParticipation: (...e) => I(t, ...e),
39
- variants: a,
40
- logout: r,
41
- statementComponent: () => /* @__PURE__ */ u(D, {}),
42
- getPdfStatements: (e, p) => O(t, e, p),
43
- useAnnouncements: F,
44
- useStudents: L,
45
- useStudentRestores: W
32
+ participations: m,
33
+ contests: a,
34
+ startParticipation: (...o) => S(t, ...o),
35
+ stopParticipation: (...o) => I(t, ...o),
36
+ finalizeParticipation: (...o) => A(t, ...o),
37
+ variants: n,
38
+ logout: i,
39
+ statementComponent: () => /* @__PURE__ */ d(E, {}),
40
+ getPdfStatements: (o, p) => F(r, o, p),
41
+ useAnnouncements: L,
42
+ useStudents: W,
43
+ useStudentRestores: G
46
44
  }
47
45
  );
48
46
  }
49
- async function O(o, t, n) {
50
- const s = y(o.app), i = await Promise.all(
51
- n.map(
52
- async (a) => [a, await C(R(s, `statements/${t}/${a}/statement.pdf`))]
47
+ async function F(t, e, r) {
48
+ const s = C(t.app), a = await Promise.all(
49
+ r.map(
50
+ async (m) => [m, await P(R(s, `statements/${e}/${m}/statement.pdf`))]
53
51
  )
54
52
  );
55
- return Object.fromEntries(i);
53
+ return Object.fromEntries(a);
56
54
  }
57
- function F(o) {
58
- return [];
55
+ function L(t) {
56
+ const [e] = c("announcements", $, {
57
+ arrayConstraints: {
58
+ contestIds: t
59
+ },
60
+ orderBy: ["createdAt", "desc"]
61
+ });
62
+ return e;
59
63
  }
60
- function L(o) {
61
- return c(`participations/${o}/students`, B, {
62
- orderBy: "createdAt",
63
- subscribe: !0
64
+ function W(t) {
65
+ return c(`participations/${t}/students`, T, {
66
+ orderBy: "createdAt"
64
67
  });
65
68
  }
66
- function W(o) {
67
- const t = b(), [n] = z("participations", o, l, {
68
- subscribe: !0
69
- }), [s] = c("studentRestores", T, {
70
- constraints: { participationId: o, token: n.token, status: "pending" },
71
- orderBy: "createdAt",
72
- subscribe: !0
73
- }), i = async (r, e) => {
74
- const p = f(t);
75
- for (const m of s)
76
- m.studentId === r && m.id !== e && p.update(d(t, "studentRestores", m.id), { status: "revoked" });
77
- await p.commit();
69
+ function G(t) {
70
+ const e = l(), [r] = D("participations", t, v), [s] = c("studentRestores", B, {
71
+ constraints: { participationId: t, token: r.token, status: "pending" },
72
+ orderBy: "createdAt"
73
+ }), a = async (n, i) => {
74
+ const o = f(e);
75
+ for (const p of s)
76
+ p.studentId === n && p.id !== i && o.update(u(e, "studentRestores", p.id), { status: "revoked" });
77
+ await o.commit();
78
78
  };
79
- return [s, async (r) => {
80
- const e = f(t);
81
- e.update(d(t, `/participations/${o}/students`, r.studentId), {
82
- uid: r.id,
83
- updatedAt: P()
84
- }), e.delete(d(t, "studentRestores", r.id)), await e.commit(), await i(r.studentId, r.id);
85
- }, i];
79
+ return [s, async (n) => {
80
+ const i = f(e);
81
+ i.update(u(e, `/participations/${t}/students`, n.studentId), {
82
+ uid: n.id,
83
+ updatedAt: b()
84
+ }), i.delete(u(e, "studentRestores", n.id)), await i.commit(), await a(n.studentId, n.id);
85
+ }, a];
86
86
  }
87
87
  export {
88
88
  rt as default
@@ -1 +1 @@
1
- {"version":3,"file":"teacher.js","sources":["../../../../src/web/entry/teacher.tsx"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { StudentRestore } from \"@olinfo/quizms/models\";\nimport { TeacherProvider } from \"@olinfo/quizms/teacher\";\nimport { getAuth, signOut } from \"firebase/auth\";\nimport { doc, type Firestore, serverTimestamp, writeBatch } from \"firebase/firestore\";\nimport { getBytes, getStorage, ref } from \"firebase/storage\";\n\nimport { finalizeParticipation, startParticipation, stopParticipation } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n studentRestoreConvert,\n variantConverter,\n} from \"~/web/common/converters\";\nimport TokenLogin from \"~/web/common/token-login\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useCollection, useDocument } from \"~/web/hooks\";\n\nimport { FirebaseStatement } from \"./student-statement\";\n\nexport default function TeacherEntry() {\n return (\n <TokenLogin allowedRole=\"teacher\">\n {({ claims }) => <TeacherInner schoolId={claims.schoolId} />}\n </TokenLogin>\n );\n}\n\nfunction TeacherInner({ schoolId }: { schoolId: string }) {\n const db = useDb();\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n subscribe: true,\n });\n const [participations] = useCollection(\"participations\", participationConverter, {\n constraints: { contestId: website.contests, schoolId: schoolId },\n subscribe: true,\n });\n const [variants] = useCollection(\"variants\", variantConverter);\n\n const logout = useCallback(async () => {\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n return (\n <TeacherProvider\n participations={participations}\n contests={contests}\n startParticipation={(...args) => startParticipation(db, ...args)}\n stopParticipation={(...args) => stopParticipation(db, ...args)}\n finalizeParticipation={(...args) => finalizeParticipation(db, ...args)}\n variants={variants}\n logout={logout}\n statementComponent={() => <FirebaseStatement />}\n getPdfStatements={(contestId, variantIds) => getPdfStatements(db, contestId, variantIds)}\n useAnnouncements={useAnnouncements}\n useStudents={useStudents}\n useStudentRestores={useStudentRestores}\n />\n );\n}\n\nasync function getPdfStatements(\n db: Firestore,\n contestId: string,\n variantIds: string[],\n): Promise<Record<string, ArrayBuffer>> {\n const storage = getStorage(db.app);\n\n const files = await Promise.all(\n variantIds.map(\n async (id) =>\n [id, await getBytes(ref(storage, `statements/${contestId}/${id}/statement.pdf`))] as const,\n ),\n );\n return Object.fromEntries(files);\n}\n\nfunction useAnnouncements(_participationId: string) {\n return [];\n}\n\nfunction useStudents(participationId: string) {\n return useCollection(`participations/${participationId}/students`, studentConverter, {\n orderBy: \"createdAt\",\n subscribe: true,\n });\n}\n\nfunction useStudentRestores(\n participationId: string,\n): readonly [\n StudentRestore[],\n (request: StudentRestore) => Promise<void>,\n (studentId: string) => Promise<void>,\n] {\n const db = useDb();\n\n const [participation] = useDocument(\"participations\", participationId, participationConverter, {\n subscribe: true,\n });\n\n const [studentRestores] = useCollection(\"studentRestores\", studentRestoreConvert, {\n constraints: { participationId, token: participation.token, status: \"pending\" },\n orderBy: \"createdAt\",\n subscribe: true,\n });\n\n const reject = async (studentId: string, excludeRestoreId?: string) => {\n const batch = writeBatch(db);\n\n for (const studentRestore of studentRestores) {\n if (studentRestore.studentId === studentId && studentRestore.id !== excludeRestoreId) {\n batch.update(doc(db, \"studentRestores\", studentRestore.id), { status: \"revoked\" });\n }\n }\n\n await batch.commit();\n };\n\n const approve = async (request: StudentRestore) => {\n const batch = writeBatch(db);\n batch.update(doc(db, `/participations/${participationId}/students`, request.studentId), {\n uid: request.id,\n updatedAt: serverTimestamp(),\n });\n batch.delete(doc(db, \"studentRestores\", request.id));\n await batch.commit();\n\n await reject(request.studentId, request.id);\n };\n\n return [studentRestores, approve, reject];\n}\n"],"names":["TeacherEntry","jsx","TokenLogin","claims","TeacherInner","schoolId","db","useDb","website","useWebsite","contests","useCollection","contestConverter","participations","participationConverter","variants","variantConverter","logout","useCallback","signOut","getAuth","TeacherProvider","args","startParticipation","stopParticipation","finalizeParticipation","FirebaseStatement","contestId","variantIds","getPdfStatements","useAnnouncements","useStudents","useStudentRestores","storage","getStorage","files","id","getBytes","ref","_participationId","participationId","studentConverter","participation","useDocument","studentRestores","studentRestoreConvert","reject","studentId","excludeRestoreId","batch","writeBatch","studentRestore","doc","request","serverTimestamp"],"mappings":"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"}
1
+ {"version":3,"file":"teacher.js","sources":["../../../../src/web/entry/teacher.tsx"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { StudentRestore } from \"@olinfo/quizms/models\";\nimport { TeacherProvider } from \"@olinfo/quizms/teacher\";\nimport { getAuth, signOut, type User } from \"firebase/auth\";\nimport { doc, type Firestore, serverTimestamp, writeBatch } from \"firebase/firestore\";\nimport { getBytes, getStorage, ref } from \"firebase/storage\";\n\nimport { finalizeParticipation, startParticipation, stopParticipation } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n announcementConverter,\n contestConverter,\n participationConverter,\n studentConverter,\n studentRestoreConvert,\n variantConverter,\n} from \"~/web/common/converters\";\nimport TokenLogin from \"~/web/common/token-login\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useCollection, useDocument } from \"~/web/hooks\";\n\nimport { FirebaseStatement } from \"./student-statement\";\n\nexport default function TeacherEntry() {\n return (\n <TokenLogin allowedRole=\"teacher\">\n {({ user, claims }) => <TeacherInner user={user} schoolId={claims.schoolId} />}\n </TokenLogin>\n );\n}\n\nfunction TeacherInner({ user, schoolId }: { user: User; schoolId: string }) {\n const db = useDb();\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n });\n const [participations] = useCollection(\"participations\", participationConverter, {\n constraints: { contestId: website.contests, schoolId: schoolId },\n });\n const [variants] = useCollection(\"variants\", variantConverter);\n\n const logout = useCallback(async () => {\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n return (\n <TeacherProvider\n participations={participations}\n contests={contests}\n startParticipation={(...args) => startParticipation(user, ...args)}\n stopParticipation={(...args) => stopParticipation(user, ...args)}\n finalizeParticipation={(...args) => finalizeParticipation(user, ...args)}\n variants={variants}\n logout={logout}\n statementComponent={() => <FirebaseStatement />}\n getPdfStatements={(contestId, variantIds) => getPdfStatements(db, contestId, variantIds)}\n useAnnouncements={useAnnouncements}\n useStudents={useStudents}\n useStudentRestores={useStudentRestores}\n />\n );\n}\n\nasync function getPdfStatements(\n db: Firestore,\n contestId: string,\n variantIds: string[],\n): Promise<Record<string, ArrayBuffer>> {\n const storage = getStorage(db.app);\n\n const files = await Promise.all(\n variantIds.map(\n async (id) =>\n [id, await getBytes(ref(storage, `statements/${contestId}/${id}/statement.pdf`))] as const,\n ),\n );\n return Object.fromEntries(files);\n}\n\nfunction useAnnouncements(contestId: string) {\n const [announcements] = useCollection(\"announcements\", announcementConverter, {\n arrayConstraints: {\n contestIds: contestId,\n },\n orderBy: [\"createdAt\", \"desc\"],\n });\n return announcements;\n}\n\nfunction useStudents(participationId: string) {\n return useCollection(`participations/${participationId}/students`, studentConverter, {\n orderBy: \"createdAt\",\n });\n}\n\nfunction useStudentRestores(\n participationId: string,\n): readonly [\n StudentRestore[],\n (request: StudentRestore) => Promise<void>,\n (studentId: string) => Promise<void>,\n] {\n const db = useDb();\n\n const [participation] = useDocument(\"participations\", participationId, participationConverter);\n\n const [studentRestores] = useCollection(\"studentRestores\", studentRestoreConvert, {\n constraints: { participationId, token: participation.token, status: \"pending\" },\n orderBy: \"createdAt\",\n });\n\n const reject = async (studentId: string, excludeRestoreId?: string) => {\n const batch = writeBatch(db);\n\n for (const studentRestore of studentRestores) {\n if (studentRestore.studentId === studentId && studentRestore.id !== excludeRestoreId) {\n batch.update(doc(db, \"studentRestores\", studentRestore.id), { status: \"revoked\" });\n }\n }\n\n await batch.commit();\n };\n\n const approve = async (request: StudentRestore) => {\n const batch = writeBatch(db);\n batch.update(doc(db, `/participations/${participationId}/students`, request.studentId), {\n uid: request.id,\n updatedAt: serverTimestamp(),\n });\n batch.delete(doc(db, \"studentRestores\", request.id));\n await batch.commit();\n\n await reject(request.studentId, request.id);\n };\n\n return [studentRestores, approve, reject];\n}\n"],"names":["TeacherEntry","jsx","TokenLogin","user","claims","TeacherInner","schoolId","db","useDb","website","useWebsite","contests","useCollection","contestConverter","participations","participationConverter","variants","variantConverter","logout","useCallback","signOut","getAuth","TeacherProvider","args","startParticipation","stopParticipation","finalizeParticipation","FirebaseStatement","contestId","variantIds","getPdfStatements","useAnnouncements","useStudents","useStudentRestores","storage","getStorage","files","id","getBytes","ref","announcements","announcementConverter","participationId","studentConverter","participation","useDocument","studentRestores","studentRestoreConvert","reject","studentId","excludeRestoreId","batch","writeBatch","studentRestore","doc","request","serverTimestamp"],"mappings":"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"}
@@ -11,56 +11,55 @@ const d = {
11
11
  rollbackOnError: !0,
12
12
  populateCache: !0
13
13
  };
14
- function j(s, t, r) {
15
- const e = E(), { showBoundary: a } = R(), c = g(e, s).withConverter(t), m = $(e, s, t, r), b = {
16
- revalidateIfStale: !r?.subscribe,
17
- revalidateOnFocus: !r?.subscribe,
18
- revalidateOnMount: !r?.subscribe,
19
- revalidateOnReconnect: !r?.subscribe,
14
+ function j(a, t, r) {
15
+ const e = E(), { showBoundary: s } = R(), i = g(e, a).withConverter(t), m = $(e, a, t, r), p = {
16
+ revalidateIfStale: !1,
17
+ revalidateOnFocus: !1,
18
+ revalidateOnMount: !1,
19
+ revalidateOnReconnect: !1,
20
20
  suspense: !0
21
- }, f = `${s}?${JSON.stringify(r)}`, { data: y, mutate: o } = q(f, () => D(m), b), h = C(
22
- (i) => x(c, i, o, r),
23
- [c, o, r]
21
+ }, f = `${a}?${JSON.stringify(r)}`, { data: y, mutate: n } = q(f, () => D(m), p), b = C(
22
+ (o) => x(i, o, n, r),
23
+ [i, n, r]
24
24
  );
25
25
  return w(
26
26
  f,
27
- (i) => {
28
- if (!r?.subscribe) return;
29
- const p = B(
27
+ (o) => {
28
+ const h = B(
30
29
  m,
31
- (u) => {
30
+ (c) => {
32
31
  try {
33
- i(u.docs.map((n) => n.data()));
34
- } catch (n) {
35
- a(n);
32
+ o(c.docs.map((u) => u.data()));
33
+ } catch (u) {
34
+ s(u);
36
35
  }
37
36
  },
38
- (u) => a(u)
37
+ (c) => s(c)
39
38
  );
40
- return () => p();
39
+ return () => h();
41
40
  },
42
- (i) => o(i, d)
43
- ), [y, h];
41
+ (o) => n(o, d)
42
+ ), [y, b];
44
43
  }
45
- async function x(s, t, r, e) {
44
+ async function x(a, t, r, e) {
46
45
  await r(
47
- async (a) => {
48
- const c = O(s, t.id);
49
- return await S(c, t), l(a, t, e);
46
+ async (s) => {
47
+ const i = O(a, t.id);
48
+ return await S(i, t), l(s, t, e);
50
49
  },
51
50
  {
52
51
  ...d,
53
- optimisticData: (a) => l(a, t, e)
52
+ optimisticData: (s) => l(s, t, e)
54
53
  }
55
54
  );
56
55
  }
57
- function l(s, t, r) {
58
- if (!s) return [t];
59
- let e = s.filter((a) => t.id !== a.id);
56
+ function l(a, t, r) {
57
+ if (!a) return [t];
58
+ let e = a.filter((s) => t.id !== s.id);
60
59
  return e.push(t), e = k(e, [r?.orderBy ?? "id"]), r?.limit && (e = e.slice(0, r.limit)), e;
61
60
  }
62
- async function D(s) {
63
- return (await v(s)).docs.map((r) => r.data({ serverTimestamps: "estimate" }));
61
+ async function D(a) {
62
+ return (await v(a)).docs.map((r) => r.data({ serverTimestamps: "estimate" }));
64
63
  }
65
64
  export {
66
65
  j as useCollection
@@ -1 +1 @@
1
- {"version":3,"file":"collection.js","sources":["../../../../src/web/hooks/collection.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport {\n type CollectionReference,\n collection,\n doc,\n type FirestoreDataConverter,\n getDocs,\n onSnapshot,\n type Query,\n setDoc,\n} from \"firebase/firestore\";\nimport { sortBy } from \"lodash-es\";\nimport { useErrorBoundary } from \"react-error-boundary\";\nimport useSWR, { type KeyedMutator, type MutatorOptions, type SWRConfiguration } from \"swr\";\n\nimport { useDb } from \"~/web/common/base-login\";\nimport query, { type QueryOption } from \"~/web/common/query\";\n\nimport { useSubscriptionListener } from \"./subscription\";\n\nconst mutationConfig: MutatorOptions = {\n revalidate: false,\n rollbackOnError: true,\n populateCache: true,\n};\n\ntype CollectionOptions<T> = QueryOption<T> & {\n subscribe?: boolean;\n};\n\nexport function useCollection<T extends { id: string }>(\n path: string,\n converter: FirestoreDataConverter<T>,\n options?: CollectionOptions<T>,\n) {\n const db = useDb();\n const { showBoundary } = useErrorBoundary();\n\n const ref = collection(db, path).withConverter(converter);\n const q = query(db, path, converter, options);\n\n const swrConfig: SWRConfiguration = {\n revalidateIfStale: !options?.subscribe,\n revalidateOnFocus: !options?.subscribe,\n revalidateOnMount: !options?.subscribe,\n revalidateOnReconnect: !options?.subscribe,\n suspense: true,\n };\n\n const key = `${path}?${JSON.stringify(options)}`;\n const { data, mutate } = useSWR<T[]>(key, () => fetcher(q), swrConfig);\n\n const setData = useCallback(\n (newDoc: T) => setDocument(ref, newDoc, mutate, options),\n [ref, mutate, options],\n );\n\n useSubscriptionListener<T[]>(\n key,\n (setData) => {\n if (!options?.subscribe) return;\n const unsubscribe = onSnapshot(\n q,\n (snap) => {\n try {\n setData(snap.docs.map((doc) => doc.data()));\n } catch (err) {\n showBoundary(err);\n }\n },\n (error) => showBoundary(error),\n );\n return () => unsubscribe();\n },\n (data) => mutate(data, mutationConfig),\n );\n\n return [data as T[], setData] as const;\n}\n\nasync function setDocument<T extends { id: string }>(\n ref: CollectionReference<T>,\n newDoc: T,\n mutator: KeyedMutator<T[]>,\n options?: CollectionOptions<T>,\n): Promise<void> {\n await mutator(\n async (prev) => {\n const docRef = doc(ref, newDoc.id);\n await setDoc(docRef, newDoc);\n return merge(prev, newDoc, options);\n },\n {\n ...mutationConfig,\n optimisticData: (prev) => merge(prev, newDoc, options),\n },\n );\n}\n\nfunction merge<T extends { id: string }>(\n prev: T[] | undefined,\n newDoc: T,\n options?: CollectionOptions<T>,\n) {\n if (!prev) return [newDoc];\n let coll = prev.filter((doc) => newDoc.id !== doc.id);\n coll.push(newDoc);\n coll = sortBy(coll, [options?.orderBy ?? \"id\"]);\n if (options?.limit) coll = coll.slice(0, options.limit);\n return coll;\n}\n\nasync function fetcher<T>(ref: Query<T>) {\n const snapshot = await getDocs(ref);\n return snapshot.docs.map((doc) => doc.data({ serverTimestamps: \"estimate\" }));\n}\n"],"names":["mutationConfig","useCollection","path","converter","options","db","useDb","showBoundary","useErrorBoundary","ref","collection","q","query","swrConfig","key","data","mutate","useSWR","fetcher","setData","useCallback","newDoc","setDocument","useSubscriptionListener","unsubscribe","onSnapshot","snap","doc","err","error","mutator","prev","docRef","setDoc","merge","coll","sortBy","getDocs"],"mappings":";;;;;;;;AAqBA,MAAMA,IAAiC;AAAA,EACrC,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAMO,SAASC,EACdC,GACAC,GACAC,GACA;AACA,QAAMC,IAAKC,EAAA,GACL,EAAE,cAAAC,EAAA,IAAiBC,EAAA,GAEnBC,IAAMC,EAAWL,GAAIH,CAAI,EAAE,cAAcC,CAAS,GAClDQ,IAAIC,EAAMP,GAAIH,GAAMC,GAAWC,CAAO,GAEtCS,IAA8B;AAAA,IAClC,mBAAmB,CAACT,GAAS;AAAA,IAC7B,mBAAmB,CAACA,GAAS;AAAA,IAC7B,mBAAmB,CAACA,GAAS;AAAA,IAC7B,uBAAuB,CAACA,GAAS;AAAA,IACjC,UAAU;AAAA,EAAA,GAGNU,IAAM,GAAGZ,CAAI,IAAI,KAAK,UAAUE,CAAO,CAAC,IACxC,EAAE,MAAAW,GAAM,QAAAC,EAAA,IAAWC,EAAYH,GAAK,MAAMI,EAAQP,CAAC,GAAGE,CAAS,GAE/DM,IAAUC;AAAA,IACd,CAACC,MAAcC,EAAYb,GAAKY,GAAQL,GAAQZ,CAAO;AAAA,IACvD,CAACK,GAAKO,GAAQZ,CAAO;AAAA,EAAA;AAGvB,SAAAmB;AAAA,IACET;AAAA,IACA,CAACK,MAAY;AACX,UAAI,CAACf,GAAS,UAAW;AACzB,YAAMoB,IAAcC;AAAA,QAClBd;AAAA,QACA,CAACe,MAAS;AACR,cAAI;AACFP,YAAAA,EAAQO,EAAK,KAAK,IAAI,CAACC,MAAQA,EAAI,KAAA,CAAM,CAAC;AAAA,UAC5C,SAASC,GAAK;AACZ,YAAArB,EAAaqB,CAAG;AAAA,UAClB;AAAA,QACF;AAAA,QACA,CAACC,MAAUtB,EAAasB,CAAK;AAAA,MAAA;AAE/B,aAAO,MAAML,EAAA;AAAA,IACf;AAAA,IACA,CAACT,MAASC,EAAOD,GAAMf,CAAc;AAAA,EAAA,GAGhC,CAACe,GAAaI,CAAO;AAC9B;AAEA,eAAeG,EACbb,GACAY,GACAS,GACA1B,GACe;AACf,QAAM0B;AAAA,IACJ,OAAOC,MAAS;AACd,YAAMC,IAASL,EAAIlB,GAAKY,EAAO,EAAE;AACjC,mBAAMY,EAAOD,GAAQX,CAAM,GACpBa,EAAMH,GAAMV,GAAQjB,CAAO;AAAA,IACpC;AAAA,IACA;AAAA,MACE,GAAGJ;AAAA,MACH,gBAAgB,CAAC+B,MAASG,EAAMH,GAAMV,GAAQjB,CAAO;AAAA,IAAA;AAAA,EACvD;AAEJ;AAEA,SAAS8B,EACPH,GACAV,GACAjB,GACA;AACA,MAAI,CAAC2B,EAAM,QAAO,CAACV,CAAM;AACzB,MAAIc,IAAOJ,EAAK,OAAO,CAACJ,MAAQN,EAAO,OAAOM,EAAI,EAAE;AACpD,SAAAQ,EAAK,KAAKd,CAAM,GAChBc,IAAOC,EAAOD,GAAM,CAAC/B,GAAS,WAAW,IAAI,CAAC,GAC1CA,GAAS,UAAO+B,IAAOA,EAAK,MAAM,GAAG/B,EAAQ,KAAK,IAC/C+B;AACT;AAEA,eAAejB,EAAWT,GAAe;AAEvC,UADiB,MAAM4B,EAAQ5B,CAAG,GAClB,KAAK,IAAI,CAACkB,MAAQA,EAAI,KAAK,EAAE,kBAAkB,WAAA,CAAY,CAAC;AAC9E;"}
1
+ {"version":3,"file":"collection.js","sources":["../../../../src/web/hooks/collection.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport {\n type CollectionReference,\n collection,\n doc,\n type FirestoreDataConverter,\n getDocs,\n onSnapshot,\n type Query,\n setDoc,\n} from \"firebase/firestore\";\nimport { sortBy } from \"lodash-es\";\nimport { useErrorBoundary } from \"react-error-boundary\";\nimport useSWR, { type KeyedMutator, type MutatorOptions, type SWRConfiguration } from \"swr\";\n\nimport { useDb } from \"~/web/common/base-login\";\nimport query, { type QueryOption } from \"~/web/common/query\";\n\nimport { useSubscriptionListener } from \"./subscription\";\n\nconst mutationConfig: MutatorOptions = {\n revalidate: false,\n rollbackOnError: true,\n populateCache: true,\n};\n\nexport function useCollection<T extends { id: string }>(\n path: string,\n converter: FirestoreDataConverter<T>,\n options?: QueryOption<T>,\n) {\n const db = useDb();\n const { showBoundary } = useErrorBoundary();\n\n const ref = collection(db, path).withConverter(converter);\n const q = query(db, path, converter, options);\n\n const swrConfig: SWRConfiguration = {\n revalidateIfStale: false,\n revalidateOnFocus: false,\n revalidateOnMount: false,\n revalidateOnReconnect: false,\n suspense: true,\n };\n\n const key = `${path}?${JSON.stringify(options)}`;\n const { data, mutate } = useSWR<T[]>(key, () => fetcher(q), swrConfig);\n\n const setData = useCallback(\n (newDoc: T) => setDocument(ref, newDoc, mutate, options),\n [ref, mutate, options],\n );\n\n useSubscriptionListener<T[]>(\n key,\n (setData) => {\n const unsubscribe = onSnapshot(\n q,\n (snap) => {\n try {\n setData(snap.docs.map((doc) => doc.data()));\n } catch (err) {\n showBoundary(err);\n }\n },\n (error) => showBoundary(error),\n );\n return () => unsubscribe();\n },\n (data) => mutate(data, mutationConfig),\n );\n\n return [data as T[], setData] as const;\n}\n\nasync function setDocument<T extends { id: string }>(\n ref: CollectionReference<T>,\n newDoc: T,\n mutator: KeyedMutator<T[]>,\n options?: QueryOption<T>,\n): Promise<void> {\n await mutator(\n async (prev) => {\n const docRef = doc(ref, newDoc.id);\n await setDoc(docRef, newDoc);\n return merge(prev, newDoc, options);\n },\n {\n ...mutationConfig,\n optimisticData: (prev) => merge(prev, newDoc, options),\n },\n );\n}\n\nfunction merge<T extends { id: string }>(\n prev: T[] | undefined,\n newDoc: T,\n options?: QueryOption<T>,\n) {\n if (!prev) return [newDoc];\n let coll = prev.filter((doc) => newDoc.id !== doc.id);\n coll.push(newDoc);\n coll = sortBy(coll, [options?.orderBy ?? \"id\"]);\n if (options?.limit) coll = coll.slice(0, options.limit);\n return coll;\n}\n\nasync function fetcher<T>(ref: Query<T>) {\n const snapshot = await getDocs(ref);\n return snapshot.docs.map((doc) => doc.data({ serverTimestamps: \"estimate\" }));\n}\n"],"names":["mutationConfig","useCollection","path","converter","options","db","useDb","showBoundary","useErrorBoundary","ref","collection","q","query","swrConfig","key","data","mutate","useSWR","fetcher","setData","useCallback","newDoc","setDocument","useSubscriptionListener","unsubscribe","onSnapshot","snap","doc","err","error","mutator","prev","docRef","setDoc","merge","coll","sortBy","getDocs"],"mappings":";;;;;;;;AAqBA,MAAMA,IAAiC;AAAA,EACrC,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAASC,EACdC,GACAC,GACAC,GACA;AACA,QAAMC,IAAKC,EAAA,GACL,EAAE,cAAAC,EAAA,IAAiBC,EAAA,GAEnBC,IAAMC,EAAWL,GAAIH,CAAI,EAAE,cAAcC,CAAS,GAClDQ,IAAIC,EAAMP,GAAIH,GAAMC,GAAWC,CAAO,GAEtCS,IAA8B;AAAA,IAClC,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,UAAU;AAAA,EAAA,GAGNC,IAAM,GAAGZ,CAAI,IAAI,KAAK,UAAUE,CAAO,CAAC,IACxC,EAAE,MAAAW,GAAM,QAAAC,EAAA,IAAWC,EAAYH,GAAK,MAAMI,EAAQP,CAAC,GAAGE,CAAS,GAE/DM,IAAUC;AAAA,IACd,CAACC,MAAcC,EAAYb,GAAKY,GAAQL,GAAQZ,CAAO;AAAA,IACvD,CAACK,GAAKO,GAAQZ,CAAO;AAAA,EAAA;AAGvB,SAAAmB;AAAA,IACET;AAAA,IACA,CAACK,MAAY;AACX,YAAMK,IAAcC;AAAA,QAClBd;AAAA,QACA,CAACe,MAAS;AACR,cAAI;AACFP,YAAAA,EAAQO,EAAK,KAAK,IAAI,CAACC,MAAQA,EAAI,KAAA,CAAM,CAAC;AAAA,UAC5C,SAASC,GAAK;AACZ,YAAArB,EAAaqB,CAAG;AAAA,UAClB;AAAA,QACF;AAAA,QACA,CAACC,MAAUtB,EAAasB,CAAK;AAAA,MAAA;AAE/B,aAAO,MAAML,EAAA;AAAA,IACf;AAAA,IACA,CAACT,MAASC,EAAOD,GAAMf,CAAc;AAAA,EAAA,GAGhC,CAACe,GAAaI,CAAO;AAC9B;AAEA,eAAeG,EACbb,GACAY,GACAS,GACA1B,GACe;AACf,QAAM0B;AAAA,IACJ,OAAOC,MAAS;AACd,YAAMC,IAASL,EAAIlB,GAAKY,EAAO,EAAE;AACjC,mBAAMY,EAAOD,GAAQX,CAAM,GACpBa,EAAMH,GAAMV,GAAQjB,CAAO;AAAA,IACpC;AAAA,IACA;AAAA,MACE,GAAGJ;AAAA,MACH,gBAAgB,CAAC+B,MAASG,EAAMH,GAAMV,GAAQjB,CAAO;AAAA,IAAA;AAAA,EACvD;AAEJ;AAEA,SAAS8B,EACPH,GACAV,GACAjB,GACA;AACA,MAAI,CAAC2B,EAAM,QAAO,CAACV,CAAM;AACzB,MAAIc,IAAOJ,EAAK,OAAO,CAACJ,MAAQN,EAAO,OAAOM,EAAI,EAAE;AACpD,SAAAQ,EAAK,KAAKd,CAAM,GAChBc,IAAOC,EAAOD,GAAM,CAAC/B,GAAS,WAAW,IAAI,CAAC,GAC1CA,GAAS,UAAO+B,IAAOA,EAAK,MAAM,GAAG/B,EAAQ,KAAK,IAC/C+B;AACT;AAEA,eAAejB,EAAWT,GAAe;AAEvC,UADiB,MAAM4B,EAAQ5B,CAAG,GAClB,KAAK,IAAI,CAACkB,MAAQA,EAAI,KAAK,EAAE,kBAAkB,WAAA,CAAY,CAAC;AAC9E;"}