@things-factory/learning 10.1.260 → 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/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 -1
- package/dist-client/pages/learning-course-list.js +6 -2
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +28 -16
- package/dist-client/pages/learning-enrollment-status.js +95 -112
- 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 +1 -0
- package/dist-server/service/course/course-query.js +14 -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-query.d.ts +5 -4
- package/dist-server/service/enrollment/enrollment-query.js +26 -35
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.js +13 -3
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +31 -18
- package/dist-server/service/enrollment/enrollment-status.js +101 -42
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +1 -14
- package/dist-server/service/enrollment/enrollment-type.js +6 -54
- 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/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 +36 -11
- package/dist-server/service/enrollment/progress-rules.js +47 -12
- 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 +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/translations/en.json +30 -7
- package/translations/es.json +30 -7
- package/translations/ja.json +30 -7
- package/translations/ko.json +30 -7
- package/translations/ms.json +30 -7
- package/translations/zh.json +30 -7
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import '@material/web/button/filled-button.js';
|
|
2
|
+
import '@material/web/button/text-button.js';
|
|
3
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
4
|
+
import { LitElement } from 'lit';
|
|
5
|
+
type SourceUnit = {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: string;
|
|
8
|
+
name: string;
|
|
9
|
+
file?: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
fullpath: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function openMaterialForm(units: SourceUnit[]): Promise<boolean>;
|
|
16
|
+
declare const LearningMaterialForm_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
17
|
+
export declare class LearningMaterialForm extends LearningMaterialForm_base {
|
|
18
|
+
static styles: import("lit").CSSResult[];
|
|
19
|
+
units: SourceUnit[];
|
|
20
|
+
private unitId;
|
|
21
|
+
private instruction;
|
|
22
|
+
private captions;
|
|
23
|
+
private busy;
|
|
24
|
+
private error;
|
|
25
|
+
private proposal;
|
|
26
|
+
private source?;
|
|
27
|
+
private controller?;
|
|
28
|
+
private generation;
|
|
29
|
+
private transcribe;
|
|
30
|
+
private propose;
|
|
31
|
+
private apply;
|
|
32
|
+
willUpdate(): void;
|
|
33
|
+
disconnectedCallback(): void;
|
|
34
|
+
private get selected();
|
|
35
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
36
|
+
private readCaptions;
|
|
37
|
+
private message;
|
|
38
|
+
private generate;
|
|
39
|
+
private save;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/button/filled-button.js';
|
|
3
|
+
import '@material/web/button/text-button.js';
|
|
4
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
5
|
+
import gql from 'graphql-tag';
|
|
6
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
7
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
8
|
+
import { MutationController } from '@operato/graphql';
|
|
9
|
+
import { i18next, localize } from '@operato/i18n';
|
|
10
|
+
import { openPopup } from '@operato/layout';
|
|
11
|
+
import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles';
|
|
12
|
+
import { captionParts, pdfMaterial } from '../utils/material-source.js';
|
|
13
|
+
import { t } from '../utils/learning-words.js';
|
|
14
|
+
export function openMaterialForm(units) {
|
|
15
|
+
const popup = openPopup(html `<learning-material-form .units=${units} @done=${() => popup.close(true)} @close=${() => popup.close()}></learning-material-form>`, { backdrop: true, size: 'large', title: t('title.learning-ai-material') });
|
|
16
|
+
return popup.result.then(value => !!value);
|
|
17
|
+
}
|
|
18
|
+
let LearningMaterialForm = class LearningMaterialForm extends localize(i18next)(LitElement) {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
this.units = [];
|
|
22
|
+
this.unitId = '';
|
|
23
|
+
this.instruction = '';
|
|
24
|
+
this.captions = '';
|
|
25
|
+
this.busy = false;
|
|
26
|
+
this.error = '';
|
|
27
|
+
this.generation = 0;
|
|
28
|
+
this.transcribe = new MutationController(this, gql `mutation ($unitId: String!) { transcribeLearningMaterial(unitId: $unitId) }`);
|
|
29
|
+
this.propose = new MutationController(this, gql `mutation ($unitId: String!, $source: Object!, $instruction: String!) { proposeLearningMaterial(unitId: $unitId, source: $source, instruction: $instruction) }`);
|
|
30
|
+
this.apply = new MutationController(this, gql `mutation ($unitId: String!, $source: Object!, $draft: Object!, $token: String!) { applyLearningMaterial(unitId: $unitId, source: $source, draft: $draft, token: $token) }`);
|
|
31
|
+
}
|
|
32
|
+
static { this.styles = [CommonPopupStyles, PopupBodyStyles, FormFieldStyles, PopupFieldStyles, css `
|
|
33
|
+
textarea { min-height: 100px; resize: vertical; }
|
|
34
|
+
.preview { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
35
|
+
.error { color: var(--md-sys-color-error); }
|
|
36
|
+
`]; }
|
|
37
|
+
willUpdate() { if (!this.unitId && this.units.length)
|
|
38
|
+
this.unitId = this.units[0].id; }
|
|
39
|
+
disconnectedCallback() { super.disconnectedCallback(); this.generation++; this.controller?.abort(); }
|
|
40
|
+
get selected() { return this.units.find(u => u.id === this.unitId); }
|
|
41
|
+
render() {
|
|
42
|
+
const draft = this.proposal?.draft;
|
|
43
|
+
return html `
|
|
44
|
+
<div class="body">
|
|
45
|
+
<p>${t('text.learning-ai-material-help')}</p>
|
|
46
|
+
<label class="field"><span>${t('field.learning-ai-source')}</span>
|
|
47
|
+
<select ?disabled=${this.busy || !!draft} .value=${this.unitId} @change=${(e) => { this.unitId = e.target.value; this.source = undefined; this.captions = ''; }}>
|
|
48
|
+
${this.units.map(u => html `<option value=${u.id}>${u.name} · ${u.file?.name}</option>`)}
|
|
49
|
+
</select>
|
|
50
|
+
</label>
|
|
51
|
+
${this.selected?.kind === 'VIDEO' ? html `
|
|
52
|
+
<label class="field"><span>${t('field.learning-ai-captions')}</span><input type="file" accept=".vtt,.srt,.txt" ?disabled=${this.busy || !!draft} @change=${(e) => this.readCaptions(e.target.files?.[0])} /></label>
|
|
53
|
+
<label class="field"><span>${t('text.learning-ai-captions-help')}</span><textarea maxlength="120000" ?disabled=${this.busy || !!draft} .value=${this.captions} @input=${(e) => this.captions = e.target.value}></textarea></label>
|
|
54
|
+
` : nothing}
|
|
55
|
+
<label class="field"><span>${t('field.learning-ai-instruction')}</span><textarea maxlength="3900" ?disabled=${this.busy || !!draft} .value=${this.instruction} @input=${(e) => this.instruction = e.target.value}></textarea></label>
|
|
56
|
+
${this.busy ? html `<p role="status">${t('text.learning-ai-working')}</p>` : nothing}
|
|
57
|
+
${this.error ? html `<p class="error" role="alert">${this.error}</p>` : nothing}
|
|
58
|
+
${draft ? html `<section aria-label=${t('title.learning-ai-preview')}>
|
|
59
|
+
<h3>${draft.title}</h3><p class="preview">${draft.introduction}</p>
|
|
60
|
+
<ul>${draft.objectives.map((s) => html `<li>${s}</li>`)}</ul>
|
|
61
|
+
${draft.units.map((u) => html `<details><summary>${u.name}</summary><p class="preview">${u.body}</p><p>${u.references.join(', ')} · ${u.minSeconds}s</p></details>`)}
|
|
62
|
+
<h3>${t('title.learning-questions')}</h3>
|
|
63
|
+
${draft.questions.map((q, i) => html `<details><summary>${i + 1}. ${q.text}</summary><ul>${q.choices.map((c) => html `<li>${c.correct ? '✓ ' : ''}${c.text}</li>`)}</ul><p>${q.explanation}</p><p>${q.references.join(', ')}</p></details>`)}
|
|
64
|
+
<details><summary>${t('field.learning-ai-source')}</summary>
|
|
65
|
+
<a href=${this.selected?.file?.fullpath ?? ''} target="_blank" rel="noopener noreferrer">${this.selected?.file?.name}</a>
|
|
66
|
+
${this.source?.parts.map(p => html `<details><summary>${p.id}</summary>${p.text ? html `<p class="preview">${p.text}</p>` : html `<img style="max-width:100%" src=${p.image} alt=${p.id} />`}</details>`)}
|
|
67
|
+
</details>
|
|
68
|
+
<h3>${t('title.learning-ai-missing')}</h3><ul>${draft.missing.map((s) => html `<li>${s}</li>`)}</ul>
|
|
69
|
+
<p>${t('text.learning-ai-apply-help')}</p>
|
|
70
|
+
<md-text-button ?disabled=${this.busy} @click=${() => { this.proposal = undefined; this.error = ''; }}>${t('button.learning-ai-revise')}</md-text-button>
|
|
71
|
+
</section>` : nothing}
|
|
72
|
+
</div>
|
|
73
|
+
<ox-popup-actions>
|
|
74
|
+
<md-text-button slot="cancel" ?disabled=${this.apply.pending} @click=${() => this.dispatchEvent(new CustomEvent('close'))}>${t('button.cancel')}</md-text-button>
|
|
75
|
+
<md-filled-button slot="confirm" ?disabled=${this.busy || !this.selected?.file} @click=${() => draft ? this.save() : this.generate()}>${draft ? t('button.learning-ai-apply') : t('button.learning-ai-generate')}</md-filled-button>
|
|
76
|
+
</ox-popup-actions>`;
|
|
77
|
+
}
|
|
78
|
+
async readCaptions(file) {
|
|
79
|
+
if (!file)
|
|
80
|
+
return;
|
|
81
|
+
this.captions = '';
|
|
82
|
+
if (file.size > 500_000) {
|
|
83
|
+
this.error = t('error.learning-ai-source-limit');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const id = ++this.generation;
|
|
87
|
+
this.busy = true;
|
|
88
|
+
this.error = '';
|
|
89
|
+
try {
|
|
90
|
+
const text = await file.text();
|
|
91
|
+
captionParts(text);
|
|
92
|
+
if (id === this.generation && this.isConnected) {
|
|
93
|
+
this.captions = text;
|
|
94
|
+
this.error = '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
if (id === this.generation)
|
|
99
|
+
this.error = t('error.learning-ai-source-limit');
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (id === this.generation)
|
|
103
|
+
this.busy = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
message(error) {
|
|
107
|
+
const key = error?.message;
|
|
108
|
+
return key?.startsWith('learning-ai-') ? t(`error.${key}`) : error?.graphQLErrors?.[0]?.message ?? key ?? t('error.learning-ai-source-read');
|
|
109
|
+
}
|
|
110
|
+
async generate() {
|
|
111
|
+
const unit = this.selected;
|
|
112
|
+
if (!unit?.file || this.busy)
|
|
113
|
+
return;
|
|
114
|
+
const id = ++this.generation;
|
|
115
|
+
this.busy = true;
|
|
116
|
+
this.error = '';
|
|
117
|
+
this.controller = new AbortController();
|
|
118
|
+
try {
|
|
119
|
+
if (unit.kind === 'VIDEO' && !this.captions.trim()) {
|
|
120
|
+
const answer = await this.transcribe.run({ unitId: unit.id }, { context: { fetchOptions: { signal: this.controller.signal } } });
|
|
121
|
+
if (id !== this.generation)
|
|
122
|
+
return;
|
|
123
|
+
if (!answer)
|
|
124
|
+
throw this.transcribe.error;
|
|
125
|
+
if (answer.transcribeLearningMaterial.fileId !== unit.file.id)
|
|
126
|
+
throw new Error('learning-ai-draft-stale');
|
|
127
|
+
this.captions = answer.transcribeLearningMaterial.captions;
|
|
128
|
+
}
|
|
129
|
+
const source = unit.kind === 'PDF' ? await pdfMaterial(unit.file.id, unit.file.fullpath, this.controller.signal) : { fileId: unit.file.id, parts: captionParts(this.captions) };
|
|
130
|
+
if (id !== this.generation)
|
|
131
|
+
return;
|
|
132
|
+
this.source = source;
|
|
133
|
+
const answer = await this.propose.run({ unitId: unit.id, source, instruction: `Language: ${i18next.language}. ${this.instruction}` }, { context: { fetchOptions: { signal: this.controller.signal } } });
|
|
134
|
+
if (id !== this.generation)
|
|
135
|
+
return;
|
|
136
|
+
if (!answer)
|
|
137
|
+
throw this.propose.error;
|
|
138
|
+
this.proposal = answer.proposeLearningMaterial;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (id === this.generation)
|
|
142
|
+
this.error = this.message(error);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
if (id === this.generation)
|
|
146
|
+
this.busy = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async save() {
|
|
150
|
+
if (!this.proposal || !this.source || this.busy)
|
|
151
|
+
return;
|
|
152
|
+
this.busy = true;
|
|
153
|
+
this.error = '';
|
|
154
|
+
try {
|
|
155
|
+
const answer = await this.apply.run({ unitId: this.unitId, source: this.source, draft: this.proposal.draft, token: this.proposal.token });
|
|
156
|
+
if (!answer) {
|
|
157
|
+
this.error = this.message(this.apply.error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.dispatchEvent(new CustomEvent('done'));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.error = this.message(error);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
this.busy = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
__decorate([
|
|
171
|
+
property({ attribute: false }),
|
|
172
|
+
__metadata("design:type", Array)
|
|
173
|
+
], LearningMaterialForm.prototype, "units", void 0);
|
|
174
|
+
__decorate([
|
|
175
|
+
state(),
|
|
176
|
+
__metadata("design:type", Object)
|
|
177
|
+
], LearningMaterialForm.prototype, "unitId", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
state(),
|
|
180
|
+
__metadata("design:type", Object)
|
|
181
|
+
], LearningMaterialForm.prototype, "instruction", void 0);
|
|
182
|
+
__decorate([
|
|
183
|
+
state(),
|
|
184
|
+
__metadata("design:type", Object)
|
|
185
|
+
], LearningMaterialForm.prototype, "captions", void 0);
|
|
186
|
+
__decorate([
|
|
187
|
+
state(),
|
|
188
|
+
__metadata("design:type", Object)
|
|
189
|
+
], LearningMaterialForm.prototype, "busy", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
state(),
|
|
192
|
+
__metadata("design:type", Object)
|
|
193
|
+
], LearningMaterialForm.prototype, "error", void 0);
|
|
194
|
+
__decorate([
|
|
195
|
+
state(),
|
|
196
|
+
__metadata("design:type", Object)
|
|
197
|
+
], LearningMaterialForm.prototype, "proposal", void 0);
|
|
198
|
+
LearningMaterialForm = __decorate([
|
|
199
|
+
customElement('learning-material-form')
|
|
200
|
+
], LearningMaterialForm);
|
|
201
|
+
export { LearningMaterialForm };
|
|
202
|
+
//# sourceMappingURL=learning-material-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learning-material-form.js","sourceRoot":"","sources":["../../client/components/learning-material-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/popup/ox-popup-actions.js'\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { MutationController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\nimport { captionParts, pdfMaterial, type MaterialSource } from '../utils/material-source.js'\nimport { t } from '../utils/learning-words.js'\n\ntype SourceUnit = { id: string; kind: string; name: string; file?: { id: string; name: string; fullpath: string } }\nexport function openMaterialForm(units: SourceUnit[]): Promise<boolean> {\n const popup = openPopup(html`<learning-material-form .units=${units} @done=${() => popup.close(true)} @close=${() => popup.close()}></learning-material-form>`, { backdrop: true, size: 'large', title: t('title.learning-ai-material') })\n return popup.result.then(value => !!value)\n}\n\n@customElement('learning-material-form')\nexport class LearningMaterialForm extends localize(i18next)(LitElement) {\n static styles = [CommonPopupStyles, PopupBodyStyles, FormFieldStyles, PopupFieldStyles, css`\n textarea { min-height: 100px; resize: vertical; }\n .preview { white-space: pre-wrap; overflow-wrap: anywhere; }\n .error { color: var(--md-sys-color-error); }\n `]\n @property({ attribute: false }) units: SourceUnit[] = []\n @state() private unitId = ''\n @state() private instruction = ''\n @state() private captions = ''\n @state() private busy = false\n @state() private error = ''\n @state() private proposal: any\n private source?: MaterialSource\n private controller?: AbortController\n private generation = 0\n private transcribe = new MutationController(this, gql`mutation ($unitId: String!) { transcribeLearningMaterial(unitId: $unitId) }`)\n private propose = new MutationController(this, gql`mutation ($unitId: String!, $source: Object!, $instruction: String!) { proposeLearningMaterial(unitId: $unitId, source: $source, instruction: $instruction) }`)\n private apply = new MutationController(this, gql`mutation ($unitId: String!, $source: Object!, $draft: Object!, $token: String!) { applyLearningMaterial(unitId: $unitId, source: $source, draft: $draft, token: $token) }`)\n\n willUpdate() { if (!this.unitId && this.units.length) this.unitId = this.units[0].id }\n disconnectedCallback() { super.disconnectedCallback(); this.generation++; this.controller?.abort() }\n private get selected() { return this.units.find(u => u.id === this.unitId) }\n\n render() {\n const draft = this.proposal?.draft\n return html`\n <div class=\"body\">\n <p>${t('text.learning-ai-material-help')}</p>\n <label class=\"field\"><span>${t('field.learning-ai-source')}</span>\n <select ?disabled=${this.busy || !!draft} .value=${this.unitId} @change=${(e: any) => { this.unitId = e.target.value; this.source = undefined; this.captions = '' }}>\n ${this.units.map(u => html`<option value=${u.id}>${u.name} · ${u.file?.name}</option>`)}\n </select>\n </label>\n ${this.selected?.kind === 'VIDEO' ? html`\n <label class=\"field\"><span>${t('field.learning-ai-captions')}</span><input type=\"file\" accept=\".vtt,.srt,.txt\" ?disabled=${this.busy || !!draft} @change=${(e: any) => this.readCaptions(e.target.files?.[0])} /></label>\n <label class=\"field\"><span>${t('text.learning-ai-captions-help')}</span><textarea maxlength=\"120000\" ?disabled=${this.busy || !!draft} .value=${this.captions} @input=${(e: any) => this.captions = e.target.value}></textarea></label>\n ` : nothing}\n <label class=\"field\"><span>${t('field.learning-ai-instruction')}</span><textarea maxlength=\"3900\" ?disabled=${this.busy || !!draft} .value=${this.instruction} @input=${(e: any) => this.instruction = e.target.value}></textarea></label>\n ${this.busy ? html`<p role=\"status\">${t('text.learning-ai-working')}</p>` : nothing}\n ${this.error ? html`<p class=\"error\" role=\"alert\">${this.error}</p>` : nothing}\n ${draft ? html`<section aria-label=${t('title.learning-ai-preview')}>\n <h3>${draft.title}</h3><p class=\"preview\">${draft.introduction}</p>\n <ul>${draft.objectives.map((s: string) => html`<li>${s}</li>`)}</ul>\n ${draft.units.map((u: any) => html`<details><summary>${u.name}</summary><p class=\"preview\">${u.body}</p><p>${u.references.join(', ')} · ${u.minSeconds}s</p></details>`)}\n <h3>${t('title.learning-questions')}</h3>\n ${draft.questions.map((q: any, i: number) => html`<details><summary>${i + 1}. ${q.text}</summary><ul>${q.choices.map((c: any) => html`<li>${c.correct ? '✓ ' : ''}${c.text}</li>`)}</ul><p>${q.explanation}</p><p>${q.references.join(', ')}</p></details>`)}\n <details><summary>${t('field.learning-ai-source')}</summary>\n <a href=${this.selected?.file?.fullpath ?? ''} target=\"_blank\" rel=\"noopener noreferrer\">${this.selected?.file?.name}</a>\n ${this.source?.parts.map(p => html`<details><summary>${p.id}</summary>${p.text ? html`<p class=\"preview\">${p.text}</p>` : html`<img style=\"max-width:100%\" src=${p.image} alt=${p.id} />`}</details>`)}\n </details>\n <h3>${t('title.learning-ai-missing')}</h3><ul>${draft.missing.map((s: string) => html`<li>${s}</li>`)}</ul>\n <p>${t('text.learning-ai-apply-help')}</p>\n <md-text-button ?disabled=${this.busy} @click=${() => { this.proposal = undefined; this.error = '' }}>${t('button.learning-ai-revise')}</md-text-button>\n </section>` : nothing}\n </div>\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" ?disabled=${this.apply.pending} @click=${() => this.dispatchEvent(new CustomEvent('close'))}>${t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${this.busy || !this.selected?.file} @click=${() => draft ? this.save() : this.generate()}>${draft ? t('button.learning-ai-apply') : t('button.learning-ai-generate')}</md-filled-button>\n </ox-popup-actions>`\n }\n private async readCaptions(file?: File) {\n if (!file) return\n this.captions = ''\n if (file.size > 500_000) { this.error = t('error.learning-ai-source-limit'); return }\n const id = ++this.generation\n this.busy = true; this.error = ''\n try {\n const text = await file.text()\n captionParts(text)\n if (id === this.generation && this.isConnected) { this.captions = text; this.error = '' }\n } catch { if (id === this.generation) this.error = t('error.learning-ai-source-limit') }\n finally { if (id === this.generation) this.busy = false }\n }\n private message(error: any) {\n const key = error?.message\n return key?.startsWith('learning-ai-') ? t(`error.${key}`) : error?.graphQLErrors?.[0]?.message ?? key ?? t('error.learning-ai-source-read')\n }\n private async generate() {\n const unit = this.selected\n if (!unit?.file || this.busy) return\n const id = ++this.generation\n this.busy = true; this.error = ''; this.controller = new AbortController()\n try {\n if (unit.kind === 'VIDEO' && !this.captions.trim()) {\n const answer = await this.transcribe.run({ unitId: unit.id }, { context: { fetchOptions: { signal: this.controller.signal } } })\n if (id !== this.generation) return\n if (!answer) throw this.transcribe.error\n if (answer.transcribeLearningMaterial.fileId !== unit.file.id) throw new Error('learning-ai-draft-stale')\n this.captions = answer.transcribeLearningMaterial.captions\n }\n const source = unit.kind === 'PDF' ? await pdfMaterial(unit.file.id, unit.file.fullpath, this.controller.signal) : { fileId: unit.file.id, parts: captionParts(this.captions) }\n if (id !== this.generation) return\n this.source = source\n const answer = await this.propose.run({ unitId: unit.id, source, instruction: `Language: ${i18next.language}. ${this.instruction}` }, { context: { fetchOptions: { signal: this.controller.signal } } })\n if (id !== this.generation) return\n if (!answer) throw this.propose.error\n this.proposal = answer.proposeLearningMaterial\n } catch (error) { if (id === this.generation) this.error = this.message(error) }\n finally { if (id === this.generation) this.busy = false }\n }\n private async save() {\n if (!this.proposal || !this.source || this.busy) return\n this.busy = true; this.error = ''\n try {\n const answer = await this.apply.run({ unitId: this.unitId, source: this.source, draft: this.proposal.draft, token: this.proposal.token })\n if (!answer) { this.error = this.message(this.apply.error); return }\n this.dispatchEvent(new CustomEvent('done'))\n } catch (error) { this.error = this.message(error) }\n finally { this.busy = false }\n }\n}\n"]}
|
|
@@ -12,6 +12,7 @@ export type UnitDraft = {
|
|
|
12
12
|
url?: string | null;
|
|
13
13
|
minSeconds?: number | null;
|
|
14
14
|
durationSeconds?: number | null;
|
|
15
|
+
pageCount?: number | null;
|
|
15
16
|
file?: {
|
|
16
17
|
name?: string;
|
|
17
18
|
} | null;
|
|
@@ -20,6 +21,7 @@ export type UnitEdit = {
|
|
|
20
21
|
unit: UnitDraft;
|
|
21
22
|
file?: File;
|
|
22
23
|
durationSeconds?: number;
|
|
24
|
+
pageCount?: number;
|
|
23
25
|
};
|
|
24
26
|
/** Opens the unit form. Resolves with what was given - the unit and a file picked for it - or null. */
|
|
25
27
|
export declare function openUnitForm(unit: UnitDraft): Promise<UnitEdit | null>;
|
|
@@ -32,6 +34,7 @@ export declare class LearningUnitForm extends LearningUnitForm_base {
|
|
|
32
34
|
private draft;
|
|
33
35
|
private file?;
|
|
34
36
|
private duration?;
|
|
37
|
+
private pageCount?;
|
|
35
38
|
willUpdate(changed: Map<string, unknown>): void;
|
|
36
39
|
render(): import("lit-html").TemplateResult<1>;
|
|
37
40
|
private patch;
|
|
@@ -7,6 +7,7 @@ import '@operato/popup/ox-popup-actions.js';
|
|
|
7
7
|
import { css, html, LitElement, nothing } from 'lit';
|
|
8
8
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
9
|
import { i18next, localize } from '@operato/i18n';
|
|
10
|
+
import { pdfPageCount } from '@operato/learning/pdf.js';
|
|
10
11
|
import { openPopup } from '@operato/layout';
|
|
11
12
|
import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles';
|
|
12
13
|
import { t, UNIT_KINDS, unitKindWord } from '../utils/learning-words.js';
|
|
@@ -76,7 +77,8 @@ let LearningUnitForm = class LearningUnitForm extends localize(i18next)(LitEleme
|
|
|
76
77
|
<span>${kind === 'VIDEO' ? t('field.learning-unit-video') : t('field.learning-unit-pdf')}</span>
|
|
77
78
|
<ox-input-file accept=${kind === 'VIDEO' ? 'video/*' : 'application/pdf'} @change=${(e) => this.pick(e)}></ox-input-file>
|
|
78
79
|
<span>${this.file ? this.file.name : this.draft.file?.name ? t('text.learning-unit-file-kept', { name: this.draft.file.name }) : t('text.learning-unit-file-none')}
|
|
79
|
-
${kind === 'VIDEO' && (this.duration ?? this.draft.durationSeconds) ? ` · ${t('text.learning-unit-duration', { minutes: Math.floor((this.duration ?? this.draft.durationSeconds ?? 0) / 60), seconds: (this.duration ?? this.draft.durationSeconds ?? 0) % 60 })}` : ''}
|
|
80
|
+
${kind === 'VIDEO' && (this.duration ?? this.draft.durationSeconds) ? ` · ${t('text.learning-unit-duration', { minutes: Math.floor((this.duration ?? this.draft.durationSeconds ?? 0) / 60), seconds: (this.duration ?? this.draft.durationSeconds ?? 0) % 60 })}` : ''}
|
|
81
|
+
${kind === 'PDF' && (this.pageCount ?? this.draft.pageCount) ? ` · ${t('text.learning-unit-pages', { pages: this.pageCount ?? this.draft.pageCount })}` : ''}</span
|
|
80
82
|
>
|
|
81
83
|
</div>`
|
|
82
84
|
: nothing}
|
|
@@ -115,9 +117,11 @@ let LearningUnitForm = class LearningUnitForm extends localize(i18next)(LitEleme
|
|
|
115
117
|
return;
|
|
116
118
|
this.file = file;
|
|
117
119
|
this.duration = this.draft.kind === 'VIDEO' ? await videoDurationOf(file) : undefined;
|
|
120
|
+
/* Every page of a PDF is to be read - the count is read from the file, as a video's length is. */
|
|
121
|
+
this.pageCount = this.draft.kind === 'PDF' ? await pdfPageCount(file) : undefined;
|
|
118
122
|
}
|
|
119
123
|
done() {
|
|
120
|
-
this.dispatchEvent(new CustomEvent('done', { detail: { unit: { ...this.draft, name: this.draft.name.trim() }, file: this.file, durationSeconds: this.duration } }));
|
|
124
|
+
this.dispatchEvent(new CustomEvent('done', { detail: { unit: { ...this.draft, name: this.draft.name.trim() }, file: this.file, durationSeconds: this.duration, pageCount: this.pageCount } }));
|
|
121
125
|
}
|
|
122
126
|
};
|
|
123
127
|
__decorate([
|
|
@@ -136,6 +140,10 @@ __decorate([
|
|
|
136
140
|
state(),
|
|
137
141
|
__metadata("design:type", Number)
|
|
138
142
|
], LearningUnitForm.prototype, "duration", void 0);
|
|
143
|
+
__decorate([
|
|
144
|
+
state(),
|
|
145
|
+
__metadata("design:type", Number)
|
|
146
|
+
], LearningUnitForm.prototype, "pageCount", void 0);
|
|
139
147
|
LearningUnitForm = __decorate([
|
|
140
148
|
customElement('learning-unit-form')
|
|
141
149
|
], LearningUnitForm);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learning-unit-form.js","sourceRoot":"","sources":["../../client/components/learning-unit-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/input/ox-input-file.js'\nimport '@operato/input/ox-input-select-buttons.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\n\nimport { t, UNIT_KINDS, unitKindWord } from '../utils/learning-words.js'\n\nexport type UnitDraft = { id?: string; kind: string; name: string; body?: string | null; url?: string | null; minSeconds?: number | null; durationSeconds?: number | null; file?: { name?: string } | null }\nexport type UnitEdit = { unit: UnitDraft; file?: File; durationSeconds?: number }\n\n/** Opens the unit form. Resolves with what was given - the unit and a file picked for it - or null. */\nexport function openUnitForm(unit: UnitDraft): Promise<UnitEdit | null> {\n const popup = openPopup(html`<learning-unit-form .unit=${unit} @done=${(e: CustomEvent) => popup.close(e.detail)} @close=${() => popup.close()}></learning-unit-form>`, { backdrop: true, size: 'large', title: unit.id ? t('title.learning-unit-edit') : t('title.learning-unit-new') })\n return popup.result.then(value => (value ?? null) as any)\n}\n\n/** A video's length in seconds, read from the file in the browser - the server needs it to know when it was watched to the end. */\nexport function videoDurationOf(file: File): Promise<number | undefined> {\n return new Promise(resolve => {\n const video = document.createElement('video')\n const url = URL.createObjectURL(file)\n const done = (value?: number) => {\n URL.revokeObjectURL(url)\n resolve(value)\n }\n video.preload = 'metadata'\n video.onloadedmetadata = () => done(Number.isFinite(video.duration) ? Math.round(video.duration) : undefined)\n video.onerror = () => done(undefined)\n video.src = url\n })\n}\n\n/* 단원 (ADR-0194 decision 3) - its kind, name and what it holds: a video or a PDF file, a text, or a link. */\n@customElement('learning-unit-form')\nexport class LearningUnitForm extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n PopupBodyStyles,\n FormFieldStyles,\n PopupFieldStyles,\n css`\n ox-input-select-buttons {\n align-self: flex-start;\n }\n textarea {\n min-height: 240px;\n resize: vertical;\n font-family: var(--ui-font-mono, monospace);\n }\n `\n ]\n\n @property({ attribute: false }) unit: UnitDraft = { kind: 'VIDEO', name: '' }\n @state() private draft: UnitDraft = { kind: 'VIDEO', name: '' }\n @state() private file?: File\n @state() private duration?: number\n\n willUpdate(changed: Map<string, unknown>) {\n if (changed.has('unit')) this.draft = { ...this.unit }\n }\n\n render() {\n const kind = this.draft.kind\n const fileKind = kind === 'VIDEO' || kind === 'PDF'\n return html`\n <div class=\"body\">\n <div class=\"field\">\n <span>${t('field.learning-unit-kind')}</span>\n <ox-input-select-buttons .value=${kind} .options=${UNIT_KINDS.map(code => ({ display: unitKindWord(code), value: code }))} @change=${(e: CustomEvent) => e.detail && this.patch({ kind: e.detail })}></ox-input-select-buttons>\n <span>${t(`text.learning-unit-${kind.toLowerCase()}-hint`)}</span>\n </div>\n <label class=\"field\">\n <span>${t('field.learning-unit-name')}</span>\n <input .value=${this.draft.name} maxlength=\"200\" @input=${(e: any) => this.patch({ name: e.target.value })} />\n </label>\n ${fileKind\n ? html`<div class=\"field\">\n <span>${kind === 'VIDEO' ? t('field.learning-unit-video') : t('field.learning-unit-pdf')}</span>\n <ox-input-file accept=${kind === 'VIDEO' ? 'video/*' : 'application/pdf'} @change=${(e: CustomEvent) => this.pick(e)}></ox-input-file>\n <span>${this.file ? this.file.name : this.draft.file?.name ? t('text.learning-unit-file-kept', { name: this.draft.file.name }) : t('text.learning-unit-file-none')}\n ${kind === 'VIDEO' && (this.duration ?? this.draft.durationSeconds) ? ` · ${t('text.learning-unit-duration', { minutes: Math.floor((this.duration ?? this.draft.durationSeconds ?? 0) / 60), seconds: (this.duration ?? this.draft.durationSeconds ?? 0) % 60 })}` : ''}</span\n >\n </div>`\n : nothing}\n ${kind === 'MARKDOWN'\n ? html`<label class=\"field\">\n <span>${t('field.learning-unit-body')}</span>\n <textarea .value=${this.draft.body ?? ''} @input=${(e: any) => this.patch({ body: e.target.value })}></textarea>\n </label>`\n : nothing}\n ${kind === 'LINK'\n ? html`<label class=\"field\">\n <span>${t('field.learning-unit-url')}</span>\n <input type=\"url\" .value=${this.draft.url ?? ''} placeholder=\"https://\" @input=${(e: any) => this.patch({ url: e.target.value })} />\n </label>`\n : nothing}\n ${kind === 'PDF' || kind === 'MARKDOWN'\n ? html`<label class=\"field\">\n <span>${t('field.learning-unit-min-seconds')}</span>\n <input type=\"number\" min=\"1\" step=\"1\" .value=${String(this.draft.minSeconds ?? 60)} @input=${(e: any) => this.patch({ minSeconds: e.target.value ? Number(e.target.value) : null })} />\n <span>${t('text.learning-unit-min-seconds-hint')}</span>\n </label>`\n : nothing}\n </div>\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>${t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.draft.name?.trim()} @click=${() => this.done()}>${t('button.confirm')}</md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private patch(change: Partial<UnitDraft>) {\n this.draft = { ...this.draft, ...change }\n }\n\n private async pick(e: CustomEvent) {\n const [file] = (e.detail as File[]) ?? []\n if (!file) return\n this.file = file\n this.duration = this.draft.kind === 'VIDEO' ? await videoDurationOf(file) : undefined\n }\n\n private done() {\n this.dispatchEvent(new CustomEvent('done', { detail: { unit: { ...this.draft, name: this.draft.name.trim() }, file: this.file, durationSeconds: this.duration } }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"learning-unit-form.js","sourceRoot":"","sources":["../../client/components/learning-unit-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/input/ox-input-file.js'\nimport '@operato/input/ox-input-select-buttons.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { pdfPageCount } from '@operato/learning/pdf.js'\nimport { openPopup } from '@operato/layout'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\n\nimport { t, UNIT_KINDS, unitKindWord } from '../utils/learning-words.js'\n\nexport type UnitDraft = { id?: string; kind: string; name: string; body?: string | null; url?: string | null; minSeconds?: number | null; durationSeconds?: number | null; pageCount?: number | null; file?: { name?: string } | null }\nexport type UnitEdit = { unit: UnitDraft; file?: File; durationSeconds?: number; pageCount?: number }\n\n/** Opens the unit form. Resolves with what was given - the unit and a file picked for it - or null. */\nexport function openUnitForm(unit: UnitDraft): Promise<UnitEdit | null> {\n const popup = openPopup(html`<learning-unit-form .unit=${unit} @done=${(e: CustomEvent) => popup.close(e.detail)} @close=${() => popup.close()}></learning-unit-form>`, { backdrop: true, size: 'large', title: unit.id ? t('title.learning-unit-edit') : t('title.learning-unit-new') })\n return popup.result.then(value => (value ?? null) as any)\n}\n\n/** A video's length in seconds, read from the file in the browser - the server needs it to know when it was watched to the end. */\nexport function videoDurationOf(file: File): Promise<number | undefined> {\n return new Promise(resolve => {\n const video = document.createElement('video')\n const url = URL.createObjectURL(file)\n const done = (value?: number) => {\n URL.revokeObjectURL(url)\n resolve(value)\n }\n video.preload = 'metadata'\n video.onloadedmetadata = () => done(Number.isFinite(video.duration) ? Math.round(video.duration) : undefined)\n video.onerror = () => done(undefined)\n video.src = url\n })\n}\n\n/* 단원 (ADR-0194 decision 3) - its kind, name and what it holds: a video or a PDF file, a text, or a link. */\n@customElement('learning-unit-form')\nexport class LearningUnitForm extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n PopupBodyStyles,\n FormFieldStyles,\n PopupFieldStyles,\n css`\n ox-input-select-buttons {\n align-self: flex-start;\n }\n textarea {\n min-height: 240px;\n resize: vertical;\n font-family: var(--ui-font-mono, monospace);\n }\n `\n ]\n\n @property({ attribute: false }) unit: UnitDraft = { kind: 'VIDEO', name: '' }\n @state() private draft: UnitDraft = { kind: 'VIDEO', name: '' }\n @state() private file?: File\n @state() private duration?: number\n @state() private pageCount?: number\n\n willUpdate(changed: Map<string, unknown>) {\n if (changed.has('unit')) this.draft = { ...this.unit }\n }\n\n render() {\n const kind = this.draft.kind\n const fileKind = kind === 'VIDEO' || kind === 'PDF'\n return html`\n <div class=\"body\">\n <div class=\"field\">\n <span>${t('field.learning-unit-kind')}</span>\n <ox-input-select-buttons .value=${kind} .options=${UNIT_KINDS.map(code => ({ display: unitKindWord(code), value: code }))} @change=${(e: CustomEvent) => e.detail && this.patch({ kind: e.detail })}></ox-input-select-buttons>\n <span>${t(`text.learning-unit-${kind.toLowerCase()}-hint`)}</span>\n </div>\n <label class=\"field\">\n <span>${t('field.learning-unit-name')}</span>\n <input .value=${this.draft.name} maxlength=\"200\" @input=${(e: any) => this.patch({ name: e.target.value })} />\n </label>\n ${fileKind\n ? html`<div class=\"field\">\n <span>${kind === 'VIDEO' ? t('field.learning-unit-video') : t('field.learning-unit-pdf')}</span>\n <ox-input-file accept=${kind === 'VIDEO' ? 'video/*' : 'application/pdf'} @change=${(e: CustomEvent) => this.pick(e)}></ox-input-file>\n <span>${this.file ? this.file.name : this.draft.file?.name ? t('text.learning-unit-file-kept', { name: this.draft.file.name }) : t('text.learning-unit-file-none')}\n ${kind === 'VIDEO' && (this.duration ?? this.draft.durationSeconds) ? ` · ${t('text.learning-unit-duration', { minutes: Math.floor((this.duration ?? this.draft.durationSeconds ?? 0) / 60), seconds: (this.duration ?? this.draft.durationSeconds ?? 0) % 60 })}` : ''}\n ${kind === 'PDF' && (this.pageCount ?? this.draft.pageCount) ? ` · ${t('text.learning-unit-pages', { pages: this.pageCount ?? this.draft.pageCount })}` : ''}</span\n >\n </div>`\n : nothing}\n ${kind === 'MARKDOWN'\n ? html`<label class=\"field\">\n <span>${t('field.learning-unit-body')}</span>\n <textarea .value=${this.draft.body ?? ''} @input=${(e: any) => this.patch({ body: e.target.value })}></textarea>\n </label>`\n : nothing}\n ${kind === 'LINK'\n ? html`<label class=\"field\">\n <span>${t('field.learning-unit-url')}</span>\n <input type=\"url\" .value=${this.draft.url ?? ''} placeholder=\"https://\" @input=${(e: any) => this.patch({ url: e.target.value })} />\n </label>`\n : nothing}\n ${kind === 'PDF' || kind === 'MARKDOWN'\n ? html`<label class=\"field\">\n <span>${t('field.learning-unit-min-seconds')}</span>\n <input type=\"number\" min=\"1\" step=\"1\" .value=${String(this.draft.minSeconds ?? 60)} @input=${(e: any) => this.patch({ minSeconds: e.target.value ? Number(e.target.value) : null })} />\n <span>${t('text.learning-unit-min-seconds-hint')}</span>\n </label>`\n : nothing}\n </div>\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>${t('button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.draft.name?.trim()} @click=${() => this.done()}>${t('button.confirm')}</md-filled-button>\n </ox-popup-actions>\n `\n }\n\n private patch(change: Partial<UnitDraft>) {\n this.draft = { ...this.draft, ...change }\n }\n\n private async pick(e: CustomEvent) {\n const [file] = (e.detail as File[]) ?? []\n if (!file) return\n this.file = file\n this.duration = this.draft.kind === 'VIDEO' ? await videoDurationOf(file) : undefined\n /* Every page of a PDF is to be read - the count is read from the file, as a video's length is. */\n this.pageCount = this.draft.kind === 'PDF' ? await pdfPageCount(file) : undefined\n }\n\n private done() {\n this.dispatchEvent(new CustomEvent('done', { detail: { unit: { ...this.draft, name: this.draft.name.trim() }, file: this.file, durationSeconds: this.duration, pageCount: this.pageCount } }))\n }\n}\n"]}
|
|
@@ -36,6 +36,7 @@ export declare class LearningCourseEditor extends LearningCourseEditor_base {
|
|
|
36
36
|
};
|
|
37
37
|
pageUpdated(changes: any, lifecycle: any): Promise<void>;
|
|
38
38
|
willUpdate(): void;
|
|
39
|
+
private materialDraft;
|
|
39
40
|
private settingsOf;
|
|
40
41
|
render(): import("lit-html").TemplateResult<1>;
|
|
41
42
|
private renderVersions;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { openMaterialForm } from '../components/learning-material-form.js';
|
|
2
3
|
import '@material/web/button/filled-button.js';
|
|
3
4
|
import '@material/web/button/outlined-button.js';
|
|
4
5
|
import '@material/web/button/text-button.js';
|
|
@@ -10,6 +11,7 @@ import '@operato/list-state/ox-page-state.js';
|
|
|
10
11
|
import gql from 'graphql-tag';
|
|
11
12
|
import { css, html, nothing } from 'lit';
|
|
12
13
|
import { customElement, state } from 'lit/decorators.js';
|
|
14
|
+
import { convertListParamToSearchString } from '@operato/data-grist';
|
|
13
15
|
import { MutationController, QueryController, uploadContext } from '@operato/graphql';
|
|
14
16
|
import { i18next, localize } from '@operato/i18n';
|
|
15
17
|
import { notify } from '@operato/layout';
|
|
@@ -22,7 +24,7 @@ import { openQuestionForm } from '../components/learning-question-form.js';
|
|
|
22
24
|
import { openUnitForm } from '../components/learning-unit-form.js';
|
|
23
25
|
import { COURSE_FIELDS, VERSION_FIELDS } from '../utils/learning-api.js';
|
|
24
26
|
import { categoryWord, dayOf, questionKindWord, SOURCE_KINDS, sourceKindWord, t, unitKindWord, versionStatusWord } from '../utils/learning-words.js';
|
|
25
|
-
const unitInput = (one) => ({ id: one.id, kind: one.kind, name: one.name, body: one.body ?? null, url: one.url ?? null, durationSeconds: one.durationSeconds ?? null, minSeconds: one.minSeconds ?? null });
|
|
27
|
+
const unitInput = (one) => ({ id: one.id, kind: one.kind, name: one.name, body: one.body ?? null, url: one.url ?? null, durationSeconds: one.durationSeconds ?? null, pageCount: one.pageCount ?? null, minSeconds: one.minSeconds ?? null });
|
|
26
28
|
const questionInput = (one) => ({ id: one.id, kind: one.kind, text: one.text, explanation: one.explanation ?? null, choices: one.choices.map((c) => ({ id: c.id, text: c.text, correct: !!c.correct })) });
|
|
27
29
|
/**
|
|
28
30
|
* 과정 편집 (ADR-0194 decisions 2-4, 7) - a course and its versions. A draft version takes its settings, units and
|
|
@@ -45,7 +47,7 @@ let LearningCourseEditor = class LearningCourseEditor extends localize(i18next)(
|
|
|
45
47
|
newVersion: new MutationController(this, gql `mutation ($courseId: String!, $fromId: String) { createCourseVersion(courseId: $courseId, fromId: $fromId) { id } }`, { refetch: () => [this.course] }),
|
|
46
48
|
settings: new MutationController(this, gql `mutation ($id: String!, $patch: CourseVersionPatch!) { updateCourseVersion(id: $id, patch: $patch) { id } }`, { refetch: () => [this.version] }),
|
|
47
49
|
units: new MutationController(this, gql `mutation ($versionId: String!, $units: [CourseUnitInput!]!) { saveCourseUnits(versionId: $versionId, units: $units) { id seq } }`, { refetch: () => [this.version] }),
|
|
48
|
-
file: new MutationController(this, gql `mutation ($unitId: String!, $file: Upload!, $durationSeconds: Int) { attachCourseUnitFile(unitId: $unitId, file: $file, durationSeconds: $durationSeconds) { id } }`, { refetch: () => [this.version] }),
|
|
50
|
+
file: new MutationController(this, gql `mutation ($unitId: String!, $file: Upload!, $durationSeconds: Int, $pageCount: Int) { attachCourseUnitFile(unitId: $unitId, file: $file, durationSeconds: $durationSeconds, pageCount: $pageCount) { id } }`, { refetch: () => [this.version] }),
|
|
49
51
|
questions: new MutationController(this, gql `mutation ($versionId: String!, $questions: [CourseQuestionInput!]!) { saveCourseQuestions(versionId: $versionId, questions: $questions) { id } }`, { refetch: () => [this.version] }),
|
|
50
52
|
publish: new MutationController(this, gql `mutation ($id: String!) { publishCourseVersion(id: $id) { id } }`, { refetch: () => [this.course, this.version] }),
|
|
51
53
|
close: new MutationController(this, gql `mutation ($id: String!) { closeCourseVersion(id: $id) { id } }`, { refetch: () => [this.course, this.version] }),
|
|
@@ -154,8 +156,10 @@ let LearningCourseEditor = class LearningCourseEditor extends localize(i18next)(
|
|
|
154
156
|
const one = this.one;
|
|
155
157
|
const actions = [];
|
|
156
158
|
if (one) {
|
|
159
|
+
if (this.draft)
|
|
160
|
+
actions.push({ title: t('button.learning-ai-material'), icon: 'auto_awesome', action: () => this.materialDraft() });
|
|
157
161
|
actions.push({ title: t('button.learning-course-edit'), icon: 'edit', action: () => this.editCourse() });
|
|
158
|
-
actions.push({ title: t('title.learning-enrollment-status'), icon: 'groups', action: () => navigate(`learning-enrollment-status
|
|
162
|
+
actions.push({ title: t('title.learning-enrollment-status'), icon: 'groups', action: () => navigate(`learning-enrollment-status?${convertListParamToSearchString({ filters: [{ name: 'course', operator: 'eq', value: one.id }] })}`) });
|
|
159
163
|
if (one.publishedVersion)
|
|
160
164
|
actions.push({ title: t('button.learning-assign'), icon: 'person_add', emphasis: { raised: true }, action: () => this.assign() });
|
|
161
165
|
}
|
|
@@ -180,6 +184,15 @@ let LearningCourseEditor = class LearningCourseEditor extends localize(i18next)(
|
|
|
180
184
|
if (current && this.settings.__for !== current.id + current.minSeconds + current.passScore + current.acknowledgement + current.sourceNote)
|
|
181
185
|
this.settings = this.settingsOf(current);
|
|
182
186
|
}
|
|
187
|
+
async materialDraft() {
|
|
188
|
+
const units = (this.current?.units ?? []).filter((u) => ['PDF', 'VIDEO'].includes(u.kind) && u.file);
|
|
189
|
+
if (!units.length) {
|
|
190
|
+
notify({ message: t('text.learning-ai-upload-first') });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (await openMaterialForm(units))
|
|
194
|
+
await this.version.refetch();
|
|
195
|
+
}
|
|
183
196
|
settingsOf(v) {
|
|
184
197
|
return {
|
|
185
198
|
__for: v.id + v.minSeconds + v.passScore + v.acknowledgement + v.sourceNote,
|
|
@@ -311,7 +324,7 @@ let LearningCourseEditor = class LearningCourseEditor extends localize(i18next)(
|
|
|
311
324
|
case 'VIDEO':
|
|
312
325
|
return [unit.file?.name ?? t('text.learning-unit-file-none'), unit.durationSeconds ? t('text.learning-unit-duration', { minutes: Math.floor(unit.durationSeconds / 60), seconds: unit.durationSeconds % 60 }) : ''].filter(Boolean).join(' · ');
|
|
313
326
|
case 'PDF':
|
|
314
|
-
return [unit.file?.name ?? t('text.learning-unit-file-none'), t('text.learning-unit-min-seconds', { seconds: unit.minSeconds ?? 60 })].join(' · ');
|
|
327
|
+
return [unit.file?.name ?? t('text.learning-unit-file-none'), unit.pageCount ? t('text.learning-unit-pages', { pages: unit.pageCount }) : '', t('text.learning-unit-min-seconds', { seconds: unit.minSeconds ?? 60 })].filter(Boolean).join(' · ');
|
|
315
328
|
case 'MARKDOWN':
|
|
316
329
|
return t('text.learning-unit-min-seconds', { seconds: unit.minSeconds ?? 60 });
|
|
317
330
|
default:
|
|
@@ -402,13 +415,13 @@ let LearningCourseEditor = class LearningCourseEditor extends localize(i18next)(
|
|
|
402
415
|
return;
|
|
403
416
|
const units = [...(this.current.units ?? [])];
|
|
404
417
|
const index = unit ? units.findIndex(one => one.id === unit.id) : units.length;
|
|
405
|
-
units[index] = { ...(unit ?? {}), ...edit.unit, ...(edit.durationSeconds ? { durationSeconds: edit.durationSeconds } : {}) };
|
|
418
|
+
units[index] = { ...(unit ?? {}), ...edit.unit, ...(edit.durationSeconds ? { durationSeconds: edit.durationSeconds } : {}), ...(edit.pageCount ? { pageCount: edit.pageCount } : {}) };
|
|
406
419
|
const saved = await this.saveUnits(units);
|
|
407
420
|
if (!saved || !edit.file)
|
|
408
421
|
return;
|
|
409
422
|
const savedUnit = saved.saveCourseUnits.find((one) => one.seq === index + 1);
|
|
410
423
|
if (savedUnit)
|
|
411
|
-
await this.saves.file.run({ unitId: savedUnit.id, file: edit.file, durationSeconds: edit.durationSeconds ?? null }, { context: uploadContext(() => { }) });
|
|
424
|
+
await this.saves.file.run({ unitId: savedUnit.id, file: edit.file, durationSeconds: edit.durationSeconds ?? null, pageCount: edit.pageCount ?? null }, { context: uploadContext(() => { }) });
|
|
412
425
|
}
|
|
413
426
|
async moveUnit(index, by) {
|
|
414
427
|
const units = [...this.current.units];
|