@webwriter/quiz 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +6 -6
  2. package/custom.d.ts +175 -175
  3. package/dist/widgets/webwriter-choice-item.css +1 -1
  4. package/dist/widgets/webwriter-choice-item.js +1886 -802
  5. package/dist/widgets/webwriter-choice.css +1 -1
  6. package/dist/widgets/webwriter-choice.js +1943 -846
  7. package/dist/widgets/webwriter-cloze-gap.css +1 -1
  8. package/dist/widgets/webwriter-cloze-gap.js +1988 -902
  9. package/dist/widgets/webwriter-cloze.css +1 -1
  10. package/dist/widgets/webwriter-cloze.js +1833 -748
  11. package/dist/widgets/webwriter-mark.css +1 -1
  12. package/dist/widgets/webwriter-mark.js +1848 -762
  13. package/dist/widgets/webwriter-order-item.css +1 -1
  14. package/dist/widgets/webwriter-order-item.js +1884 -799
  15. package/dist/widgets/webwriter-order.css +1 -1
  16. package/dist/widgets/webwriter-order.js +1882 -790
  17. package/dist/widgets/webwriter-pairing-item.css +1 -1
  18. package/dist/widgets/webwriter-pairing-item.js +1718 -614
  19. package/dist/widgets/webwriter-pairing.css +1 -1
  20. package/dist/widgets/webwriter-pairing.js +1873 -781
  21. package/dist/widgets/webwriter-quiz.css +1 -1
  22. package/dist/widgets/webwriter-quiz.js +2241 -1026
  23. package/dist/widgets/webwriter-speech.css +1 -1
  24. package/dist/widgets/webwriter-speech.js +2179 -961
  25. package/dist/widgets/webwriter-task-explainer.css +1 -1
  26. package/dist/widgets/webwriter-task-explainer.js +1704 -600
  27. package/dist/widgets/webwriter-task-hint.css +1 -1
  28. package/dist/widgets/webwriter-task-hint.js +1695 -591
  29. package/dist/widgets/webwriter-task-prompt.css +1 -1
  30. package/dist/widgets/webwriter-task-prompt.js +1670 -566
  31. package/dist/widgets/webwriter-task.css +1 -1
  32. package/dist/widgets/webwriter-task.js +2361 -1143
  33. package/dist/widgets/webwriter-text.css +1 -1
  34. package/dist/widgets/webwriter-text.js +1876 -770
  35. package/icon.svg +1 -0
  36. package/lit-localize.json +15 -0
  37. package/localization/bg.xlf +54 -0
  38. package/localization/cs.xlf +54 -0
  39. package/localization/da.xlf +54 -0
  40. package/localization/de.xlf +54 -0
  41. package/localization/el.xlf +54 -0
  42. package/localization/es.xlf +54 -0
  43. package/localization/et.xlf +54 -0
  44. package/localization/fi.xlf +54 -0
  45. package/localization/fr.xlf +54 -0
  46. package/localization/generated/bg.ts +25 -0
  47. package/localization/generated/cs.ts +25 -0
  48. package/localization/generated/da.ts +25 -0
  49. package/localization/generated/de.ts +25 -0
  50. package/localization/generated/el.ts +25 -0
  51. package/localization/generated/es.ts +25 -0
  52. package/localization/generated/et.ts +25 -0
  53. package/localization/generated/fi.ts +25 -0
  54. package/localization/generated/fr.ts +25 -0
  55. package/localization/generated/hu.ts +25 -0
  56. package/localization/generated/id.ts +25 -0
  57. package/localization/generated/index.js +3 -0
  58. package/localization/generated/it.ts +25 -0
  59. package/localization/generated/ja.ts +25 -0
  60. package/localization/generated/ko.ts +25 -0
  61. package/localization/generated/locale-codes.js +79 -0
  62. package/localization/generated/lt.ts +25 -0
  63. package/localization/generated/lv.ts +25 -0
  64. package/localization/generated/nb.ts +25 -0
  65. package/localization/generated/nl.ts +25 -0
  66. package/localization/generated/pl.ts +25 -0
  67. package/localization/generated/pt-BR.ts +25 -0
  68. package/localization/generated/pt-PT.ts +25 -0
  69. package/localization/generated/ro.ts +25 -0
  70. package/localization/generated/ru.ts +25 -0
  71. package/localization/generated/sk.ts +25 -0
  72. package/localization/generated/sl.ts +25 -0
  73. package/localization/generated/sv.ts +25 -0
  74. package/localization/generated/tr.ts +25 -0
  75. package/localization/generated/uk.ts +25 -0
  76. package/localization/generated/zh-hans.ts +25 -0
  77. package/localization/hu.xlf +54 -0
  78. package/localization/id.xlf +54 -0
  79. package/localization/it.xlf +54 -0
  80. package/localization/ja.xlf +54 -0
  81. package/localization/ko.xlf +54 -0
  82. package/localization/lt.xlf +54 -0
  83. package/localization/lv.xlf +54 -0
  84. package/localization/nb.xlf +54 -0
  85. package/localization/nl.xlf +54 -0
  86. package/localization/pl.xlf +54 -0
  87. package/localization/pt-BR.xlf +54 -0
  88. package/localization/pt-PT.xlf +54 -0
  89. package/localization/ro.xlf +54 -0
  90. package/localization/ru.xlf +54 -0
  91. package/localization/sk.xlf +54 -0
  92. package/localization/sl.xlf +54 -0
  93. package/localization/sv.xlf +54 -0
  94. package/localization/tr.xlf +54 -0
  95. package/localization/uk.xlf +54 -0
  96. package/localization/zh-hans.xlf +54 -0
  97. package/package.json +281 -193
  98. package/src/lib/combobox.ts +455 -455
  99. package/src/lib/highlighter-fill.svg +5 -5
  100. package/src/snippets/Beispiel-Choice.html +57 -0
  101. package/src/snippets/Beispiel-Mark.html +14 -0
  102. package/src/snippets/Beispiel-Order.html +52 -0
  103. package/src/snippets/Beispiel-Text.html +20 -0
  104. package/src/snippets/choice.html +7 -7
  105. package/src/snippets/cloze.html +3 -3
  106. package/src/snippets/mark.html +3 -3
  107. package/src/snippets/order.html +7 -7
  108. package/src/snippets/pairing.html +9 -9
  109. package/src/snippets/speech.html +3 -3
  110. package/src/snippets/text.html +3 -3
  111. package/src/snippets/wordsearch.html +3 -3
  112. package/src/widgets/webwriter-choice-item.ts +250 -250
  113. package/src/widgets/webwriter-choice.ts +314 -309
  114. package/src/widgets/webwriter-cloze-gap.ts +215 -216
  115. package/src/widgets/webwriter-cloze.ts +135 -135
  116. package/src/widgets/webwriter-mark.ts +431 -434
  117. package/src/widgets/webwriter-order-item.ts +437 -436
  118. package/src/widgets/webwriter-order.ts +284 -279
  119. package/src/widgets/webwriter-pairing-item.ts +160 -155
  120. package/src/widgets/webwriter-pairing.ts +192 -187
  121. package/src/widgets/webwriter-quiz.ts +281 -280
  122. package/src/widgets/webwriter-speech.ts +272 -267
  123. package/src/widgets/webwriter-task-explainer.ts +42 -37
  124. package/src/widgets/webwriter-task-hint.ts +22 -16
  125. package/src/widgets/webwriter-task-prompt.ts +23 -17
  126. package/src/widgets/webwriter-task.ts +546 -543
  127. package/src/widgets/webwriter-text.ts +135 -130
  128. package/tsconfig.json +6 -6
@@ -1,268 +1,273 @@
1
- import {html, css, PropertyValueMap} from "lit"
2
- import {LitElementWw} from "@webwriter/lit"
3
- import {customElement, property, query} from "lit/decorators.js"
4
- import "@shoelace-style/shoelace/dist/themes/light.css"
5
-
6
- import IconMic from "bootstrap-icons/icons/mic.svg"
7
- import IconMicFill from "bootstrap-icons/icons/mic-fill.svg"
8
- import IconStopFill from "bootstrap-icons/icons/stop-fill.svg"
9
- import IconTrash from "bootstrap-icons/icons/trash.svg"
10
- import IconPlay from "bootstrap-icons/icons/play.svg"
11
- import IconPause from "bootstrap-icons/icons/pause.svg"
12
- import IconVolumeDown from "bootstrap-icons/icons/volume-down.svg"
13
- import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
14
- import SlRange from "@shoelace-style/shoelace/dist/components/range/range.component.js"
15
- import SlButtonGroup from "@shoelace-style/shoelace/dist/components/button-group/button-group.component.js"
16
- import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
17
- import SlFormatDate from "@shoelace-style/shoelace/dist/components/format-date/format-date.component.js"
18
- import SlPopup from "@shoelace-style/shoelace/dist/components/popup/popup.component.js"
19
-
20
- declare global {interface HTMLElementTagNameMap {
21
- "webwriter-speech": WebwriterSpeech;
22
- }}
23
-
24
- @customElement("webwriter-speech")
25
- export class WebwriterSpeech extends LitElementWw {
26
-
27
- static scopedElements = {
28
- "sl-button": SlButton,
29
- "sl-icon": SlIcon,
30
- "sl-button-group": SlButtonGroup,
31
- "sl-range": SlRange,
32
- "sl-format-date": SlFormatDate,
33
- "sl-popup": SlPopup
34
- }
35
-
36
- static styles = css`
37
-
38
- @keyframes blinker {
39
- 50% {
40
- opacity: 0;
41
- }
42
- }
43
-
44
- sl-icon {
45
- height: 20px;
46
- width: 20px;
47
- }
48
-
49
- sl-button-group {
50
- width: 100%;
51
- }
52
-
53
- #record {
54
- position: relative;
55
- #recording-indicator {
56
- background: darkred;
57
- border-radius: 100%;
58
- height: 10px;
59
- width: 10px;
60
- position: absolute;
61
- top: 5px;
62
- right: 5px;
63
- animation: blinker 1.25s linear infinite;
64
- }
65
- }
66
-
67
- #record:hover .idle {
68
- display: none;
69
- }
70
-
71
- :host([recording]) #record .torecord, :host(:not([recording])) #record:not(:hover) .torecord {
72
- display: none;
73
- }
74
-
75
- :host(:not([recording])) #record .tostop, :host([recording]) #record:not(:hover) .tostop {
76
- display: none;
77
- }
78
-
79
- :host(:not([recording])) #recording-indicator {
80
- display: none;
81
- }
82
-
83
- #playback {
84
- width: 100%;
85
- display: flex;
86
- flex-direction: row;
87
- align-items: center;
88
- border-top: 1px solid var(--sl-color-gray-300);
89
- border-bottom: 1px solid var(--sl-color-gray-300);
90
- padding: 0 1ch;
91
- gap: 1ch;
92
- color: var(--sl-color-gray-800);
93
- font-size: var(--sl-font-size-small);
94
- user-select: none;
95
-
96
- sl-range {
97
- flex-grow: 1;
98
- display: flex;
99
- flex-direction: row;
100
- align-items: center;
101
-
102
- &::part(form-control) {
103
- width: 100%;
104
- }
105
- }
106
- }
107
-
108
- :host(:not([src])) #playback {
109
- opacity: 0.5;
110
- }
111
-
112
- sl-button {
113
- &::part(label) {
114
- display: flex;
115
- flex-direction: row;
116
- align-items: center;
117
- gap: 1ch;
118
- }
119
- }
120
-
121
- sl-tooltip {
122
- z-index: 1;
123
- }
124
-
125
- #volume {
126
- --arrow-color: var(--sl-color-gray-600);
127
- &::part(popup) {
128
- background: var(--arrow-color);
129
- height: 30px;
130
- padding: 1ch;
131
- }
132
- }
133
- `
134
-
135
- mediaRecorder: MediaRecorder
136
- chunks = [] as Blob[]
137
-
138
- @property({type: Boolean, attribute: true})
139
- accessor loading = false
140
-
141
- @property({type: String, attribute: true, reflect: true})
142
- accessor src: string
143
-
144
- firstUpdated() {
145
- const evs = ["abort", "canplay", "canplaythrough", "durationchange", "emptied", "ended", "error", "loadeddata", "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", "ratechange", "resize", "seeked", "seeking", "stalled", "suspend", "timeupdate", "volumechange", "waiting"] as const
146
- evs.forEach(k => this.audioEl.addEventListener(k, ()=>this.requestUpdate()))
147
- }
148
-
149
- connectedCallback(): void {
150
- super.connectedCallback()
151
- this.initializeRecorder()
152
- }
153
-
154
- async initializeRecorder() {
155
- if(!this.mediaRecorder) {
156
- this.loading = true
157
- try {
158
- const stream = await navigator.mediaDevices.getUserMedia({audio: true})
159
- this.mediaRecorder = new MediaRecorder(stream)
160
- this.mediaRecorder.addEventListener("start", e => {
161
- this.requestUpdate("recording")
162
- })
163
- this.mediaRecorder.addEventListener("dataavailable", e => {
164
- this.chunks.push(e.data)
165
- })
166
- this.mediaRecorder.addEventListener("stop", async e => {
167
- const blob = new Blob(this.chunks, {type: "audio/ogg; codecs=opus"})
168
- this.chunks = []
169
- this.src = URL.createObjectURL(blob)
170
- const reader = new FileReader()
171
- reader.readAsDataURL(blob)
172
- reader.addEventListener("load", e => {
173
- this.src = reader.result as string
174
- this.requestUpdate("recording")
175
- })
176
- })
177
- }
178
- finally {
179
- this.loading = false
180
- }
181
- }
182
- }
183
-
184
- toggleRecording = async () => {
185
- if(this.mediaRecorder?.state === "recording") {
186
- this.mediaRecorder.stop()
187
- }
188
- else {
189
- await this.initializeRecorder()
190
- this.mediaRecorder.start()
191
- }
192
- }
193
-
194
- @query("audio")
195
- accessor audioEl: HTMLAudioElement
196
-
197
- get duration() {
198
- const d = this.audioEl?.duration
199
- return !Number.isNaN(d) && Number.isFinite(d)? d: 0
200
- }
201
-
202
- get currentTime() {
203
- return this.audioEl?.currentTime
204
- }
205
-
206
- get playing() {
207
- return !this.audioEl?.paused && !this.audioEl?.ended
208
- }
209
-
210
-
211
- get recording() {
212
- return this.mediaRecorder?.state === "recording"
213
- }
214
-
215
- @property({type: Boolean, attribute: true, reflect: true})
216
- set recording(value: boolean) {
217
- return
218
- }
219
-
220
- get volume() {
221
- return Math.round(this.audioEl?.volume * 100)
222
- }
223
-
224
- set volume(value: number) {
225
- this.audioEl.volume = value / 100
226
- }
227
-
228
- togglePlay = () => {
229
- !this.playing? this.audioEl.play(): this.audioEl.pause()
230
- }
231
-
232
- @property({type: Boolean})
233
- accessor volumeOpen = false
234
-
235
- render() {
236
- return html`<sl-button-group>
237
- <sl-button id="record" @click=${this.toggleRecording} ?loading=${this.loading} ?disabled=${this.loading}>
238
- <sl-icon class="idle" src=${IconMic}></sl-icon>
239
- <sl-icon class="torecord" src=${IconMicFill}></sl-icon>
240
- <sl-icon class="tostop" src=${IconStopFill}></sl-icon>
241
- <div id="recording-indicator"></div>
242
- </sl-button>
243
- <sl-button id="playpause" ?disabled=${this.loading || !this.src} @click=${this.togglePlay}>
244
- <sl-icon src=${!this.playing? IconPlay: IconPause}></sl-icon>
245
- </sl-button>
246
- <div id="playback">
247
- <audio src=${this.src} preload="metadata"></audio>
248
- <div>
249
- <sl-format-date .date=${new Date(this.currentTime * 1000)} minute="numeric" second="numeric"></sl-format-date>
250
- /
251
- <sl-format-date .date=${new Date(this.duration * 1000)} minute="numeric" second="numeric"></sl-format-date>
252
- </div>
253
- <sl-range ?disabled=${this.loading || !this.src}></sl-range>
254
- </div>
255
- <sl-popup id="volume" ?active=${this.volumeOpen} hoist strategy="fixed" arrow distance="6">
256
- <sl-button ?disabled=${this.loading || !this.src} slot="anchor" @click=${() => this.volumeOpen = !this.volumeOpen}>
257
- <sl-icon src=${IconVolumeDown}></sl-icon>
258
- </sl-button>
259
- <sl-range ?disabled=${this.loading || !this.src} id="volume-range" value=${this.volume} @sl-change=${(e: any) => this.volume = e.target.value}>
260
-
261
- </sl-range>
262
- </sl-popup>
263
- <sl-button ?disabled=${this.loading || !this.src} id="delete" @click=${() => this.src = undefined}>
264
- <sl-icon src=${IconTrash}></sl-icon>
265
- </sl-button>
266
- </sl-button-group>`
267
- }
1
+ import {html, css, PropertyValueMap} from "lit"
2
+ import {LitElementWw} from "@webwriter/lit"
3
+ import {customElement, property, query} from "lit/decorators.js"
4
+ import "@shoelace-style/shoelace/dist/themes/light.css"
5
+
6
+ import IconMic from "bootstrap-icons/icons/mic.svg"
7
+ import IconMicFill from "bootstrap-icons/icons/mic-fill.svg"
8
+ import IconStopFill from "bootstrap-icons/icons/stop-fill.svg"
9
+ import IconTrash from "bootstrap-icons/icons/trash.svg"
10
+ import IconPlay from "bootstrap-icons/icons/play.svg"
11
+ import IconPause from "bootstrap-icons/icons/pause.svg"
12
+ import IconVolumeDown from "bootstrap-icons/icons/volume-down.svg"
13
+ import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
14
+ import SlRange from "@shoelace-style/shoelace/dist/components/range/range.component.js"
15
+ import SlButtonGroup from "@shoelace-style/shoelace/dist/components/button-group/button-group.component.js"
16
+ import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
17
+ import SlFormatDate from "@shoelace-style/shoelace/dist/components/format-date/format-date.component.js"
18
+ import SlPopup from "@shoelace-style/shoelace/dist/components/popup/popup.component.js"
19
+
20
+ declare global {interface HTMLElementTagNameMap {
21
+ "webwriter-speech": WebwriterSpeech;
22
+ }}
23
+
24
+ import LOCALIZE from "../../localization/generated"
25
+ import {msg} from "@lit/localize"
26
+
27
+ @customElement("webwriter-speech")
28
+ export class WebwriterSpeech extends LitElementWw {
29
+
30
+ localize = LOCALIZE
31
+
32
+ static scopedElements = {
33
+ "sl-button": SlButton,
34
+ "sl-icon": SlIcon,
35
+ "sl-button-group": SlButtonGroup,
36
+ "sl-range": SlRange,
37
+ "sl-format-date": SlFormatDate,
38
+ "sl-popup": SlPopup
39
+ }
40
+
41
+ static styles = css`
42
+
43
+ @keyframes blinker {
44
+ 50% {
45
+ opacity: 0;
46
+ }
47
+ }
48
+
49
+ sl-icon {
50
+ height: 20px;
51
+ width: 20px;
52
+ }
53
+
54
+ sl-button-group {
55
+ width: 100%;
56
+ }
57
+
58
+ #record {
59
+ position: relative;
60
+ #recording-indicator {
61
+ background: darkred;
62
+ border-radius: 100%;
63
+ height: 10px;
64
+ width: 10px;
65
+ position: absolute;
66
+ top: 5px;
67
+ right: 5px;
68
+ animation: blinker 1.25s linear infinite;
69
+ }
70
+ }
71
+
72
+ #record:hover .idle {
73
+ display: none;
74
+ }
75
+
76
+ :host([recording]) #record .torecord, :host(:not([recording])) #record:not(:hover) .torecord {
77
+ display: none;
78
+ }
79
+
80
+ :host(:not([recording])) #record .tostop, :host([recording]) #record:not(:hover) .tostop {
81
+ display: none;
82
+ }
83
+
84
+ :host(:not([recording])) #recording-indicator {
85
+ display: none;
86
+ }
87
+
88
+ #playback {
89
+ width: 100%;
90
+ display: flex;
91
+ flex-direction: row;
92
+ align-items: center;
93
+ border-top: 1px solid var(--sl-color-gray-300);
94
+ border-bottom: 1px solid var(--sl-color-gray-300);
95
+ padding: 0 1ch;
96
+ gap: 1ch;
97
+ color: var(--sl-color-gray-800);
98
+ font-size: var(--sl-font-size-small);
99
+ user-select: none;
100
+
101
+ sl-range {
102
+ flex-grow: 1;
103
+ display: flex;
104
+ flex-direction: row;
105
+ align-items: center;
106
+
107
+ &::part(form-control) {
108
+ width: 100%;
109
+ }
110
+ }
111
+ }
112
+
113
+ :host(:not([src])) #playback {
114
+ opacity: 0.5;
115
+ }
116
+
117
+ sl-button {
118
+ &::part(label) {
119
+ display: flex;
120
+ flex-direction: row;
121
+ align-items: center;
122
+ gap: 1ch;
123
+ }
124
+ }
125
+
126
+ sl-tooltip {
127
+ z-index: 1;
128
+ }
129
+
130
+ #volume {
131
+ --arrow-color: var(--sl-color-gray-600);
132
+ &::part(popup) {
133
+ background: var(--arrow-color);
134
+ height: 30px;
135
+ padding: 1ch;
136
+ }
137
+ }
138
+ `
139
+
140
+ mediaRecorder: MediaRecorder
141
+ chunks = [] as Blob[]
142
+
143
+ @property({type: Boolean, attribute: true})
144
+ accessor loading = false
145
+
146
+ @property({type: String, attribute: true, reflect: true})
147
+ accessor src: string
148
+
149
+ firstUpdated() {
150
+ const evs = ["abort", "canplay", "canplaythrough", "durationchange", "emptied", "ended", "error", "loadeddata", "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", "ratechange", "resize", "seeked", "seeking", "stalled", "suspend", "timeupdate", "volumechange", "waiting"] as const
151
+ evs.forEach(k => this.audioEl.addEventListener(k, ()=>this.requestUpdate()))
152
+ }
153
+
154
+ connectedCallback(): void {
155
+ super.connectedCallback()
156
+ this.initializeRecorder()
157
+ }
158
+
159
+ async initializeRecorder() {
160
+ if(!this.mediaRecorder) {
161
+ this.loading = true
162
+ try {
163
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true})
164
+ this.mediaRecorder = new MediaRecorder(stream)
165
+ this.mediaRecorder.addEventListener("start", e => {
166
+ this.requestUpdate("recording")
167
+ })
168
+ this.mediaRecorder.addEventListener("dataavailable", e => {
169
+ this.chunks.push(e.data)
170
+ })
171
+ this.mediaRecorder.addEventListener("stop", async e => {
172
+ const blob = new Blob(this.chunks, {type: "audio/ogg; codecs=opus"})
173
+ this.chunks = []
174
+ this.src = URL.createObjectURL(blob)
175
+ const reader = new FileReader()
176
+ reader.readAsDataURL(blob)
177
+ reader.addEventListener("load", e => {
178
+ this.src = reader.result as string
179
+ this.requestUpdate("recording")
180
+ })
181
+ })
182
+ }
183
+ finally {
184
+ this.loading = false
185
+ }
186
+ }
187
+ }
188
+
189
+ toggleRecording = async () => {
190
+ if(this.mediaRecorder?.state === "recording") {
191
+ this.mediaRecorder.stop()
192
+ }
193
+ else {
194
+ await this.initializeRecorder()
195
+ this.mediaRecorder.start()
196
+ }
197
+ }
198
+
199
+ @query("audio")
200
+ accessor audioEl: HTMLAudioElement
201
+
202
+ get duration() {
203
+ const d = this.audioEl?.duration
204
+ return !Number.isNaN(d) && Number.isFinite(d)? d: 0
205
+ }
206
+
207
+ get currentTime() {
208
+ return this.audioEl?.currentTime
209
+ }
210
+
211
+ get playing() {
212
+ return !this.audioEl?.paused && !this.audioEl?.ended
213
+ }
214
+
215
+
216
+ get recording() {
217
+ return this.mediaRecorder?.state === "recording"
218
+ }
219
+
220
+ @property({type: Boolean, attribute: true, reflect: true})
221
+ set recording(value: boolean) {
222
+ return
223
+ }
224
+
225
+ get volume() {
226
+ return Math.round(this.audioEl?.volume * 100)
227
+ }
228
+
229
+ set volume(value: number) {
230
+ this.audioEl.volume = value / 100
231
+ }
232
+
233
+ togglePlay = () => {
234
+ !this.playing? this.audioEl.play(): this.audioEl.pause()
235
+ }
236
+
237
+ @property({type: Boolean})
238
+ accessor volumeOpen = false
239
+
240
+ render() {
241
+ return html`<sl-button-group>
242
+ <sl-button id="record" @click=${this.toggleRecording} ?loading=${this.loading} ?disabled=${this.loading}>
243
+ <sl-icon class="idle" src=${IconMic}></sl-icon>
244
+ <sl-icon class="torecord" src=${IconMicFill}></sl-icon>
245
+ <sl-icon class="tostop" src=${IconStopFill}></sl-icon>
246
+ <div id="recording-indicator"></div>
247
+ </sl-button>
248
+ <sl-button id="playpause" ?disabled=${this.loading || !this.src} @click=${this.togglePlay}>
249
+ <sl-icon src=${!this.playing? IconPlay: IconPause}></sl-icon>
250
+ </sl-button>
251
+ <div id="playback">
252
+ <audio src=${this.src} preload="metadata"></audio>
253
+ <div>
254
+ <sl-format-date .date=${new Date(this.currentTime * 1000)} minute="numeric" second="numeric"></sl-format-date>
255
+ /
256
+ <sl-format-date .date=${new Date(this.duration * 1000)} minute="numeric" second="numeric"></sl-format-date>
257
+ </div>
258
+ <sl-range ?disabled=${this.loading || !this.src}></sl-range>
259
+ </div>
260
+ <sl-popup id="volume" ?active=${this.volumeOpen} hoist strategy="fixed" arrow distance="6">
261
+ <sl-button ?disabled=${this.loading || !this.src} slot="anchor" @click=${() => this.volumeOpen = !this.volumeOpen}>
262
+ <sl-icon src=${IconVolumeDown}></sl-icon>
263
+ </sl-button>
264
+ <sl-range ?disabled=${this.loading || !this.src} id="volume-range" value=${this.volume} @sl-change=${(e: any) => this.volume = e.target.value}>
265
+
266
+ </sl-range>
267
+ </sl-popup>
268
+ <sl-button ?disabled=${this.loading || !this.src} id="delete" @click=${() => this.src = undefined}>
269
+ <sl-icon src=${IconTrash}></sl-icon>
270
+ </sl-button>
271
+ </sl-button-group>`
272
+ }
268
273
  }
@@ -1,38 +1,43 @@
1
- import {html, css} from "lit"
2
- import {LitElementWw} from "@webwriter/lit"
3
- import {customElement, property} from "lit/decorators.js"
4
- import "@shoelace-style/shoelace/dist/themes/light.css"
5
- import { styleMap } from "lit/directives/style-map.js";
6
-
7
- declare global {interface HTMLElementTagNameMap {
8
- "webwriter-task-explainer": WebwriterTaskExplainer;
9
- }}
10
-
11
- @customElement("webwriter-task-explainer")
12
- export class WebwriterTaskExplainer extends LitElementWw {
13
-
14
- @property({type: Boolean, attribute: true, reflect: true})
15
- accessor active = false
16
-
17
- static styles = css`
18
- :host {
19
- background: var(--sl-color-gray-100);
20
- border: 2px solid var(--sl-color-gray-200);
21
- box-sizing: border-box;
22
- padding: 10px;
23
- font-size: 0.8rem;
24
- height: 100px;
25
- min-height: 100px;
26
- overflow-y: auto;
27
- scrollbar-width: thin;
28
- resize: vertical;
29
- display: block;
30
- }
31
- `
32
-
33
- render() {
34
- return html`<slot>
35
-
36
- </slot>`
37
- }
1
+ import {html, css} from "lit"
2
+ import {LitElementWw} from "@webwriter/lit"
3
+ import {customElement, property} from "lit/decorators.js"
4
+ import "@shoelace-style/shoelace/dist/themes/light.css"
5
+ import { styleMap } from "lit/directives/style-map.js";
6
+
7
+ import LOCALIZE from "../../localization/generated"
8
+ import {msg} from "@lit/localize"
9
+
10
+ declare global {interface HTMLElementTagNameMap {
11
+ "webwriter-task-explainer": WebwriterTaskExplainer;
12
+ }}
13
+
14
+ @customElement("webwriter-task-explainer")
15
+ export class WebwriterTaskExplainer extends LitElementWw {
16
+
17
+ localize = LOCALIZE
18
+
19
+ @property({type: Boolean, attribute: true, reflect: true})
20
+ accessor active = false
21
+
22
+ static styles = css`
23
+ :host {
24
+ background: var(--sl-color-gray-100);
25
+ border: 2px solid var(--sl-color-gray-200);
26
+ box-sizing: border-box;
27
+ padding: 10px;
28
+ font-size: 0.8rem;
29
+ height: 100px;
30
+ min-height: 100px;
31
+ overflow-y: auto;
32
+ scrollbar-width: thin;
33
+ resize: vertical;
34
+ display: block;
35
+ }
36
+ `
37
+
38
+ render() {
39
+ return html`<slot>
40
+
41
+ </slot>`
42
+ }
38
43
  }