|
1 | | -<script> |
| 1 | +<script lang="ts"> |
2 | 2 | import { browser } from '$app/environment'; |
3 | 3 | import { afterNavigate, beforeNavigate } from '$app/navigation'; |
4 | 4 | import { acceptCompletion } from '@codemirror/autocomplete'; |
|
18 | 18 | import { files, selected_file, selected_name, update_file } from './state.js'; |
19 | 19 | import { toStore } from 'svelte/store'; |
20 | 20 | import { autocomplete_for_svelte } from '@sveltejs/site-kit/codemirror'; |
| 21 | + import type { Diagnostic } from '@codemirror/lint'; |
| 22 | + import type { Exercise, Stub } from '$lib/tutorial'; |
21 | 23 |
|
22 | | - /** @type {import('$lib/tutorial').Exercise}*/ |
23 | | - export let exercise; |
| 24 | + interface Props { |
| 25 | + exercise: Exercise; |
| 26 | + } |
| 27 | +
|
| 28 | + let { exercise }: Props = $props(); |
24 | 29 |
|
25 | | - /** @type {HTMLDivElement} */ |
26 | | - let container; |
| 30 | + let container = $state() as HTMLDivElement; |
27 | 31 |
|
28 | | - let preserve_editor_focus = false; |
| 32 | + let preserve_editor_focus = $state(false); |
29 | 33 | let skip_reset = true; |
30 | 34 |
|
31 | | - /** @type {any} */ |
32 | | - let remove_focus_timeout; |
| 35 | + let remove_focus_timeout = $state<any>(); |
33 | 36 |
|
34 | | - /** @type {Map<string, import('@codemirror/state').EditorState>} */ |
35 | | - let editor_states = new Map(); |
| 37 | + let editor_states = new Map<string, EditorState>(); |
36 | 38 |
|
37 | | - /** @type {import('@codemirror/view').EditorView} */ |
38 | | - let editor_view; |
| 39 | + let editor_view = $state() as EditorView; |
39 | 40 |
|
40 | 41 | const warnings = toStore(() => adapter_state.warnings); |
41 | 42 |
|
|
47 | 48 | svelteTheme |
48 | 49 | ]; |
49 | 50 |
|
50 | | - $: reset($files); |
51 | | -
|
52 | | - $: select_state($selected_name); |
53 | | -
|
54 | | - $: if (editor_view) { |
55 | | - if ($selected_name) { |
56 | | - const current_warnings = $warnings[$selected_name] || []; |
57 | | - const diagnostics = current_warnings.map((warning) => { |
58 | | - /** @type {import('@codemirror/lint').Diagnostic} */ |
59 | | - const diagnostic = { |
60 | | - from: warning.start.character, |
61 | | - to: warning.end.character, |
62 | | - severity: 'warning', |
63 | | - message: warning.message |
64 | | - }; |
65 | | -
|
66 | | - return diagnostic; |
67 | | - }); |
68 | | - const transaction = setDiagnostics(editor_view.state, diagnostics); |
69 | | -
|
70 | | - editor_view.dispatch(transaction); |
71 | | - } |
72 | | - } |
73 | | -
|
74 | 51 | let installed_vim = false; |
75 | 52 |
|
76 | 53 | /** @param {import('$lib/tutorial').Stub[]} $files */ |
77 | | - async function reset($files) { |
| 54 | + async function reset($files: Stub[]) { |
78 | 55 | if (skip_reset) return; |
79 | 56 |
|
80 | 57 | let should_install_vim = localStorage.getItem('vim') === 'true'; |
|
126 | 103 | lang = [ |
127 | 104 | svelte(), |
128 | 105 | ...autocomplete_for_svelte( |
129 | | - () => /** @type {import('$lib/tutorial').FileStub} */ ($selected_file).name, |
| 106 | + () => $selected_file!.name, |
130 | 107 | () => |
131 | 108 | $files |
132 | 109 | .filter( |
|
152 | 129 | } |
153 | 130 | } |
154 | 131 |
|
155 | | - /** @param {string | null} $selected_name */ |
156 | | - function select_state($selected_name) { |
| 132 | + function select_state($selected_name: string | null) { |
157 | 133 | if (skip_reset) return; |
158 | 134 |
|
159 | 135 | const state = |
|
210 | 186 | select_state($selected_name); |
211 | 187 | } |
212 | 188 | }); |
| 189 | +
|
| 190 | + $effect(() => { |
| 191 | + reset($files); |
| 192 | + }); |
| 193 | +
|
| 194 | + $effect(() => { |
| 195 | + select_state($selected_name); |
| 196 | + }); |
| 197 | +
|
| 198 | + $effect(() => { |
| 199 | + if (editor_view) { |
| 200 | + if ($selected_name) { |
| 201 | + const current_warnings = $warnings[$selected_name] || []; |
| 202 | + const diagnostics = current_warnings.map((warning) => { |
| 203 | + /** @type {import('@codemirror/lint').Diagnostic} */ |
| 204 | + const diagnostic: Diagnostic = { |
| 205 | + from: warning.start.character, |
| 206 | + to: warning.end.character, |
| 207 | + severity: 'warning', |
| 208 | + message: warning.message |
| 209 | + }; |
| 210 | +
|
| 211 | + return diagnostic; |
| 212 | + }); |
| 213 | + const transaction = setDiagnostics(editor_view.state, diagnostics); |
| 214 | +
|
| 215 | + editor_view.dispatch(transaction); |
| 216 | + } |
| 217 | + } |
| 218 | + }); |
213 | 219 | </script> |
214 | 220 |
|
215 | 221 | <svelte:window |
216 | | - on:pointerdown={(e) => { |
217 | | - if (!container.contains(/** @type {HTMLElement} */ (e.target))) { |
| 222 | + onpointerdown={(e) => { |
| 223 | + if (!container.contains((e.target as HTMLElement))) { |
218 | 224 | preserve_editor_focus = false; |
219 | 225 | } |
220 | 226 | }} |
221 | | - on:message={(e) => { |
| 227 | + onmessage={(e) => { |
222 | 228 | if (preserve_editor_focus && e.data.type === 'iframe_took_focus') { |
223 | 229 | editor_view.focus(); |
224 | 230 | } |
|
228 | 234 | <div |
229 | 235 | class="container" |
230 | 236 | bind:this={container} |
231 | | - on:focusin={() => { |
| 237 | + onfocusin={() => { |
232 | 238 | clearTimeout(remove_focus_timeout); |
233 | 239 | preserve_editor_focus = true; |
234 | 240 | }} |
235 | | - on:focusout={() => { |
| 241 | + onfocusout={() => { |
236 | 242 | // Heuristic: user did refocus themmselves if iframe_took_focus |
237 | 243 | // doesn't happen in the next few miliseconds. Needed |
238 | 244 | // because else navigations inside the iframe refocus the editor. |
|
0 commit comments