@octolearn/training-ui 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +413 -3
- package/THIRD-PARTY-NOTICES.md +211 -0
- package/dist/chunks/core-rJUDTpGS.js +592 -0
- package/dist/chunks/define-elements-CeSpZJXj.js +6111 -0
- package/dist/chunks/font-data-DlFp9eSH.js +8 -0
- package/dist/core.js +11 -0
- package/dist/define.js +52 -0
- package/dist/index.js +50 -0
- package/dist/octolearn-training-ui.bundle.js +6441 -0
- package/dist/types/base-element.d.ts +35 -0
- package/dist/types/content.d.ts +51 -0
- package/dist/types/core.d.ts +13 -0
- package/dist/types/d-icons.d.ts +10 -0
- package/dist/types/define-elements.d.ts +18 -0
- package/dist/types/define.d.ts +1 -0
- package/dist/types/diff.d.ts +34 -0
- package/dist/types/elements/arithmetic-question.d.ts +31 -0
- package/dist/types/elements/card.d.ts +43 -0
- package/dist/types/elements/choice-pair.d.ts +21 -0
- package/dist/types/elements/dictation-question.d.ts +51 -0
- package/dist/types/elements/feedback-toast.d.ts +29 -0
- package/dist/types/elements/feedback.d.ts +87 -0
- package/dist/types/elements/flashcard.d.ts +46 -0
- package/dist/types/elements/matching-round.d.ts +61 -0
- package/dist/types/elements/multiple-choice.d.ts +33 -0
- package/dist/types/elements/picture-question.d.ts +30 -0
- package/dist/types/elements/progress.d.ts +24 -0
- package/dist/types/elements/question-answer.d.ts +20 -0
- package/dist/types/elements/question-parts.d.ts +88 -0
- package/dist/types/elements/question.d.ts +37 -0
- package/dist/types/elements/text-answer.d.ts +57 -0
- package/dist/types/elements/tile-answer.d.ts +43 -0
- package/dist/types/elements/today-stats.d.ts +17 -0
- package/dist/types/elements/trainer-overlay.d.ts +45 -0
- package/dist/types/elements/trainer-panel.d.ts +46 -0
- package/dist/types/elements/trainer.d.ts +57 -0
- package/dist/types/elements/true-false.d.ts +37 -0
- package/dist/types/events.d.ts +53 -0
- package/dist/types/feedback-message.d.ts +34 -0
- package/dist/types/font-data.d.ts +2 -0
- package/dist/types/fonts.d.ts +9 -0
- package/dist/types/i18n/strings.d.ts +233 -0
- package/dist/types/icons.d.ts +21 -0
- package/dist/types/images.d.ts +2 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/keys.d.ts +6 -0
- package/dist/types/session/third-party-transport.d.ts +22 -0
- package/dist/types/session/training-session.d.ts +167 -0
- package/dist/types/session/transport.d.ts +44 -0
- package/dist/types/speech.d.ts +71 -0
- package/dist/types/styles/base.d.ts +9 -0
- package/dist/types/styles/d-parts.d.ts +13 -0
- package/dist/types/styles/tokens.d.ts +95 -0
- package/dist/types/viewport.d.ts +35 -0
- package/package.json +67 -6
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { LitElement, type CSSResultGroup } from 'lit';
|
|
2
|
+
import { type TrainingLanguage, type TrainingStrings } from './i18n/strings';
|
|
3
|
+
/**
|
|
4
|
+
* Base of every training element.
|
|
5
|
+
*
|
|
6
|
+
* - `lang` (attribute): the language of the texts, de or en. Without it the element uses the nearest `lang` above it
|
|
7
|
+
* (also across shadow roots and up to <html lang>), else German. Only the element's own attribute is watched for
|
|
8
|
+
* changes; to switch the language at runtime, set it on the element.
|
|
9
|
+
* - `strings` (property): replaces single texts, e.g. `{ check: 'Prüfen' }`.
|
|
10
|
+
* - `theme` (attribute): `dark` for dark surfaces.
|
|
11
|
+
* - `short` (reflected attribute, set by the element itself): the visible area is short, e.g. a phone's on-screen keyboard
|
|
12
|
+
* covers half of it. The elements then use a compact layout (`:host([short])` in their styles).
|
|
13
|
+
*
|
|
14
|
+
* Templates start with `<div class="root" lang=…>` (see baseStyles), which also tells screen readers the language.
|
|
15
|
+
*/
|
|
16
|
+
export declare class OctolearnElement extends LitElement {
|
|
17
|
+
static shadowRootOptions: ShadowRootInit;
|
|
18
|
+
static styles: CSSResultGroup;
|
|
19
|
+
/** Mapped to the `lang` attribute without replacing HTMLElement's own `lang` property. */
|
|
20
|
+
language?: string;
|
|
21
|
+
strings?: Partial<TrainingStrings>;
|
|
22
|
+
theme?: 'light' | 'dark';
|
|
23
|
+
/** The visible area is short (on-screen keyboard open): compact layout. Follows `window.visualViewport`. */
|
|
24
|
+
short: boolean;
|
|
25
|
+
private endViewportWatch?;
|
|
26
|
+
connectedCallback(): void;
|
|
27
|
+
disconnectedCallback(): void;
|
|
28
|
+
protected get resolvedLanguage(): TrainingLanguage;
|
|
29
|
+
/** The texts in the element's language, with the page's overrides. */
|
|
30
|
+
protected get t(): TrainingStrings;
|
|
31
|
+
/** Dispatches an event that crosses shadow roots, so the page can listen on any ancestor. */
|
|
32
|
+
protected emit(type: string, detail: unknown, options?: {
|
|
33
|
+
cancelable?: boolean;
|
|
34
|
+
}): boolean;
|
|
35
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** How a learning content is trained. Same values as octolearn's API. */
|
|
2
|
+
export type LearningMode = 'Dictation' | 'Arithmetic' | 'QuestionAnswer' | 'Class' | 'Picture' | 'Vocabulary';
|
|
3
|
+
/**
|
|
4
|
+
* How the learner answers. Same values as octolearn's API. Games get only MultipleChoice and TextInput unless they name more
|
|
5
|
+
* in `filterQueryFormat` (OC-120); Flashcard exists only in the octolearn web app.
|
|
6
|
+
*/
|
|
7
|
+
export type QueryFormat = 'MultipleChoice' | 'TextInput' | 'LetterTiles' | 'WordTiles' | 'Fragment' | 'TrueFalse' | 'Flashcard';
|
|
8
|
+
/**
|
|
9
|
+
* One question as octolearn's API sends it (`FrontendContent`, e.g. from GET /api/third-party/next-relevant-questions).
|
|
10
|
+
* The answer is never part of it: the server decides whether an answer is correct.
|
|
11
|
+
*/
|
|
12
|
+
export interface FrontendContent {
|
|
13
|
+
id?: number;
|
|
14
|
+
/** The word, the question, the arithmetic task ("12+5") or, for a picture riddle, the picture's URL. */
|
|
15
|
+
question?: string | null;
|
|
16
|
+
learningMode?: LearningMode | null;
|
|
17
|
+
queryFormat?: QueryFormat | null;
|
|
18
|
+
/** Multiple-choice options, already shuffled by the server; which one is correct is not marked. */
|
|
19
|
+
options?: string[] | null;
|
|
20
|
+
/** LetterTiles / WordTiles: the shuffled letters or words of the answer, never in its order. */
|
|
21
|
+
tiles?: string[] | null;
|
|
22
|
+
/** Fragment: first and last letter of the answer, "_" for each letter between ("S____e"). */
|
|
23
|
+
hint?: string | null;
|
|
24
|
+
/** TrueFalse: the answer or another answer of the same folder; which one is not sent. */
|
|
25
|
+
statement?: string | null;
|
|
26
|
+
/** False for supplementary practice that changes no learning level. */
|
|
27
|
+
learningLevelShallBeChanged?: boolean | null;
|
|
28
|
+
learningContentFolderName?: string | null;
|
|
29
|
+
isItVocabulary?: boolean | null;
|
|
30
|
+
sourceLanguage?: string | null;
|
|
31
|
+
targetLanguage?: string | null;
|
|
32
|
+
learningLevel?: number | null;
|
|
33
|
+
examPreparationModeActive?: boolean | null;
|
|
34
|
+
}
|
|
35
|
+
/** One question of a "Paare verbinden" round (OC-125); its answer is among the round's answers, unassigned. */
|
|
36
|
+
export interface MatchingRoundItem {
|
|
37
|
+
id: number;
|
|
38
|
+
/** The question, or for a picture riddle the picture's URL. */
|
|
39
|
+
question: string;
|
|
40
|
+
learningMode?: LearningMode | null;
|
|
41
|
+
learningLevelShallBeChanged?: boolean | null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A round of "Paare verbinden" (OC-125, octolearn web app only): a few questions of one folder and their answers,
|
|
45
|
+
* shuffled. Which answer belongs to which question is not sent; every pair is checked like an answer. No items: no round.
|
|
46
|
+
*/
|
|
47
|
+
export interface MatchingRound {
|
|
48
|
+
folderName?: string | null;
|
|
49
|
+
items: MatchingRoundItem[];
|
|
50
|
+
answers: string[];
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@octolearn/training-ui/core`: the parts without Lit and without custom elements: TrainingSession, the transports, the
|
|
3
|
+
* SpeechController and the types. For code that drives the elements (or replaces them) and for test runners that should
|
|
4
|
+
* not load Lit.
|
|
5
|
+
*/
|
|
6
|
+
export type { FrontendContent, LearningMode, MatchingRound, MatchingRoundItem, QueryFormat } from './content';
|
|
7
|
+
export type { FeedbackMessage, FeedbackResult, QuestionResult } from './feedback-message';
|
|
8
|
+
export { DONT_KNOW_ANSWER } from './events';
|
|
9
|
+
export type { AnswerEventDetail, PairCheckedDetail, SpeakEventDetail } from './events';
|
|
10
|
+
export { createThirdPartyTransport, type ThirdPartyTransportOptions } from './session/third-party-transport';
|
|
11
|
+
export { TrainingSession, type AnswerOutcome, type PairOutcome, type RoundProgress, type SessionState, type TrainingSessionOptions, } from './session/training-session';
|
|
12
|
+
export { TransportError, type AnswerEvaluation, type TodaysStatistic, type Transport } from './session/transport';
|
|
13
|
+
export { getSpeechController, MAX_RATE, MIN_RATE, SpeechController, type SpeakOptions, type SpeakResult, type SpeechControllerOptions, } from './speech';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* The stroke icons of design D, drawn on the canvas as inline SVG (not Font Awesome). Decorative: hidden from screen
|
|
4
|
+
* readers; the button around them carries the name.
|
|
5
|
+
*/
|
|
6
|
+
export type DIconName = 'speaker' | 'speaker-quiet' | 'check' | 'cross' | 'arrow' | 'zoom';
|
|
7
|
+
/** A D icon of the given size in px; strokes in currentColor. */
|
|
8
|
+
export declare function dIcon(name: DIconName, size: number, strokeWidth?: number): TemplateResult;
|
|
9
|
+
/** D's five-point star (24×24 box). */
|
|
10
|
+
export declare const STAR_PATH = "M12 2.5l2.9 6 6.6.9-4.8 4.6 1.2 6.5L12 17.4l-5.9 3.1 1.2-6.5-4.8-4.6 6.6-.9z";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every element of the package with its tag name. Importing an element module registers nothing by itself; that keeps
|
|
3
|
+
* the modules free of side effects, so bundlers can tree-shake what a page doesn't use.
|
|
4
|
+
*/
|
|
5
|
+
export declare const trainingElements: readonly (readonly [string, CustomElementConstructor])[];
|
|
6
|
+
export interface DefineTrainingElementsOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Load the octolearn fonts into the page (default true). Font faces declared inside a shadow root are ignored by the
|
|
9
|
+
* browsers, so they have to live in the document. Pass false when the page already loads Montserrat itself.
|
|
10
|
+
*/
|
|
11
|
+
fonts?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Registers all training elements with the page's custom element registry. Names that are already taken are skipped,
|
|
15
|
+
* so a page that ends up with two copies of the package (say a game bundle and the CDN bundle) keeps working instead of
|
|
16
|
+
* throwing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function defineTrainingElements(options?: DefineTrainingElementsOptions): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Letter-by-letter comparison of a wrong answer with the solution, only for showing the difference (design D: "So ist es
|
|
3
|
+
* richtig: Kartof|f|el", "Du hast geschrieben: Kartof_el"). Never used to decide whether an answer is right: the server
|
|
4
|
+
* does that.
|
|
5
|
+
*/
|
|
6
|
+
export type DiffOp =
|
|
7
|
+
/** in both */
|
|
8
|
+
{
|
|
9
|
+
kind: 'same';
|
|
10
|
+
solution: string;
|
|
11
|
+
answer: string;
|
|
12
|
+
}
|
|
13
|
+
/** in the solution, missing from the answer */
|
|
14
|
+
| {
|
|
15
|
+
kind: 'missing';
|
|
16
|
+
solution: string;
|
|
17
|
+
}
|
|
18
|
+
/** in the answer, not in the solution */
|
|
19
|
+
| {
|
|
20
|
+
kind: 'extra';
|
|
21
|
+
answer: string;
|
|
22
|
+
};
|
|
23
|
+
export interface LetterDiff {
|
|
24
|
+
ops: DiffOp[];
|
|
25
|
+
missing: number;
|
|
26
|
+
extra: number;
|
|
27
|
+
}
|
|
28
|
+
/** Longer texts are not compared: a sentence full of marks helps nobody. */
|
|
29
|
+
export declare const MAX_DIFF_LENGTH = 40;
|
|
30
|
+
/**
|
|
31
|
+
* Aligns answer and solution along their longest common subsequence (case-insensitive, so "haus" vs "Haus" only marks the
|
|
32
|
+
* letters that really differ). Returns null when either text is empty or too long.
|
|
33
|
+
*/
|
|
34
|
+
export declare function letterDiff(answer: string, solution: string): LetterDiff | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { nothing, type TemplateResult } from 'lit';
|
|
2
|
+
import { QuestionElement } from './question-parts';
|
|
3
|
+
type Operator = '+' | '-' | '*' | '/';
|
|
4
|
+
export interface ArithmeticTask {
|
|
5
|
+
first: string;
|
|
6
|
+
operator: Operator;
|
|
7
|
+
second: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Splits "12+5" like octolearn's arithmetic-question: the first of + - * found after the first character (so a leading
|
|
11
|
+
* minus belongs to the number), else /. Unlike octolearn, which looked only at the first "-", "-3-5" works too.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseArithmeticTask(task: string): ArithmeticTask;
|
|
14
|
+
/**
|
|
15
|
+
* `<octolearn-arithmetic-question>`: design D's "Rechnen": "Rechne aus!" with "Vorlesen" beside it, then the task big
|
|
16
|
+
* ("7 · 8 =") with the answer box right after it. With multiple choice the task ends in "= ?" and the tiles follow.
|
|
17
|
+
* Screen readers hear "7 mal 8 gleich: Was ist das Ergebnis?".
|
|
18
|
+
*
|
|
19
|
+
* @fires octolearn-answer - `{ answer, dontKnow? }`
|
|
20
|
+
* @fires octolearn-speak - cancelable, `{ text, reason: 'read' }`
|
|
21
|
+
*/
|
|
22
|
+
export declare class OctolearnArithmeticQuestion extends QuestionElement {
|
|
23
|
+
static styles: (import("lit").CSSResult | import("lit").CSSResultGroup[])[];
|
|
24
|
+
render(): TemplateResult | typeof nothing;
|
|
25
|
+
}
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'octolearn-arithmetic-question': OctolearnArithmeticQuestion;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
import type { QuestionResult } from './progress';
|
|
4
|
+
export type CardState = 'question' | 'right' | 'wrong';
|
|
5
|
+
/**
|
|
6
|
+
* `<octolearn-card>`: design D's question card. Lined paper, a coloured band on top (orange while asking, green after a
|
|
7
|
+
* right answer, red after a wrong one), a header with the octolearn logo, the format label ("DIKTAT"), an optional chip
|
|
8
|
+
* ("Deutsch → Englisch") and the progress stars, and Inkie peeking over the top edge while a question is open. The
|
|
9
|
+
* question goes into the default slot. While the visible area is short (`short`: a phone's keyboard is open) the header
|
|
10
|
+
* and Inkie step aside and the card gets tighter.
|
|
11
|
+
*
|
|
12
|
+
* ```html
|
|
13
|
+
* <octolearn-card label="Diktat" current="3" total="5">
|
|
14
|
+
* <octolearn-question …></octolearn-question>
|
|
15
|
+
* </octolearn-card>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @slot - the question (and, after an answer, the feedback)
|
|
19
|
+
*/
|
|
20
|
+
export declare class OctolearnCard extends OctolearnElement {
|
|
21
|
+
static styles: import("lit").CSSResultGroup[];
|
|
22
|
+
state: CardState;
|
|
23
|
+
/** The format label in the header, e.g. "Diktat". */
|
|
24
|
+
label: string;
|
|
25
|
+
/** A small note next to the label, e.g. "Deutsch → Englisch". */
|
|
26
|
+
chip?: string;
|
|
27
|
+
/** 1-based number of the current question; with `total` the header shows the stars. */
|
|
28
|
+
current?: number;
|
|
29
|
+
total?: number;
|
|
30
|
+
results: readonly QuestionResult[];
|
|
31
|
+
/** No Inkie over the top edge (e.g. in a narrow side panel). */
|
|
32
|
+
noInkie: boolean;
|
|
33
|
+
/** A lighter shadow, for a card that sits on the page instead of over a game. */
|
|
34
|
+
flat: boolean;
|
|
35
|
+
/** No header row (logo, label, stars), e.g. in the side panel, which shows them itself. The label still names the card. */
|
|
36
|
+
noHeader: boolean;
|
|
37
|
+
render(): TemplateResult;
|
|
38
|
+
}
|
|
39
|
+
declare global {
|
|
40
|
+
interface HTMLElementTagNameMap {
|
|
41
|
+
'octolearn-card': OctolearnCard;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
/** One of the two big answer buttons ("Stimmt" / "Stimmt nicht", "Gewusst" / "Nicht gewusst"). */
|
|
3
|
+
export interface PairChoice {
|
|
4
|
+
/** What is sent as the answer. */
|
|
5
|
+
value: string;
|
|
6
|
+
text: string;
|
|
7
|
+
icon: 'check' | 'cross';
|
|
8
|
+
}
|
|
9
|
+
export interface ChoicePairOptions {
|
|
10
|
+
/** Accessible name of the group. */
|
|
11
|
+
label: string;
|
|
12
|
+
choices: readonly [PairChoice, PairChoice];
|
|
13
|
+
/** The value already chosen; both buttons lock then. */
|
|
14
|
+
chosen: string | null;
|
|
15
|
+
onChoose: (value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/** Design D's two big buttons with a round icon each ("Stimmt das?"); ← picks the first, → the second. */
|
|
18
|
+
export declare function choicePair(options: ChoicePairOptions): TemplateResult;
|
|
19
|
+
/** The arrow key's choice: ArrowLeft the first, ArrowRight the second, anything else none. */
|
|
20
|
+
export declare function arrowChoice(key: string, choices: readonly [PairChoice, PairChoice]): string | null;
|
|
21
|
+
export declare const choicePairStyles: import("lit").CSSResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import type { SpeakEventDetail } from '../events';
|
|
3
|
+
import { QuestionElement } from './question-parts';
|
|
4
|
+
/** Speed of "Langsam anhören". */
|
|
5
|
+
export declare const SLOW_RATE = 0.7;
|
|
6
|
+
/**
|
|
7
|
+
* `<octolearn-dictation-question>`: design D's "Diktat". The big orange listen button, "Hör zu und schreib das Wort.",
|
|
8
|
+
* "Langsam anhören", then the answer.
|
|
9
|
+
*
|
|
10
|
+
* The element reads the word aloud itself when the question arrives and on the buttons, using the browser's speech
|
|
11
|
+
* synthesis. A page with its own speech output calls `preventDefault()` on `octolearn-speak` / `octolearn-stop`. If the
|
|
12
|
+
* browser blocks speech until the first tap, a "Abspielen" hint appears. The word is read in its language (the content's
|
|
13
|
+
* `sourceLanguage`, else German); if the device has no voice for it, a note says so instead of letting another
|
|
14
|
+
* language's voice mispronounce it.
|
|
15
|
+
*
|
|
16
|
+
* @fires octolearn-speak - cancelable, `{ text, reason: 'auto' | 'repeat' | 'slow', lang, rate? }`
|
|
17
|
+
* @fires octolearn-stop - cancelable
|
|
18
|
+
* @fires octolearn-settings - "Einstellungen" pressed (the button shows only with the `settings` attribute)
|
|
19
|
+
* @fires octolearn-answer - `{ answer, dontKnow? }`
|
|
20
|
+
*/
|
|
21
|
+
export declare class OctolearnDictationQuestion extends QuestionElement {
|
|
22
|
+
static styles: (import("lit").CSSResult | import("lit").CSSResultGroup[])[];
|
|
23
|
+
/** Shows an "Einstellungen" button, which fires `octolearn-settings` (the page opens its own voice settings). */
|
|
24
|
+
settings: boolean;
|
|
25
|
+
/** Don't read a new word aloud by itself; only the buttons do. */
|
|
26
|
+
noAutospeak: boolean;
|
|
27
|
+
private settingsButton?;
|
|
28
|
+
/** Where the keyboard focus belongs when the page's voice settings close. */
|
|
29
|
+
focusSettings(): void;
|
|
30
|
+
/** Reads the word aloud, as the listen button does. */
|
|
31
|
+
speak(reason?: SpeakEventDetail['reason']): void;
|
|
32
|
+
stop(): void;
|
|
33
|
+
connectedCallback(): void;
|
|
34
|
+
disconnectedCallback(): void;
|
|
35
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
36
|
+
render(): TemplateResult | typeof nothing;
|
|
37
|
+
/** The word's language: the content's, else German (octolearn's dictations are German). */
|
|
38
|
+
private get wordLanguage();
|
|
39
|
+
/** Shown once the browser's voice list is known and has no voice for the word's language. */
|
|
40
|
+
private noVoiceHint;
|
|
41
|
+
private readonly handleVoicesChanged;
|
|
42
|
+
private controls;
|
|
43
|
+
private repeat;
|
|
44
|
+
private slow;
|
|
45
|
+
private openSettings;
|
|
46
|
+
}
|
|
47
|
+
declare global {
|
|
48
|
+
interface HTMLElementTagNameMap {
|
|
49
|
+
'octolearn-dictation-question': OctolearnDictationQuestion;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
import type { FeedbackMessage } from './feedback';
|
|
4
|
+
export type { FeedbackMessage } from './feedback';
|
|
5
|
+
/**
|
|
6
|
+
* `<octolearn-feedback-toast>`: shows compact `<octolearn-feedback>` cards (design D colours) at the bottom centre of the
|
|
7
|
+
* screen, for games that want a short notice instead of the feedback inside the card. Stays 5 s after a right answer,
|
|
8
|
+
* 10 s after a wrong one (`life` overrides). Clicks pass through the cards (they never block the next question's answers), and screen readers hear
|
|
9
|
+
* each new card. Call `show({ result, answer, correctAnswer })` after each evaluated answer.
|
|
10
|
+
*/
|
|
11
|
+
export declare class OctolearnFeedbackToast extends OctolearnElement {
|
|
12
|
+
static styles: import("lit").CSSResultGroup[];
|
|
13
|
+
/** At most this many cards at once; the oldest goes first. */
|
|
14
|
+
static readonly MAX_MESSAGES = 3;
|
|
15
|
+
private messages;
|
|
16
|
+
private nextId;
|
|
17
|
+
private readonly timers;
|
|
18
|
+
show(message: FeedbackMessage): void;
|
|
19
|
+
clear(): void;
|
|
20
|
+
disconnectedCallback(): void;
|
|
21
|
+
render(): TemplateResult;
|
|
22
|
+
private dismiss;
|
|
23
|
+
private forget;
|
|
24
|
+
}
|
|
25
|
+
declare global {
|
|
26
|
+
interface HTMLElementTagNameMap {
|
|
27
|
+
'octolearn-feedback-toast': OctolearnFeedbackToast;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
import type { QueryFormat } from '../content';
|
|
4
|
+
import type { FeedbackMessage, FeedbackResult } from '../feedback-message';
|
|
5
|
+
export type { FeedbackMessage, FeedbackResult } from '../feedback-message';
|
|
6
|
+
/** How long a right answer stays before the next question comes by itself (user decision, OC-256). */
|
|
7
|
+
export declare const AUTO_CONTINUE_MS = 3000;
|
|
8
|
+
/**
|
|
9
|
+
* `<octolearn-feedback>`: design D's feedback inside the card after an answer.
|
|
10
|
+
*
|
|
11
|
+
* - Right: green panel "Richtig!" with praise, happy Inkie and stars; for multiple choice the tiles with the right one
|
|
12
|
+
* marked; "+2 Lernpunkte" and "3 richtig hintereinander"; "Weiter" fills a ring and moves on by itself after 3 s
|
|
13
|
+
* (not while the pointer rests on the card; never with `no-auto`).
|
|
14
|
+
* - Wrong: red panel "Nicht ganz." with the reason ("Da fehlt ein Buchstabe."), "So ist es richtig:" with the letters
|
|
15
|
+
* that differ marked, "Du hast geschrieben:" with the gaps marked, "Kein Problem – das Wort kommt bald wieder!" and
|
|
16
|
+
* "Weiter", which waits for the learner.
|
|
17
|
+
* - `compact`: a small card for `<octolearn-feedback-toast>`.
|
|
18
|
+
*
|
|
19
|
+
* The marks only show the difference; whether an answer is right is decided by the server.
|
|
20
|
+
*
|
|
21
|
+
* @fires octolearn-continue - "Weiter" pressed, or a right answer's 3 s are over
|
|
22
|
+
* @fires octolearn-speak - cancelable, "Wort noch einmal anhören"
|
|
23
|
+
*/
|
|
24
|
+
export declare class OctolearnFeedback extends OctolearnElement {
|
|
25
|
+
static styles: import("lit").CSSResultGroup[];
|
|
26
|
+
result: FeedbackResult;
|
|
27
|
+
answer: string;
|
|
28
|
+
correctAnswer?: string | null;
|
|
29
|
+
typo: boolean;
|
|
30
|
+
note?: string | null;
|
|
31
|
+
dontKnow: boolean;
|
|
32
|
+
unchecked: boolean;
|
|
33
|
+
points?: number;
|
|
34
|
+
streak?: number;
|
|
35
|
+
options?: readonly string[] | null;
|
|
36
|
+
/** The question was a word (dictation, vocabulary). */
|
|
37
|
+
word: boolean;
|
|
38
|
+
/** Offer "Wort noch einmal anhören" for the solution (dictation). */
|
|
39
|
+
listenable: boolean;
|
|
40
|
+
/** TrueFalse / Flashcard: the answer is a token ("true", "known"), shown as what the learner tapped. */
|
|
41
|
+
queryFormat?: QueryFormat | null;
|
|
42
|
+
/** Never move on by itself. */
|
|
43
|
+
noAuto: boolean;
|
|
44
|
+
/** Time until a right answer moves on, in ms. */
|
|
45
|
+
delay: number;
|
|
46
|
+
/** The small toast card. */
|
|
47
|
+
compact: boolean;
|
|
48
|
+
private nextButton?;
|
|
49
|
+
private timer?;
|
|
50
|
+
private remaining;
|
|
51
|
+
private startedAt;
|
|
52
|
+
private continued;
|
|
53
|
+
/** Sets everything from one message (what TrainingSession hands out). */
|
|
54
|
+
set message(message: FeedbackMessage);
|
|
55
|
+
/** Puts the focus on "Weiter", so Enter moves on. */
|
|
56
|
+
focusContinue(): void;
|
|
57
|
+
disconnectedCallback(): void;
|
|
58
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
59
|
+
render(): TemplateResult;
|
|
60
|
+
private get autoContinues();
|
|
61
|
+
private renderFull;
|
|
62
|
+
private renderRightPanel;
|
|
63
|
+
private renderWrongPanel;
|
|
64
|
+
private reason;
|
|
65
|
+
private get diff();
|
|
66
|
+
/** The answer was a button with a token (Richtig/Falsch, Karteikarte), not typed text. */
|
|
67
|
+
private get tapped();
|
|
68
|
+
/** What the learner answered, as they saw it: "Stimmt" for "true", "Gewusst" for "known". */
|
|
69
|
+
private shownAnswer;
|
|
70
|
+
private renderTiles;
|
|
71
|
+
private renderSolution;
|
|
72
|
+
private renderPoints;
|
|
73
|
+
private renderStreak;
|
|
74
|
+
private renderRing;
|
|
75
|
+
private renderCompact;
|
|
76
|
+
private continue;
|
|
77
|
+
private listenAgain;
|
|
78
|
+
private startTimer;
|
|
79
|
+
private stopTimer;
|
|
80
|
+
private pause;
|
|
81
|
+
private resume;
|
|
82
|
+
}
|
|
83
|
+
declare global {
|
|
84
|
+
interface HTMLElementTagNameMap {
|
|
85
|
+
'octolearn-feedback': OctolearnFeedback;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
import type { FrontendContent } from '../content';
|
|
4
|
+
/** What the server takes as the self-rating of a Karteikarte (OC-128). */
|
|
5
|
+
export declare const KNOWN_ANSWER = "known";
|
|
6
|
+
export declare const NOT_KNOWN_ANSWER = "unknown";
|
|
7
|
+
/** Fetches the answer of a Karteikarte, e.g. `content => session.revealFlashcard(content)`. */
|
|
8
|
+
export type FlashcardRevealer = (content: FrontendContent) => Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* `<octolearn-flashcard>`: the Karteikarte (OC-128, octolearn's web app only), for long answers that are hardly ever typed
|
|
11
|
+
* letter for letter. The learner thinks of the answer and turns the card with "Antwort zeigen": `reveal` fetches the
|
|
12
|
+
* answer (the server hands it out only while the content is a Karteikarte). Then "Gewusst" sends "known" (counts like a
|
|
13
|
+
* right answer) and "Nicht gewusst" "unknown" (like a wrong one); ← and → choose too. Without `reveal` the card can't be
|
|
14
|
+
* turned.
|
|
15
|
+
*
|
|
16
|
+
* @fires octolearn-answer - `{ answer: 'known' | 'unknown' }`
|
|
17
|
+
*/
|
|
18
|
+
export declare class OctolearnFlashcard extends OctolearnElement {
|
|
19
|
+
static styles: import("lit").CSSResultGroup[];
|
|
20
|
+
/** The question; a new one turns the card back. */
|
|
21
|
+
content?: FrontendContent;
|
|
22
|
+
/** Fetches the answer when the learner turns the card. */
|
|
23
|
+
reveal?: FlashcardRevealer;
|
|
24
|
+
/** Don't react to ← and →. */
|
|
25
|
+
noKeys: boolean;
|
|
26
|
+
private answer;
|
|
27
|
+
private loading;
|
|
28
|
+
private failed;
|
|
29
|
+
private chosen;
|
|
30
|
+
private firstChoice?;
|
|
31
|
+
connectedCallback(): void;
|
|
32
|
+
disconnectedCallback(): void;
|
|
33
|
+
/** The answer shown, null while the card is not turned. */
|
|
34
|
+
get revealedAnswer(): string | null;
|
|
35
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
36
|
+
private get choices();
|
|
37
|
+
render(): TemplateResult;
|
|
38
|
+
private turn;
|
|
39
|
+
private rate;
|
|
40
|
+
private readonly onKeyDown;
|
|
41
|
+
}
|
|
42
|
+
declare global {
|
|
43
|
+
interface HTMLElementTagNameMap {
|
|
44
|
+
'octolearn-flashcard': OctolearnFlashcard;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
import type { MatchingRound, MatchingRoundItem } from '../content';
|
|
4
|
+
import type { AnswerEvaluation } from '../session/transport';
|
|
5
|
+
/** Checks one pair with the server, e.g. `(item, answer) => session.checkPair(item, answer)`; null leaves it open. */
|
|
6
|
+
export type PairChecker = (item: MatchingRoundItem, answer: string) => Promise<AnswerEvaluation | null>;
|
|
7
|
+
/**
|
|
8
|
+
* `<octolearn-matching-round>`: design D's "Paare finden" (OC-125, octolearn's web app only). Questions on the left,
|
|
9
|
+
* their answers shuffled on the right; tap a question, then its answer (or the other way round). Each pair is checked
|
|
10
|
+
* on its own through `checkPair` and decided after one try, so every question is graded exactly once:
|
|
11
|
+
* - right: both turn green and stay;
|
|
12
|
+
* - wrong: the question turns red with the right answer under it, that answer leaves the game (red), the one tapped by
|
|
13
|
+
* mistake shakes and stays open, as it belongs to another question.
|
|
14
|
+
* A pair the server could not check stays open. Everything is a native button: touch, mouse and keyboard, no dragging
|
|
15
|
+
* (WCAG 2.5.7). When all pairs are decided, "Weiter" fires `octolearn-round-done`.
|
|
16
|
+
*
|
|
17
|
+
* @fires octolearn-pair - `{ item, answer, correct, correctAnswer }` after each checked pair
|
|
18
|
+
* @fires octolearn-round-done - "Weiter" after the last pair
|
|
19
|
+
*/
|
|
20
|
+
export declare class OctolearnMatchingRound extends OctolearnElement {
|
|
21
|
+
static styles: import("lit").CSSResultGroup[];
|
|
22
|
+
/** The round from the server; a new one starts over. */
|
|
23
|
+
round: MatchingRound | null;
|
|
24
|
+
/** Checks one pair. */
|
|
25
|
+
checkPair?: PairChecker;
|
|
26
|
+
private questions;
|
|
27
|
+
private answers;
|
|
28
|
+
private selectedQuestion;
|
|
29
|
+
private selectedAnswer;
|
|
30
|
+
private busy;
|
|
31
|
+
private failed;
|
|
32
|
+
private announcement;
|
|
33
|
+
private shaking;
|
|
34
|
+
private links;
|
|
35
|
+
private resizeObserver?;
|
|
36
|
+
/** Every pair is decided. */
|
|
37
|
+
get done(): boolean;
|
|
38
|
+
get correctCount(): number;
|
|
39
|
+
disconnectedCallback(): void;
|
|
40
|
+
protected updated(): void;
|
|
41
|
+
/** Where the connectors of the decided pairs run; set only when they moved, so it settles after one more update. */
|
|
42
|
+
private measureLinks;
|
|
43
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
44
|
+
render(): TemplateResult | typeof nothing;
|
|
45
|
+
private renderQuestion;
|
|
46
|
+
private renderAnswer;
|
|
47
|
+
private stateIcon;
|
|
48
|
+
private isPicture;
|
|
49
|
+
private selectQuestion;
|
|
50
|
+
private selectAnswer;
|
|
51
|
+
private tryPair;
|
|
52
|
+
private decide;
|
|
53
|
+
/** The buttons just decided are disabled: the focus goes to the next open question, or to "Weiter" at the end. */
|
|
54
|
+
private focusNext;
|
|
55
|
+
private finish;
|
|
56
|
+
}
|
|
57
|
+
declare global {
|
|
58
|
+
interface HTMLElementTagNameMap {
|
|
59
|
+
'octolearn-matching-round': OctolearnMatchingRound;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
/**
|
|
4
|
+
* `<octolearn-multiple-choice>`: design D's answer tiles, two columns with number badges. Tap a tile or press its number
|
|
5
|
+
* key; the choice is sent at once and the tiles lock until new options arrive (no double answers). The options are
|
|
6
|
+
* shown in the order given: the server shuffles them and never says which one is correct.
|
|
7
|
+
*
|
|
8
|
+
* Number keys work while nothing else takes them: not while typing in a field, not when another handler already used the
|
|
9
|
+
* key, and not with `no-keys` (for a game that needs 1–4 itself).
|
|
10
|
+
*
|
|
11
|
+
* @fires octolearn-answer - `{ answer }` with the chosen option
|
|
12
|
+
*/
|
|
13
|
+
export declare class OctolearnMultipleChoice extends OctolearnElement {
|
|
14
|
+
static styles: import("lit").CSSResultGroup[];
|
|
15
|
+
/** The options in the order the server sent them. */
|
|
16
|
+
options: readonly string[];
|
|
17
|
+
/** Accessible name of the group, e.g. "Richtige Antwort auf „Haus“ auswählen". */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** Don't react to the number keys. */
|
|
20
|
+
noKeys: boolean;
|
|
21
|
+
private chosen;
|
|
22
|
+
connectedCallback(): void;
|
|
23
|
+
disconnectedCallback(): void;
|
|
24
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
25
|
+
render(): TemplateResult;
|
|
26
|
+
private choose;
|
|
27
|
+
private readonly onKeyDown;
|
|
28
|
+
}
|
|
29
|
+
declare global {
|
|
30
|
+
interface HTMLElementTagNameMap {
|
|
31
|
+
'octolearn-multiple-choice': OctolearnMultipleChoice;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { QuestionElement } from './question-parts';
|
|
3
|
+
/**
|
|
4
|
+
* `<octolearn-picture-question>`: design D's "Bilderrätsel": "Was siehst du auf dem Bild?", the picture in a cream frame
|
|
5
|
+
* left of "Schreib das Wort" and the field (above it on narrow screens), or above the answer tiles. `content.question` is
|
|
6
|
+
* the picture's URL. Tapping the picture shows it large in a modal dialog (closed by a tap anywhere, the close button or
|
|
7
|
+
* Escape); while it is open, the keys don't reach the answer (no number key picks a choice behind it).
|
|
8
|
+
*
|
|
9
|
+
* @fires octolearn-answer - `{ answer, dontKnow? }`
|
|
10
|
+
*/
|
|
11
|
+
export declare class OctolearnPictureQuestion extends QuestionElement {
|
|
12
|
+
static styles: (import("lit").CSSResult | import("lit").CSSResultGroup[])[];
|
|
13
|
+
/** The large picture is showing. */
|
|
14
|
+
private zoomed;
|
|
15
|
+
private zoomDialog?;
|
|
16
|
+
/** Shows the picture large (same as tapping it). */
|
|
17
|
+
enlargePicture(): void;
|
|
18
|
+
/** Closes the large picture. */
|
|
19
|
+
closePicture(): void;
|
|
20
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
21
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
22
|
+
render(): TemplateResult | typeof nothing;
|
|
23
|
+
private onZoomKeydown;
|
|
24
|
+
private onZoomClosed;
|
|
25
|
+
}
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'octolearn-picture-question': OctolearnPictureQuestion;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { OctolearnElement } from '../base-element';
|
|
3
|
+
import type { QuestionResult } from '../feedback-message';
|
|
4
|
+
export type { QuestionResult } from '../feedback-message';
|
|
5
|
+
/**
|
|
6
|
+
* `<octolearn-progress current="3" total="5">`: design D's row of stars, "Frage 3 von 5". Answered questions are filled
|
|
7
|
+
* orange, the current one is bigger (cream while open, green when right, red outline when wrong), the rest are outlines.
|
|
8
|
+
* `results` holds the result of each question answered so far; the current question's result, once known, colours its
|
|
9
|
+
* star and is added to the announcement ("Frage 3 von 5, richtig").
|
|
10
|
+
*/
|
|
11
|
+
export declare class OctolearnProgress extends OctolearnElement {
|
|
12
|
+
static styles: import("lit").CSSResultGroup[];
|
|
13
|
+
/** 1-based number of the current question. */
|
|
14
|
+
current: number;
|
|
15
|
+
total: number;
|
|
16
|
+
/** Results of the questions so far, index 0 = question 1. */
|
|
17
|
+
results: readonly QuestionResult[];
|
|
18
|
+
render(): TemplateResult;
|
|
19
|
+
}
|
|
20
|
+
declare global {
|
|
21
|
+
interface HTMLElementTagNameMap {
|
|
22
|
+
'octolearn-progress': OctolearnProgress;
|
|
23
|
+
}
|
|
24
|
+
}
|