Skip to content

Commit ca4da1f

Browse files
authored
Modernise tutorial editor a bit (#415)
* remove store indirection * make warnings a prop * state.js -> state.svelte.ts * convert to ts * extract helper * note to self * migrate tutorial page * migrate to ts * note to self * fix type errors
1 parent bd3b7d3 commit ca4da1f

File tree

9 files changed

+139
-153
lines changed

9 files changed

+139
-153
lines changed

apps/svelte.dev/src/routes/tutorial/[...slug]/+page.svelte

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<script>
1+
<script lang="ts">
22
import { afterNavigate, beforeNavigate } from '$app/navigation';
33
import { SplitPane } from '@rich_harris/svelte-split-pane';
4-
import { reset } from './adapter.svelte';
4+
import { adapter_state, reset } from './adapter.svelte';
55
import Editor from './Editor.svelte';
66
import ContextMenu from './filetree/ContextMenu.svelte';
77
import Filetree from './filetree/Filetree.svelte';
@@ -10,40 +10,39 @@
1010
import { ScreenToggle } from '@sveltejs/site-kit/components';
1111
import Sidebar from './Sidebar.svelte';
1212
import {
13-
create_directories,
1413
creating,
1514
files,
1615
reset_files,
1716
selected_file,
1817
selected_name,
1918
solution
20-
} from './state.js';
19+
} from './state.svelte';
20+
import { create_directories } from './utils';
2121
import { needs_webcontainers, text_files } from './shared';
2222
import OutputRollup from './OutputRollup.svelte';
2323
import { page } from '$app/stores';
2424
import Controls from './Controls.svelte';
25+
import type { Stub } from '$lib/tutorial';
26+
import type { Snapshot } from './$types.js';
2527
26-
export let data;
28+
interface Props {
29+
data: any;
30+
}
31+
32+
let { data }: Props = $props();
2733
2834
let path = data.exercise.path;
29-
let show_editor = false;
30-
let show_filetree = false;
31-
let paused = false;
32-
let w = 1000;
35+
let show_editor = $state(false);
36+
let show_filetree = $state(false);
37+
let paused = $state(false);
38+
let w = $state(1000);
3339
34-
/** @type {import('$lib/tutorial').Stub[]} */
35-
let previous_files = [];
40+
let previous_files: Stub[] = [];
3641
37-
/**
38-
* @param {Record<string, string>} map
39-
* @returns {Record<string, import('$lib/tutorial').Stub>}
40-
*/
41-
function create_files(map) {
42-
/** @type {Record<string, import('$lib/tutorial').Stub>} */
43-
const files = {};
42+
function create_files(map: Record<string, string>): Record<string, Stub> {
43+
const files: Record<string, Stub> = {};
4444
45-
/** @type {string[]} */
46-
const to_delete = [];
45+
const to_delete: string[] = [];
4746
4847
for (const key in map) {
4948
const contents = map[key];
@@ -53,7 +52,7 @@
5352
}
5453
5554
const parts = key.split('/');
56-
const basename = /** @type {string} */ (parts.pop());
55+
const basename = parts.pop()!;
5756
const ext = basename.slice(basename.lastIndexOf('.'));
5857
5958
if (basename === '__delete') {
@@ -63,7 +62,7 @@
6362
6463
while (parts.length > 0) {
6564
const dir = `/${parts.join('/')}`;
66-
const basename = /** @type {string} */ (parts.pop());
65+
const basename = parts.pop()!;
6766
6867
files[dir] ??= {
6968
type: 'directory',
@@ -94,14 +93,7 @@
9493
return files;
9594
}
9695
97-
$: a = create_files(data.exercise.a);
98-
$: b = create_files({ ...data.exercise.a, ...data.exercise.b });
99-
100-
$: mobile = w < 800; // for the things we can't do with media queries
101-
$: files.set(Object.values(a));
102-
$: solution.set(b);
103-
$: selected_name.set(data.exercise.focus);
104-
$: completed = is_completed($files, b);
96+
// for the things we can't do with media queries
10597
10698
beforeNavigate(() => {
10799
previous_files = $files;
@@ -119,11 +111,7 @@
119111
paused = false;
120112
});
121113
122-
/**
123-
* @param {import('$lib/tutorial').Stub[]} files
124-
* @param {Record<string, import('$lib/tutorial').Stub> | null} solution
125-
*/
126-
function is_completed(files, solution) {
114+
function is_completed(files: Stub[], solution: Record<string, Stub> | null) {
127115
if (!solution) return true;
128116
129117
for (const file of files) {
@@ -143,14 +131,12 @@
143131
return true;
144132
}
145133
146-
/** @param {string} code */
147-
function normalise(code) {
134+
function normalise(code: string) {
148135
// TODO think about more sophisticated normalisation (e.g. truncate multiple newlines)
149136
return code.replace(/\s+/g, ' ').trim();
150137
}
151138
152-
/** @param {string | null} name */
153-
function select_file(name) {
139+
function select_file(name: string | null) {
154140
const file = name && $files.find((file) => file.name === name);
155141
156142
if (!file && name) {
@@ -178,8 +164,7 @@
178164
show_editor = true;
179165
}
180166
181-
/** @param {string} name */
182-
function navigate_to_file(name) {
167+
function navigate_to_file(name: string) {
183168
if (name === $selected_name) return;
184169
185170
select_file(name);
@@ -190,11 +175,10 @@
190175
}
191176
}
192177
193-
/** @type {HTMLElement} */
194-
let sidebar;
178+
let sidebar = $state() as HTMLElement;
195179
196-
/** @type {import('./$types').Snapshot<number>} */
197-
export const snapshot = {
180+
// TODO this doesn't seem to work any more?
181+
export const snapshot: Snapshot<number> = {
198182
capture: () => {
199183
const scroll = sidebar.scrollTop;
200184
sidebar.scrollTop = 0;
@@ -204,6 +188,24 @@
204188
sidebar.scrollTop = scroll;
205189
}
206190
};
191+
192+
let a = $derived(create_files(data.exercise.a));
193+
let b = $derived(create_files({ ...data.exercise.a, ...data.exercise.b }));
194+
let mobile = $derived(w < 800);
195+
196+
$effect(() => {
197+
files.set(Object.values(a));
198+
});
199+
200+
$effect(() => {
201+
solution.set(b);
202+
});
203+
204+
$effect(() => {
205+
selected_name.set(data.exercise.focus);
206+
});
207+
208+
let completed = $derived(is_completed($files, b));
207209
</script>
208210

209211
<svelte:head>
@@ -225,7 +227,7 @@
225227

226228
<svelte:window
227229
bind:innerWidth={w}
228-
on:popstate={(e) => {
230+
onpopstate={(e) => {
229231
const q = new URLSearchParams(location.search);
230232
const file = q.get('file');
231233

@@ -272,9 +274,9 @@
272274
max="300px"
273275
pos="200px"
274276
>
275-
<section class="navigator" slot="a">
277+
<section slot="a" class="navigator">
276278
{#if mobile}
277-
<button class="file" on:click={() => (show_filetree = !show_filetree)}>
279+
<button class="file" onclick={() => (show_filetree = !show_filetree)}>
278280
{$selected_file?.name.replace(
279281
data.exercise.scope.prefix,
280282
data.exercise.scope.name + '/'
@@ -290,8 +292,8 @@
290292
{/if}
291293
</section>
292294

293-
<section class="editor-container" slot="b">
294-
<Editor exercise={data.exercise} />
295+
<section slot="b" class="editor-container">
296+
<Editor exercise={data.exercise} warnings={adapter_state.warnings} />
295297
<ImageViewer selected={$selected_file} />
296298

297299
{#if mobile && show_filetree}

apps/svelte.dev/src/routes/tutorial/[...slug]/Editor.svelte

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@
1313
import { svelteTheme } from '@sveltejs/repl/theme';
1414
import { basicSetup } from 'codemirror';
1515
import { onMount, tick } from 'svelte';
16-
import { adapter_state } from './adapter.svelte';
17-
import './codemirror.css';
18-
import { files, selected_file, selected_name, update_file } from './state.js';
19-
import { toStore } from 'svelte/store';
16+
import { files, selected_file, selected_name, update_file } from './state.svelte';
2017
import { autocomplete_for_svelte } from '@sveltejs/site-kit/codemirror';
2118
import type { Diagnostic } from '@codemirror/lint';
2219
import type { Exercise, Stub } from '$lib/tutorial';
20+
import type { Warning } from 'svelte/compiler';
21+
import './codemirror.css';
2322
2423
interface Props {
2524
exercise: Exercise;
25+
warnings: Record<string, Warning[]>;
2626
}
2727
28-
let { exercise }: Props = $props();
28+
let { exercise, warnings }: Props = $props();
2929
3030
let container = $state() as HTMLDivElement;
3131
@@ -38,8 +38,6 @@
3838
3939
let editor_view = $state() as EditorView;
4040
41-
const warnings = toStore(() => adapter_state.warnings);
42-
4341
const extensions = [
4442
basicSetup,
4543
EditorState.tabSize.of(2),
@@ -198,20 +196,20 @@
198196
$effect(() => {
199197
if (editor_view) {
200198
if ($selected_name) {
201-
const current_warnings = $warnings[$selected_name] || [];
199+
const current_warnings = warnings[$selected_name] || [];
202200
const diagnostics = current_warnings.map((warning) => {
203201
/** @type {import('@codemirror/lint').Diagnostic} */
204202
const diagnostic: Diagnostic = {
205-
from: warning.start.character,
206-
to: warning.end.character,
203+
from: warning.start!.character,
204+
to: warning.end!.character,
207205
severity: 'warning',
208206
message: warning.message
209207
};
210208
211209
return diagnostic;
212210
});
213-
const transaction = setDiagnostics(editor_view.state, diagnostics);
214211
212+
const transaction = setDiagnostics(editor_view.state, diagnostics);
215213
editor_view.dispatch(transaction);
216214
}
217215
}

apps/svelte.dev/src/routes/tutorial/[...slug]/Loading.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { load_webcontainer, reset } from './adapter.svelte';
3-
import { files } from './state.js';
3+
import { files } from './state.svelte';
44
55
interface Props {
66
initial: boolean;

apps/svelte.dev/src/routes/tutorial/[...slug]/filetree/File.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as context from './context.js';
33
import Item from './Item.svelte';
44
import file_icon from '$lib/icons/file.svg';
5-
import { selected_name, solution } from '../state.js';
5+
import { selected_name, solution } from '../state.svelte';
66
77
/** @type {import('$lib/tutorial').FileStub} */
88
export let file;

apps/svelte.dev/src/routes/tutorial/[...slug]/filetree/Filetree.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import Folder from './Folder.svelte';
55
import * as context from './context.js';
66
import Modal from '$lib/components/Modal.svelte';
7-
import { files, solution, reset_files, create_directories, selected_name } from '../state.js';
7+
import { files, solution, reset_files, selected_name } from '../state.svelte';
8+
import { create_directories } from '../utils';
89
import { afterNavigate } from '$app/navigation';
910
1011
/** @type {import('$lib/tutorial').Exercise} */

apps/svelte.dev/src/routes/tutorial/[...slug]/filetree/Folder.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import Item from './Item.svelte';
66
import folder_closed from '$lib/icons/folder.svg';
77
import folder_open from '$lib/icons/folder-open.svg';
8-
import { files, solution, creating } from '../state.js';
8+
import { files, solution, creating } from '../state.svelte';
99
1010
/** @type {import('$lib/tutorial').DirectoryStub} */
1111
export let directory;

apps/svelte.dev/src/routes/tutorial/[...slug]/state.js

Lines changed: 0 additions & 88 deletions
This file was deleted.

0 commit comments

Comments
 (0)