@instructure/platform-modules 0.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 (81) hide show
  1. package/README.md +75 -0
  2. package/dist/adapters/canvasGraphql/index.d.ts +54 -0
  3. package/dist/adapters/canvasGraphql/index.d.ts.map +1 -0
  4. package/dist/adapters/canvasGraphql/normalize.d.ts +345 -0
  5. package/dist/adapters/canvasGraphql/normalize.d.ts.map +1 -0
  6. package/dist/adapters/canvasGraphql/queries.d.ts +69 -0
  7. package/dist/adapters/canvasGraphql/queries.d.ts.map +1 -0
  8. package/dist/adapters/inMemory.d.ts +63 -0
  9. package/dist/adapters/inMemory.d.ts.map +1 -0
  10. package/dist/adapters/index.d.ts +3 -0
  11. package/dist/adapters/index.d.ts.map +1 -0
  12. package/dist/adapters/limits.d.ts +86 -0
  13. package/dist/adapters/limits.d.ts.map +1 -0
  14. package/dist/adapters/types.d.ts +87 -0
  15. package/dist/adapters/types.d.ts.map +1 -0
  16. package/dist/cache/index.d.ts +3 -0
  17. package/dist/cache/index.d.ts.map +1 -0
  18. package/dist/cache/invalidate.d.ts +52 -0
  19. package/dist/cache/invalidate.d.ts.map +1 -0
  20. package/dist/cache/keys.d.ts +86 -0
  21. package/dist/cache/keys.d.ts.map +1 -0
  22. package/dist/components/FilterChips.d.ts +19 -0
  23. package/dist/components/FilterChips.d.ts.map +1 -0
  24. package/dist/components/ModuleCard.d.ts +32 -0
  25. package/dist/components/ModuleCard.d.ts.map +1 -0
  26. package/dist/components/ModuleItemRow.d.ts +17 -0
  27. package/dist/components/ModuleItemRow.d.ts.map +1 -0
  28. package/dist/components/ModulesList.d.ts +69 -0
  29. package/dist/components/ModulesList.d.ts.map +1 -0
  30. package/dist/components/ModulesList.stories.d.ts +138 -0
  31. package/dist/components/ModulesList.stories.d.ts.map +1 -0
  32. package/dist/components/StatusPill.d.ts +18 -0
  33. package/dist/components/StatusPill.d.ts.map +1 -0
  34. package/dist/components/index.d.ts +7 -0
  35. package/dist/components/index.d.ts.map +1 -0
  36. package/dist/components/labels.d.ts +89 -0
  37. package/dist/components/labels.d.ts.map +1 -0
  38. package/dist/config/index.d.ts +3 -0
  39. package/dist/config/index.d.ts.map +1 -0
  40. package/dist/config/registry.d.ts +38 -0
  41. package/dist/config/registry.d.ts.map +1 -0
  42. package/dist/config/schemas.d.ts +16 -0
  43. package/dist/config/schemas.d.ts.map +1 -0
  44. package/dist/fixtures/generate.d.ts +48 -0
  45. package/dist/fixtures/generate.d.ts.map +1 -0
  46. package/dist/fixtures/index.d.ts +3 -0
  47. package/dist/fixtures/index.d.ts.map +1 -0
  48. package/dist/fixtures/scenarios.d.ts +42 -0
  49. package/dist/fixtures/scenarios.d.ts.map +1 -0
  50. package/dist/hooks/constants.d.ts +49 -0
  51. package/dist/hooks/constants.d.ts.map +1 -0
  52. package/dist/hooks/index.d.ts +7 -0
  53. package/dist/hooks/index.d.ts.map +1 -0
  54. package/dist/hooks/queryOptions.d.ts +58 -0
  55. package/dist/hooks/queryOptions.d.ts.map +1 -0
  56. package/dist/hooks/useItemsMode.d.ts +114 -0
  57. package/dist/hooks/useItemsMode.d.ts.map +1 -0
  58. package/dist/hooks/useModuleItemQueries.d.ts +56 -0
  59. package/dist/hooks/useModuleItemQueries.d.ts.map +1 -0
  60. package/dist/hooks/useModuleItems.d.ts +41 -0
  61. package/dist/hooks/useModuleItems.d.ts.map +1 -0
  62. package/dist/hooks/useModules.d.ts +50 -0
  63. package/dist/hooks/useModules.d.ts.map +1 -0
  64. package/dist/hooks/useModulesPageData.d.ts +78 -0
  65. package/dist/hooks/useModulesPageData.d.ts.map +1 -0
  66. package/dist/i18n.d.ts +92 -0
  67. package/dist/i18n.d.ts.map +1 -0
  68. package/dist/index.d.ts +19 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +1966 -0
  71. package/dist/types/domain.d.ts +147 -0
  72. package/dist/types/domain.d.ts.map +1 -0
  73. package/dist/types/enums.d.ts +93 -0
  74. package/dist/types/enums.d.ts.map +1 -0
  75. package/dist/types/extensions.d.ts +48 -0
  76. package/dist/types/extensions.d.ts.map +1 -0
  77. package/dist/types/index.d.ts +4 -0
  78. package/dist/types/index.d.ts.map +1 -0
  79. package/dist/types/schemas.d.ts +38 -0
  80. package/dist/types/schemas.d.ts.map +1 -0
  81. package/package.json +80 -0
package/dist/index.js ADDED
@@ -0,0 +1,1966 @@
1
+ import { z as g } from "zod";
2
+ import { gql as ee } from "graphql-tag";
3
+ import { useInfiniteQuery as be, useQueryClient as Fe, InfiniteQueryObserver as Ct } from "@tanstack/react-query";
4
+ import { useMemo as L, useCallback as D, useRef as B, useState as X, useEffect as ae, createContext as vt, useContext as Tt, useId as ke, memo as ze } from "react";
5
+ import Nt from "p-limit";
6
+ import { jsx as o, jsxs as y } from "react/jsx-runtime";
7
+ import { Button as Z, IconButton as At } from "@instructure/ui-buttons";
8
+ import { Flex as P } from "@instructure/ui-flex";
9
+ import { Text as I } from "@instructure/ui-text";
10
+ import { View as S } from "@instructure/ui-view";
11
+ import { politeAnnounce as Oe, getLiveRegion as Ue } from "@instructure/platform-instui-bindings";
12
+ import { Alert as $e } from "@instructure/ui-alerts";
13
+ import { Heading as Be } from "@instructure/ui-heading";
14
+ import { IconLockLine as Ge, IconWarningLine as Ye, IconCheckLine as ue, IconClockLine as Qe, IconArrowNestLine as Lt, IconEmptyLine as Dt, IconLtiLine as Rt, IconLinkLine as qt, IconAttachMediaLine as Ft, IconDocumentLine as kt, IconDiscussionLine as zt, IconQuizLine as Ot, IconAssignmentLine as Ut, IconArrowOpenUpLine as $t, IconArrowOpenDownLine as je, IconSearchLine as Bt } from "@instructure/ui-icons";
15
+ import { ProgressBar as Gt } from "@instructure/ui-progress";
16
+ import { Spinner as We } from "@instructure/ui-spinner";
17
+ import { Pill as Yt } from "@instructure/ui-pill";
18
+ import { Menu as fe } from "@instructure/ui-menu";
19
+ import { TextInput as Qt } from "@instructure/ui-text-input";
20
+ const jt = g.enum([
21
+ "assignment",
22
+ "quiz",
23
+ "discussion",
24
+ "page",
25
+ "file",
26
+ "external_url",
27
+ "external_tool",
28
+ "sub_header"
29
+ ]), Wt = g.enum(["not_started", "in_progress", "submitted", "graded", "missing", "locked"]), Kt = g.enum(["not_started", "in_progress", "all_submitted", "all_graded", "missing", "locked"]), Ht = g.enum([
30
+ "must_view",
31
+ "must_mark_done",
32
+ "must_contribute",
33
+ "must_submit",
34
+ "min_score",
35
+ "min_percentage",
36
+ "unknown"
37
+ ]), Vt = g.object({
38
+ status: Kt,
39
+ requirementsMet: g.number(),
40
+ requirementCount: g.number(),
41
+ percentComplete: g.number().min(0).max(100),
42
+ latestDueAt: g.string().nullish(),
43
+ missingCount: g.number()
44
+ }), Ke = g.object({
45
+ itemId: g.string(),
46
+ type: Ht,
47
+ minScore: g.number().nullish(),
48
+ minPercentage: g.number().nullish()
49
+ }), He = g.object({
50
+ id: g.string(),
51
+ moduleId: g.string(),
52
+ title: g.string(),
53
+ type: jt,
54
+ position: g.number(),
55
+ indent: g.number(),
56
+ published: g.boolean(),
57
+ url: g.string().nullish(),
58
+ completionRequirement: Ke.nullish(),
59
+ dueAt: g.string().nullish(),
60
+ status: Wt.optional(),
61
+ pointsPossible: g.number().nullish(),
62
+ extensions: g.record(g.unknown()).optional()
63
+ }), Xt = g.object({
64
+ id: g.string(),
65
+ name: g.string(),
66
+ position: g.number(),
67
+ published: g.boolean(),
68
+ itemCount: g.number(),
69
+ items: g.array(He).optional(),
70
+ unlockAt: g.string().nullish(),
71
+ prerequisiteModuleIds: g.array(g.string()),
72
+ requirementCount: g.number().nullish(),
73
+ requireSequentialProgress: g.boolean(),
74
+ completionRequirements: g.array(Ke).optional(),
75
+ progress: Vt.optional(),
76
+ extensions: g.record(g.unknown()).optional()
77
+ }), Ve = g.object({
78
+ hasNextPage: g.boolean(),
79
+ endCursor: g.string().nullable()
80
+ }).refine((e) => !e.hasNextPage || e.endCursor !== null, {
81
+ message: 'hasNextPage is true but endCursor is null: there is another page and no cursor to ask for it with, which renders a "Load more" that can never load.',
82
+ path: ["endCursor"]
83
+ }), Zt = g.object({
84
+ modules: g.array(Xt),
85
+ pageInfo: Ve
86
+ }), Jt = g.object({
87
+ items: g.array(He),
88
+ pageInfo: Ve
89
+ }), Xe = "career.estimatedDurationMinutes";
90
+ function en(e) {
91
+ var n;
92
+ const t = (n = e.extensions) == null ? void 0 : n[Xe];
93
+ return typeof t != "number" || !Number.isFinite(t) || t <= 0 ? null : t;
94
+ }
95
+ function Ze(e, t) {
96
+ return t == null ? e : { ...e, [Xe]: t };
97
+ }
98
+ const pe = 100, Me = 2e3;
99
+ class tn extends Error {
100
+ constructor(t, n) {
101
+ super(
102
+ `This in-memory adapter was built for course ${t} but was asked for course ${n}. A fixture serves exactly one course.`
103
+ ), this.name = "InMemoryCourseMismatchError", this.expected = t, this.received = n;
104
+ }
105
+ }
106
+ const nn = (e) => `offset:${e}`, sn = (e) => {
107
+ if (!e) return 0;
108
+ const t = Number.parseInt(e.replace("offset:", ""), 10);
109
+ return Number.isFinite(t) && t >= 0 ? t : 0;
110
+ }, Pe = (e) => {
111
+ if (e != null && e.aborted)
112
+ throw new DOMException("The operation was aborted.", "AbortError");
113
+ };
114
+ function xe(e, t, n) {
115
+ const s = sn(t), r = e.slice(s, s + n), i = s + r.length, u = i < e.length;
116
+ return {
117
+ slice: r,
118
+ pageInfo: { hasNextPage: u, endCursor: u ? nn(i) : null }
119
+ };
120
+ }
121
+ function er(e, t = {}) {
122
+ const n = (r) => e.itemsByModuleId[r] ?? [], s = (r) => {
123
+ if (r !== e.courseId)
124
+ throw new tn(e.courseId, r);
125
+ };
126
+ return {
127
+ async listModules({
128
+ courseId: r,
129
+ cursor: i,
130
+ pageSize: u,
131
+ includeItems: a,
132
+ inlineItemsFirst: d,
133
+ signal: c
134
+ }) {
135
+ var h;
136
+ Pe(c), s(r), (h = t.onRequest) == null || h.call(t, "listModules", { operation: "listModules", courseId: r });
137
+ const { slice: l, pageInfo: m } = xe(e.modules, i, u), M = Math.min(d ?? pe, pe);
138
+ return { modules: l.map((f) => {
139
+ const w = n(f.id), v = a && w.length <= M;
140
+ return { ...f, itemCount: w.length, items: v ? w : void 0 };
141
+ }), pageInfo: m };
142
+ },
143
+ async listModuleItems({
144
+ courseId: r,
145
+ moduleId: i,
146
+ cursor: u,
147
+ pageSize: a,
148
+ signal: d
149
+ }) {
150
+ var m;
151
+ Pe(d), s(r), (m = t.onRequest) == null || m.call(t, "listModuleItems", { operation: "listModuleItems", courseId: r, moduleId: i });
152
+ const { slice: c, pageInfo: l } = xe(n(i), u, a);
153
+ return { items: c, pageInfo: l };
154
+ }
155
+ };
156
+ }
157
+ class Je extends Error {
158
+ constructor(t, n, s) {
159
+ const r = t === "module" ? "module" : "module item";
160
+ super(
161
+ `Canvas returned ${r} ${n} without a usable ${s}. This is a partial response; refusing to guess a value for it.`
162
+ ), this.name = "PartialCanvasResponseError", this.subject = t, this.id = n, this.field = s;
163
+ }
164
+ }
165
+ function et(e, t, n) {
166
+ if (e != null) return e;
167
+ throw new Je(t, n, "published");
168
+ }
169
+ function rn(e, t) {
170
+ if (e.onUnsupportedItem) {
171
+ e.onUnsupportedItem(t);
172
+ return;
173
+ }
174
+ console.warn(
175
+ `[platform-modules] dropped module item ${t.id}: content type "${t.typename ?? "null"}" is outside the closed ModuleItemType enum. Pass \`onUnsupportedItem\` to handle these yourself.`
176
+ );
177
+ }
178
+ const on = {
179
+ Assignment: "assignment",
180
+ Quiz: "quiz",
181
+ Discussion: "discussion",
182
+ Page: "page",
183
+ File: "file",
184
+ ExternalUrl: "external_url",
185
+ // Canvas models an LTI placement in a module as `ModuleExternalTool` and a
186
+ // course-level tool as `ExternalTool`; both render identically here.
187
+ ModuleExternalTool: "external_tool",
188
+ ExternalTool: "external_tool",
189
+ SubHeader: "sub_header",
190
+ // `PeerReviewSubAssignmentType < Types::AssignmentType` on master, so it
191
+ // inherits `ModuleItemInterface` and is assignment-shaped down to
192
+ // `pointsPossible`, `dueAt` and `submissionsConnection`. It was being dropped
193
+ // for no better reason than not being listed.
194
+ PeerReviewSubAssignment: "assignment"
195
+ };
196
+ function ln(e) {
197
+ const t = e == null ? void 0 : e.__typename;
198
+ if (!t) return null;
199
+ const n = on[t];
200
+ return n ? n === "assignment" && (e == null ? void 0 : e.isNewQuiz) === !0 ? "quiz" : n : null;
201
+ }
202
+ const an = /^P(?!$)(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?!$)(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
203
+ function un(e) {
204
+ if (!e) return null;
205
+ const t = an.exec(e);
206
+ if (!t) return null;
207
+ const [, n, s, r, i, u] = t, a = Number(n ?? 0) * 7 * 24 * 60 + Number(s ?? 0) * 24 * 60 + Number(r ?? 0) * 60 + Number(i ?? 0) + Number(u ?? 0) / 60;
208
+ return Math.round(a);
209
+ }
210
+ const dn = /* @__PURE__ */ new Set([
211
+ "must_view",
212
+ "must_mark_done",
213
+ "must_contribute",
214
+ "must_submit",
215
+ "min_score",
216
+ "min_percentage"
217
+ ]);
218
+ function cn(e) {
219
+ return dn.has(e) ? e : "unknown";
220
+ }
221
+ function mn(e) {
222
+ return {
223
+ itemId: String(e.id),
224
+ type: cn(e.type),
225
+ minScore: e.minScore ?? null,
226
+ minPercentage: e.minPercentage ?? null
227
+ };
228
+ }
229
+ function tt(e) {
230
+ return {
231
+ hasNextPage: (e == null ? void 0 : e.hasNextPage) ?? !1,
232
+ endCursor: (e == null ? void 0 : e.endCursor) ?? null
233
+ };
234
+ }
235
+ const gn = /* @__PURE__ */ new Set(["graded"]), hn = /* @__PURE__ */ new Set(["submitted", "pending_review"]), fn = /* @__PURE__ */ new Set(["graded", "excused"]);
236
+ function nt(e) {
237
+ var t;
238
+ for (const n of ((t = e == null ? void 0 : e.submissionsConnection) == null ? void 0 : t.nodes) ?? [])
239
+ if (n) return n;
240
+ return null;
241
+ }
242
+ function st(e, t) {
243
+ if (!e) return;
244
+ const n = new Map(t.map((r) => [r.itemId, r.type])), s = /* @__PURE__ */ new Set();
245
+ for (const r of e.requirementsMet ?? []) {
246
+ if (!r) continue;
247
+ const i = String(r.id);
248
+ n.get(i) === r.type && s.add(i);
249
+ }
250
+ return {
251
+ locked: e.locked === !0 || e.workflowState === "locked",
252
+ workflowState: e.workflowState ?? null,
253
+ completed: e.completed === !0 || e.workflowState === "completed",
254
+ started: e.started === !0 || e.workflowState === "started",
255
+ requirementsMetItemIds: s
256
+ };
257
+ }
258
+ function pn(e, t, n) {
259
+ if (n.locked) return "locked";
260
+ const s = nt(e);
261
+ if (s) {
262
+ if (s.missing === !0) return "missing";
263
+ if (gn.has(s.state ?? "") || fn.has(s.gradingStatus ?? ""))
264
+ return "graded";
265
+ if (s.submittedAt != null || hn.has(s.state ?? ""))
266
+ return "submitted";
267
+ if ((s.attempt ?? 0) > 0) return "in_progress";
268
+ }
269
+ return n.requirementsMetItemIds.has(t) ? "in_progress" : "not_started";
270
+ }
271
+ const In = (e) => e.pointsPossible != null;
272
+ function bn(e, t, n, s) {
273
+ if (e.locked) return "locked";
274
+ if (t > 0 && !e.completed) return "missing";
275
+ if (e.completed) {
276
+ const r = (s ?? []).filter(In);
277
+ return r.length > 0 && r.every((u) => u.status === "graded") ? "all_graded" : "all_submitted";
278
+ }
279
+ return e.started || n > 0 ? "in_progress" : "not_started";
280
+ }
281
+ function Mn(e, t, n, s) {
282
+ var a, d;
283
+ if (!n) return;
284
+ const r = e.requirementCount ?? t.length, i = Math.min(n.requirementsMetItemIds.size, r), u = ((a = e.submissionStatistics) == null ? void 0 : a.missingAssignmentCount) ?? 0;
285
+ return {
286
+ status: bn(n, u, i, s),
287
+ requirementsMet: i,
288
+ requirementCount: r,
289
+ percentComplete: r > 0 ? Math.round(i / r * 100) : 0,
290
+ latestDueAt: ((d = e.submissionStatistics) == null ? void 0 : d.latestDueAt) ?? null,
291
+ missingCount: u
292
+ };
293
+ }
294
+ function _n(e, t, n, s = {}, r) {
295
+ var d, c, l;
296
+ const i = String(e._id), u = ln(e.content);
297
+ if (!u)
298
+ return rn(s, { id: i, typename: ((d = e.content) == null ? void 0 : d.__typename) ?? null }), null;
299
+ const a = nt(e.content);
300
+ return {
301
+ id: i,
302
+ moduleId: t,
303
+ title: e.title ?? "",
304
+ type: u,
305
+ position: e.position ?? 0,
306
+ indent: e.indent ?? 0,
307
+ published: et(e.published, "moduleItem", i),
308
+ url: e.url ?? null,
309
+ completionRequirement: n.get(i) ?? null,
310
+ dueAt: (a == null ? void 0 : a.cachedDueDate) ?? ((c = e.content) == null ? void 0 : c.dueAt) ?? null,
311
+ status: r ? pn(e.content, i, r) : void 0,
312
+ pointsPossible: ((l = e.content) == null ? void 0 : l.pointsPossible) ?? null,
313
+ // Career-only, so it rides in `extensions` rather than widening the shared
314
+ // model. Absent entirely for a Canvas LMS item, which is every item here.
315
+ extensions: Ze(
316
+ void 0,
317
+ un(e.estimatedDuration)
318
+ )
319
+ };
320
+ }
321
+ function rt(e, t, n, s, r) {
322
+ const i = [];
323
+ for (const u of e ?? []) {
324
+ if (!u) continue;
325
+ const a = _n(
326
+ u,
327
+ t,
328
+ n,
329
+ s,
330
+ r
331
+ );
332
+ a && i.push(a);
333
+ }
334
+ return i;
335
+ }
336
+ function ot(e) {
337
+ const t = [], n = /* @__PURE__ */ new Map();
338
+ for (const s of e ?? []) {
339
+ if (!s) continue;
340
+ const r = mn(s);
341
+ t.push(r), n.set(r.itemId, r);
342
+ }
343
+ return { list: t, byItemId: n };
344
+ }
345
+ function Sn(e, t = {}) {
346
+ var c;
347
+ const n = String(e._id);
348
+ if (e.moduleItemsTotalCount == null)
349
+ throw new Je("module", n, "moduleItemsTotalCount");
350
+ const { list: s, byItemId: r } = ot(e.completionRequirements), i = e.moduleItemsConnection, u = i != null && !((c = i.pageInfo) != null && c.hasNextPage), a = st(e.progression, s), d = u ? rt(i.nodes, n, r, t, a) : void 0;
351
+ return {
352
+ id: n,
353
+ name: e.name ?? "",
354
+ position: e.position ?? 0,
355
+ published: et(e.published, "module", n),
356
+ itemCount: e.moduleItemsTotalCount,
357
+ items: d,
358
+ unlockAt: e.unlockAt ?? null,
359
+ prerequisiteModuleIds: (e.prerequisites ?? []).filter((l) => l != null).map((l) => String(l.id)),
360
+ requirementCount: e.requirementCount ?? null,
361
+ requireSequentialProgress: e.requireSequentialProgress ?? !1,
362
+ completionRequirements: s,
363
+ // Derived after the items so `all_graded` can see them when they are inline.
364
+ progress: Mn(e, s, a, d)
365
+ };
366
+ }
367
+ function yn(e, t = {}) {
368
+ var r;
369
+ const n = (r = e == null ? void 0 : e.legacyNode) == null ? void 0 : r.modulesConnection, s = [];
370
+ for (const i of (n == null ? void 0 : n.nodes) ?? [])
371
+ i && s.push(Sn(i, t));
372
+ return { modules: s, pageInfo: tt(n == null ? void 0 : n.pageInfo) };
373
+ }
374
+ function Pn(e, t, n = {}) {
375
+ const s = e == null ? void 0 : e.legacyNode, { list: r, byItemId: i } = ot(s == null ? void 0 : s.completionRequirements), u = s == null ? void 0 : s.moduleItemsConnection, a = st(s == null ? void 0 : s.progression, r);
376
+ return {
377
+ items: rt(u == null ? void 0 : u.nodes, t, i, n, a),
378
+ pageInfo: tt(u == null ? void 0 : u.pageInfo)
379
+ };
380
+ }
381
+ const xn = ee`
382
+ fragment PlatformModulesSubmissionFields on Submission {
383
+ _id
384
+ state
385
+ gradingStatus
386
+ submittedAt
387
+ attempt
388
+ missing
389
+ cachedDueDate
390
+ }
391
+ `, it = ee`
392
+ fragment PlatformModulesItemFields on ModuleItem {
393
+ _id
394
+ title
395
+ url
396
+ position
397
+ indent
398
+ published
399
+ estimatedDuration
400
+ content {
401
+ __typename
402
+ pointsPossible
403
+ ... on Assignment {
404
+ isNewQuiz
405
+ dueAt
406
+ submissionsConnection(
407
+ first: 1
408
+ filter: { includeUnsubmitted: true, userId: $studentId }
409
+ ) @include(if: $includeSubmissions) {
410
+ nodes {
411
+ ...PlatformModulesSubmissionFields
412
+ }
413
+ }
414
+ }
415
+ ... on Quiz {
416
+ submissionsConnection(
417
+ first: 1
418
+ filter: { includeUnsubmitted: true, userId: $studentId }
419
+ ) @include(if: $includeSubmissions) {
420
+ nodes {
421
+ ...PlatformModulesSubmissionFields
422
+ }
423
+ }
424
+ }
425
+ ... on Discussion {
426
+ submissionsConnection(
427
+ first: 1
428
+ filter: { includeUnsubmitted: true, userId: $studentId }
429
+ ) @include(if: $includeSubmissions) {
430
+ nodes {
431
+ ...PlatformModulesSubmissionFields
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+ ${xn}
438
+ `, lt = ee`
439
+ fragment PlatformModulesProgressionFields on ModuleProgression {
440
+ workflowState
441
+ completed
442
+ locked
443
+ started
444
+ requirementsMet {
445
+ id
446
+ type
447
+ minScore
448
+ minPercentage
449
+ }
450
+ }
451
+ `, wn = ee`
452
+ query PlatformModulesGetModules(
453
+ $courseId: ID!
454
+ $first: Int!
455
+ $after: String
456
+ $includeItems: Boolean!
457
+ $itemsFirst: Int!
458
+ $includeSubmissions: Boolean!
459
+ $studentId: ID
460
+ ) {
461
+ legacyNode(_id: $courseId, type: Course) {
462
+ ... on Course {
463
+ modulesConnection(first: $first, after: $after) {
464
+ nodes {
465
+ _id
466
+ name
467
+ position
468
+ published
469
+ unlockAt
470
+ moduleItemsTotalCount
471
+ requirementCount
472
+ requireSequentialProgress
473
+ prerequisites {
474
+ id
475
+ }
476
+ completionRequirements {
477
+ id
478
+ type
479
+ minScore
480
+ minPercentage
481
+ }
482
+ progression {
483
+ ...PlatformModulesProgressionFields
484
+ }
485
+ submissionStatistics {
486
+ latestDueAt
487
+ missingAssignmentCount
488
+ }
489
+ moduleItemsConnection(first: $itemsFirst) @include(if: $includeItems) {
490
+ nodes {
491
+ ...PlatformModulesItemFields
492
+ }
493
+ pageInfo {
494
+ hasNextPage
495
+ endCursor
496
+ }
497
+ }
498
+ }
499
+ pageInfo {
500
+ hasNextPage
501
+ endCursor
502
+ }
503
+ }
504
+ }
505
+ }
506
+ }
507
+ ${it}
508
+ ${lt}
509
+ `, En = ee`
510
+ query PlatformModulesGetModuleItems(
511
+ $moduleId: ID!
512
+ $first: Int!
513
+ $after: String
514
+ $includeSubmissions: Boolean!
515
+ $studentId: ID
516
+ ) {
517
+ legacyNode(_id: $moduleId, type: Module) {
518
+ ... on Module {
519
+ completionRequirements {
520
+ id
521
+ type
522
+ minScore
523
+ minPercentage
524
+ }
525
+ progression {
526
+ ...PlatformModulesProgressionFields
527
+ }
528
+ moduleItemsConnection(first: $first, after: $after) {
529
+ nodes {
530
+ ...PlatformModulesItemFields
531
+ }
532
+ pageInfo {
533
+ hasNextPage
534
+ endCursor
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+ ${it}
541
+ ${lt}
542
+ `;
543
+ function at() {
544
+ return new DOMException("The operation was aborted.", "AbortError");
545
+ }
546
+ function le(e) {
547
+ if (e != null && e.aborted) throw at();
548
+ }
549
+ function we(e, t) {
550
+ return t ? new Promise((n, s) => {
551
+ const r = () => s(at());
552
+ t.addEventListener("abort", r, { once: !0 }), e.then(n, s).finally(() => t.removeEventListener("abort", r));
553
+ }) : e;
554
+ }
555
+ function tr({
556
+ executeQuery: e,
557
+ inlineItemLimit: t = pe,
558
+ nodeBudget: n = Me,
559
+ onUnsupportedItem: s,
560
+ studentId: r,
561
+ validate: i = !0
562
+ }) {
563
+ const u = { onUnsupportedItem: s }, a = r != null;
564
+ return {
565
+ // Declared so `resolveItemsMode` bounds the query by *this* transport's
566
+ // limit rather than assuming one. @see ModulesDataAdapter.inlineNodeBudget
567
+ inlineNodeBudget: n,
568
+ async listModules({
569
+ courseId: d,
570
+ cursor: c,
571
+ pageSize: l,
572
+ includeItems: m,
573
+ inlineItemsFirst: M,
574
+ inlineFirst: _,
575
+ signal: h
576
+ }) {
577
+ le(h);
578
+ const f = m ? Math.min(_ ?? l, l) : l, w = Math.min(M ?? t, t), v = Math.max(1, Math.min(w, Math.floor(n / f))), A = await we(
579
+ e(wn, {
580
+ courseId: d,
581
+ first: f,
582
+ // Pass through what the caller was given; never derive an offset.
583
+ after: c ?? null,
584
+ includeItems: m,
585
+ itemsFirst: v,
586
+ includeSubmissions: a,
587
+ studentId: r ?? null
588
+ }),
589
+ h
590
+ );
591
+ le(h);
592
+ const N = yn(A, u);
593
+ return i ? Zt.parse(N) : N;
594
+ },
595
+ async listModuleItems({
596
+ moduleId: d,
597
+ cursor: c,
598
+ pageSize: l,
599
+ signal: m
600
+ }) {
601
+ le(m);
602
+ const M = await we(
603
+ e(En, {
604
+ moduleId: d,
605
+ first: l,
606
+ after: c ?? null,
607
+ includeSubmissions: a,
608
+ studentId: r ?? null
609
+ }),
610
+ m
611
+ );
612
+ le(m);
613
+ const _ = Pn(M, d, u);
614
+ return i ? Jt.parse(_) : _;
615
+ }
616
+ };
617
+ }
618
+ const _e = 100, ut = 100, dt = 100, Cn = 4, ce = 5 * 60 * 1e3, ct = 20, de = { hasNextPage: !1, endCursor: null }, W = ["platform-modules"], te = {
619
+ all: W,
620
+ course: (e) => [...W, e],
621
+ list: (e, t = {}) => [...W, e, "list", t],
622
+ items: (e, t, n = {}) => [...W, e, "module", t, "items", n]
623
+ };
624
+ function vn(e) {
625
+ return [...W, e, "list"];
626
+ }
627
+ function mt(e, t) {
628
+ return [...W, e, "module", t, "items"];
629
+ }
630
+ function gt(e) {
631
+ return e.pageInfo.hasNextPage ? e.pageInfo.endCursor : null;
632
+ }
633
+ function ht(e) {
634
+ const {
635
+ courseId: t,
636
+ adapter: n,
637
+ pageSize: s,
638
+ includeItems: r,
639
+ inlineItemsFirst: i,
640
+ inlineFirst: u,
641
+ view: a,
642
+ staleTime: d,
643
+ enabled: c
644
+ } = e;
645
+ return {
646
+ // `inlineItemsFirst` is deliberately NOT in the key. It is derived from the
647
+ // probe of this very list, so it cannot vary for a fixed (view, pageSize,
648
+ // includeItems) — including it would only add a segment that never differs.
649
+ queryKey: te.list(t, { view: a, pageSize: s, includeItems: r }),
650
+ initialPageParam: null,
651
+ queryFn: ({ pageParam: l, signal: m }) => n.listModules({
652
+ courseId: t,
653
+ cursor: l,
654
+ pageSize: s,
655
+ includeItems: r,
656
+ inlineItemsFirst: i,
657
+ inlineFirst: u,
658
+ signal: m
659
+ }),
660
+ getNextPageParam: gt,
661
+ enabled: c,
662
+ staleTime: d,
663
+ // A refetch of an infinite query re-walks every page it has loaded. Doing
664
+ // that because someone alt-tabbed is the Canvas behaviour we are replacing.
665
+ refetchOnWindowFocus: !1
666
+ };
667
+ }
668
+ function ft(e) {
669
+ const { courseId: t, moduleId: n, adapter: s, pageSize: r, view: i, staleTime: u, enabled: a, limit: d } = e;
670
+ return {
671
+ queryKey: te.items(t, n, { view: i, pageSize: r }),
672
+ initialPageParam: null,
673
+ queryFn: ({ pageParam: c, signal: l }) => {
674
+ const m = () => s.listModuleItems({ courseId: t, moduleId: n, cursor: c, pageSize: r, signal: l });
675
+ return d ? d(() => {
676
+ if (l.aborted)
677
+ throw new DOMException("The operation was aborted.", "AbortError");
678
+ return m();
679
+ }) : m();
680
+ },
681
+ getNextPageParam: gt,
682
+ enabled: a,
683
+ staleTime: u,
684
+ refetchOnWindowFocus: !1
685
+ };
686
+ }
687
+ function Tn(e) {
688
+ var w;
689
+ const {
690
+ courseId: t,
691
+ adapter: n,
692
+ pageSize: s = _e,
693
+ includeItems: r = !1,
694
+ inlineItemsFirst: i,
695
+ inlineFirst: u,
696
+ view: a,
697
+ staleTime: d = ce,
698
+ enabled: c = !0
699
+ } = e, l = be(
700
+ ht({
701
+ courseId: t,
702
+ adapter: n,
703
+ pageSize: s,
704
+ includeItems: r,
705
+ inlineItemsFirst: i,
706
+ inlineFirst: u,
707
+ view: a,
708
+ staleTime: d,
709
+ enabled: c
710
+ })
711
+ ), m = (w = l.data) == null ? void 0 : w.pages, M = L(() => (m ?? []).flatMap((v) => v.modules), [m]), _ = m && m.length > 0 ? m[m.length - 1].pageInfo : de, h = l.fetchNextPage, f = D(() => {
712
+ h();
713
+ }, [h]);
714
+ return {
715
+ modules: M,
716
+ pageInfo: _,
717
+ hasNextPage: _.hasNextPage,
718
+ fetchNextPage: f,
719
+ isLoading: l.isLoading,
720
+ isFetchingNextPage: l.isFetchingNextPage,
721
+ isPending: l.isPending,
722
+ error: l.error
723
+ };
724
+ }
725
+ function nr(e) {
726
+ var f;
727
+ const {
728
+ courseId: t,
729
+ moduleId: n,
730
+ adapter: s,
731
+ pageSize: r = ut,
732
+ view: i,
733
+ enabled: u = !0,
734
+ staleTime: a = ce,
735
+ limit: d
736
+ } = e, c = be(
737
+ ft({
738
+ courseId: t,
739
+ moduleId: n,
740
+ adapter: s,
741
+ pageSize: r,
742
+ view: i,
743
+ staleTime: a,
744
+ enabled: u,
745
+ limit: d
746
+ })
747
+ ), l = (f = c.data) == null ? void 0 : f.pages, m = L(() => (l ?? []).flatMap((w) => w.items), [l]), M = l && l.length > 0 ? l[l.length - 1].pageInfo : de, _ = c.fetchNextPage, h = D(() => {
748
+ _();
749
+ }, [_]);
750
+ return {
751
+ items: m,
752
+ pageInfo: M,
753
+ hasNextPage: M.hasNextPage,
754
+ fetchNextPage: h,
755
+ isLoading: c.isLoading,
756
+ isFetchingNextPage: c.isFetchingNextPage,
757
+ error: c.error
758
+ };
759
+ }
760
+ function Nn(e, t, n = Me) {
761
+ if (e.pageInfo.hasNextPage) return "onDemand";
762
+ let s = 0, r = 0;
763
+ for (const i of e.modules) {
764
+ if (i.itemCount > t) return "onDemand";
765
+ s += i.itemCount, i.itemCount > r && (r = i.itemCount);
766
+ }
767
+ return s > t * 2 ? "onDemand" : Math.max(e.modules.length, 1) * Math.max(r, 1) <= n ? "inline" : "onDemand";
768
+ }
769
+ function An(e) {
770
+ var M;
771
+ const {
772
+ courseId: t,
773
+ adapter: n,
774
+ mode: s = "auto",
775
+ inlineThreshold: r = dt,
776
+ nodeBudget: i = n.inlineNodeBudget ?? Me,
777
+ pageSize: u = _e,
778
+ view: a,
779
+ staleTime: d = ce
780
+ } = e, c = s === "auto", l = be(
781
+ ht({
782
+ courseId: t,
783
+ adapter: n,
784
+ pageSize: u,
785
+ includeItems: !1,
786
+ view: a,
787
+ staleTime: d,
788
+ enabled: c
789
+ })
790
+ );
791
+ if (!c)
792
+ return {
793
+ resolvedMode: s,
794
+ isResolving: !1,
795
+ probed: !1,
796
+ error: null,
797
+ inlineShape: null
798
+ };
799
+ if (l.error)
800
+ return {
801
+ resolvedMode: "onDemand",
802
+ isResolving: !1,
803
+ probed: !0,
804
+ error: l.error,
805
+ inlineShape: null
806
+ };
807
+ const m = (M = l.data) == null ? void 0 : M.pages[0];
808
+ return m ? {
809
+ resolvedMode: Nn(m, r, i),
810
+ isResolving: !1,
811
+ probed: !0,
812
+ error: null,
813
+ inlineShape: Ln(m)
814
+ } : { resolvedMode: null, isResolving: !0, probed: !0, error: null, inlineShape: null };
815
+ }
816
+ function Ln(e) {
817
+ let t = 1;
818
+ for (const n of e.modules)
819
+ n.itemCount > t && (t = n.itemCount);
820
+ return { moduleCount: Math.max(e.modules.length, 1), maxItemCount: t };
821
+ }
822
+ const Ee = "\0", Dn = /* @__PURE__ */ new Map();
823
+ function Rn(e, t, n = ct) {
824
+ const s = Fe(), r = B(/* @__PURE__ */ new Map()), [i, u] = X(Dn), a = e.join(Ee), d = L(() => a === "" ? [] : a.split(Ee), [a]);
825
+ ae(() => {
826
+ const l = r.current, m = () => {
827
+ u((h) => {
828
+ const f = /* @__PURE__ */ new Map();
829
+ let w = h.size !== l.size;
830
+ for (const [v, A] of l) {
831
+ const N = A.getCurrentResult();
832
+ f.set(v, N), !w && h.get(v) !== N && (w = !0);
833
+ }
834
+ return w ? f : h;
835
+ });
836
+ }, M = new Set(d);
837
+ for (const h of d) {
838
+ if (l.has(h)) continue;
839
+ const f = new Ct(
840
+ s,
841
+ t(h, !0)
842
+ );
843
+ l.set(h, f), f.subscribe(m);
844
+ }
845
+ for (const [h, f] of l)
846
+ f.setOptions(t(h, M.has(h)));
847
+ for (const h of d) {
848
+ const f = l.get(h);
849
+ f && (l.delete(h), l.set(h, f));
850
+ }
851
+ let _ = l.size - M.size;
852
+ if (_ > n)
853
+ for (const [h, f] of l) {
854
+ if (_ <= n) break;
855
+ M.has(h) || (f.destroy(), l.delete(h), _ -= 1);
856
+ }
857
+ m();
858
+ }, [s, t, d, n]), ae(() => {
859
+ const l = r.current;
860
+ return () => {
861
+ for (const m of l.values()) m.destroy();
862
+ l.clear();
863
+ };
864
+ }, []);
865
+ const c = D((l) => {
866
+ var m;
867
+ (m = r.current.get(l)) == null || m.fetchNextPage();
868
+ }, []);
869
+ return { results: i, fetchNextPage: c };
870
+ }
871
+ const qn = [], Fn = () => {
872
+ };
873
+ function sr(e) {
874
+ var Q, oe;
875
+ const {
876
+ courseId: t,
877
+ adapter: n,
878
+ view: s,
879
+ itemsMode: r = "auto",
880
+ inlineThreshold: i = dt,
881
+ pageSize: u = _e,
882
+ itemPageSize: a = ut,
883
+ itemConcurrency: d = Cn,
884
+ retainCollapsedLimit: c = ct,
885
+ staleTime: l = ce,
886
+ initialExpandedModuleIds: m
887
+ } = e, M = Fe(), _ = An({
888
+ courseId: t,
889
+ adapter: n,
890
+ mode: r,
891
+ inlineThreshold: i,
892
+ pageSize: u,
893
+ view: s,
894
+ staleTime: l
895
+ }), h = _.resolvedMode, f = Tn({
896
+ courseId: t,
897
+ adapter: n,
898
+ pageSize: u,
899
+ view: s,
900
+ staleTime: l,
901
+ includeItems: h === "inline",
902
+ // Measured by the probe, not assumed. Canvas charges GraphQL complexity on
903
+ // the requested shape, so asking for the ceiling rejects the query outright
904
+ // even on a tiny course. @see UseItemsModeResult.inlineShape
905
+ inlineItemsFirst: (Q = _.inlineShape) == null ? void 0 : Q.maxItemCount,
906
+ // Both axes, not just one: the budget is charged on the product, so asking
907
+ // for 100 modules when page 1 held 6 is what forces `inlineItemsFirst` down.
908
+ inlineFirst: (oe = _.inlineShape) == null ? void 0 : oe.moduleCount,
909
+ enabled: h !== null
910
+ }), [w, v] = X(
911
+ () => m ?? []
912
+ ), A = L(() => {
913
+ const b = /* @__PURE__ */ new Map();
914
+ for (const p of f.modules) b.set(p.id, p);
915
+ return b;
916
+ }, [f.modules]), N = L(
917
+ () => w.filter((b) => {
918
+ const p = A.get(b);
919
+ return p !== void 0 && p.items === void 0;
920
+ }),
921
+ [w, A]
922
+ ), ne = L(() => new Set(N), [N]), E = L(() => Nt(d), [d]), z = D(
923
+ (b, p) => ft({
924
+ courseId: t,
925
+ moduleId: b,
926
+ adapter: n,
927
+ pageSize: a,
928
+ view: s,
929
+ staleTime: l,
930
+ enabled: p,
931
+ limit: E
932
+ }),
933
+ [t, n, a, s, l, E]
934
+ ), G = Rn(N, z, c), K = G.fetchNextPage, q = B(/* @__PURE__ */ new Map()), H = D(
935
+ (b) => {
936
+ let p = q.current.get(b);
937
+ return p || (p = () => K(b), q.current.set(b, p)), p;
938
+ },
939
+ [K]
940
+ ), O = D((b) => {
941
+ v(
942
+ (p) => p.includes(b) ? p : [...p, b]
943
+ );
944
+ }, []), Y = D(
945
+ (b) => {
946
+ v((p) => p.filter((C) => C !== b)), M.cancelQueries({ queryKey: mt(t, b) });
947
+ },
948
+ [M, t]
949
+ ), V = B(w);
950
+ V.current = w;
951
+ const se = D(
952
+ (b) => {
953
+ V.current.includes(b) ? Y(b) : O(b);
954
+ },
955
+ [Y, O]
956
+ ), U = G.results, re = B(/* @__PURE__ */ new Map()), $ = D(
957
+ (b) => {
958
+ var ye;
959
+ const p = A.get(b), C = U.get(b), x = ne.has(b), T = re.current.get(b);
960
+ if (T && T.items === (p == null ? void 0 : p.items) && T.result === C && T.active === x)
961
+ return T.value;
962
+ const j = (ie) => (re.current.set(b, { items: p == null ? void 0 : p.items, result: C, active: x, value: ie }), ie);
963
+ if ((p == null ? void 0 : p.items) !== void 0)
964
+ return j({
965
+ items: p.items,
966
+ pageInfo: de,
967
+ hasNextPage: !1,
968
+ fetchNextPage: Fn,
969
+ isLoading: !1,
970
+ isFetchingNextPage: !1,
971
+ error: null,
972
+ source: "inline"
973
+ });
974
+ const F = (ye = C == null ? void 0 : C.data) == null ? void 0 : ye.pages, Et = F ? F.flatMap((ie) => ie.items) : qn, Se = F && F.length > 0 ? F[F.length - 1].pageInfo : de;
975
+ return j({
976
+ items: Et,
977
+ pageInfo: Se,
978
+ hasNextPage: Se.hasNextPage,
979
+ fetchNextPage: H(b),
980
+ // Between the click and the observer being wired up there is one commit
981
+ // with no result yet; report that as loading rather than as "empty".
982
+ isLoading: C ? C.isLoading : x,
983
+ isFetchingNextPage: (C == null ? void 0 : C.isFetchingNextPage) ?? !1,
984
+ error: (C == null ? void 0 : C.error) ?? null,
985
+ source: "onDemand"
986
+ });
987
+ },
988
+ [A, U, H, ne]
989
+ );
990
+ return {
991
+ modules: f.modules,
992
+ pageInfo: f.pageInfo,
993
+ hasNextPage: f.hasNextPage,
994
+ fetchNextPage: f.fetchNextPage,
995
+ isLoading: _.isResolving || f.isPending,
996
+ isFetchingNextPage: f.isFetchingNextPage,
997
+ error: f.error ?? _.error,
998
+ itemsMode: h,
999
+ getItems: $,
1000
+ expandModule: O,
1001
+ collapseModule: Y,
1002
+ toggleModule: se,
1003
+ expandedModuleIds: w
1004
+ };
1005
+ }
1006
+ function rr(e, t, n, s) {
1007
+ const r = s ? te.items(t, n, s) : mt(t, n);
1008
+ return e.invalidateQueries({ queryKey: r });
1009
+ }
1010
+ function or(e, t, n) {
1011
+ const s = n ? te.list(t, n) : vn(t);
1012
+ return e.invalidateQueries({ queryKey: s });
1013
+ }
1014
+ function ir(e, t) {
1015
+ return e.invalidateQueries({ queryKey: te.course(t) });
1016
+ }
1017
+ const kn = /%\{(\w+)\}/g, pt = (e, t) => t ? e.replace(
1018
+ kn,
1019
+ (n, s) => s in t ? String(t[s]) : n
1020
+ ) : e, k = "en-US", zn = { t: pt, locale: k }, It = vt(null);
1021
+ function lr({ t: e, locale: t, children: n }) {
1022
+ const s = L(
1023
+ () => ({ t: e ?? pt, locale: t ?? k }),
1024
+ [e, t]
1025
+ );
1026
+ return /* @__PURE__ */ o(It.Provider, { value: s, children: n });
1027
+ }
1028
+ function R() {
1029
+ return Tt(It) ?? zn;
1030
+ }
1031
+ const On = 864e5, Ce = /* @__PURE__ */ new Map(), ve = /* @__PURE__ */ new Map(), Te = /* @__PURE__ */ new Map();
1032
+ function me(e) {
1033
+ const t = ve.get(e);
1034
+ if (t) return t;
1035
+ const n = new Intl.NumberFormat(e);
1036
+ return ve.set(e, n), n;
1037
+ }
1038
+ function Un(e) {
1039
+ const t = Te.get(e);
1040
+ if (t) return t;
1041
+ const n = new Intl.NumberFormat(e, { minimumIntegerDigits: 2, useGrouping: !1 });
1042
+ return Te.set(e, n), n;
1043
+ }
1044
+ function ge(e = k) {
1045
+ return me(e);
1046
+ }
1047
+ function $n(e) {
1048
+ const t = Ce.get(e);
1049
+ if (t) return t;
1050
+ const n = {
1051
+ time: new Intl.DateTimeFormat(e, { hour: "numeric", minute: "2-digit" }),
1052
+ day: new Intl.DateTimeFormat(e, { month: "long", day: "numeric" }),
1053
+ dayYear: new Intl.DateTimeFormat(e, { month: "long", day: "numeric", year: "numeric" })
1054
+ };
1055
+ return Ce.set(e, n), n;
1056
+ }
1057
+ function bt(e, t) {
1058
+ const n = Date.UTC(e.getFullYear(), e.getMonth(), e.getDate()), s = Date.UTC(t.getFullYear(), t.getMonth(), t.getDate());
1059
+ return Math.round((n - s) / On);
1060
+ }
1061
+ function Mt(e, t, n, s = k) {
1062
+ const r = new Date(t);
1063
+ if (Number.isNaN(r.getTime())) return t;
1064
+ const i = $n(s), u = i.time.format(r), a = bt(r, n);
1065
+ if (a === 0) return e("today, %{time}", { time: u });
1066
+ if (a === -1) return e("yesterday, %{time}", { time: u });
1067
+ if (a === 1) return e("tomorrow, %{time}", { time: u });
1068
+ const d = r.getFullYear() === n.getFullYear() ? i.day.format(r) : i.dayYear.format(r);
1069
+ return e("%{date}, %{time}", { date: d, time: u });
1070
+ }
1071
+ function _t(e, t, n, s) {
1072
+ return e("Due %{when}", { when: Mt(e, t, n, s) });
1073
+ }
1074
+ function Ne(e, t, n, s) {
1075
+ return e("Will unlock on %{when}", { when: Mt(e, t, n, s) });
1076
+ }
1077
+ function Bn(e, t = k) {
1078
+ return Un(t).format(e);
1079
+ }
1080
+ function Gn(e, t) {
1081
+ switch (t) {
1082
+ case "assignment":
1083
+ return e("Assignment");
1084
+ case "quiz":
1085
+ return e("Quiz");
1086
+ case "discussion":
1087
+ return e("Discussion");
1088
+ case "page":
1089
+ return e("Page");
1090
+ case "file":
1091
+ return e("File");
1092
+ case "external_url":
1093
+ return e("External URL");
1094
+ case "external_tool":
1095
+ return e("External tool");
1096
+ case "sub_header":
1097
+ return e("Text header");
1098
+ }
1099
+ }
1100
+ function Yn(e, t, n = k) {
1101
+ return t == null ? null : e("%{points} pts", { points: me(n).format(t) });
1102
+ }
1103
+ function Qn(e, t, n = k) {
1104
+ const s = (r) => me(n).format(r);
1105
+ switch (t.type) {
1106
+ case "must_view":
1107
+ return e("View the item");
1108
+ case "must_submit":
1109
+ return e("Submit the assignment");
1110
+ case "must_contribute":
1111
+ return e("Contribute to the page");
1112
+ case "must_mark_done":
1113
+ return e("Mark as done");
1114
+ case "min_score":
1115
+ return t.minScore === null || t.minScore === void 0 ? e("Score at least the minimum") : e("Score at least %{score}", { score: s(t.minScore) });
1116
+ case "min_percentage":
1117
+ return t.minPercentage === null || t.minPercentage === void 0 ? e("Score at least the minimum percentage") : e("Score at least %{percentage}%", { percentage: s(t.minPercentage) });
1118
+ case "unknown":
1119
+ return e("Complete the requirement");
1120
+ }
1121
+ }
1122
+ function jn(e, t, n, s, r = k) {
1123
+ const i = me(r);
1124
+ return e("%{percent}% | %{met} of %{total} requirements met", {
1125
+ percent: i.format(t),
1126
+ met: i.format(n),
1127
+ total: i.format(s)
1128
+ });
1129
+ }
1130
+ const Wn = [
1131
+ "all",
1132
+ "in_progress",
1133
+ "complete",
1134
+ "due_this_week",
1135
+ "missing"
1136
+ ];
1137
+ function Kn(e, t) {
1138
+ switch (t) {
1139
+ case "all":
1140
+ return e("All");
1141
+ case "in_progress":
1142
+ return e("In progress");
1143
+ case "complete":
1144
+ return e("Complete");
1145
+ case "due_this_week":
1146
+ return e("Due this week");
1147
+ case "missing":
1148
+ return e("Missing");
1149
+ }
1150
+ }
1151
+ const Hn = 7;
1152
+ function Ae(e, t, n) {
1153
+ if (t === "all") return !0;
1154
+ const s = e.progress;
1155
+ if (!s) return !1;
1156
+ switch (t) {
1157
+ case "in_progress":
1158
+ return s.status === "in_progress";
1159
+ case "complete":
1160
+ return s.status === "all_submitted" || s.status === "all_graded";
1161
+ case "missing":
1162
+ return s.missingCount > 0;
1163
+ case "due_this_week": {
1164
+ if (!s.latestDueAt) return !1;
1165
+ const r = new Date(s.latestDueAt);
1166
+ if (Number.isNaN(r.getTime())) return !1;
1167
+ const i = bt(r, n);
1168
+ return i >= 0 && i <= Hn;
1169
+ }
1170
+ default:
1171
+ return !0;
1172
+ }
1173
+ }
1174
+ function Le(e, t, n) {
1175
+ const s = t.trim().toLocaleLowerCase(n);
1176
+ return s === "" ? !0 : e.name.toLocaleLowerCase(n).includes(s);
1177
+ }
1178
+ function Vn({ value: e, onChange: t, counts: n }) {
1179
+ const { t: s, locale: r } = R(), i = ke(), u = ge(r);
1180
+ return /* @__PURE__ */ y(S, { as: "div", margin: "medium 0 0 0", children: [
1181
+ /* @__PURE__ */ o(S, { as: "div", margin: "0 0 x-small 0", children: /* @__PURE__ */ o(I, { size: "small", weight: "bold", id: i, children: s("Filter") }) }),
1182
+ /* @__PURE__ */ o(
1183
+ P,
1184
+ {
1185
+ gap: "x-small",
1186
+ wrap: "wrap",
1187
+ as: "div",
1188
+ role: "group",
1189
+ "aria-labelledby": i,
1190
+ "data-testid": "filter-chips",
1191
+ children: Wn.map((a) => {
1192
+ const d = a === e, c = n == null ? void 0 : n[a], l = Kn(s, a);
1193
+ return /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(
1194
+ Z,
1195
+ {
1196
+ size: "small",
1197
+ color: d ? "primary" : "secondary",
1198
+ "aria-pressed": d,
1199
+ onClick: () => t(a),
1200
+ "data-testid": `filter-chip-${a}`,
1201
+ themeOverride: { borderRadius: "1.5rem" },
1202
+ children: c === void 0 ? l : s("%{label} (%{count})", { label: l, count: u.format(c) })
1203
+ }
1204
+ ) }, a);
1205
+ })
1206
+ }
1207
+ )
1208
+ ] });
1209
+ }
1210
+ const Xn = {
1211
+ not_started: {},
1212
+ in_progress: { color: "info", icon: /* @__PURE__ */ o(Qe, {}) },
1213
+ all_submitted: { color: "success", icon: /* @__PURE__ */ o(ue, {}) },
1214
+ all_graded: { color: "success", icon: /* @__PURE__ */ o(ue, {}) },
1215
+ missing: { color: "danger", icon: /* @__PURE__ */ o(Ye, {}) },
1216
+ locked: { icon: /* @__PURE__ */ o(Ge, {}) }
1217
+ }, Zn = {
1218
+ not_started: {},
1219
+ in_progress: { color: "info", icon: /* @__PURE__ */ o(Qe, {}) },
1220
+ submitted: { color: "success", icon: /* @__PURE__ */ o(ue, {}) },
1221
+ graded: { color: "success", icon: /* @__PURE__ */ o(ue, {}) },
1222
+ missing: { color: "danger", icon: /* @__PURE__ */ o(Ye, {}) },
1223
+ locked: { icon: /* @__PURE__ */ o(Ge, {}) }
1224
+ };
1225
+ function Jn(e, t) {
1226
+ switch (t) {
1227
+ case "not_started":
1228
+ return e("Not started");
1229
+ case "in_progress":
1230
+ return e("In progress");
1231
+ case "all_submitted":
1232
+ return e("All items complete");
1233
+ case "all_graded":
1234
+ return e("All items graded");
1235
+ case "missing":
1236
+ return e("Missing");
1237
+ case "locked":
1238
+ return e("Locked");
1239
+ }
1240
+ }
1241
+ function es(e, t) {
1242
+ switch (t) {
1243
+ case "not_started":
1244
+ return e("Not started");
1245
+ case "in_progress":
1246
+ return e("In progress");
1247
+ case "submitted":
1248
+ return e("Submitted");
1249
+ case "graded":
1250
+ return e("Graded");
1251
+ case "missing":
1252
+ return e("Missing");
1253
+ case "locked":
1254
+ return e("Locked");
1255
+ }
1256
+ }
1257
+ function St(e, t, n) {
1258
+ return /* @__PURE__ */ o(Yt, { color: e.color, renderIcon: e.icon, "data-testid": n, children: t });
1259
+ }
1260
+ function ts({ status: e }) {
1261
+ const { t } = R();
1262
+ return St(
1263
+ Xn[e],
1264
+ Jn(t, e),
1265
+ "module-status-pill"
1266
+ );
1267
+ }
1268
+ function ns({ status: e }) {
1269
+ const { t } = R();
1270
+ return St(
1271
+ Zn[e],
1272
+ es(t, e),
1273
+ "item-status-pill"
1274
+ );
1275
+ }
1276
+ const ss = {
1277
+ assignment: /* @__PURE__ */ o(Ut, {}),
1278
+ quiz: /* @__PURE__ */ o(Ot, {}),
1279
+ discussion: /* @__PURE__ */ o(zt, {}),
1280
+ page: /* @__PURE__ */ o(kt, {}),
1281
+ file: /* @__PURE__ */ o(Ft, {}),
1282
+ external_url: /* @__PURE__ */ o(qt, {}),
1283
+ external_tool: /* @__PURE__ */ o(Rt, {}),
1284
+ sub_header: /* @__PURE__ */ o(Dt, {})
1285
+ }, rs = 1.75, os = {
1286
+ contentVisibility: "auto",
1287
+ containIntrinsicSize: "auto 3.5rem"
1288
+ };
1289
+ function is({ item: e, today: t }) {
1290
+ const { t: n, locale: s } = R();
1291
+ if (e.type === "sub_header")
1292
+ return /* @__PURE__ */ o(
1293
+ S,
1294
+ {
1295
+ as: "div",
1296
+ padding: "x-small small",
1297
+ margin: "x-small 0 0 0",
1298
+ "data-testid": `module-item-${e.id}`,
1299
+ children: /* @__PURE__ */ o(I, { size: "small", weight: "bold", transform: "uppercase", letterSpacing: "expanded", children: e.title })
1300
+ }
1301
+ );
1302
+ const r = [Gn(n, e.type)];
1303
+ e.dueAt && r.push(_t(n, e.dueAt, t, s));
1304
+ const i = Yn(n, e.pointsPossible, s);
1305
+ i && r.push(i), e.completionRequirement && r.push(Qn(n, e.completionRequirement, s));
1306
+ const u = en(e);
1307
+ return u !== null && r.push(n("%{minutes} min", { minutes: ge(s).format(u) })), e.published || r.push(n("Unpublished")), /* @__PURE__ */ o("div", { style: { marginLeft: `${e.indent * rs}rem`, ...os }, children: /* @__PURE__ */ o(
1308
+ S,
1309
+ {
1310
+ as: "div",
1311
+ background: "secondary",
1312
+ borderRadius: "medium",
1313
+ padding: "x-small small",
1314
+ margin: "x-small 0 0 0",
1315
+ "data-testid": `module-item-${e.id}`,
1316
+ children: /* @__PURE__ */ y(P, { alignItems: "center", gap: "small", children: [
1317
+ e.indent > 0 ? /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(I, { color: "secondary", children: /* @__PURE__ */ o(Lt, {}) }) }) : null,
1318
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(
1319
+ S,
1320
+ {
1321
+ as: "div",
1322
+ background: "primary",
1323
+ borderWidth: "small",
1324
+ borderRadius: "medium",
1325
+ padding: "x-small",
1326
+ width: "2.25rem",
1327
+ height: "2.25rem",
1328
+ textAlign: "center",
1329
+ children: /* @__PURE__ */ o(I, { color: "secondary", children: ss[e.type] })
1330
+ }
1331
+ ) }),
1332
+ /* @__PURE__ */ y(P.Item, { shouldGrow: !0, shouldShrink: !0, children: [
1333
+ /* @__PURE__ */ o(S, { as: "div", children: /* @__PURE__ */ o(I, { weight: "bold", size: "small", "data-testid": "module-item-title", children: e.title }) }),
1334
+ /* @__PURE__ */ o(S, { as: "div", children: /* @__PURE__ */ o(I, { size: "x-small", color: "secondary", "data-testid": "module-item-meta", children: r.map((a, d) => /* @__PURE__ */ y("span", { children: [
1335
+ d > 0 ? /* @__PURE__ */ o("span", { "aria-hidden": "true", children: " | " }) : null,
1336
+ /* @__PURE__ */ o("span", { children: a })
1337
+ ] }, a)) }) })
1338
+ ] }),
1339
+ e.status ? /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(ns, { status: e.status }) }) : null
1340
+ ] })
1341
+ }
1342
+ ) });
1343
+ }
1344
+ const ls = ze(is), as = "3.5rem";
1345
+ function us({ module: e, today: t }) {
1346
+ var a, d;
1347
+ const { t: n, locale: s } = R(), r = ((a = e.progress) == null ? void 0 : a.status) === "locked", i = e.unlockAt;
1348
+ if (r && i)
1349
+ return /* @__PURE__ */ o(I, { size: "small", color: "secondary", "data-testid": "module-byline", children: Ne(n, i, t, s) });
1350
+ if (r)
1351
+ return /* @__PURE__ */ o(I, { size: "small", color: "secondary", "data-testid": "module-byline", children: n("Locked until prerequisites are met") });
1352
+ const u = (d = e.progress) == null ? void 0 : d.latestDueAt;
1353
+ return u ? /* @__PURE__ */ o(I, { size: "small", color: "secondary", "data-testid": "module-byline", children: _t(n, u, t, s) }) : !e.progress && i ? /* @__PURE__ */ o(I, { size: "small", color: "secondary", "data-testid": "module-byline", children: Ne(n, i, t, s) }) : null;
1354
+ }
1355
+ function ds({ module: e }) {
1356
+ const { t, locale: n } = R(), s = e.progress;
1357
+ return !s || s.requirementCount <= 0 ? null : (
1358
+ // Indented to sit under the module name rather than under the number badge,
1359
+ // as the mock draws it. Plain `style` because InstUI's `margin` only takes
1360
+ // spacing keywords and the badge width is not one of them.
1361
+ /* @__PURE__ */ o("div", { style: { marginLeft: as }, children: /* @__PURE__ */ y(P, { alignItems: "center", gap: "small", margin: "small 0 0 0", "data-testid": "module-progress", children: [
1362
+ /* @__PURE__ */ o(P.Item, { size: "22rem", shouldShrink: !0, children: /* @__PURE__ */ o(
1363
+ Gt,
1364
+ {
1365
+ size: "x-small",
1366
+ meterColor: s.percentComplete >= 100 ? "success" : "brand",
1367
+ valueNow: s.requirementsMet,
1368
+ valueMax: Math.max(s.requirementCount, 1),
1369
+ shouldAnimate: !1,
1370
+ screenReaderLabel: t("%{name} progress", { name: e.name })
1371
+ }
1372
+ ) }),
1373
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(I, { size: "small", color: "secondary", children: jn(
1374
+ t,
1375
+ s.percentComplete,
1376
+ s.requirementsMet,
1377
+ s.requirementCount,
1378
+ n
1379
+ ) }) })
1380
+ ] }) })
1381
+ );
1382
+ }
1383
+ const De = (e) => `platform-modules-panel-${e}`;
1384
+ function cs({
1385
+ module: e,
1386
+ state: t,
1387
+ today: n,
1388
+ showDiagnostics: s
1389
+ }) {
1390
+ const { t: r, locale: i } = R(), u = ge(i), a = B(null), d = t.items.length, c = !t.isLoading && !t.isFetchingNextPage;
1391
+ return ae(() => {
1392
+ !c || d === 0 || a.current !== d && (a.current = d, Oe(
1393
+ r("%{loaded} of %{total} items loaded in %{name}", {
1394
+ loaded: u.format(d),
1395
+ total: u.format(e.itemCount),
1396
+ name: e.name
1397
+ })
1398
+ ));
1399
+ }, [c, d, e.itemCount, e.name, u, r]), t.error ? /* @__PURE__ */ o(S, { as: "div", margin: "small 0 0 0", children: /* @__PURE__ */ o(
1400
+ $e,
1401
+ {
1402
+ variant: "error",
1403
+ margin: "0",
1404
+ liveRegion: Ue,
1405
+ liveRegionPoliteness: "assertive",
1406
+ "data-testid": "items-error",
1407
+ children: r("Items failed to load: %{message}", { message: t.error.message })
1408
+ }
1409
+ ) }) : t.isLoading ? /* @__PURE__ */ y(S, { as: "div", margin: "small 0 0 0", "data-testid": "items-loading", children: [
1410
+ /* @__PURE__ */ o(We, { size: "x-small", renderTitle: r("Loading items") }),
1411
+ " ",
1412
+ /* @__PURE__ */ o(I, { size: "small", color: "secondary", children: r("Loading items…") })
1413
+ ] }) : /* @__PURE__ */ y(S, { as: "div", margin: "small 0 0 0", children: [
1414
+ s ? /* @__PURE__ */ y(I, { size: "x-small", color: "secondary", "data-testid": "items-source", children: [
1415
+ "items source: ",
1416
+ t.source,
1417
+ " · loaded ",
1418
+ t.items.length,
1419
+ " of ",
1420
+ e.itemCount
1421
+ ] }) : null,
1422
+ t.items.length === 0 ? /* @__PURE__ */ o(S, { as: "div", padding: "small", "data-testid": "items-empty", children: /* @__PURE__ */ o(I, { size: "small", color: "secondary", children: r("No items in this module.") }) }) : /* @__PURE__ */ o("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, "data-testid": "items-list", children: t.items.map((l) => /* @__PURE__ */ o("li", { children: /* @__PURE__ */ o(ls, { item: l, today: n }) }, l.id)) }),
1423
+ t.hasNextPage ? /* @__PURE__ */ o(S, { as: "div", margin: "small 0 0 0", textAlign: "center", children: /* @__PURE__ */ o(
1424
+ Z,
1425
+ {
1426
+ size: "small",
1427
+ onClick: t.fetchNextPage,
1428
+ interaction: t.isFetchingNextPage ? "disabled" : "enabled",
1429
+ "data-testid": "load-more-items",
1430
+ children: t.isFetchingNextPage ? r("Loading more items…") : r("Load more items (%{loaded} of %{total} loaded)", {
1431
+ loaded: u.format(t.items.length),
1432
+ total: u.format(e.itemCount)
1433
+ })
1434
+ }
1435
+ ) }) : null
1436
+ ] });
1437
+ }
1438
+ function ms({
1439
+ module: e,
1440
+ expanded: t,
1441
+ onToggle: n,
1442
+ itemsState: s,
1443
+ today: r,
1444
+ showDiagnostics: i = !1,
1445
+ headingLevel: u = "h3"
1446
+ }) {
1447
+ var l;
1448
+ const { t: a, locale: d } = R(), c = ((l = e.progress) == null ? void 0 : l.status) === "locked";
1449
+ return /* @__PURE__ */ y(
1450
+ S,
1451
+ {
1452
+ as: "div",
1453
+ background: "primary",
1454
+ borderRadius: "large",
1455
+ borderWidth: "small",
1456
+ shadow: "resting",
1457
+ padding: "medium",
1458
+ margin: "0 0 small 0",
1459
+ "data-testid": `module-card-${e.id}`,
1460
+ children: [
1461
+ /* @__PURE__ */ y(P, { alignItems: "start", gap: "small", children: [
1462
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(
1463
+ S,
1464
+ {
1465
+ as: "div",
1466
+ borderWidth: "small",
1467
+ borderRadius: "medium",
1468
+ padding: "x-small",
1469
+ width: "2.75rem",
1470
+ textAlign: "center",
1471
+ "data-testid": "module-number",
1472
+ children: /* @__PURE__ */ o(I, { weight: "bold", size: "small", children: Bn(e.position, d) })
1473
+ }
1474
+ ) }),
1475
+ /* @__PURE__ */ y(P.Item, { shouldGrow: !0, shouldShrink: !0, children: [
1476
+ /* @__PURE__ */ y(S, { as: "div", children: [
1477
+ /* @__PURE__ */ o(
1478
+ Be,
1479
+ {
1480
+ level: u,
1481
+ themeOverride: { h2FontSize: "1.375rem", h3FontSize: "1.375rem" },
1482
+ "data-testid": "module-name",
1483
+ children: /* @__PURE__ */ o(I, { size: "large", weight: "bold", color: c ? "secondary" : "primary", children: e.name })
1484
+ }
1485
+ ),
1486
+ e.published ? null : /* @__PURE__ */ y(I, { size: "x-small", color: "secondary", children: [
1487
+ " ",
1488
+ a("(unpublished)")
1489
+ ] })
1490
+ ] }),
1491
+ /* @__PURE__ */ o(S, { as: "div", children: /* @__PURE__ */ o(us, { module: e, today: r }) })
1492
+ ] }),
1493
+ e.progress ? /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(ts, { status: e.progress.status }) }) : null,
1494
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(
1495
+ At,
1496
+ {
1497
+ size: "small",
1498
+ withBorder: !0,
1499
+ withBackground: !1,
1500
+ screenReaderLabel: t ? a("Collapse %{name}", { name: e.name }) : a("Expand %{name}", { name: e.name }),
1501
+ "aria-expanded": t,
1502
+ "aria-controls": De(e.id),
1503
+ onClick: () => n(e.id),
1504
+ "data-testid": `toggle-module-${e.id}`,
1505
+ children: t ? /* @__PURE__ */ o($t, {}) : /* @__PURE__ */ o(je, {})
1506
+ }
1507
+ ) })
1508
+ ] }),
1509
+ /* @__PURE__ */ o(ds, { module: e }),
1510
+ /* @__PURE__ */ o("div", { id: De(e.id), role: "group", "aria-label": e.name, hidden: !t, children: t ? /* @__PURE__ */ o(
1511
+ cs,
1512
+ {
1513
+ module: e,
1514
+ state: s,
1515
+ today: r,
1516
+ showDiagnostics: i
1517
+ }
1518
+ ) : null })
1519
+ ]
1520
+ }
1521
+ );
1522
+ }
1523
+ const gs = ze(ms);
1524
+ function hs({
1525
+ counts: e,
1526
+ itemsMode: t,
1527
+ loadedModules: n
1528
+ }) {
1529
+ const s = e.listModules + e.listModuleItems;
1530
+ return /* @__PURE__ */ o(
1531
+ S,
1532
+ {
1533
+ as: "div",
1534
+ background: "secondary",
1535
+ borderRadius: "medium",
1536
+ borderWidth: "small",
1537
+ padding: "x-small small",
1538
+ margin: "0 0 small 0",
1539
+ "data-testid": "dev-diagnostics",
1540
+ children: /* @__PURE__ */ y(P, { gap: "medium", wrap: "wrap", children: [
1541
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ o(I, { size: "x-small", weight: "bold", children: "Adapter requests" }) }),
1542
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ y(I, { size: "x-small", "data-testid": "request-count-list-modules", children: [
1543
+ "listModules: ",
1544
+ e.listModules
1545
+ ] }) }),
1546
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ y(I, { size: "x-small", "data-testid": "request-count-list-module-items", children: [
1547
+ "listModuleItems: ",
1548
+ e.listModuleItems
1549
+ ] }) }),
1550
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ y(I, { size: "x-small", weight: "bold", "data-testid": "request-count-total", children: [
1551
+ "total: ",
1552
+ s
1553
+ ] }) }),
1554
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ y(I, { size: "x-small", "data-testid": "resolved-items-mode", children: [
1555
+ "itemsMode: ",
1556
+ t ?? "probing…"
1557
+ ] }) }),
1558
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ y(I, { size: "x-small", "data-testid": "modules-loaded-count", children: [
1559
+ "modules loaded: ",
1560
+ n
1561
+ ] }) })
1562
+ ] })
1563
+ }
1564
+ );
1565
+ }
1566
+ function ar({
1567
+ data: e,
1568
+ courseName: t,
1569
+ courseContext: n,
1570
+ today: s,
1571
+ requestCounts: r,
1572
+ showDiagnostics: i = r !== void 0,
1573
+ initialFilter: u = "all",
1574
+ as: a = "div",
1575
+ headingLevel: d = "h2"
1576
+ }) {
1577
+ const {
1578
+ modules: c,
1579
+ hasNextPage: l,
1580
+ fetchNextPage: m,
1581
+ isFetchingNextPage: M,
1582
+ isLoading: _,
1583
+ error: h,
1584
+ itemsMode: f,
1585
+ expandedModuleIds: w,
1586
+ toggleModule: v,
1587
+ expandModule: A,
1588
+ collapseModule: N,
1589
+ getItems: ne
1590
+ } = e, { t: E, locale: z } = R(), G = ge(z), K = t ?? E("Course modules"), [q, H] = X(""), [O, Y] = X(u), V = B(null), se = ke(), U = B(!1), [re] = X(() => /* @__PURE__ */ new Date()), $ = s ?? re;
1591
+ ae(() => {
1592
+ var x;
1593
+ !U.current || M || (U.current = !1, Oe(E("%{count} modules loaded", { count: String(c.length) })), (x = V.current) == null || x.focus());
1594
+ }, [M, c.length, E]);
1595
+ const Q = L(
1596
+ () => c.filter(
1597
+ (x) => Le(x, q, z) && Ae(x, O, $)
1598
+ ),
1599
+ [c, q, O, $, z]
1600
+ ), oe = L(() => {
1601
+ const x = c.filter((j) => Le(j, q, z)), T = (j) => x.filter((F) => Ae(F, j, $)).length;
1602
+ return {
1603
+ all: x.length,
1604
+ in_progress: T("in_progress"),
1605
+ complete: T("complete"),
1606
+ due_this_week: T("due_this_week"),
1607
+ missing: T("missing")
1608
+ };
1609
+ }, [c, q, $, z]), b = () => {
1610
+ for (const x of Q) A(x.id);
1611
+ }, p = () => {
1612
+ for (const x of w) N(x);
1613
+ }, C = c.length > 0 && Q.length === 0 && !_ && !h;
1614
+ return /* @__PURE__ */ y(S, { as: a, display: "block", "data-testid": "modules-list", children: [
1615
+ n ? /* @__PURE__ */ y(S, { as: "div", children: [
1616
+ /* @__PURE__ */ o(I, { size: "small", color: "brand", children: n }),
1617
+ /* @__PURE__ */ y(I, { size: "small", color: "secondary", children: [
1618
+ " ",
1619
+ "/",
1620
+ " "
1621
+ ] }),
1622
+ /* @__PURE__ */ o(I, { size: "small", color: "secondary", children: K })
1623
+ ] }) : null,
1624
+ /* @__PURE__ */ o(Be, { level: d, margin: "x-small 0 medium 0", children: K }),
1625
+ r ? /* @__PURE__ */ o(
1626
+ hs,
1627
+ {
1628
+ counts: r,
1629
+ itemsMode: f,
1630
+ loadedModules: c.length
1631
+ }
1632
+ ) : null,
1633
+ /* @__PURE__ */ y(P, { gap: "small", wrap: "wrap", alignItems: "end", children: [
1634
+ /* @__PURE__ */ o(P.Item, { size: "24rem", shouldShrink: !0, children: /* @__PURE__ */ o(
1635
+ Qt,
1636
+ {
1637
+ renderLabel: E("Search module names"),
1638
+ placeholder: E("Search…"),
1639
+ "aria-describedby": se,
1640
+ value: q,
1641
+ onChange: (x, T) => H(T),
1642
+ renderBeforeInput: /* @__PURE__ */ o(Bt, { inline: !1 }),
1643
+ "data-testid": "modules-search"
1644
+ }
1645
+ ) }),
1646
+ /* @__PURE__ */ o(P.Item, { shouldGrow: !0 }),
1647
+ /* @__PURE__ */ o(P.Item, { children: /* @__PURE__ */ y(
1648
+ fe,
1649
+ {
1650
+ placement: "bottom end",
1651
+ trigger: /* @__PURE__ */ o(Z, { renderIcon: /* @__PURE__ */ o(je, {}), "data-testid": "expand-collapse-trigger", children: E("Expand / collapse") }),
1652
+ children: [
1653
+ /* @__PURE__ */ o(fe.Item, { onSelect: b, "data-testid": "expand-all", children: E("Expand all") }),
1654
+ /* @__PURE__ */ o(fe.Item, { onSelect: p, "data-testid": "collapse-all", children: E("Collapse all") })
1655
+ ]
1656
+ }
1657
+ ) })
1658
+ ] }),
1659
+ /* @__PURE__ */ o(S, { as: "div", margin: "xx-small 0 0 0", children: /* @__PURE__ */ o(I, { size: "x-small", color: "secondary", id: se, children: E("Search matches module names only — items of collapsed modules are not loaded.") }) }),
1660
+ /* @__PURE__ */ o(Vn, { value: O, onChange: Y, counts: oe }),
1661
+ /* @__PURE__ */ y(S, { as: "div", margin: "medium 0 0 0", children: [
1662
+ h ? (
1663
+ // `liveRegion` is what makes this announce; without it InstUI's Alert
1664
+ // sets no role and no aria-live. See the same fix in ModuleCard.
1665
+ /* @__PURE__ */ o(
1666
+ $e,
1667
+ {
1668
+ variant: "error",
1669
+ margin: "0 0 small 0",
1670
+ liveRegion: Ue,
1671
+ liveRegionPoliteness: "assertive",
1672
+ "data-testid": "modules-error",
1673
+ children: E("Modules failed to load: %{message}", { message: h.message })
1674
+ }
1675
+ )
1676
+ ) : null,
1677
+ _ ? /* @__PURE__ */ o(S, { as: "div", padding: "large", textAlign: "center", "data-testid": "modules-loading", children: /* @__PURE__ */ o(We, { renderTitle: E("Loading modules") }) }) : null,
1678
+ !_ && !h && c.length === 0 ? /* @__PURE__ */ o(
1679
+ S,
1680
+ {
1681
+ as: "div",
1682
+ background: "secondary",
1683
+ borderRadius: "large",
1684
+ padding: "large",
1685
+ textAlign: "center",
1686
+ "data-testid": "modules-empty",
1687
+ children: /* @__PURE__ */ o(I, { color: "secondary", children: E("This course has no modules.") })
1688
+ }
1689
+ ) : null,
1690
+ C ? /* @__PURE__ */ y(
1691
+ S,
1692
+ {
1693
+ as: "div",
1694
+ background: "secondary",
1695
+ borderRadius: "large",
1696
+ padding: "large",
1697
+ textAlign: "center",
1698
+ "data-testid": "modules-filtered-empty",
1699
+ children: [
1700
+ /* @__PURE__ */ o(S, { as: "div", margin: "0 0 small 0", children: /* @__PURE__ */ o(I, { color: "secondary", children: E("None of the %{count} loaded modules match this filter.", {
1701
+ count: G.format(c.length)
1702
+ }) }) }),
1703
+ /* @__PURE__ */ o(
1704
+ Z,
1705
+ {
1706
+ size: "small",
1707
+ onClick: () => {
1708
+ Y("all"), H(""), U.current = !0;
1709
+ },
1710
+ children: E("Clear filters")
1711
+ }
1712
+ )
1713
+ ]
1714
+ }
1715
+ ) : null,
1716
+ /* @__PURE__ */ o("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, "data-testid": "modules-stack", children: Q.map((x) => /* @__PURE__ */ o("li", { children: /* @__PURE__ */ o(
1717
+ gs,
1718
+ {
1719
+ module: x,
1720
+ expanded: w.includes(x.id),
1721
+ onToggle: v,
1722
+ itemsState: ne(x.id),
1723
+ today: $,
1724
+ showDiagnostics: i,
1725
+ headingLevel: d === "h1" ? "h2" : "h3"
1726
+ }
1727
+ ) }, x.id)) })
1728
+ ] }),
1729
+ l ? /* @__PURE__ */ o(S, { as: "div", textAlign: "center", margin: "small 0 0 0", children: /* @__PURE__ */ o(
1730
+ Z,
1731
+ {
1732
+ color: "primary",
1733
+ onClick: () => {
1734
+ U.current = !0, m();
1735
+ },
1736
+ interaction: M ? "disabled" : "enabled",
1737
+ "data-testid": "load-more-modules",
1738
+ children: M ? E("Loading more modules…") : E("Load more modules (%{count} loaded)", { count: G.format(c.length) })
1739
+ }
1740
+ ) }) : c.length > 0 ? /* @__PURE__ */ o(S, { as: "div", textAlign: "center", margin: "small 0 0 0", children: /* @__PURE__ */ o(
1741
+ I,
1742
+ {
1743
+ size: "x-small",
1744
+ color: "secondary",
1745
+ "data-testid": "modules-all-loaded",
1746
+ elementRef: (x) => {
1747
+ V.current = x;
1748
+ },
1749
+ tabIndex: -1,
1750
+ children: E("All %{count} modules in this course are loaded.", {
1751
+ count: G.format(c.length)
1752
+ })
1753
+ }
1754
+ ) }) : null
1755
+ ] });
1756
+ }
1757
+ const J = [
1758
+ "assignment",
1759
+ "quiz",
1760
+ "discussion",
1761
+ "page",
1762
+ "file",
1763
+ "external_url",
1764
+ "external_tool",
1765
+ "sub_header"
1766
+ ], Ie = [
1767
+ "not_started",
1768
+ "in_progress",
1769
+ "all_submitted",
1770
+ "all_graded",
1771
+ "missing",
1772
+ "locked"
1773
+ ], Re = [
1774
+ "must_view",
1775
+ "must_submit",
1776
+ "must_contribute",
1777
+ "must_mark_done",
1778
+ "min_score",
1779
+ "min_percentage"
1780
+ ], fs = ["assignment", "quiz", "discussion"], ps = ["assignment", "quiz", "discussion"], Is = Date.UTC(2025, 0, 6), bs = 864e5, yt = "1", Pt = (e) => new Date(Is + e * bs).toISOString(), Ms = (e, t) => {
1781
+ const n = Re[Math.floor(t / 3) % Re.length];
1782
+ return n === "min_score" ? { itemId: e, type: n, minScore: 10 } : n === "min_percentage" ? { itemId: e, type: n, minPercentage: 80 } : { itemId: e, type: n };
1783
+ }, xt = (e, t) => t % 3 === 0 && e !== "sub_header", wt = (e) => Ie[(e - 1) % Ie.length], _s = (e, t) => !ps.includes(e) || e === "discussion" && Math.floor(t / J.length) % 2 === 1 ? null : 5 + t % 4 * 5, Ss = (e, t, n, s) => {
1784
+ const r = n ? "in_progress" : "not_started";
1785
+ switch (e) {
1786
+ case "locked":
1787
+ return "locked";
1788
+ case "not_started":
1789
+ return "not_started";
1790
+ case "all_submitted":
1791
+ return t ? "submitted" : r;
1792
+ case "all_graded":
1793
+ return t ? "graded" : r;
1794
+ case "missing":
1795
+ return n ? t ? "missing" : "not_started" : t ? s % 2 === 0 ? "missing" : "graded" : "not_started";
1796
+ case "in_progress":
1797
+ return t ? s % 3 === 0 ? "submitted" : s % 3 === 1 ? "in_progress" : "not_started" : s % 2 === 0 ? r : "not_started";
1798
+ }
1799
+ }, ys = (e) => e.status === "submitted" || e.status === "graded" || e.status === "in_progress" && e.pointsPossible == null, Ps = (e, t, n, s, r, i) => {
1800
+ const u = String(e), a = String(e * 1e3 + t + 1), d = J[n % J.length], c = d === "sub_header", l = fs.includes(d) && (n % 4 === 0 || s === "missing"), m = xt(d, n) ? Ms(a, n) : null;
1801
+ return {
1802
+ id: a,
1803
+ moduleId: u,
1804
+ title: `${d} ${t + 1}`,
1805
+ type: d,
1806
+ position: t + 1,
1807
+ indent: t % 5 === 4 ? 1 : 0,
1808
+ // 1 in 7 unpublished, so a teacher view always has something to grey out.
1809
+ // Same rule as the module: a student's page contains no unpublished items,
1810
+ // so only a teacher fixture greys any out.
1811
+ published: i === "teacher" ? n % 7 !== 0 : !0,
1812
+ url: c ? null : `/courses/${yt}/modules/items/${a}`,
1813
+ completionRequirement: m,
1814
+ dueAt: l ? Pt(n % 90) : null,
1815
+ status: s,
1816
+ pointsPossible: r,
1817
+ // Canvas Career only, so it rides in `extensions`. Sparse on purpose: a
1818
+ // handful per fixture proves the escape hatch survives a round trip, and
1819
+ // every other item must carry no `extensions` key at all.
1820
+ extensions: Ze(
1821
+ void 0,
1822
+ n % 37 === 0 ? 5 + n % 4 * 10 : null
1823
+ )
1824
+ };
1825
+ }, xs = (e, t, n, s) => {
1826
+ const r = wt(e), i = [];
1827
+ let u = 0;
1828
+ for (let a = 0; a < t; a += 1) {
1829
+ const d = n + a, c = J[d % J.length], l = _s(c, d), m = l !== null, M = m ? u++ : a, _ = Ss(r, m, xt(c, d), M);
1830
+ i.push(Ps(e, a, d, _, l, s));
1831
+ }
1832
+ return i;
1833
+ }, ws = (e, t, n) => {
1834
+ const s = wt(e);
1835
+ return s === "locked" || s === "not_started" ? s : t.length === 0 ? "not_started" : !t.some((r) => r.pointsPossible != null) || s === "missing" && n === 0 ? "in_progress" : s;
1836
+ }, Es = (e) => {
1837
+ const t = e.map((n) => n.dueAt).filter((n) => typeof n == "string").sort();
1838
+ return t.length > 0 ? t[t.length - 1] : null;
1839
+ }, Cs = (e, t, n) => {
1840
+ const s = t.filter((d) => d.completionRequirement), r = n ?? s.length, i = Math.min(s.filter(ys).length, r), u = t.filter((d) => d.status === "missing").length;
1841
+ return {
1842
+ status: ws(e, t, u),
1843
+ requirementsMet: i,
1844
+ requirementCount: r,
1845
+ // Derived from the clamped pair, so the bar and the fraction always agree.
1846
+ percentComplete: r > 0 ? Math.round(i / r * 100) : 0,
1847
+ latestDueAt: Es(t),
1848
+ missingCount: u
1849
+ };
1850
+ }, vs = (e, t, n) => {
1851
+ const s = e % 2 === 0 ? null : 1;
1852
+ return {
1853
+ id: String(e),
1854
+ name: `Module ${e}`,
1855
+ position: e,
1856
+ // A student sees only published modules; publish state varies for a teacher.
1857
+ published: n === "teacher" ? e % 4 !== 0 : !0,
1858
+ itemCount: t.length,
1859
+ unlockAt: e % 5 === 0 ? Pt(e) : null,
1860
+ prerequisiteModuleIds: e > 1 && e % 3 === 0 ? [String(e - 1)] : [],
1861
+ requirementCount: s,
1862
+ requireSequentialProgress: e % 6 === 0,
1863
+ completionRequirements: t.flatMap(
1864
+ (r) => r.completionRequirement ? [r.completionRequirement] : []
1865
+ ),
1866
+ progress: Cs(e, t, s)
1867
+ };
1868
+ };
1869
+ function Ts(e) {
1870
+ return {
1871
+ courseId: e.courseId,
1872
+ modules: e.modules.map(({ progress: t, ...n }) => n),
1873
+ itemsByModuleId: Object.fromEntries(
1874
+ Object.entries(e.itemsByModuleId).map(([t, n]) => [
1875
+ t,
1876
+ n.map(({ status: s, ...r }) => r)
1877
+ ])
1878
+ )
1879
+ };
1880
+ }
1881
+ function he(e, t = {}) {
1882
+ const n = t.view ?? "student", s = t.courseId ?? yt, r = [], i = {};
1883
+ let u = 0;
1884
+ e.forEach((d, c) => {
1885
+ const l = c + 1, m = xs(l, d, u, n);
1886
+ u += d, r.push(vs(l, m, n)), i[String(l)] = m;
1887
+ });
1888
+ const a = { courseId: s, modules: r, itemsByModuleId: i };
1889
+ return n === "teacher" ? Ts(a) : a;
1890
+ }
1891
+ const Ns = Ie.length, As = 8, Ls = 120, Ds = 25, Rs = 250, qe = 8;
1892
+ function ur() {
1893
+ return he(Array.from({ length: Ns }, () => As));
1894
+ }
1895
+ function dr() {
1896
+ return he(Array.from({ length: Ls }, () => Ds));
1897
+ }
1898
+ function cr() {
1899
+ return he([
1900
+ qe,
1901
+ Rs,
1902
+ qe
1903
+ ]);
1904
+ }
1905
+ function mr() {
1906
+ return he([]);
1907
+ }
1908
+ export {
1909
+ Ke as CompletionRequirementSchema,
1910
+ Ht as CompletionRequirementTypeSchema,
1911
+ dt as DEFAULT_INLINE_THRESHOLD,
1912
+ Cn as DEFAULT_ITEM_CONCURRENCY,
1913
+ ut as DEFAULT_ITEM_PAGE_SIZE,
1914
+ k as DEFAULT_LOCALE,
1915
+ _e as DEFAULT_PAGE_SIZE,
1916
+ ct as DEFAULT_RETAIN_COLLAPSED_LIMIT,
1917
+ ce as DEFAULT_STALE_TIME_MS,
1918
+ Xe as EXT_CAREER_ESTIMATED_DURATION_MINUTES,
1919
+ yt as FIXTURE_COURSE_ID,
1920
+ Vn as FilterChips,
1921
+ wn as GET_MODULES,
1922
+ En as GET_MODULE_ITEMS,
1923
+ pe as INLINE_ITEM_LIMIT,
1924
+ Me as INLINE_QUERY_NODE_BUDGET,
1925
+ tn as InMemoryCourseMismatchError,
1926
+ Wn as MODULES_FILTER_IDS,
1927
+ gs as ModuleCard,
1928
+ ls as ModuleItemRow,
1929
+ He as ModuleItemSchema,
1930
+ ns as ModuleItemStatusPill,
1931
+ Wt as ModuleItemStatusSchema,
1932
+ jt as ModuleItemTypeSchema,
1933
+ Jt as ModuleItemsPageSchema,
1934
+ Vt as ModuleProgressSchema,
1935
+ Xt as ModuleSchema,
1936
+ ts as ModuleStatusPill,
1937
+ Kt as ModuleStatusSchema,
1938
+ lr as ModulesI18nProvider,
1939
+ ar as ModulesList,
1940
+ Zt as ModulesPageSchema,
1941
+ Ve as PageInfoSchema,
1942
+ Je as PartialCanvasResponseError,
1943
+ he as buildFixture,
1944
+ mr as courseEmpty,
1945
+ cr as courseFatModule,
1946
+ dr as courseLarge,
1947
+ ur as courseSmall,
1948
+ tr as createCanvasGraphqlAdapter,
1949
+ er as createInMemoryAdapter,
1950
+ pt as defaultTranslate,
1951
+ Kn as filterLabel,
1952
+ en as getEstimatedDurationMinutes,
1953
+ ir as invalidateCourse,
1954
+ rr as invalidateModuleItems,
1955
+ or as invalidateModuleList,
1956
+ es as moduleItemStatusLabel,
1957
+ mt as moduleItemsPrefix,
1958
+ Jn as moduleStatusLabel,
1959
+ te as modulesKeys,
1960
+ vn as modulesListPrefix,
1961
+ nr as useModuleItems,
1962
+ Tn as useModules,
1963
+ R as useModulesI18n,
1964
+ sr as useModulesPageData,
1965
+ Ze as withEstimatedDurationMinutes
1966
+ };