@things-factory/learning 10.1.260 → 10.1.262
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/components/learning-material-form.d.ts +41 -0
- package/dist-client/components/learning-material-form.js +202 -0
- package/dist-client/components/learning-material-form.js.map +1 -0
- package/dist-client/components/learning-unit-form.d.ts +3 -0
- package/dist-client/components/learning-unit-form.js +10 -2
- package/dist-client/components/learning-unit-form.js.map +1 -1
- package/dist-client/pages/learning-course-editor.d.ts +1 -0
- package/dist-client/pages/learning-course-editor.js +19 -6
- package/dist-client/pages/learning-course-editor.js.map +1 -1
- package/dist-client/pages/learning-course-list.d.ts +1 -5
- package/dist-client/pages/learning-course-list.js +54 -16
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +31 -16
- package/dist-client/pages/learning-enrollment-status.js +104 -117
- package/dist-client/pages/learning-enrollment-status.js.map +1 -1
- package/dist-client/pages/learning-view.d.ts +11 -0
- package/dist-client/pages/learning-view.js +94 -19
- package/dist-client/pages/learning-view.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/learning-api.d.ts +48 -3
- package/dist-client/utils/learning-api.js +51 -2
- package/dist-client/utils/learning-api.js.map +1 -1
- package/dist-client/utils/material-source.d.ts +12 -0
- package/dist-client/utils/material-source.js +77 -0
- package/dist-client/utils/material-source.js.map +1 -0
- package/dist-server/ai-reads.js +1 -1
- package/dist-server/ai-reads.js.map +1 -1
- package/dist-server/service/course/course-mutation.d.ts +1 -1
- package/dist-server/service/course/course-mutation.js +7 -6
- package/dist-server/service/course/course-mutation.js.map +1 -1
- package/dist-server/service/course/course-query.d.ts +2 -0
- package/dist-server/service/course/course-query.js +27 -3
- package/dist-server/service/course/course-query.js.map +1 -1
- package/dist-server/service/course/course-rules.d.ts +3 -1
- package/dist-server/service/course/course-rules.js +5 -1
- package/dist-server/service/course/course-rules.js.map +1 -1
- package/dist-server/service/course/course-service.d.ts +5 -4
- package/dist-server/service/course/course-service.js +10 -14
- package/dist-server/service/course/course-service.js.map +1 -1
- package/dist-server/service/course/course-type.d.ts +1 -0
- package/dist-server/service/course/course-type.js +4 -0
- package/dist-server/service/course/course-type.js.map +1 -1
- package/dist-server/service/course/course-unit.d.ts +3 -2
- package/dist-server/service/course/course-unit.js +7 -2
- package/dist-server/service/course/course-unit.js.map +1 -1
- package/dist-server/service/course/index.d.ts +2 -1
- package/dist-server/service/course/index.js +2 -1
- package/dist-server/service/course/index.js.map +1 -1
- package/dist-server/service/course/material-audio.d.ts +1 -0
- package/dist-server/service/course/material-audio.js +43 -0
- package/dist-server/service/course/material-audio.js.map +1 -0
- package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
- package/dist-server/service/course/material-draft-mutation.js +55 -0
- package/dist-server/service/course/material-draft-mutation.js.map +1 -0
- package/dist-server/service/course/material-draft-rules.d.ts +38 -0
- package/dist-server/service/course/material-draft-rules.js +83 -0
- package/dist-server/service/course/material-draft-rules.js.map +1 -0
- package/dist-server/service/course/material-draft-service.d.ts +15 -0
- package/dist-server/service/course/material-draft-service.js +162 -0
- package/dist-server/service/course/material-draft-service.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-mutation.d.ts +1 -1
- package/dist-server/service/enrollment/enrollment-mutation.js +6 -6
- package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-query.d.ts +8 -4
- package/dist-server/service/enrollment/enrollment-query.js +47 -36
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.d.ts +4 -3
- package/dist-server/service/enrollment/enrollment-service.js +41 -18
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +42 -18
- package/dist-server/service/enrollment/enrollment-status.js +143 -42
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +2 -8
- package/dist-server/service/enrollment/enrollment-type.js +20 -42
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
- package/dist-server/service/enrollment/enrollment.d.ts +1 -0
- package/dist-server/service/enrollment/enrollment.js +5 -0
- package/dist-server/service/enrollment/enrollment.js.map +1 -1
- package/dist-server/service/enrollment/index.d.ts +3 -3
- package/dist-server/service/enrollment/index.js +3 -3
- package/dist-server/service/enrollment/index.js.map +1 -1
- package/dist-server/service/enrollment/learning-ai.d.ts +9 -2
- package/dist-server/service/enrollment/learning-ai.js +25 -28
- package/dist-server/service/enrollment/learning-ai.js.map +1 -1
- package/dist-server/service/enrollment/learning-documents.js +3 -4
- package/dist-server/service/enrollment/learning-documents.js.map +1 -1
- package/dist-server/service/enrollment/progress-rules.d.ts +40 -11
- package/dist-server/service/enrollment/progress-rules.js +54 -13
- package/dist-server/service/enrollment/progress-rules.js.map +1 -1
- package/dist-server/service/enrollment/unit-progress.d.ts +4 -0
- package/dist-server/service/enrollment/unit-progress.js +20 -0
- package/dist-server/service/enrollment/unit-progress.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +2 -2
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/translations/en.json +42 -9
- package/translations/es.json +42 -9
- package/translations/ja.json +42 -9
- package/translations/ko.json +42 -9
- package/translations/ms.json +42 -9
- package/translations/zh.json +42 -9
|
@@ -15,8 +15,14 @@ import { i18next, localize } from '@operato/i18n';
|
|
|
15
15
|
import { PageView } from '@operato/shell';
|
|
16
16
|
import { DocumentPageStyles } from '@operato/styles';
|
|
17
17
|
import { issueDocument } from '@things-factory/pdf/dist-client';
|
|
18
|
-
import { LEARNING_FIELDS, unitsDoneOf } from '../utils/learning-api.js';
|
|
18
|
+
import { LEARNING_FIELDS, LessonStarts, pageLookedOf, PROGRESS_FIELDS, resumeUnitId, unitsDoneOf } from '../utils/learning-api.js';
|
|
19
19
|
import { dayOf, minutesOf, missingWord, stateWord, t, unitKindWord } from '../utils/learning-words.js';
|
|
20
|
+
const TICK_MUTATION = `mutation ($enrollmentId: String!, $unitId: String!, $seconds: Int!, $position: Int!) {
|
|
21
|
+
recordLearningTick(enrollmentId: $enrollmentId, unitId: $unitId, seconds: $seconds, position: $position) {
|
|
22
|
+
enrollment { id status }
|
|
23
|
+
progress { ${PROGRESS_FIELDS} }
|
|
24
|
+
}
|
|
25
|
+
}`;
|
|
20
26
|
/**
|
|
21
27
|
* 과정 화면 (ADR-0194 decisions 3-9) - one enrolment: the units in order with the one open played by `ox-lesson`, whose
|
|
22
28
|
* ticks go to the server as they come (the server counts the time); the quiz drawn by the server and scored there; the
|
|
@@ -29,16 +35,13 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
29
35
|
this.unitId = '';
|
|
30
36
|
this.agreed = false;
|
|
31
37
|
this.tickError = '';
|
|
38
|
+
/* While one lesson leaves for another, nothing is shown - the leaving one's last tick is answered first. */
|
|
39
|
+
this.switching = false;
|
|
32
40
|
this.read = new QueryController(this, gql `query ($id: String!) { myEnrollment(id: $id) { ${LEARNING_FIELDS} } }`, {
|
|
33
41
|
variables: () => (this.enrollmentId ? { id: this.enrollmentId } : undefined),
|
|
34
42
|
keepPrevious: true
|
|
35
43
|
});
|
|
36
|
-
this.tickSave = new MutationController(this, gql
|
|
37
|
-
recordLearningTick(enrollmentId: $enrollmentId, unitId: $unitId, seconds: $seconds, position: $position) {
|
|
38
|
-
enrollment { id status }
|
|
39
|
-
progress { learnedSeconds watchedSeconds positionSeconds doneAt }
|
|
40
|
-
}
|
|
41
|
-
}`, { showsError: true });
|
|
44
|
+
this.tickSave = new MutationController(this, gql(TICK_MUTATION), { showsError: true });
|
|
42
45
|
this.quizStart = new MutationController(this, gql `mutation ($enrollmentId: String!) { startQuizAttempt(enrollmentId: $enrollmentId) { attemptId questions { id kind text choices { id text } } } }`, {
|
|
43
46
|
showsError: true
|
|
44
47
|
});
|
|
@@ -47,8 +50,13 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
47
50
|
showsError: true
|
|
48
51
|
});
|
|
49
52
|
this.acknowledgement = new MutationController(this, gql `mutation ($id: String!) { acknowledgeEnrollment(id: $id) { id status } }`, { refetch: () => [this.read], showsError: true });
|
|
50
|
-
/*
|
|
53
|
+
/*
|
|
54
|
+
* Ticks go one after another - the leaving one too - so the server takes them in the order they were looked at: a
|
|
55
|
+
* stop right after a start is not dropped while the start is out, and a page left on is never overtaken by an older one.
|
|
56
|
+
*/
|
|
51
57
|
this.ticks = Promise.resolve();
|
|
58
|
+
/** Where each lesson starts - the server's last answer for its unit, else what the page read (LessonStarts). */
|
|
59
|
+
this.starts = new LessonStarts();
|
|
52
60
|
}
|
|
53
61
|
static { this.styles = [
|
|
54
62
|
DocumentPageStyles,
|
|
@@ -154,6 +162,12 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
154
162
|
margin: 0;
|
|
155
163
|
line-height: 1.6;
|
|
156
164
|
}
|
|
165
|
+
.saved {
|
|
166
|
+
margin: 0;
|
|
167
|
+
min-height: 1.4em;
|
|
168
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
169
|
+
font-size: var(--ui-t-small, 12px);
|
|
170
|
+
}
|
|
157
171
|
.result {
|
|
158
172
|
margin: 0;
|
|
159
173
|
font-weight: 600;
|
|
@@ -181,9 +195,8 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
181
195
|
updated() {
|
|
182
196
|
const one = this.enrollment;
|
|
183
197
|
if (one && !this.unitId && one.units?.length) {
|
|
184
|
-
/* Where the learner
|
|
185
|
-
|
|
186
|
-
this.unitId = next.unit.id;
|
|
198
|
+
/* Where the learner was - the unit learned last (resumeUnitId). */
|
|
199
|
+
this.unitId = resumeUnitId(one);
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
202
|
render() {
|
|
@@ -242,7 +255,9 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
242
255
|
}
|
|
243
256
|
renderLesson(one, row) {
|
|
244
257
|
const unit = row.unit;
|
|
245
|
-
|
|
258
|
+
if (this.switching)
|
|
259
|
+
return html `<h3>${unit.name}</h3>`;
|
|
260
|
+
const progress = unit.kind === 'LINK' ? {} : this.starts.startOf(one.id, unit.id, row.progress);
|
|
246
261
|
if (unit.kind === 'LINK')
|
|
247
262
|
return html `<h3>${unit.name}</h3>
|
|
248
263
|
<p class="page-section-note">${t('text.learning-link-not-counted')}</p>
|
|
@@ -258,14 +273,19 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
258
273
|
.src=${unit.file?.fullpath}
|
|
259
274
|
.body=${unit.body ?? ''}
|
|
260
275
|
.label=${unit.name}
|
|
261
|
-
.watched=${progress.watchedSeconds ?? 0}
|
|
262
|
-
.position=${progress.positionSeconds ?? 0}
|
|
276
|
+
.watched=${unit.kind === 'PDF' ? (progress.pagesRead ?? 0) : (progress.watchedSeconds ?? 0)}
|
|
277
|
+
.position=${unit.kind === 'PDF' ? (progress.pageAt ?? 0) : (progress.positionSeconds ?? 0)}
|
|
278
|
+
.pages=${unit.pageCount ?? 0}
|
|
279
|
+
.pageSeconds=${unit.secondsPerPage ?? undefined}
|
|
280
|
+
.pageLooked=${unit.kind === 'PDF' ? pageLookedOf(progress) : 0}
|
|
263
281
|
.requiredSeconds=${unit.minSeconds ?? 60}
|
|
264
282
|
.learnedSeconds=${progress.learnedSeconds ?? 0}
|
|
265
|
-
.done=${!!progress.doneAt || one.status === 'COMPLETED'}
|
|
283
|
+
.done=${!!row.progress?.doneAt || !!progress.doneAt || one.status === 'COMPLETED'}
|
|
266
284
|
@lesson-tick=${(e) => this.tick(one.id, unit.id, e.detail)}
|
|
267
285
|
></ox-lesson>`)}
|
|
268
|
-
${this.tickError
|
|
286
|
+
${this.tickError
|
|
287
|
+
? html `<div class="error" role="alert">${this.tickError}</div>`
|
|
288
|
+
: html `<p class="saved" role="status">${this.savedAt ? t('text.learning-saved-at', { time: this.savedAt.toLocaleTimeString(i18next.language) }) : ''}</p>`}`;
|
|
269
289
|
}
|
|
270
290
|
renderQuiz(one) {
|
|
271
291
|
const completed = one.status === 'COMPLETED';
|
|
@@ -301,26 +321,73 @@ let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
|
301
321
|
${this.acknowledgement.error ? html `<div class="error" role="alert">${this.errorText(this.acknowledgement.error)}</div>` : nothing}`}
|
|
302
322
|
</section>`;
|
|
303
323
|
}
|
|
304
|
-
|
|
305
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Opens a unit. The lesson shown leaves first and its last tick is answered, so the unit opened - the same one coming
|
|
326
|
+
* back too - starts from what the server counted, not from what the page read when it opened (2026-11-08).
|
|
327
|
+
*/
|
|
328
|
+
async open(unitId) {
|
|
329
|
+
if (unitId === this.unitId || this.switching)
|
|
330
|
+
return;
|
|
306
331
|
this.tickError = '';
|
|
332
|
+
this.switching = true;
|
|
333
|
+
await this.updateComplete;
|
|
334
|
+
await this.ticks;
|
|
335
|
+
this.starts.reopen();
|
|
336
|
+
this.unitId = unitId;
|
|
337
|
+
this.switching = false;
|
|
338
|
+
}
|
|
339
|
+
/** Keeps the server's answer for a unit - the next time it opens, it starts there. */
|
|
340
|
+
keep(enrollmentId, unitId, answer) {
|
|
341
|
+
this.starts.keep(enrollmentId, unitId, answer?.progress);
|
|
342
|
+
this.savedAt = new Date();
|
|
307
343
|
}
|
|
308
344
|
tick(enrollmentId, unitId, detail) {
|
|
345
|
+
const variables = { enrollmentId, unitId, seconds: Math.round(detail.seconds), position: Math.floor(detail.position) };
|
|
346
|
+
/*
|
|
347
|
+
* Leaving - the tab hidden, closed or reloaded - the last piece goes out on a request the browser finishes after the
|
|
348
|
+
* page is gone (keepalive). Through the usual queue it was dropped with the page, and up to a tick was lost.
|
|
349
|
+
*/
|
|
350
|
+
if (document.visibilityState === 'hidden') {
|
|
351
|
+
this.ticks = this.ticks.then(() => this.tickLeaving(variables));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
309
354
|
this.ticks = this.ticks.then(async () => {
|
|
310
355
|
const before = this.enrollment;
|
|
311
|
-
const answer = await this.tickSave.run(
|
|
356
|
+
const answer = await this.tickSave.run(variables);
|
|
312
357
|
if (!answer) {
|
|
313
358
|
this.tickError = this.errorText(this.tickSave.error);
|
|
314
359
|
await this.read.refetch();
|
|
315
360
|
return;
|
|
316
361
|
}
|
|
317
362
|
this.tickError = '';
|
|
363
|
+
this.keep(enrollmentId, unitId, answer.recordLearningTick);
|
|
318
364
|
const row = before?.units?.find((one) => one.unit.id === unitId);
|
|
319
365
|
const newlyDone = !!answer.recordLearningTick.progress.doneAt && !row?.progress?.doneAt;
|
|
320
366
|
if (newlyDone || answer.recordLearningTick.enrollment.status !== before?.status)
|
|
321
367
|
await this.read.refetch();
|
|
322
368
|
});
|
|
323
369
|
}
|
|
370
|
+
/*
|
|
371
|
+
* The leaving tick goes after the ticks before it, on a request the browser finishes after the page is gone
|
|
372
|
+
* (keepalive). Only when the page is closed while an earlier tick is still out is it not sent - the order is kept.
|
|
373
|
+
*/
|
|
374
|
+
async tickLeaving(variables) {
|
|
375
|
+
await fetch('/graphql', {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
keepalive: true,
|
|
378
|
+
credentials: 'include',
|
|
379
|
+
headers: { 'Content-Type': 'application/json', 'Apollo-Require-Preflight': 'true' },
|
|
380
|
+
body: JSON.stringify({ query: TICK_MUTATION, variables })
|
|
381
|
+
})
|
|
382
|
+
.then(response => response.json())
|
|
383
|
+
.then(answer => {
|
|
384
|
+
if (answer?.data?.recordLearningTick)
|
|
385
|
+
this.keep(variables.enrollmentId, variables.unitId, answer.data.recordLearningTick);
|
|
386
|
+
else
|
|
387
|
+
this.tickError = answer?.errors?.[0]?.message ?? '';
|
|
388
|
+
})
|
|
389
|
+
.catch(error => (this.tickError = error?.message ?? String(error)));
|
|
390
|
+
}
|
|
324
391
|
async startQuiz(enrollmentId) {
|
|
325
392
|
this.result = undefined;
|
|
326
393
|
const answer = await this.quizStart.run({ enrollmentId });
|
|
@@ -364,6 +431,14 @@ __decorate([
|
|
|
364
431
|
state(),
|
|
365
432
|
__metadata("design:type", Object)
|
|
366
433
|
], LearningView.prototype, "tickError", void 0);
|
|
434
|
+
__decorate([
|
|
435
|
+
state(),
|
|
436
|
+
__metadata("design:type", Object)
|
|
437
|
+
], LearningView.prototype, "switching", void 0);
|
|
438
|
+
__decorate([
|
|
439
|
+
state(),
|
|
440
|
+
__metadata("design:type", Date)
|
|
441
|
+
], LearningView.prototype, "savedAt", void 0);
|
|
367
442
|
LearningView = __decorate([
|
|
368
443
|
customElement('learning-view')
|
|
369
444
|
], LearningView);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learning-view.js","sourceRoot":"","sources":["../../client/pages/learning-view.ts"],"names":[],"mappings":";AAAA,OAAO,uCAAuC,CAAA;AAC9C,OAAO,qCAAqC,CAAA;AAC5C,OAAO,4BAA4B,CAAA;AACnC,OAAO,2CAA2C,CAAA;AAClD,OAAO,gCAAgC,CAAA;AACvC,OAAO,8BAA8B,CAAA;AACrC,OAAO,sCAAsC,CAAA;AAE7C,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAE/D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACvE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAEtG;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAAtD;;QAgHY,iBAAY,GAAG,EAAE,CAAA;QACjB,WAAM,GAAG,EAAE,CAAA;QAGX,WAAM,GAAG,KAAK,CAAA;QACd,cAAS,GAAG,EAAE,CAAA;QAEvB,SAAI,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,GAAG,CAAA,kDAAkD,eAAe,MAAM,EAAE;YACnH,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YAC5E,YAAY,EAAE,IAAI;SACnB,CAAC,CAAA;QAEM,aAAQ,GAAG,IAAI,kBAAkB,CACvC,IAAI,EACJ,GAAG,CAAA;;;;;MAKD,EACF,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAA;QACO,cAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,kJAAkJ,EAAE;YACtM,UAAU,EAAE,IAAI;SACjB,CAAC,CAAA;QACM,eAAU,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,0KAA0K,EAAE;YAC/N,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC1B,UAAU,EAAE,IAAI;SACjB,CAAC,CAAA;QACM,oBAAe,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,0EAA0E,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;QAEvL,oGAAoG;QAC5F,UAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAA;IAkMrD,CAAC;aAjVQ,WAAM,GAAG;QACd,kBAAkB;QAClB,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0GF;KACF,AA7GY,CA6GZ;IAoCD,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,IAAI,IAAI,CAAA;IAC7C,CAAC;IAED,IAAI,OAAO;QACT,qIAAqI;QACrI,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,yBAAyB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IAClL,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAc;QAC5C,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;YAAE,OAAM;QAC/D,MAAM,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,EAAE,CAAA;QAChC,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAA;YACtB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;YAChB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;YACtB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACzB,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAA;QAC3B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;YAC7C,6FAA6F;YAC7F,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YAC9F,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;QAC5B,CAAC;IACH,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAA;QAC3B,OAAO,IAAI,CAAA,2BAA2B,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,GAAG,gBAAgB,IAAI,CAAC,IAAI,CAAC,UAAU,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QAC5K,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO;qBAC7B,CAAA;IACnB,CAAC;IAEO,gBAAgB,CAAC,GAAQ;QAC/B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,CAAA;QACzE,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,WAAW,CAAA;QAC5C,OAAO,IAAI,CAAA;;cAED,GAAG,CAAC,MAAM,CAAC,IAAI;;kBAEX,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;YAC1B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,SAAS,CAAC,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;YAC/G,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,SAAS,CAAC,CAAC,sBAAsB,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;kBACnF,CAAC,CAAC,+BAA+B,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;;uCAEvG,IAAI,SAAS,KAAK,UAAU,SAAS,UAAU,CAAC,CAAC,2BAA2B,CAAC;UAC1G,CAAC,SAAS,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,gCAAgC,CAAC,CAAC,4BAA4B,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;UACnK,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA,gCAAgC,GAAG,CAAC,MAAM,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,OAAO;;;;0BAInF,CAAC,CAAC,sBAAsB,CAAC;;cAErC,GAAG,CAAC,KAAK,CAAC,GAAG,CACb,CAAC,GAAQ,EAAE,EAAE,CAAC,IAAI,CAAA;;iCAEC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;+BAChD,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;2BACzI,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;;mCAEpB,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,uBAAuB,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,MAAM;0DAC5G,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI;0DAC9B,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;;oBAEjE,CACP;;;8BAGiB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO;;;QAGzE,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO;QACjD,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO;QACxE,SAAS;YACT,CAAC,CAAC,IAAI,CAAA;sDACwC,CAAC,CAAC,4BAA4B,CAAC;2CAC1C,CAAC,CAAC,gCAAgC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;qEAC3E,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,6BAA6B,CAAC;qBACxJ;YACb,CAAC,CAAC,OAAO;WACN,CAAA;IACT,CAAC;IAEO,YAAY,CAAC,GAAQ,EAAE,GAAQ;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;QACrB,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAA;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YACtB,OAAO,IAAI,CAAA,OAAO,IAAI,CAAC,IAAI;uCACM,CAAC,CAAC,gCAAgC,CAAC;kBACxD,IAAI,CAAC,GAAG,8CAA8C,IAAI,CAAC,GAAG,MAAM,CAAA;QAClF;;;;WAIG;QACH,OAAO,IAAI,CAAA,OAAO,IAAI,CAAC,IAAI;QACvB,KAAK,CACL,GAAG,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,EAAE,EACtB,IAAI,CAAA;gBACI,IAAI,CAAC,IAAI;eACV,IAAI,CAAC,IAAI,EAAE,QAAQ;gBAClB,IAAI,CAAC,IAAI,IAAI,EAAE;iBACd,IAAI,CAAC,IAAI;mBACP,QAAQ,CAAC,cAAc,IAAI,CAAC;oBAC3B,QAAQ,CAAC,eAAe,IAAI,CAAC;2BACtB,IAAI,CAAC,UAAU,IAAI,EAAE;0BACtB,QAAQ,CAAC,cAAc,IAAI,CAAC;gBACtC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW;uBACxC,CAAC,CAA0B,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;oBACvE,CACb;QACC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAA;IAChG,CAAC;IAEO,UAAU,CAAC,GAAQ;QACzB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,WAAW,CAAA;QAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,SAAS,IAAI,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAA;QACxG,OAAO,IAAI,CAAA;gDACiC,CAAC,CAAC,qBAAqB,CAAC;;UAE9D,CAAC,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;UAClF,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;UAC7F,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;;QAE9E,IAAI,CAAC,MAAM;YACX,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM;YAC/L,CAAC,CAAC,OAAO;QACT,IAAI,CAAC,KAAK;YACV,CAAC,CAAC,IAAI,CAAA,uBAAuB,IAAI,CAAC,KAAK,CAAC,SAAS,UAAU,IAAI,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAwC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa;YAC3L,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,YAAY,KAAK,CAAC;gBAC/C,CAAC,CAAC,IAAI,CAAA,6DAA6D,IAAI,CAAC,SAAS,CAAC,OAAO,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,4BAA4B,CAAC,qBAAqB;gBACxL,CAAC,CAAC,OAAO;QACX,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;eAC/J,CAAA;IACb,CAAC;IAEO,qBAAqB,CAAC,GAAQ;QACpC,OAAO,IAAI,CAAA;gDACiC,CAAC,CAAC,gCAAgC,CAAC;mCAChD,GAAG,CAAC,OAAO,CAAC,eAAe;QACtD,GAAG,CAAC,cAAc;YAClB,CAAC,CAAC,IAAI,CAAA,gCAAgC,CAAC,CAAC,+BAA+B,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM;YAClH,CAAC,CAAC,IAAI,CAAA,wDAAwD,IAAI,CAAC,MAAM,YAAY,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC;wEAC3G,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;iBAC5J,CAAC,CAAC,6BAA6B,CAAC;;cAEnC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE;eACjI,CAAA;IACb,CAAC;IAEO,IAAI,CAAC,MAAc;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;IACrB,CAAC;IAEO,IAAI,CAAC,YAAoB,EAAE,MAAc,EAAE,MAAkB;QACnE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAA;YAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;YAC5I,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACpD,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;gBACzB,OAAM;YACR,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;YACnB,MAAM,GAAG,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAA;YACrE,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAA;YACvF,IAAI,SAAS,IAAI,MAAM,CAAC,kBAAkB,CAAC,UAAU,CAAC,MAAM,KAAK,MAAM,EAAE,MAAM;gBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;QAC5G,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,YAAoB;QAC1C,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;QACzD,IAAI,MAAM;YAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAA;IAClD,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,OAAoB;QACvC,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAM;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAA;QACtF,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAA;QACtC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;IACxB,CAAC;IAEO,SAAS,CAAC,KAAU;QAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,IAAI,EAAE,CAAA;IACnE,CAAC;;AAjOgB;IAAhB,KAAK,EAAE;;kDAA0B;AACjB;IAAhB,KAAK,EAAE;;4CAAoB;AACX;IAAhB,KAAK,EAAE;;2CAAiE;AACxD;IAAhB,KAAK,EAAE;;4CAAkF;AACzE;IAAhB,KAAK,EAAE;;4CAAuB;AACd;IAAhB,KAAK,EAAE;;+CAAuB;AArHpB,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAkVxB","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/icon/icon.js'\nimport '@operato/learning/ox-learning-progress.js'\nimport '@operato/learning/ox-lesson.js'\nimport '@operato/learning/ox-quiz.js'\nimport '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { keyed } from 'lit/directives/keyed.js'\n\nimport { MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport type { LessonTick, QuizAnswers, QuizQuestion } from '@operato/learning'\nimport { PageView } from '@operato/shell'\nimport { DocumentPageStyles } from '@operato/styles'\nimport { issueDocument } from '@things-factory/pdf/dist-client'\n\nimport { LEARNING_FIELDS, unitsDoneOf } from '../utils/learning-api.js'\nimport { dayOf, minutesOf, missingWord, stateWord, t, unitKindWord } from '../utils/learning-words.js'\n\n/**\n * 과정 화면 (ADR-0194 decisions 3-9) - one enrolment: the units in order with the one open played by `ox-lesson`, whose\n * ticks go to the server as they come (the server counts the time); the quiz drawn by the server and scored there; the\n * acknowledgement; and the certificate once completed. On a phone the unit list sits above the lesson.\n */\n@customElement('learning-view')\nexport class LearningView extends localize(i18next)(PageView) {\n static styles = [\n DocumentPageStyles,\n css`\n .head {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small, 4px);\n }\n .head h2 {\n margin: 0;\n font-size: var(--ui-t-title-large, 22px);\n overflow-wrap: anywhere;\n }\n .meta {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 12px);\n }\n .study {\n display: grid;\n grid-template-columns: minmax(200px, 280px) minmax(0, 1fr);\n gap: var(--spacing-large, 12px);\n align-items: start;\n }\n @media (max-width: 760px) {\n .study {\n grid-template-columns: minmax(0, 1fr);\n }\n }\n ol.units {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n ol.units button {\n display: flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n width: 100%;\n min-height: 40px;\n padding: 6px 8px;\n border: 0;\n border-radius: var(--ui-r-sm, 6px);\n background: transparent;\n color: var(--ui-ink);\n font: inherit;\n text-align: left;\n cursor: pointer;\n }\n ol.units button[aria-current='true'] {\n background: var(--ui-line-soft, var(--md-sys-color-surface-container));\n font-weight: 600;\n }\n ol.units button:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -2px;\n }\n ol.units md-icon {\n --md-icon-size: 18px;\n color: var(--ui-muted);\n flex: none;\n }\n ol.units md-icon[done] {\n color: var(--ui-ok, var(--md-sys-color-primary));\n }\n ol.units .name {\n flex: 1;\n min-width: 0;\n overflow-wrap: anywhere;\n }\n ol.units .kind {\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 12px);\n }\n .lesson {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .lesson h3 {\n margin: 0;\n font-size: var(--ui-t-title, 15px);\n }\n .error {\n color: var(--ui-crit, var(--md-sys-color-error));\n font-size: var(--ui-t-small, 12px);\n }\n .check {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-small, 4px);\n line-height: 1.6;\n }\n .acknowledgement {\n white-space: pre-wrap;\n margin: 0;\n line-height: 1.6;\n }\n .result {\n margin: 0;\n font-weight: 600;\n }\n `\n ]\n\n @state() private enrollmentId = ''\n @state() private unitId = ''\n @state() private drawn?: { attemptId: string; questions: QuizQuestion[] }\n @state() private result?: { score: number; passed: boolean; attemptsLeft?: number | null }\n @state() private agreed = false\n @state() private tickError = ''\n\n private read = new QueryController(this, gql`query ($id: String!) { myEnrollment(id: $id) { ${LEARNING_FIELDS} } }`, {\n variables: () => (this.enrollmentId ? { id: this.enrollmentId } : undefined),\n keepPrevious: true\n })\n\n private tickSave = new MutationController(\n this,\n gql`mutation ($enrollmentId: String!, $unitId: String!, $seconds: Int!, $position: Int!) {\n recordLearningTick(enrollmentId: $enrollmentId, unitId: $unitId, seconds: $seconds, position: $position) {\n enrollment { id status }\n progress { learnedSeconds watchedSeconds positionSeconds doneAt }\n }\n }`,\n { showsError: true }\n )\n private quizStart = new MutationController(this, gql`mutation ($enrollmentId: String!) { startQuizAttempt(enrollmentId: $enrollmentId) { attemptId questions { id kind text choices { id text } } } }`, {\n showsError: true\n })\n private quizSubmit = new MutationController(this, gql`mutation ($attemptId: String!, $answers: Object!) { submitQuizAttempt(attemptId: $attemptId, answers: $answers) { score passed attemptsLeft enrollment { id status } } }`, {\n refetch: () => [this.read],\n showsError: true\n })\n private acknowledgement = new MutationController(this, gql`mutation ($id: String!) { acknowledgeEnrollment(id: $id) { id status } }`, { refetch: () => [this.read], showsError: true })\n\n /* Ticks go one after another - a stop right after a start is not dropped while the start is out. */\n private ticks: Promise<unknown> = Promise.resolve()\n\n get enrollment(): any {\n return this.read.data?.myEnrollment ?? null\n }\n\n get context() {\n /* The enrolment is the dock's subject - a question on the course is answered from its own version's units (ADR-0194 decision 12). */\n return { title: t('title.learning-learning'), frame: 'document', menu: 'my-learning', ...(this.enrollmentId ? { subject: { type: 'enrollment', id: this.enrollmentId } } : {}) }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (!this.active || !(changes.active || changes.params)) return\n const id = lifecycle?.params?.id\n if (id && id !== this.enrollmentId) {\n this.enrollmentId = id\n this.unitId = ''\n this.drawn = undefined\n this.result = undefined\n }\n }\n\n updated() {\n const one = this.enrollment\n if (one && !this.unitId && one.units?.length) {\n /* Where the learner left off: the first unit that counts and is not done, else the first. */\n const next = one.units.find((row: any) => row.counts && !row.progress?.doneAt) ?? one.units[0]\n this.unitId = next.unit.id\n }\n }\n\n render() {\n const one = this.enrollment\n return html`<ox-page-state .loading=${this.read.loading && !one} .error=${this.read.error} .held=${!!one} .answeredAt=${this.read.answeredAt} @retry=${() => this.read.refetch()}>\n ${one ? this.renderEnrollment(one) : nothing}\n </ox-page-state>`\n }\n\n private renderEnrollment(one: any) {\n const { done, total } = unitsDoneOf(one.units)\n const current = one.units.find((row: any) => row.unit.id === this.unitId)\n const completed = one.status === 'COMPLETED'\n return html`<div class=\"page\">\n <section class=\"page-section head\">\n <h2>${one.course.name}</h2>\n <div class=\"meta\">\n <span>${stateWord(one.state)}</span>\n ${one.version ? html`<span>${t('text.learning-version-number', { number: one.version.number })}</span>` : nothing}\n ${one.dueOn ? html`<span>${t('text.learning-due-on', { day: one.dueOn })}</span>` : nothing}\n <span>${t('text.learning-learned-minutes', { minutes: minutesOf(one.learnedSeconds), required: minutesOf(one.version?.minSeconds) })}</span>\n </div>\n <ox-learning-progress .value=${done} .max=${total} ?done=${completed} label=${t('label.learning-units-done')}></ox-learning-progress>\n ${!completed && one.missing?.length ? html`<p class=\"page-section-note\">${t('text.learning-missing-lead')} ${one.missing.map(missingWord).join(' · ')}</p>` : nothing}\n ${one.course.description ? html`<p class=\"page-section-note\">${one.course.description}</p>` : nothing}\n </section>\n\n <section class=\"page-section study\">\n <nav aria-label=${t('label.learning-units')}>\n <ol class=\"units\">\n ${one.units.map(\n (row: any) => html`<li>\n <button\n aria-current=${row.unit.id === this.unitId ? 'true' : 'false'}\n aria-label=${`${row.unit.seq}. ${row.unit.name}, ${unitKindWord(row.unit.kind)}${row.progress?.doneAt ? `, ${t('text.learning-unit-done')}` : ''}`}\n @click=${() => this.open(row.unit.id)}\n >\n <md-icon ?done=${!!row.progress?.doneAt} aria-hidden=\"true\">${row.progress?.doneAt ? 'check_circle' : row.counts ? 'radio_button_unchecked' : 'link'}</md-icon>\n <span class=\"name\" aria-hidden=\"true\">${row.unit.seq}. ${row.unit.name}</span>\n <span class=\"kind\" aria-hidden=\"true\">${unitKindWord(row.unit.kind)}</span>\n </button>\n </li>`\n )}\n </ol>\n </nav>\n <div class=\"lesson\">${current ? this.renderLesson(one, current) : nothing}</div>\n </section>\n\n ${one.quizSize > 0 ? this.renderQuiz(one) : nothing}\n ${one.version?.acknowledgement ? this.renderAcknowledgement(one) : nothing}\n ${completed\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${t('title.learning-certificate')}</h3></header>\n <p class=\"page-section-note\">${t('text.learning-completed-detail', { day: dayOf(one.completedAt), number: one.certificateNo ?? '' })}</p>\n <md-filled-button style=\"align-self:flex-start\" @click=${() => issueDocument({ kind: 'learning.certificate', refId: one.id })}>${t('button.learning-certificate')}</md-filled-button>\n </section>`\n : nothing}\n </div>`\n }\n\n private renderLesson(one: any, row: any) {\n const unit = row.unit\n const progress = row.progress ?? {}\n if (unit.kind === 'LINK')\n return html`<h3>${unit.name}</h3>\n <p class=\"page-section-note\">${t('text.learning-link-not-counted')}</p>\n <a href=${unit.url} target=\"_blank\" rel=\"noopener noreferrer\">${unit.url}</a>`\n /*\n * One element per enrolment and unit: leaving one, its element says its own time to its own enrolment and unit -\n * never under the next. Keyed by the unit alone, the same unit opened from another enrolment kept the element and\n * its clock, and the first enrolment's seconds went out under the second (2026-11-08).\n */\n return html`<h3>${unit.name}</h3>\n ${keyed(\n `${one.id}:${unit.id}`,\n html`<ox-lesson\n .kind=${unit.kind}\n .src=${unit.file?.fullpath}\n .body=${unit.body ?? ''}\n .label=${unit.name}\n .watched=${progress.watchedSeconds ?? 0}\n .position=${progress.positionSeconds ?? 0}\n .requiredSeconds=${unit.minSeconds ?? 60}\n .learnedSeconds=${progress.learnedSeconds ?? 0}\n .done=${!!progress.doneAt || one.status === 'COMPLETED'}\n @lesson-tick=${(e: CustomEvent<LessonTick>) => this.tick(one.id, unit.id, e.detail)}\n ></ox-lesson>`\n )}\n ${this.tickError ? html`<div class=\"error\" role=\"alert\">${this.tickError}</div>` : nothing}`\n }\n\n private renderQuiz(one: any) {\n const completed = one.status === 'COMPLETED'\n const passed = one.score != null && one.version?.passScore != null && one.score >= one.version.passScore\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${t('title.learning-quiz')}</h3></header>\n <p class=\"page-section-note\">\n ${t('text.learning-quiz-rule', { count: one.quizSize, pass: one.version.passScore })}\n ${one.attemptsLeft != null ? t('text.learning-attempts-left', { count: one.attemptsLeft }) : ''}\n ${one.score != null ? t('text.learning-best-score', { score: one.score }) : ''}\n </p>\n ${this.result\n ? html`<p class=\"result\" role=\"status\">${this.result.passed ? t('text.learning-quiz-passed', { score: this.result.score }) : t('text.learning-quiz-failed', { score: this.result.score })}</p>`\n : nothing}\n ${this.drawn\n ? html`<ox-quiz .questions=${this.drawn.questions} .busy=${this.quizSubmit.pending} @quiz-submit=${(e: CustomEvent<{ answers: QuizAnswers }>) => this.submit(e.detail.answers)}></ox-quiz>`\n : !completed && !passed && one.attemptsLeft !== 0\n ? html`<md-filled-button style=\"align-self:flex-start\" ?disabled=${this.quizStart.pending} @click=${() => this.startQuiz(one.id)}>${t('button.learning-quiz-start')}</md-filled-button>`\n : nothing}\n ${this.quizStart.error || this.quizSubmit.error ? html`<div class=\"error\" role=\"alert\">${this.errorText(this.quizStart.error || this.quizSubmit.error)}</div>` : nothing}\n </section>`\n }\n\n private renderAcknowledgement(one: any) {\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${t('title.learning-acknowledgement')}</h3></header>\n <p class=\"acknowledgement\">${one.version.acknowledgement}</p>\n ${one.acknowledgedAt\n ? html`<p class=\"page-section-note\">${t('text.learning-acknowledged-on', { day: dayOf(one.acknowledgedAt) })}</p>`\n : html`<label class=\"check\"><input type=\"checkbox\" .checked=${this.agreed} @change=${(e: any) => (this.agreed = e.target.checked)} />${t('text.learning-acknowledge-label')}</label>\n <md-filled-button style=\"align-self:flex-start\" ?disabled=${!this.agreed || this.acknowledgement.pending} @click=${() => this.acknowledgement.run({ id: one.id })}\n >${t('button.learning-acknowledge')}</md-filled-button\n >\n ${this.acknowledgement.error ? html`<div class=\"error\" role=\"alert\">${this.errorText(this.acknowledgement.error)}</div>` : nothing}`}\n </section>`\n }\n\n private open(unitId: string) {\n this.unitId = unitId\n this.tickError = ''\n }\n\n private tick(enrollmentId: string, unitId: string, detail: LessonTick) {\n this.ticks = this.ticks.then(async () => {\n const before = this.enrollment\n const answer = await this.tickSave.run({ enrollmentId, unitId, seconds: Math.round(detail.seconds), position: Math.floor(detail.position) })\n if (!answer) {\n this.tickError = this.errorText(this.tickSave.error)\n await this.read.refetch()\n return\n }\n this.tickError = ''\n const row = before?.units?.find((one: any) => one.unit.id === unitId)\n const newlyDone = !!answer.recordLearningTick.progress.doneAt && !row?.progress?.doneAt\n if (newlyDone || answer.recordLearningTick.enrollment.status !== before?.status) await this.read.refetch()\n })\n }\n\n private async startQuiz(enrollmentId: string) {\n this.result = undefined\n const answer = await this.quizStart.run({ enrollmentId })\n if (answer) this.drawn = answer.startQuizAttempt\n }\n\n private async submit(answers: QuizAnswers) {\n if (!this.drawn) return\n const answer = await this.quizSubmit.run({ attemptId: this.drawn.attemptId, answers })\n if (!answer) return\n this.result = answer.submitQuizAttempt\n this.drawn = undefined\n }\n\n private errorText(error: any): string {\n return error?.graphQLErrors?.[0]?.message ?? error?.message ?? ''\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"learning-view.js","sourceRoot":"","sources":["../../client/pages/learning-view.ts"],"names":[],"mappings":";AAAA,OAAO,uCAAuC,CAAA;AAC9C,OAAO,qCAAqC,CAAA;AAC5C,OAAO,4BAA4B,CAAA;AACnC,OAAO,2CAA2C,CAAA;AAClD,OAAO,gCAAgC,CAAA;AACvC,OAAO,8BAA8B,CAAA;AACrC,OAAO,sCAAsC,CAAA;AAE7C,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAE/D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAClI,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAEtG,MAAM,aAAa,GAAG;;;iBAGL,eAAe;;EAE9B,CAAA;AAEF;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAAtD;;QAsHY,iBAAY,GAAG,EAAE,CAAA;QACjB,WAAM,GAAG,EAAE,CAAA;QAGX,WAAM,GAAG,KAAK,CAAA;QACd,cAAS,GAAG,EAAE,CAAA;QAC/B,4GAA4G;QAC3F,cAAS,GAAG,KAAK,CAAA;QAE1B,SAAI,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,GAAG,CAAA,kDAAkD,eAAe,MAAM,EAAE;YACnH,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YAC5E,YAAY,EAAE,IAAI;SACnB,CAAC,CAAA;QAIM,aAAQ,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;QACjF,cAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,kJAAkJ,EAAE;YACtM,UAAU,EAAE,IAAI;SACjB,CAAC,CAAA;QACM,eAAU,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,0KAA0K,EAAE;YAC/N,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC1B,UAAU,EAAE,IAAI;SACjB,CAAC,CAAA;QACM,oBAAe,GAAG,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAA,0EAA0E,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;QAEvL;;;WAGG;QACK,UAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAA;QACnD,gHAAgH;QACxG,WAAM,GAAG,IAAI,YAAY,EAAE,CAAA;IAqPrC,CAAC;aA1YQ,WAAM,GAAG;QACd,kBAAkB;QAClB,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgHF;KACF,AAnHY,CAmHZ;IAoCD,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,IAAI,IAAI,CAAA;IAC7C,CAAC;IAED,IAAI,OAAO;QACT,qIAAqI;QACrI,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,yBAAyB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IAClL,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAc;QAC5C,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;YAAE,OAAM;QAC/D,MAAM,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,EAAE,CAAA;QAChC,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAA;YACtB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;YAChB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;YACtB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACzB,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAA;QAC3B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;YAC7C,mEAAmE;YACnE,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IAED,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAA;QAC3B,OAAO,IAAI,CAAA,2BAA2B,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,GAAG,gBAAgB,IAAI,CAAC,IAAI,CAAC,UAAU,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QAC5K,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO;qBAC7B,CAAA;IACnB,CAAC;IAEO,gBAAgB,CAAC,GAAQ;QAC/B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,CAAA;QACzE,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,WAAW,CAAA;QAC5C,OAAO,IAAI,CAAA;;cAED,GAAG,CAAC,MAAM,CAAC,IAAI;;kBAEX,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;YAC1B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,SAAS,CAAC,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;YAC/G,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,SAAS,CAAC,CAAC,sBAAsB,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;kBACnF,CAAC,CAAC,+BAA+B,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;;uCAEvG,IAAI,SAAS,KAAK,UAAU,SAAS,UAAU,CAAC,CAAC,2BAA2B,CAAC;UAC1G,CAAC,SAAS,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,gCAAgC,CAAC,CAAC,4BAA4B,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;UACnK,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA,gCAAgC,GAAG,CAAC,MAAM,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,OAAO;;;;0BAInF,CAAC,CAAC,sBAAsB,CAAC;;cAErC,GAAG,CAAC,KAAK,CAAC,GAAG,CACb,CAAC,GAAQ,EAAE,EAAE,CAAC,IAAI,CAAA;;iCAEC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;+BAChD,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;2BACzI,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;;mCAEpB,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,uBAAuB,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,MAAM;0DAC5G,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI;0DAC9B,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;;oBAEjE,CACP;;;8BAGiB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO;;;QAGzE,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO;QACjD,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO;QACxE,SAAS;YACT,CAAC,CAAC,IAAI,CAAA;sDACwC,CAAC,CAAC,4BAA4B,CAAC;2CAC1C,CAAC,CAAC,gCAAgC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;qEAC3E,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,6BAA6B,CAAC;qBACxJ;YACb,CAAC,CAAC,OAAO;WACN,CAAA;IACT,CAAC;IAEO,YAAY,CAAC,GAAQ,EAAE,GAAQ;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;QACrB,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAA,OAAO,IAAI,CAAC,IAAI,OAAO,CAAA;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC/F,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YACtB,OAAO,IAAI,CAAA,OAAO,IAAI,CAAC,IAAI;uCACM,CAAC,CAAC,gCAAgC,CAAC;kBACxD,IAAI,CAAC,GAAG,8CAA8C,IAAI,CAAC,GAAG,MAAM,CAAA;QAClF;;;;WAIG;QACH,OAAO,IAAI,CAAA,OAAO,IAAI,CAAC,IAAI;QACvB,KAAK,CACL,GAAG,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,EAAE,EACtB,IAAI,CAAA;gBACI,IAAI,CAAC,IAAI;eACV,IAAI,CAAC,IAAI,EAAE,QAAQ;gBAClB,IAAI,CAAC,IAAI,IAAI,EAAE;iBACd,IAAI,CAAC,IAAI;mBACP,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAC;oBAC/E,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,IAAI,CAAC,CAAC;iBACjF,IAAI,CAAC,SAAS,IAAI,CAAC;uBACb,IAAI,CAAC,cAAc,IAAI,SAAS;sBACjC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;2BAC3C,IAAI,CAAC,UAAU,IAAI,EAAE;0BACtB,QAAQ,CAAC,cAAc,IAAI,CAAC;gBACtC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW;uBAClE,CAAC,CAA0B,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;oBACvE,CACb;QACC,IAAI,CAAC,SAAS;YACd,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,SAAS,QAAQ;YAC/D,CAAC,CAAC,IAAI,CAAA,kCAAkC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAA;IAClK,CAAC;IAEO,UAAU,CAAC,GAAQ;QACzB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,WAAW,CAAA;QAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE,SAAS,IAAI,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAA;QACxG,OAAO,IAAI,CAAA;gDACiC,CAAC,CAAC,qBAAqB,CAAC;;UAE9D,CAAC,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;UAClF,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;UAC7F,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;;QAE9E,IAAI,CAAC,MAAM;YACX,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM;YAC/L,CAAC,CAAC,OAAO;QACT,IAAI,CAAC,KAAK;YACV,CAAC,CAAC,IAAI,CAAA,uBAAuB,IAAI,CAAC,KAAK,CAAC,SAAS,UAAU,IAAI,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAwC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa;YAC3L,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,YAAY,KAAK,CAAC;gBAC/C,CAAC,CAAC,IAAI,CAAA,6DAA6D,IAAI,CAAC,SAAS,CAAC,OAAO,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,4BAA4B,CAAC,qBAAqB;gBACxL,CAAC,CAAC,OAAO;QACX,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;eAC/J,CAAA;IACb,CAAC;IAEO,qBAAqB,CAAC,GAAQ;QACpC,OAAO,IAAI,CAAA;gDACiC,CAAC,CAAC,gCAAgC,CAAC;mCAChD,GAAG,CAAC,OAAO,CAAC,eAAe;QACtD,GAAG,CAAC,cAAc;YAClB,CAAC,CAAC,IAAI,CAAA,gCAAgC,CAAC,CAAC,+BAA+B,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM;YAClH,CAAC,CAAC,IAAI,CAAA,wDAAwD,IAAI,CAAC,MAAM,YAAY,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC;wEAC3G,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;iBAC5J,CAAC,CAAC,6BAA6B,CAAC;;cAEnC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE;eACjI,CAAA;IACb,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,IAAI,CAAC,MAAc;QAC/B,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QACpD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;QACnB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,MAAM,IAAI,CAAC,cAAc,CAAA;QACzB,MAAM,IAAI,CAAC,KAAK,CAAA;QAChB,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAA;QACpB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;IACxB,CAAC;IAED,sFAAsF;IAC9E,IAAI,CAAC,YAAoB,EAAE,MAAc,EAAE,MAAW;QAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;QACxD,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAA;IAC3B,CAAC;IAEO,IAAI,CAAC,YAAoB,EAAE,MAAc,EAAE,MAAkB;QACnE,MAAM,SAAS,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAA;QACtH;;;WAGG;QACH,IAAI,QAAQ,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;YAC1C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAA;YAC/D,OAAM;QACR,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAA;YAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YACjD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACpD,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;gBACzB,OAAM;YACR,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;YACnB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAA;YAC1D,MAAM,GAAG,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAA;YACrE,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAA;YACvF,IAAI,SAAS,IAAI,MAAM,CAAC,kBAAkB,CAAC,UAAU,CAAC,MAAM,KAAK,MAAM,EAAE,MAAM;gBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;QAC5G,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,WAAW,CAAC,SAAmD;QAC3E,MAAM,KAAK,CAAC,UAAU,EAAE;YACtB,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,EAAE;YACnF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;SAC1D,CAAC;aACC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;aACjC,IAAI,CAAC,MAAM,CAAC,EAAE;YACb,IAAI,MAAM,EAAE,IAAI,EAAE,kBAAkB;gBAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;;gBACpH,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAA;QAC1D,CAAC,CAAC;aACD,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACvE,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,YAAoB;QAC1C,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;QACzD,IAAI,MAAM;YAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAA;IAClD,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,OAAoB;QACvC,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAM;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAA;QACtF,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAA;QACtC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;IACxB,CAAC;IAEO,SAAS,CAAC,KAAU;QAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,IAAI,EAAE,CAAA;IACnE,CAAC;;AApRgB;IAAhB,KAAK,EAAE;;kDAA0B;AACjB;IAAhB,KAAK,EAAE;;4CAAoB;AACX;IAAhB,KAAK,EAAE;;2CAAiE;AACxD;IAAhB,KAAK,EAAE;;4CAAkF;AACzE;IAAhB,KAAK,EAAE;;4CAAuB;AACd;IAAhB,KAAK,EAAE;;+CAAuB;AAEd;IAAhB,KAAK,EAAE;;+CAA0B;AAOjB;IAAhB,KAAK,EAAE;8BAAmB,IAAI;6CAAA;AApIpB,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CA2YxB","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/icon/icon.js'\nimport '@operato/learning/ox-learning-progress.js'\nimport '@operato/learning/ox-lesson.js'\nimport '@operato/learning/ox-quiz.js'\nimport '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { keyed } from 'lit/directives/keyed.js'\n\nimport { MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport type { LessonTick, QuizAnswers, QuizQuestion } from '@operato/learning'\nimport { PageView } from '@operato/shell'\nimport { DocumentPageStyles } from '@operato/styles'\nimport { issueDocument } from '@things-factory/pdf/dist-client'\n\nimport { LEARNING_FIELDS, LessonStarts, pageLookedOf, PROGRESS_FIELDS, resumeUnitId, unitsDoneOf } from '../utils/learning-api.js'\nimport { dayOf, minutesOf, missingWord, stateWord, t, unitKindWord } from '../utils/learning-words.js'\n\nconst TICK_MUTATION = `mutation ($enrollmentId: String!, $unitId: String!, $seconds: Int!, $position: Int!) {\n recordLearningTick(enrollmentId: $enrollmentId, unitId: $unitId, seconds: $seconds, position: $position) {\n enrollment { id status }\n progress { ${PROGRESS_FIELDS} }\n }\n}`\n\n/**\n * 과정 화면 (ADR-0194 decisions 3-9) - one enrolment: the units in order with the one open played by `ox-lesson`, whose\n * ticks go to the server as they come (the server counts the time); the quiz drawn by the server and scored there; the\n * acknowledgement; and the certificate once completed. On a phone the unit list sits above the lesson.\n */\n@customElement('learning-view')\nexport class LearningView extends localize(i18next)(PageView) {\n static styles = [\n DocumentPageStyles,\n css`\n .head {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small, 4px);\n }\n .head h2 {\n margin: 0;\n font-size: var(--ui-t-title-large, 22px);\n overflow-wrap: anywhere;\n }\n .meta {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 12px);\n }\n .study {\n display: grid;\n grid-template-columns: minmax(200px, 280px) minmax(0, 1fr);\n gap: var(--spacing-large, 12px);\n align-items: start;\n }\n @media (max-width: 760px) {\n .study {\n grid-template-columns: minmax(0, 1fr);\n }\n }\n ol.units {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n ol.units button {\n display: flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n width: 100%;\n min-height: 40px;\n padding: 6px 8px;\n border: 0;\n border-radius: var(--ui-r-sm, 6px);\n background: transparent;\n color: var(--ui-ink);\n font: inherit;\n text-align: left;\n cursor: pointer;\n }\n ol.units button[aria-current='true'] {\n background: var(--ui-line-soft, var(--md-sys-color-surface-container));\n font-weight: 600;\n }\n ol.units button:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -2px;\n }\n ol.units md-icon {\n --md-icon-size: 18px;\n color: var(--ui-muted);\n flex: none;\n }\n ol.units md-icon[done] {\n color: var(--ui-ok, var(--md-sys-color-primary));\n }\n ol.units .name {\n flex: 1;\n min-width: 0;\n overflow-wrap: anywhere;\n }\n ol.units .kind {\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 12px);\n }\n .lesson {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .lesson h3 {\n margin: 0;\n font-size: var(--ui-t-title, 15px);\n }\n .error {\n color: var(--ui-crit, var(--md-sys-color-error));\n font-size: var(--ui-t-small, 12px);\n }\n .check {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-small, 4px);\n line-height: 1.6;\n }\n .acknowledgement {\n white-space: pre-wrap;\n margin: 0;\n line-height: 1.6;\n }\n .saved {\n margin: 0;\n min-height: 1.4em;\n color: var(--md-sys-color-on-surface-variant);\n font-size: var(--ui-t-small, 12px);\n }\n .result {\n margin: 0;\n font-weight: 600;\n }\n `\n ]\n\n @state() private enrollmentId = ''\n @state() private unitId = ''\n @state() private drawn?: { attemptId: string; questions: QuizQuestion[] }\n @state() private result?: { score: number; passed: boolean; attemptsLeft?: number | null }\n @state() private agreed = false\n @state() private tickError = ''\n /* While one lesson leaves for another, nothing is shown - the leaving one's last tick is answered first. */\n @state() private switching = false\n\n private read = new QueryController(this, gql`query ($id: String!) { myEnrollment(id: $id) { ${LEARNING_FIELDS} } }`, {\n variables: () => (this.enrollmentId ? { id: this.enrollmentId } : undefined),\n keepPrevious: true\n })\n\n @state() private savedAt?: Date\n\n private tickSave = new MutationController(this, gql(TICK_MUTATION), { showsError: true })\n private quizStart = new MutationController(this, gql`mutation ($enrollmentId: String!) { startQuizAttempt(enrollmentId: $enrollmentId) { attemptId questions { id kind text choices { id text } } } }`, {\n showsError: true\n })\n private quizSubmit = new MutationController(this, gql`mutation ($attemptId: String!, $answers: Object!) { submitQuizAttempt(attemptId: $attemptId, answers: $answers) { score passed attemptsLeft enrollment { id status } } }`, {\n refetch: () => [this.read],\n showsError: true\n })\n private acknowledgement = new MutationController(this, gql`mutation ($id: String!) { acknowledgeEnrollment(id: $id) { id status } }`, { refetch: () => [this.read], showsError: true })\n\n /*\n * Ticks go one after another - the leaving one too - so the server takes them in the order they were looked at: a\n * stop right after a start is not dropped while the start is out, and a page left on is never overtaken by an older one.\n */\n private ticks: Promise<unknown> = Promise.resolve()\n /** Where each lesson starts - the server's last answer for its unit, else what the page read (LessonStarts). */\n private starts = new LessonStarts()\n\n get enrollment(): any {\n return this.read.data?.myEnrollment ?? null\n }\n\n get context() {\n /* The enrolment is the dock's subject - a question on the course is answered from its own version's units (ADR-0194 decision 12). */\n return { title: t('title.learning-learning'), frame: 'document', menu: 'my-learning', ...(this.enrollmentId ? { subject: { type: 'enrollment', id: this.enrollmentId } } : {}) }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (!this.active || !(changes.active || changes.params)) return\n const id = lifecycle?.params?.id\n if (id && id !== this.enrollmentId) {\n this.enrollmentId = id\n this.unitId = ''\n this.drawn = undefined\n this.result = undefined\n }\n }\n\n updated() {\n const one = this.enrollment\n if (one && !this.unitId && one.units?.length) {\n /* Where the learner was - the unit learned last (resumeUnitId). */\n this.unitId = resumeUnitId(one)\n }\n }\n\n render() {\n const one = this.enrollment\n return html`<ox-page-state .loading=${this.read.loading && !one} .error=${this.read.error} .held=${!!one} .answeredAt=${this.read.answeredAt} @retry=${() => this.read.refetch()}>\n ${one ? this.renderEnrollment(one) : nothing}\n </ox-page-state>`\n }\n\n private renderEnrollment(one: any) {\n const { done, total } = unitsDoneOf(one.units)\n const current = one.units.find((row: any) => row.unit.id === this.unitId)\n const completed = one.status === 'COMPLETED'\n return html`<div class=\"page\">\n <section class=\"page-section head\">\n <h2>${one.course.name}</h2>\n <div class=\"meta\">\n <span>${stateWord(one.state)}</span>\n ${one.version ? html`<span>${t('text.learning-version-number', { number: one.version.number })}</span>` : nothing}\n ${one.dueOn ? html`<span>${t('text.learning-due-on', { day: one.dueOn })}</span>` : nothing}\n <span>${t('text.learning-learned-minutes', { minutes: minutesOf(one.learnedSeconds), required: minutesOf(one.version?.minSeconds) })}</span>\n </div>\n <ox-learning-progress .value=${done} .max=${total} ?done=${completed} label=${t('label.learning-units-done')}></ox-learning-progress>\n ${!completed && one.missing?.length ? html`<p class=\"page-section-note\">${t('text.learning-missing-lead')} ${one.missing.map(missingWord).join(' · ')}</p>` : nothing}\n ${one.course.description ? html`<p class=\"page-section-note\">${one.course.description}</p>` : nothing}\n </section>\n\n <section class=\"page-section study\">\n <nav aria-label=${t('label.learning-units')}>\n <ol class=\"units\">\n ${one.units.map(\n (row: any) => html`<li>\n <button\n aria-current=${row.unit.id === this.unitId ? 'true' : 'false'}\n aria-label=${`${row.unit.seq}. ${row.unit.name}, ${unitKindWord(row.unit.kind)}${row.progress?.doneAt ? `, ${t('text.learning-unit-done')}` : ''}`}\n @click=${() => this.open(row.unit.id)}\n >\n <md-icon ?done=${!!row.progress?.doneAt} aria-hidden=\"true\">${row.progress?.doneAt ? 'check_circle' : row.counts ? 'radio_button_unchecked' : 'link'}</md-icon>\n <span class=\"name\" aria-hidden=\"true\">${row.unit.seq}. ${row.unit.name}</span>\n <span class=\"kind\" aria-hidden=\"true\">${unitKindWord(row.unit.kind)}</span>\n </button>\n </li>`\n )}\n </ol>\n </nav>\n <div class=\"lesson\">${current ? this.renderLesson(one, current) : nothing}</div>\n </section>\n\n ${one.quizSize > 0 ? this.renderQuiz(one) : nothing}\n ${one.version?.acknowledgement ? this.renderAcknowledgement(one) : nothing}\n ${completed\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${t('title.learning-certificate')}</h3></header>\n <p class=\"page-section-note\">${t('text.learning-completed-detail', { day: dayOf(one.completedAt), number: one.certificateNo ?? '' })}</p>\n <md-filled-button style=\"align-self:flex-start\" @click=${() => issueDocument({ kind: 'learning.certificate', refId: one.id })}>${t('button.learning-certificate')}</md-filled-button>\n </section>`\n : nothing}\n </div>`\n }\n\n private renderLesson(one: any, row: any) {\n const unit = row.unit\n if (this.switching) return html`<h3>${unit.name}</h3>`\n const progress = unit.kind === 'LINK' ? {} : this.starts.startOf(one.id, unit.id, row.progress)\n if (unit.kind === 'LINK')\n return html`<h3>${unit.name}</h3>\n <p class=\"page-section-note\">${t('text.learning-link-not-counted')}</p>\n <a href=${unit.url} target=\"_blank\" rel=\"noopener noreferrer\">${unit.url}</a>`\n /*\n * One element per enrolment and unit: leaving one, its element says its own time to its own enrolment and unit -\n * never under the next. Keyed by the unit alone, the same unit opened from another enrolment kept the element and\n * its clock, and the first enrolment's seconds went out under the second (2026-11-08).\n */\n return html`<h3>${unit.name}</h3>\n ${keyed(\n `${one.id}:${unit.id}`,\n html`<ox-lesson\n .kind=${unit.kind}\n .src=${unit.file?.fullpath}\n .body=${unit.body ?? ''}\n .label=${unit.name}\n .watched=${unit.kind === 'PDF' ? (progress.pagesRead ?? 0) : (progress.watchedSeconds ?? 0)}\n .position=${unit.kind === 'PDF' ? (progress.pageAt ?? 0) : (progress.positionSeconds ?? 0)}\n .pages=${unit.pageCount ?? 0}\n .pageSeconds=${unit.secondsPerPage ?? undefined}\n .pageLooked=${unit.kind === 'PDF' ? pageLookedOf(progress) : 0}\n .requiredSeconds=${unit.minSeconds ?? 60}\n .learnedSeconds=${progress.learnedSeconds ?? 0}\n .done=${!!row.progress?.doneAt || !!progress.doneAt || one.status === 'COMPLETED'}\n @lesson-tick=${(e: CustomEvent<LessonTick>) => this.tick(one.id, unit.id, e.detail)}\n ></ox-lesson>`\n )}\n ${this.tickError\n ? html`<div class=\"error\" role=\"alert\">${this.tickError}</div>`\n : html`<p class=\"saved\" role=\"status\">${this.savedAt ? t('text.learning-saved-at', { time: this.savedAt.toLocaleTimeString(i18next.language) }) : ''}</p>`}`\n }\n\n private renderQuiz(one: any) {\n const completed = one.status === 'COMPLETED'\n const passed = one.score != null && one.version?.passScore != null && one.score >= one.version.passScore\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${t('title.learning-quiz')}</h3></header>\n <p class=\"page-section-note\">\n ${t('text.learning-quiz-rule', { count: one.quizSize, pass: one.version.passScore })}\n ${one.attemptsLeft != null ? t('text.learning-attempts-left', { count: one.attemptsLeft }) : ''}\n ${one.score != null ? t('text.learning-best-score', { score: one.score }) : ''}\n </p>\n ${this.result\n ? html`<p class=\"result\" role=\"status\">${this.result.passed ? t('text.learning-quiz-passed', { score: this.result.score }) : t('text.learning-quiz-failed', { score: this.result.score })}</p>`\n : nothing}\n ${this.drawn\n ? html`<ox-quiz .questions=${this.drawn.questions} .busy=${this.quizSubmit.pending} @quiz-submit=${(e: CustomEvent<{ answers: QuizAnswers }>) => this.submit(e.detail.answers)}></ox-quiz>`\n : !completed && !passed && one.attemptsLeft !== 0\n ? html`<md-filled-button style=\"align-self:flex-start\" ?disabled=${this.quizStart.pending} @click=${() => this.startQuiz(one.id)}>${t('button.learning-quiz-start')}</md-filled-button>`\n : nothing}\n ${this.quizStart.error || this.quizSubmit.error ? html`<div class=\"error\" role=\"alert\">${this.errorText(this.quizStart.error || this.quizSubmit.error)}</div>` : nothing}\n </section>`\n }\n\n private renderAcknowledgement(one: any) {\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${t('title.learning-acknowledgement')}</h3></header>\n <p class=\"acknowledgement\">${one.version.acknowledgement}</p>\n ${one.acknowledgedAt\n ? html`<p class=\"page-section-note\">${t('text.learning-acknowledged-on', { day: dayOf(one.acknowledgedAt) })}</p>`\n : html`<label class=\"check\"><input type=\"checkbox\" .checked=${this.agreed} @change=${(e: any) => (this.agreed = e.target.checked)} />${t('text.learning-acknowledge-label')}</label>\n <md-filled-button style=\"align-self:flex-start\" ?disabled=${!this.agreed || this.acknowledgement.pending} @click=${() => this.acknowledgement.run({ id: one.id })}\n >${t('button.learning-acknowledge')}</md-filled-button\n >\n ${this.acknowledgement.error ? html`<div class=\"error\" role=\"alert\">${this.errorText(this.acknowledgement.error)}</div>` : nothing}`}\n </section>`\n }\n\n /**\n * Opens a unit. The lesson shown leaves first and its last tick is answered, so the unit opened - the same one coming\n * back too - starts from what the server counted, not from what the page read when it opened (2026-11-08).\n */\n private async open(unitId: string) {\n if (unitId === this.unitId || this.switching) return\n this.tickError = ''\n this.switching = true\n await this.updateComplete\n await this.ticks\n this.starts.reopen()\n this.unitId = unitId\n this.switching = false\n }\n\n /** Keeps the server's answer for a unit - the next time it opens, it starts there. */\n private keep(enrollmentId: string, unitId: string, answer: any) {\n this.starts.keep(enrollmentId, unitId, answer?.progress)\n this.savedAt = new Date()\n }\n\n private tick(enrollmentId: string, unitId: string, detail: LessonTick) {\n const variables = { enrollmentId, unitId, seconds: Math.round(detail.seconds), position: Math.floor(detail.position) }\n /*\n * Leaving - the tab hidden, closed or reloaded - the last piece goes out on a request the browser finishes after the\n * page is gone (keepalive). Through the usual queue it was dropped with the page, and up to a tick was lost.\n */\n if (document.visibilityState === 'hidden') {\n this.ticks = this.ticks.then(() => this.tickLeaving(variables))\n return\n }\n this.ticks = this.ticks.then(async () => {\n const before = this.enrollment\n const answer = await this.tickSave.run(variables)\n if (!answer) {\n this.tickError = this.errorText(this.tickSave.error)\n await this.read.refetch()\n return\n }\n this.tickError = ''\n this.keep(enrollmentId, unitId, answer.recordLearningTick)\n const row = before?.units?.find((one: any) => one.unit.id === unitId)\n const newlyDone = !!answer.recordLearningTick.progress.doneAt && !row?.progress?.doneAt\n if (newlyDone || answer.recordLearningTick.enrollment.status !== before?.status) await this.read.refetch()\n })\n }\n\n /*\n * The leaving tick goes after the ticks before it, on a request the browser finishes after the page is gone\n * (keepalive). Only when the page is closed while an earlier tick is still out is it not sent - the order is kept.\n */\n private async tickLeaving(variables: { enrollmentId: string; unitId: string }) {\n await fetch('/graphql', {\n method: 'POST',\n keepalive: true,\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', 'Apollo-Require-Preflight': 'true' },\n body: JSON.stringify({ query: TICK_MUTATION, variables })\n })\n .then(response => response.json())\n .then(answer => {\n if (answer?.data?.recordLearningTick) this.keep(variables.enrollmentId, variables.unitId, answer.data.recordLearningTick)\n else this.tickError = answer?.errors?.[0]?.message ?? ''\n })\n .catch(error => (this.tickError = error?.message ?? String(error)))\n }\n\n private async startQuiz(enrollmentId: string) {\n this.result = undefined\n const answer = await this.quizStart.run({ enrollmentId })\n if (answer) this.drawn = answer.startQuizAttempt\n }\n\n private async submit(answers: QuizAnswers) {\n if (!this.drawn) return\n const answer = await this.quizSubmit.run({ attemptId: this.drawn.attemptId, answers })\n if (!answer) return\n this.result = answer.submitQuizAttempt\n this.drawn = undefined\n }\n\n private errorText(error: any): string {\n return error?.graphQLErrors?.[0]?.message ?? error?.message ?? ''\n }\n}\n"]}
|