@things-factory/learning 0.0.0-stage → 10.1.261
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/bootstrap.d.ts +2 -0
- package/dist-client/bootstrap.js +4 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/components/learning-assign-form.d.ts +24 -0
- package/dist-client/components/learning-assign-form.js +77 -0
- package/dist-client/components/learning-assign-form.js.map +1 -0
- package/dist-client/components/learning-course-form.d.ts +29 -0
- package/dist-client/components/learning-course-form.js +104 -0
- package/dist-client/components/learning-course-form.js.map +1 -0
- 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-question-form.d.ts +33 -0
- package/dist-client/components/learning-question-form.js +142 -0
- package/dist-client/components/learning-question-form.js.map +1 -0
- package/dist-client/components/learning-todo.d.ts +12 -0
- package/dist-client/components/learning-todo.js +43 -0
- package/dist-client/components/learning-todo.js.map +1 -0
- package/dist-client/components/learning-unit-form.d.ts +44 -0
- package/dist-client/components/learning-unit-form.js +151 -0
- package/dist-client/components/learning-unit-form.js.map +1 -0
- package/dist-client/index.d.ts +2 -0
- package/dist-client/index.js +3 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/pages/learning-course-editor.d.ts +63 -0
- package/dist-client/pages/learning-course-editor.js +476 -0
- package/dist-client/pages/learning-course-editor.js.map +1 -0
- package/dist-client/pages/learning-course-list.d.ts +37 -0
- package/dist-client/pages/learning-course-list.js +92 -0
- package/dist-client/pages/learning-course-list.js.map +1 -0
- package/dist-client/pages/learning-enrollment-status.d.ts +65 -0
- package/dist-client/pages/learning-enrollment-status.js +211 -0
- package/dist-client/pages/learning-enrollment-status.js.map +1 -0
- package/dist-client/pages/learning-view.d.ts +63 -0
- package/dist-client/pages/learning-view.js +446 -0
- package/dist-client/pages/learning-view.js.map +1 -0
- package/dist-client/pages/my-learning.d.ts +22 -0
- package/dist-client/pages/my-learning.js +110 -0
- package/dist-client/pages/my-learning.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +21 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/utils/learning-api.d.ts +61 -0
- package/dist-client/utils/learning-api.js +85 -0
- package/dist-client/utils/learning-api.js.map +1 -0
- package/dist-client/utils/learning-words.d.ts +20 -0
- package/dist-client/utils/learning-words.js +22 -0
- package/dist-client/utils/learning-words.js.map +1 -0
- 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/activities.d.ts +38 -0
- package/dist-server/activities.js +42 -0
- package/dist-server/activities.js.map +1 -0
- package/dist-server/ai-reads.d.ts +9 -0
- package/dist-server/ai-reads.js +70 -0
- package/dist-server/ai-reads.js.map +1 -0
- package/dist-server/attachment-reader.d.ts +1 -0
- package/dist-server/attachment-reader.js +23 -0
- package/dist-server/attachment-reader.js.map +1 -0
- package/dist-server/index.d.ts +4 -0
- package/dist-server/index.js +33 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/learning-subjects.d.ts +10 -0
- package/dist-server/learning-subjects.js +31 -0
- package/dist-server/learning-subjects.js.map +1 -0
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +17 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/service/course/course-mutation.d.ts +19 -0
- package/dist-server/service/course/course-mutation.js +177 -0
- package/dist-server/service/course/course-mutation.js.map +1 -0
- package/dist-server/service/course/course-query.d.ts +27 -0
- package/dist-server/service/course/course-query.js +183 -0
- package/dist-server/service/course/course-query.js.map +1 -0
- package/dist-server/service/course/course-question.d.ts +41 -0
- package/dist-server/service/course/course-question.js +116 -0
- package/dist-server/service/course/course-question.js.map +1 -0
- package/dist-server/service/course/course-rules.d.ts +51 -0
- package/dist-server/service/course/course-rules.js +93 -0
- package/dist-server/service/course/course-rules.js.map +1 -0
- package/dist-server/service/course/course-service.d.ts +72 -0
- package/dist-server/service/course/course-service.js +398 -0
- package/dist-server/service/course/course-service.js.map +1 -0
- package/dist-server/service/course/course-type.d.ts +56 -0
- package/dist-server/service/course/course-type.js +202 -0
- package/dist-server/service/course/course-type.js.map +1 -0
- package/dist-server/service/course/course-unit.d.ts +37 -0
- package/dist-server/service/course/course-unit.js +128 -0
- package/dist-server/service/course/course-unit.js.map +1 -0
- package/dist-server/service/course/course-version.d.ts +45 -0
- package/dist-server/service/course/course-version.js +151 -0
- package/dist-server/service/course/course-version.js.map +1 -0
- package/dist-server/service/course/course.d.ts +23 -0
- package/dist-server/service/course/course.js +92 -0
- package/dist-server/service/course/course.js.map +1 -0
- package/dist-server/service/course/index.d.ts +15 -0
- package/dist-server/service/course/index.js +25 -0
- package/dist-server/service/course/index.js.map +1 -0
- 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 +11 -0
- package/dist-server/service/enrollment/enrollment-mutation.js +124 -0
- package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-query.d.ts +24 -0
- package/dist-server/service/enrollment/enrollment-query.js +236 -0
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-remind.d.ts +1 -0
- package/dist-server/service/enrollment/enrollment-remind.js +45 -0
- package/dist-server/service/enrollment/enrollment-remind.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-service.d.ts +110 -0
- package/dist-server/service/enrollment/enrollment-service.js +427 -0
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-status.d.ts +53 -0
- package/dist-server/service/enrollment/enrollment-status.js +194 -0
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-type.d.ts +82 -0
- package/dist-server/service/enrollment/enrollment-type.js +293 -0
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -0
- package/dist-server/service/enrollment/enrollment.d.ts +53 -0
- package/dist-server/service/enrollment/enrollment.js +191 -0
- package/dist-server/service/enrollment/enrollment.js.map +1 -0
- package/dist-server/service/enrollment/index.d.ts +15 -0
- package/dist-server/service/enrollment/index.js +26 -0
- package/dist-server/service/enrollment/index.js.map +1 -0
- package/dist-server/service/enrollment/learning-ai.d.ts +45 -0
- package/dist-server/service/enrollment/learning-ai.js +93 -0
- package/dist-server/service/enrollment/learning-ai.js.map +1 -0
- package/dist-server/service/enrollment/learning-documents.d.ts +7 -0
- package/dist-server/service/enrollment/learning-documents.js +159 -0
- package/dist-server/service/enrollment/learning-documents.js.map +1 -0
- package/dist-server/service/enrollment/learning-outcome.d.ts +10 -0
- package/dist-server/service/enrollment/learning-outcome.js +16 -0
- package/dist-server/service/enrollment/learning-outcome.js.map +1 -0
- package/dist-server/service/enrollment/progress-rules.d.ts +119 -0
- package/dist-server/service/enrollment/progress-rules.js +132 -0
- package/dist-server/service/enrollment/progress-rules.js.map +1 -0
- package/dist-server/service/enrollment/quiz-attempt.d.ts +34 -0
- package/dist-server/service/enrollment/quiz-attempt.js +105 -0
- package/dist-server/service/enrollment/quiz-attempt.js.map +1 -0
- package/dist-server/service/enrollment/unit-progress.d.ts +34 -0
- package/dist-server/service/enrollment/unit-progress.js +134 -0
- package/dist-server/service/enrollment/unit-progress.js.map +1 -0
- package/dist-server/service/index.d.ts +14 -0
- package/dist-server/service/index.js +43 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +57 -4
- package/things-factory.config.js +14 -0
- package/translations/en.json +278 -0
- package/translations/es.json +278 -0
- package/translations/ja.json +278 -0
- package/translations/ko.json +278 -0
- package/translations/ms.json +278 -0
- package/translations/zh.json +278 -0
- package/README.md +0 -3
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/button/filled-button.js';
|
|
3
|
+
import '@material/web/button/text-button.js';
|
|
4
|
+
import '@material/web/icon/icon.js';
|
|
5
|
+
import '@operato/learning/ox-learning-progress.js';
|
|
6
|
+
import '@operato/learning/ox-lesson.js';
|
|
7
|
+
import '@operato/learning/ox-quiz.js';
|
|
8
|
+
import '@operato/list-state/ox-page-state.js';
|
|
9
|
+
import gql from 'graphql-tag';
|
|
10
|
+
import { css, html, nothing } from 'lit';
|
|
11
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
12
|
+
import { keyed } from 'lit/directives/keyed.js';
|
|
13
|
+
import { MutationController, QueryController } from '@operato/graphql';
|
|
14
|
+
import { i18next, localize } from '@operato/i18n';
|
|
15
|
+
import { PageView } from '@operato/shell';
|
|
16
|
+
import { DocumentPageStyles } from '@operato/styles';
|
|
17
|
+
import { issueDocument } from '@things-factory/pdf/dist-client';
|
|
18
|
+
import { LEARNING_FIELDS, LessonStarts, pageLookedOf, PROGRESS_FIELDS, resumeUnitId, unitsDoneOf } from '../utils/learning-api.js';
|
|
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
|
+
}`;
|
|
26
|
+
/**
|
|
27
|
+
* 과정 화면 (ADR-0194 decisions 3-9) - one enrolment: the units in order with the one open played by `ox-lesson`, whose
|
|
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
|
+
* acknowledgement; and the certificate once completed. On a phone the unit list sits above the lesson.
|
|
30
|
+
*/
|
|
31
|
+
let LearningView = class LearningView extends localize(i18next)(PageView) {
|
|
32
|
+
constructor() {
|
|
33
|
+
super(...arguments);
|
|
34
|
+
this.enrollmentId = '';
|
|
35
|
+
this.unitId = '';
|
|
36
|
+
this.agreed = false;
|
|
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;
|
|
40
|
+
this.read = new QueryController(this, gql `query ($id: String!) { myEnrollment(id: $id) { ${LEARNING_FIELDS} } }`, {
|
|
41
|
+
variables: () => (this.enrollmentId ? { id: this.enrollmentId } : undefined),
|
|
42
|
+
keepPrevious: true
|
|
43
|
+
});
|
|
44
|
+
this.tickSave = new MutationController(this, gql(TICK_MUTATION), { showsError: true });
|
|
45
|
+
this.quizStart = new MutationController(this, gql `mutation ($enrollmentId: String!) { startQuizAttempt(enrollmentId: $enrollmentId) { attemptId questions { id kind text choices { id text } } } }`, {
|
|
46
|
+
showsError: true
|
|
47
|
+
});
|
|
48
|
+
this.quizSubmit = new MutationController(this, gql `mutation ($attemptId: String!, $answers: Object!) { submitQuizAttempt(attemptId: $attemptId, answers: $answers) { score passed attemptsLeft enrollment { id status } } }`, {
|
|
49
|
+
refetch: () => [this.read],
|
|
50
|
+
showsError: true
|
|
51
|
+
});
|
|
52
|
+
this.acknowledgement = new MutationController(this, gql `mutation ($id: String!) { acknowledgeEnrollment(id: $id) { id status } }`, { refetch: () => [this.read], showsError: true });
|
|
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
|
+
*/
|
|
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();
|
|
60
|
+
}
|
|
61
|
+
static { this.styles = [
|
|
62
|
+
DocumentPageStyles,
|
|
63
|
+
css `
|
|
64
|
+
.head {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
gap: var(--spacing-small, 4px);
|
|
68
|
+
}
|
|
69
|
+
.head h2 {
|
|
70
|
+
margin: 0;
|
|
71
|
+
font-size: var(--ui-t-title-large, 22px);
|
|
72
|
+
overflow-wrap: anywhere;
|
|
73
|
+
}
|
|
74
|
+
.meta {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-wrap: wrap;
|
|
77
|
+
gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
|
|
78
|
+
color: var(--ui-muted);
|
|
79
|
+
font-size: var(--ui-t-small, 12px);
|
|
80
|
+
}
|
|
81
|
+
.study {
|
|
82
|
+
display: grid;
|
|
83
|
+
grid-template-columns: minmax(200px, 280px) minmax(0, 1fr);
|
|
84
|
+
gap: var(--spacing-large, 12px);
|
|
85
|
+
align-items: start;
|
|
86
|
+
}
|
|
87
|
+
@media (max-width: 760px) {
|
|
88
|
+
.study {
|
|
89
|
+
grid-template-columns: minmax(0, 1fr);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
ol.units {
|
|
93
|
+
list-style: none;
|
|
94
|
+
margin: 0;
|
|
95
|
+
padding: 0;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: 2px;
|
|
99
|
+
}
|
|
100
|
+
ol.units button {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: var(--spacing-small, 4px);
|
|
104
|
+
width: 100%;
|
|
105
|
+
min-height: 40px;
|
|
106
|
+
padding: 6px 8px;
|
|
107
|
+
border: 0;
|
|
108
|
+
border-radius: var(--ui-r-sm, 6px);
|
|
109
|
+
background: transparent;
|
|
110
|
+
color: var(--ui-ink);
|
|
111
|
+
font: inherit;
|
|
112
|
+
text-align: left;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
115
|
+
ol.units button[aria-current='true'] {
|
|
116
|
+
background: var(--ui-line-soft, var(--md-sys-color-surface-container));
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
}
|
|
119
|
+
ol.units button:focus-visible {
|
|
120
|
+
outline: 2px solid var(--md-sys-color-primary);
|
|
121
|
+
outline-offset: -2px;
|
|
122
|
+
}
|
|
123
|
+
ol.units md-icon {
|
|
124
|
+
--md-icon-size: 18px;
|
|
125
|
+
color: var(--ui-muted);
|
|
126
|
+
flex: none;
|
|
127
|
+
}
|
|
128
|
+
ol.units md-icon[done] {
|
|
129
|
+
color: var(--ui-ok, var(--md-sys-color-primary));
|
|
130
|
+
}
|
|
131
|
+
ol.units .name {
|
|
132
|
+
flex: 1;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
overflow-wrap: anywhere;
|
|
135
|
+
}
|
|
136
|
+
ol.units .kind {
|
|
137
|
+
color: var(--ui-muted);
|
|
138
|
+
font-size: var(--ui-t-small, 12px);
|
|
139
|
+
}
|
|
140
|
+
.lesson {
|
|
141
|
+
min-width: 0;
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-direction: column;
|
|
144
|
+
gap: var(--spacing-medium, 8px);
|
|
145
|
+
}
|
|
146
|
+
.lesson h3 {
|
|
147
|
+
margin: 0;
|
|
148
|
+
font-size: var(--ui-t-title, 15px);
|
|
149
|
+
}
|
|
150
|
+
.error {
|
|
151
|
+
color: var(--ui-crit, var(--md-sys-color-error));
|
|
152
|
+
font-size: var(--ui-t-small, 12px);
|
|
153
|
+
}
|
|
154
|
+
.check {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: flex-start;
|
|
157
|
+
gap: var(--spacing-small, 4px);
|
|
158
|
+
line-height: 1.6;
|
|
159
|
+
}
|
|
160
|
+
.acknowledgement {
|
|
161
|
+
white-space: pre-wrap;
|
|
162
|
+
margin: 0;
|
|
163
|
+
line-height: 1.6;
|
|
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
|
+
}
|
|
171
|
+
.result {
|
|
172
|
+
margin: 0;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
}
|
|
175
|
+
`
|
|
176
|
+
]; }
|
|
177
|
+
get enrollment() {
|
|
178
|
+
return this.read.data?.myEnrollment ?? null;
|
|
179
|
+
}
|
|
180
|
+
get context() {
|
|
181
|
+
/* The enrolment is the dock's subject - a question on the course is answered from its own version's units (ADR-0194 decision 12). */
|
|
182
|
+
return { title: t('title.learning-learning'), frame: 'document', menu: 'my-learning', ...(this.enrollmentId ? { subject: { type: 'enrollment', id: this.enrollmentId } } : {}) };
|
|
183
|
+
}
|
|
184
|
+
async pageUpdated(changes, lifecycle) {
|
|
185
|
+
if (!this.active || !(changes.active || changes.params))
|
|
186
|
+
return;
|
|
187
|
+
const id = lifecycle?.params?.id;
|
|
188
|
+
if (id && id !== this.enrollmentId) {
|
|
189
|
+
this.enrollmentId = id;
|
|
190
|
+
this.unitId = '';
|
|
191
|
+
this.drawn = undefined;
|
|
192
|
+
this.result = undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
updated() {
|
|
196
|
+
const one = this.enrollment;
|
|
197
|
+
if (one && !this.unitId && one.units?.length) {
|
|
198
|
+
/* Where the learner was - the unit learned last (resumeUnitId). */
|
|
199
|
+
this.unitId = resumeUnitId(one);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
render() {
|
|
203
|
+
const one = this.enrollment;
|
|
204
|
+
return html `<ox-page-state .loading=${this.read.loading && !one} .error=${this.read.error} .held=${!!one} .answeredAt=${this.read.answeredAt} @retry=${() => this.read.refetch()}>
|
|
205
|
+
${one ? this.renderEnrollment(one) : nothing}
|
|
206
|
+
</ox-page-state>`;
|
|
207
|
+
}
|
|
208
|
+
renderEnrollment(one) {
|
|
209
|
+
const { done, total } = unitsDoneOf(one.units);
|
|
210
|
+
const current = one.units.find((row) => row.unit.id === this.unitId);
|
|
211
|
+
const completed = one.status === 'COMPLETED';
|
|
212
|
+
return html `<div class="page">
|
|
213
|
+
<section class="page-section head">
|
|
214
|
+
<h2>${one.course.name}</h2>
|
|
215
|
+
<div class="meta">
|
|
216
|
+
<span>${stateWord(one.state)}</span>
|
|
217
|
+
${one.version ? html `<span>${t('text.learning-version-number', { number: one.version.number })}</span>` : nothing}
|
|
218
|
+
${one.dueOn ? html `<span>${t('text.learning-due-on', { day: one.dueOn })}</span>` : nothing}
|
|
219
|
+
<span>${t('text.learning-learned-minutes', { minutes: minutesOf(one.learnedSeconds), required: minutesOf(one.version?.minSeconds) })}</span>
|
|
220
|
+
</div>
|
|
221
|
+
<ox-learning-progress .value=${done} .max=${total} ?done=${completed} label=${t('label.learning-units-done')}></ox-learning-progress>
|
|
222
|
+
${!completed && one.missing?.length ? html `<p class="page-section-note">${t('text.learning-missing-lead')} ${one.missing.map(missingWord).join(' · ')}</p>` : nothing}
|
|
223
|
+
${one.course.description ? html `<p class="page-section-note">${one.course.description}</p>` : nothing}
|
|
224
|
+
</section>
|
|
225
|
+
|
|
226
|
+
<section class="page-section study">
|
|
227
|
+
<nav aria-label=${t('label.learning-units')}>
|
|
228
|
+
<ol class="units">
|
|
229
|
+
${one.units.map((row) => html `<li>
|
|
230
|
+
<button
|
|
231
|
+
aria-current=${row.unit.id === this.unitId ? 'true' : 'false'}
|
|
232
|
+
aria-label=${`${row.unit.seq}. ${row.unit.name}, ${unitKindWord(row.unit.kind)}${row.progress?.doneAt ? `, ${t('text.learning-unit-done')}` : ''}`}
|
|
233
|
+
@click=${() => this.open(row.unit.id)}
|
|
234
|
+
>
|
|
235
|
+
<md-icon ?done=${!!row.progress?.doneAt} aria-hidden="true">${row.progress?.doneAt ? 'check_circle' : row.counts ? 'radio_button_unchecked' : 'link'}</md-icon>
|
|
236
|
+
<span class="name" aria-hidden="true">${row.unit.seq}. ${row.unit.name}</span>
|
|
237
|
+
<span class="kind" aria-hidden="true">${unitKindWord(row.unit.kind)}</span>
|
|
238
|
+
</button>
|
|
239
|
+
</li>`)}
|
|
240
|
+
</ol>
|
|
241
|
+
</nav>
|
|
242
|
+
<div class="lesson">${current ? this.renderLesson(one, current) : nothing}</div>
|
|
243
|
+
</section>
|
|
244
|
+
|
|
245
|
+
${one.quizSize > 0 ? this.renderQuiz(one) : nothing}
|
|
246
|
+
${one.version?.acknowledgement ? this.renderAcknowledgement(one) : nothing}
|
|
247
|
+
${completed
|
|
248
|
+
? html `<section class="page-section">
|
|
249
|
+
<header class="page-section-header"><h3>${t('title.learning-certificate')}</h3></header>
|
|
250
|
+
<p class="page-section-note">${t('text.learning-completed-detail', { day: dayOf(one.completedAt), number: one.certificateNo ?? '' })}</p>
|
|
251
|
+
<md-filled-button style="align-self:flex-start" @click=${() => issueDocument({ kind: 'learning.certificate', refId: one.id })}>${t('button.learning-certificate')}</md-filled-button>
|
|
252
|
+
</section>`
|
|
253
|
+
: nothing}
|
|
254
|
+
</div>`;
|
|
255
|
+
}
|
|
256
|
+
renderLesson(one, row) {
|
|
257
|
+
const unit = row.unit;
|
|
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);
|
|
261
|
+
if (unit.kind === 'LINK')
|
|
262
|
+
return html `<h3>${unit.name}</h3>
|
|
263
|
+
<p class="page-section-note">${t('text.learning-link-not-counted')}</p>
|
|
264
|
+
<a href=${unit.url} target="_blank" rel="noopener noreferrer">${unit.url}</a>`;
|
|
265
|
+
/*
|
|
266
|
+
* One element per enrolment and unit: leaving one, its element says its own time to its own enrolment and unit -
|
|
267
|
+
* never under the next. Keyed by the unit alone, the same unit opened from another enrolment kept the element and
|
|
268
|
+
* its clock, and the first enrolment's seconds went out under the second (2026-11-08).
|
|
269
|
+
*/
|
|
270
|
+
return html `<h3>${unit.name}</h3>
|
|
271
|
+
${keyed(`${one.id}:${unit.id}`, html `<ox-lesson
|
|
272
|
+
.kind=${unit.kind}
|
|
273
|
+
.src=${unit.file?.fullpath}
|
|
274
|
+
.body=${unit.body ?? ''}
|
|
275
|
+
.label=${unit.name}
|
|
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}
|
|
281
|
+
.requiredSeconds=${unit.minSeconds ?? 60}
|
|
282
|
+
.learnedSeconds=${progress.learnedSeconds ?? 0}
|
|
283
|
+
.done=${!!row.progress?.doneAt || !!progress.doneAt || one.status === 'COMPLETED'}
|
|
284
|
+
@lesson-tick=${(e) => this.tick(one.id, unit.id, e.detail)}
|
|
285
|
+
></ox-lesson>`)}
|
|
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>`}`;
|
|
289
|
+
}
|
|
290
|
+
renderQuiz(one) {
|
|
291
|
+
const completed = one.status === 'COMPLETED';
|
|
292
|
+
const passed = one.score != null && one.version?.passScore != null && one.score >= one.version.passScore;
|
|
293
|
+
return html `<section class="page-section">
|
|
294
|
+
<header class="page-section-header"><h3>${t('title.learning-quiz')}</h3></header>
|
|
295
|
+
<p class="page-section-note">
|
|
296
|
+
${t('text.learning-quiz-rule', { count: one.quizSize, pass: one.version.passScore })}
|
|
297
|
+
${one.attemptsLeft != null ? t('text.learning-attempts-left', { count: one.attemptsLeft }) : ''}
|
|
298
|
+
${one.score != null ? t('text.learning-best-score', { score: one.score }) : ''}
|
|
299
|
+
</p>
|
|
300
|
+
${this.result
|
|
301
|
+
? 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>`
|
|
302
|
+
: nothing}
|
|
303
|
+
${this.drawn
|
|
304
|
+
? html `<ox-quiz .questions=${this.drawn.questions} .busy=${this.quizSubmit.pending} @quiz-submit=${(e) => this.submit(e.detail.answers)}></ox-quiz>`
|
|
305
|
+
: !completed && !passed && one.attemptsLeft !== 0
|
|
306
|
+
? 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>`
|
|
307
|
+
: nothing}
|
|
308
|
+
${this.quizStart.error || this.quizSubmit.error ? html `<div class="error" role="alert">${this.errorText(this.quizStart.error || this.quizSubmit.error)}</div>` : nothing}
|
|
309
|
+
</section>`;
|
|
310
|
+
}
|
|
311
|
+
renderAcknowledgement(one) {
|
|
312
|
+
return html `<section class="page-section">
|
|
313
|
+
<header class="page-section-header"><h3>${t('title.learning-acknowledgement')}</h3></header>
|
|
314
|
+
<p class="acknowledgement">${one.version.acknowledgement}</p>
|
|
315
|
+
${one.acknowledgedAt
|
|
316
|
+
? html `<p class="page-section-note">${t('text.learning-acknowledged-on', { day: dayOf(one.acknowledgedAt) })}</p>`
|
|
317
|
+
: html `<label class="check"><input type="checkbox" .checked=${this.agreed} @change=${(e) => (this.agreed = e.target.checked)} />${t('text.learning-acknowledge-label')}</label>
|
|
318
|
+
<md-filled-button style="align-self:flex-start" ?disabled=${!this.agreed || this.acknowledgement.pending} @click=${() => this.acknowledgement.run({ id: one.id })}
|
|
319
|
+
>${t('button.learning-acknowledge')}</md-filled-button
|
|
320
|
+
>
|
|
321
|
+
${this.acknowledgement.error ? html `<div class="error" role="alert">${this.errorText(this.acknowledgement.error)}</div>` : nothing}`}
|
|
322
|
+
</section>`;
|
|
323
|
+
}
|
|
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;
|
|
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();
|
|
343
|
+
}
|
|
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
|
+
}
|
|
354
|
+
this.ticks = this.ticks.then(async () => {
|
|
355
|
+
const before = this.enrollment;
|
|
356
|
+
const answer = await this.tickSave.run(variables);
|
|
357
|
+
if (!answer) {
|
|
358
|
+
this.tickError = this.errorText(this.tickSave.error);
|
|
359
|
+
await this.read.refetch();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.tickError = '';
|
|
363
|
+
this.keep(enrollmentId, unitId, answer.recordLearningTick);
|
|
364
|
+
const row = before?.units?.find((one) => one.unit.id === unitId);
|
|
365
|
+
const newlyDone = !!answer.recordLearningTick.progress.doneAt && !row?.progress?.doneAt;
|
|
366
|
+
if (newlyDone || answer.recordLearningTick.enrollment.status !== before?.status)
|
|
367
|
+
await this.read.refetch();
|
|
368
|
+
});
|
|
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
|
+
}
|
|
391
|
+
async startQuiz(enrollmentId) {
|
|
392
|
+
this.result = undefined;
|
|
393
|
+
const answer = await this.quizStart.run({ enrollmentId });
|
|
394
|
+
if (answer)
|
|
395
|
+
this.drawn = answer.startQuizAttempt;
|
|
396
|
+
}
|
|
397
|
+
async submit(answers) {
|
|
398
|
+
if (!this.drawn)
|
|
399
|
+
return;
|
|
400
|
+
const answer = await this.quizSubmit.run({ attemptId: this.drawn.attemptId, answers });
|
|
401
|
+
if (!answer)
|
|
402
|
+
return;
|
|
403
|
+
this.result = answer.submitQuizAttempt;
|
|
404
|
+
this.drawn = undefined;
|
|
405
|
+
}
|
|
406
|
+
errorText(error) {
|
|
407
|
+
return error?.graphQLErrors?.[0]?.message ?? error?.message ?? '';
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
__decorate([
|
|
411
|
+
state(),
|
|
412
|
+
__metadata("design:type", Object)
|
|
413
|
+
], LearningView.prototype, "enrollmentId", void 0);
|
|
414
|
+
__decorate([
|
|
415
|
+
state(),
|
|
416
|
+
__metadata("design:type", Object)
|
|
417
|
+
], LearningView.prototype, "unitId", void 0);
|
|
418
|
+
__decorate([
|
|
419
|
+
state(),
|
|
420
|
+
__metadata("design:type", Object)
|
|
421
|
+
], LearningView.prototype, "drawn", void 0);
|
|
422
|
+
__decorate([
|
|
423
|
+
state(),
|
|
424
|
+
__metadata("design:type", Object)
|
|
425
|
+
], LearningView.prototype, "result", void 0);
|
|
426
|
+
__decorate([
|
|
427
|
+
state(),
|
|
428
|
+
__metadata("design:type", Object)
|
|
429
|
+
], LearningView.prototype, "agreed", void 0);
|
|
430
|
+
__decorate([
|
|
431
|
+
state(),
|
|
432
|
+
__metadata("design:type", Object)
|
|
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);
|
|
442
|
+
LearningView = __decorate([
|
|
443
|
+
customElement('learning-view')
|
|
444
|
+
], LearningView);
|
|
445
|
+
export { LearningView };
|
|
446
|
+
//# sourceMappingURL=learning-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learning-view.js","sourceRoot":"","sources":["../../client/pages/learning-view.ts"],"names":[],"mappings":"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","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"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import '@material/web/button/filled-tonal-button.js';
|
|
2
|
+
import '@operato/learning/ox-learning-progress.js';
|
|
3
|
+
import '@operato/list-state/ox-empty-note.js';
|
|
4
|
+
import '@operato/list-state/ox-page-state.js';
|
|
5
|
+
import { PageView } from '@operato/shell';
|
|
6
|
+
declare const MyLearning_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
7
|
+
/**
|
|
8
|
+
* 내 학습 (ADR-0194 decision 10) - the courses the person is to take, by their due day, with how far each went, and the
|
|
9
|
+
* ones completed. A card opens the course; a phone shows the same cards in one column.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MyLearning extends MyLearning_base {
|
|
12
|
+
static styles: import("lit").CSSResult[];
|
|
13
|
+
private read;
|
|
14
|
+
get context(): {
|
|
15
|
+
title: string;
|
|
16
|
+
frame: string;
|
|
17
|
+
};
|
|
18
|
+
pageUpdated(changes: any): Promise<void>;
|
|
19
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
20
|
+
private card;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import '@material/web/button/filled-tonal-button.js';
|
|
3
|
+
import '@operato/learning/ox-learning-progress.js';
|
|
4
|
+
import '@operato/list-state/ox-empty-note.js';
|
|
5
|
+
import '@operato/list-state/ox-page-state.js';
|
|
6
|
+
import gql from 'graphql-tag';
|
|
7
|
+
import { css, html, nothing } from 'lit';
|
|
8
|
+
import { customElement } from 'lit/decorators.js';
|
|
9
|
+
import { QueryController } from '@operato/graphql';
|
|
10
|
+
import { i18next, localize } from '@operato/i18n';
|
|
11
|
+
import { navigate, PageView } from '@operato/shell';
|
|
12
|
+
import { DocumentPageStyles } from '@operato/styles';
|
|
13
|
+
import { MY_LIST_FIELDS, unitsDoneOf } from '../utils/learning-api.js';
|
|
14
|
+
import { categoryWord, dayOf, stateWord, t } from '../utils/learning-words.js';
|
|
15
|
+
/**
|
|
16
|
+
* 내 학습 (ADR-0194 decision 10) - the courses the person is to take, by their due day, with how far each went, and the
|
|
17
|
+
* ones completed. A card opens the course; a phone shows the same cards in one column.
|
|
18
|
+
*/
|
|
19
|
+
let MyLearning = class MyLearning extends localize(i18next)(PageView) {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.read = new QueryController(this, gql `query { myEnrollments { ${MY_LIST_FIELDS} } }`);
|
|
23
|
+
}
|
|
24
|
+
static { this.styles = [
|
|
25
|
+
DocumentPageStyles,
|
|
26
|
+
css `
|
|
27
|
+
.page-card {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: var(--spacing-medium, 8px);
|
|
31
|
+
min-width: 0;
|
|
32
|
+
}
|
|
33
|
+
.page-card h4 {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: var(--ui-t-title, 15px);
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
overflow-wrap: anywhere;
|
|
38
|
+
}
|
|
39
|
+
.meta {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
|
|
43
|
+
color: var(--ui-muted);
|
|
44
|
+
font-size: var(--ui-t-small, 12px);
|
|
45
|
+
}
|
|
46
|
+
.state {
|
|
47
|
+
padding: 0 8px;
|
|
48
|
+
border-radius: 999px;
|
|
49
|
+
background: var(--ui-line-soft, var(--md-sys-color-surface-container));
|
|
50
|
+
color: var(--ui-ink);
|
|
51
|
+
}
|
|
52
|
+
.state[overdue] {
|
|
53
|
+
background: var(--ui-crit-soft, var(--md-sys-color-error-container));
|
|
54
|
+
color: var(--ui-crit, var(--md-sys-color-on-error-container));
|
|
55
|
+
}
|
|
56
|
+
.page-card md-filled-tonal-button {
|
|
57
|
+
align-self: flex-start;
|
|
58
|
+
}
|
|
59
|
+
`
|
|
60
|
+
]; }
|
|
61
|
+
get context() {
|
|
62
|
+
return { title: t('title.learning-my-learning'), frame: 'document' };
|
|
63
|
+
}
|
|
64
|
+
async pageUpdated(changes) {
|
|
65
|
+
if (this.active && changes.active && this.read.data)
|
|
66
|
+
await this.read.refetch();
|
|
67
|
+
}
|
|
68
|
+
render() {
|
|
69
|
+
const rows = this.read.data?.myEnrollments ?? [];
|
|
70
|
+
const open = rows.filter(one => one.status !== 'COMPLETED');
|
|
71
|
+
const done = rows.filter(one => one.status === 'COMPLETED');
|
|
72
|
+
return html `<ox-page-state .loading=${this.read.loading} .error=${this.read.error} .held=${!!this.read.data} .answeredAt=${this.read.answeredAt} @retry=${() => this.read.refetch()}>
|
|
73
|
+
<div class="page">
|
|
74
|
+
<section class="page-section">
|
|
75
|
+
<header class="page-section-header"><h3>${t('title.learning-to-take')}</h3></header>
|
|
76
|
+
${open.length ? html `<div class="page-card-grid">${open.map(one => this.card(one))}</div>` : html `<ox-empty-note title=${t('text.learning-nothing-to-take')}></ox-empty-note>`}
|
|
77
|
+
</section>
|
|
78
|
+
${done.length
|
|
79
|
+
? html `<section class="page-section">
|
|
80
|
+
<header class="page-section-header"><h3>${t('title.learning-completed')}</h3></header>
|
|
81
|
+
<div class="page-card-grid">${done.map(one => this.card(one))}</div>
|
|
82
|
+
</section>`
|
|
83
|
+
: nothing}
|
|
84
|
+
</div>
|
|
85
|
+
</ox-page-state>`;
|
|
86
|
+
}
|
|
87
|
+
card(one) {
|
|
88
|
+
const { done, total } = unitsDoneOf(one.units);
|
|
89
|
+
const action = one.status === 'COMPLETED' ? 'button.learning-review' : one.status === 'ASSIGNED' ? 'button.learning-start' : 'button.learning-continue';
|
|
90
|
+
return html `<article class="page-card">
|
|
91
|
+
<h4>${one.course?.name}</h4>
|
|
92
|
+
<div class="meta">
|
|
93
|
+
<span class="state" ?overdue=${one.state === 'OVERDUE'}>${stateWord(one.state)}</span>
|
|
94
|
+
${one.course?.category ? html `<span>${categoryWord(one.course.category)}</span>` : nothing}
|
|
95
|
+
${one.status === 'COMPLETED'
|
|
96
|
+
? html `<span>${t('text.learning-completed-on', { day: dayOf(one.completedAt) })}</span>`
|
|
97
|
+
: one.dueOn
|
|
98
|
+
? html `<span>${t('text.learning-due-on', { day: one.dueOn })}</span>`
|
|
99
|
+
: nothing}
|
|
100
|
+
</div>
|
|
101
|
+
<ox-learning-progress .value=${done} .max=${total} ?done=${one.status === 'COMPLETED'} label=${t('label.learning-units-done')}></ox-learning-progress>
|
|
102
|
+
<md-filled-tonal-button @click=${() => navigate(`learning-view?id=${one.id}`)}>${t(action)}</md-filled-tonal-button>
|
|
103
|
+
</article>`;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
MyLearning = __decorate([
|
|
107
|
+
customElement('my-learning')
|
|
108
|
+
], MyLearning);
|
|
109
|
+
export { MyLearning };
|
|
110
|
+
//# sourceMappingURL=my-learning.js.map
|