Skip to content

Commit e9115f2

Browse files
committed
convert stores to $state objects
1 parent 23e405b commit e9115f2

File tree

12 files changed

+191
-241
lines changed

12 files changed

+191
-241
lines changed

src/lib/client/adapters/webcontainer/index.js

Lines changed: 28 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { escape_html, get_depth } from '../../../utils.js';
66
import { ready } from '../common/index.js';
77

88
/**
9-
* @typedef {import("../../../../routes/tutorial/[slug]/state.js").CompilerWarning} CompilerWarning
9+
* @typedef {import("../../../../routes/tutorial/[slug]/state.svelte.js").CompilerWarning} CompilerWarning
1010
*/
1111

1212
const converter = new AnsiToHtml({
@@ -17,15 +17,11 @@ const converter = new AnsiToHtml({
1717
let vm;
1818

1919
/**
20-
* @param {import('svelte/store').Writable<string | null>} base
21-
* @param {import('svelte/store').Writable<Error | null>} error
22-
* @param {import('svelte/store').Writable<{ value: number, text: string }>} progress
23-
* @param {import('svelte/store').Writable<string[]>} logs
24-
* @param {import('svelte/store').Writable<Record<string, CompilerWarning[]>>} warnings
20+
* @param {typeof import('../../../../routes/tutorial/[slug]/adapter.svelte.js').a} state
2521
* @returns {Promise<import('$lib/types').Adapter>}
2622
*/
27-
export async function create(base, error, progress, logs, warnings) {
28-
progress.set({ value: 0, text: 'loading files' });
23+
export async function create(state) {
24+
state.progress = { value: 0, text: 'loading files' };
2925

3026
const q = yootils.queue(1);
3127
/** @type {Map<string, Array<import('$lib/types').FileStub>>} */
@@ -34,10 +30,10 @@ export async function create(base, error, progress, logs, warnings) {
3430
/** Paths and contents of the currently loaded file stubs */
3531
let current_stubs = stubs_to_map([]);
3632

37-
progress.set({ value: 1 / 5, text: 'booting webcontainer' });
33+
state.progress = { value: 1 / 5, text: 'booting webcontainer' };
3834
vm = await WebContainer.boot();
3935

40-
progress.set({ value: 2 / 5, text: 'writing virtual files' });
36+
state.progress = { value: 2 / 5, text: 'writing virtual files' };
4137
const common = await ready;
4238
await vm.mount({
4339
'common.zip': {
@@ -48,48 +44,31 @@ export async function create(base, error, progress, logs, warnings) {
4844
}
4945
});
5046

51-
/** @type {Record<string, CompilerWarning[]>} */
52-
let $warnings;
53-
warnings.subscribe((value) => $warnings = value);
54-
55-
/** @type {any} */
56-
let timeout;
57-
58-
/** @param {number} msec */
59-
function schedule_to_update_warning(msec) {
60-
clearTimeout(timeout);
61-
timeout = setTimeout(() => warnings.set($warnings), msec);
62-
}
63-
6447
const log_stream = () =>
6548
new WritableStream({
6649
write(chunk) {
6750
if (chunk === '\x1B[1;1H') {
6851
// clear screen
69-
logs.set([]);
70-
52+
state.logs = [];
7153
} else if (chunk?.startsWith('svelte:warnings:')) {
7254
/** @type {CompilerWarning} */
7355
const warn = JSON.parse(chunk.slice(16));
74-
const current = $warnings[warn.filename];
56+
const current = state.warnings[warn.filename];
7557

7658
if (!current) {
77-
$warnings[warn.filename] = [warn];
78-
// the exact same warning may be given multiple times in a row
79-
} else if (!current.some((s) => (s.code === warn.code && s.pos === warn.pos))) {
59+
state.warnings[warn.filename] = [warn];
60+
// the exact same warning may be given multiple times in a row
61+
} else if (!current.some((s) => s.code === warn.code && s.pos === warn.pos)) {
8062
current.push(warn);
8163
}
82-
83-
schedule_to_update_warning(100);
84-
8564
} else {
8665
const log = converter.toHtml(escape_html(chunk)).replace(/\n/g, '<br>');
87-
logs.update(($logs) => [...$logs, log]);
66+
state.logs = [...state.logs, log];
8867
}
8968
}
9069
});
9170

92-
progress.set({ value: 3 / 5, text: 'unzipping files' });
71+
state.progress = { value: 3 / 5, text: 'unzipping files' };
9372
const unzip = await vm.spawn('node', ['unzip.cjs']);
9473
unzip.output.pipeTo(log_stream());
9574
const code = await unzip.exit;
@@ -101,11 +80,11 @@ export async function create(base, error, progress, logs, warnings) {
10180
await vm.spawn('chmod', ['a+x', 'node_modules/vite/bin/vite.js']);
10281

10382
vm.on('server-ready', (_port, url) => {
104-
base.set(url);
83+
state.base = url;
10584
});
10685

10786
vm.on('error', ({ message }) => {
108-
error.set(new Error(message));
87+
state.error = new Error(message);
10988
});
11089

11190
let launched = false;
@@ -114,7 +93,7 @@ export async function create(base, error, progress, logs, warnings) {
11493
if (launched) return;
11594
launched = true;
11695

117-
progress.set({ value: 4 / 5, text: 'starting dev server' });
96+
state.progress = { value: 4 / 5, text: 'starting dev server' };
11897

11998
await new Promise(async (fulfil, reject) => {
12099
const error_unsub = vm.on('error', (error) => {
@@ -124,7 +103,7 @@ export async function create(base, error, progress, logs, warnings) {
124103

125104
const ready_unsub = vm.on('server-ready', (_port, base) => {
126105
ready_unsub();
127-
progress.set({ value: 5 / 5, text: 'ready' });
106+
state.progress = { value: 5 / 5, text: 'ready' };
128107
fulfil(base); // this will be the last thing that happens if everything goes well
129108
});
130109

@@ -179,30 +158,26 @@ export async function create(base, error, progress, logs, warnings) {
179158
// Don't delete the node_modules folder when switching from one exercise to another
180159
// where, as this crashes the dev server.
181160
const to_delete = [
182-
...Array.from(current_stubs.keys()).filter(
183-
(s) => !s.startsWith('/node_modules')
184-
),
161+
...Array.from(current_stubs.keys()).filter((s) => !s.startsWith('/node_modules')),
185162
...force_delete
186163
];
187164

188165
// initialize warnings of written files
189166
to_write
190-
.filter((stub) => stub.type === 'file' && $warnings[stub.name])
191-
.forEach((stub) => $warnings[stub.name] = []);
167+
.filter((stub) => stub.type === 'file' && state.warnings[stub.name])
168+
.forEach((stub) => (state.warnings[stub.name] = []));
192169
// remove warnings of deleted files
193170
to_delete
194-
.filter((stubname) => $warnings[stubname])
195-
.forEach((stubname) => delete $warnings[stubname]);
196-
197-
warnings.set($warnings);
171+
.filter((stubname) => state.warnings[stubname])
172+
.forEach((stubname) => delete state.warnings[stubname]);
198173

199174
current_stubs = stubs_to_map(stubs);
200175

201176
// For some reason, server-ready is fired again when the vite dev server is restarted.
202177
// We need to wait for it to finish before we can continue, else we might
203178
// request files from Vite before it's ready, leading to a timeout.
204-
const will_restart = launched &&
205-
(to_write.some(is_config) || to_delete.some(is_config_path));
179+
const will_restart =
180+
launched && (to_write.some(is_config) || to_delete.some(is_config_path));
206181
const promise = will_restart ? wait_for_restart_vite() : Promise.resolve();
207182

208183
for (const file of to_delete) {
@@ -223,14 +198,13 @@ export async function create(base, error, progress, logs, warnings) {
223198
});
224199
},
225200
update: (file) => {
226-
227201
let queue = q_per_file.get(file.name);
228202
if (queue) {
229203
queue.push(file);
230204
return Promise.resolve(false);
231205
}
232206

233-
q_per_file.set(file.name, queue = [file]);
207+
q_per_file.set(file.name, (queue = [file]));
234208

235209
return q.add(async () => {
236210
/** @type {import('@webcontainer/api').FileSystemTree} */
@@ -257,16 +231,14 @@ export async function create(base, error, progress, logs, warnings) {
257231
const will_restart = is_config(file);
258232

259233
while (queue && queue.length > 0) {
260-
261234
// if the file is updated many times rapidly, get the most recently updated one
262235
const file = /** @type {import('$lib/types').FileStub} */ (queue.pop());
263-
queue.length = 0
236+
queue.length = 0;
264237

265238
tree[basename] = to_file(file);
266239

267240
// initialize warnings of this file
268-
$warnings[file.name] = [];
269-
schedule_to_update_warning(100);
241+
state.warnings[file.name] = [];
270242

271243
await vm.mount(root);
272244

@@ -280,7 +252,7 @@ export async function create(base, error, progress, logs, warnings) {
280252
await new Promise((f) => setTimeout(f, 50));
281253
}
282254

283-
q_per_file.delete(file.name)
255+
q_per_file.delete(file.name);
284256

285257
return will_restart;
286258
});

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

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,15 @@
22
import { afterNavigate, beforeNavigate } from '$app/navigation';
33
import { SplitPane } from '@rich_harris/svelte-split-pane';
44
import { Icon } from '@sveltejs/site-kit/components';
5-
import { reset } from './adapter.js';
5+
import { reset } from './adapter.svelte.js';
66
import Editor from './Editor.svelte';
77
import ContextMenu from './filetree/ContextMenu.svelte';
88
import Filetree from './filetree/Filetree.svelte';
99
import ImageViewer from './ImageViewer.svelte';
1010
import Output from './Output.svelte';
1111
import ScreenToggle from './ScreenToggle.svelte';
1212
import Sidebar from './Sidebar.svelte';
13-
import {
14-
create_directories,
15-
creating,
16-
files,
17-
reset_files,
18-
selected_file,
19-
selected_name,
20-
solution
21-
} from './state.js';
13+
import { create_directories, reset_files, s } from './state.svelte.js';
2214
2315
let { data } = $props();
2416
@@ -32,20 +24,20 @@
3224
let previous_files = [];
3325
3426
let mobile = $derived(w < 800); // for the things we can't do with media queries
35-
let completed = $derived(is_completed($files, data.exercise.b));
27+
let completed = $derived(is_completed(s.files, data.exercise.b));
3628
3729
// meh: I have to duplicate this because $effect.pre doesn't run on the server
38-
files.set(Object.values(data.exercise.a));
39-
solution.set(data.exercise.b);
40-
selected_name.set(data.exercise.focus);
30+
s.files = Object.values(data.exercise.a);
31+
s.solution = data.exercise.b;
32+
s.selected_name = data.exercise.focus;
4133
$effect.pre(() => {
42-
files.set(Object.values(data.exercise.a));
43-
solution.set(data.exercise.b);
44-
selected_name.set(data.exercise.focus);
34+
s.files = Object.values(data.exercise.a);
35+
s.solution = data.exercise.b;
36+
s.selected_name = data.exercise.focus;
4537
});
4638
4739
beforeNavigate(() => {
48-
previous_files = $files;
40+
previous_files = s.files;
4941
});
5042
5143
afterNavigate(async () => {
@@ -54,7 +46,7 @@
5446
const will_delete = previous_files.some((file) => !(file.name in data.exercise.a));
5547
5648
if (data.exercise.path !== path || will_delete) paused = true;
57-
await reset($files);
49+
await reset(s.files);
5850
5951
path = data.exercise.path;
6052
paused = false;
@@ -92,41 +84,41 @@
9284
9385
/** @param {string | null} name */
9486
function select_file(name) {
95-
const file = name && $files.find((file) => file.name === name);
87+
const file = name && s.files.find((file) => file.name === name);
9688
9789
if (!file && name) {
9890
// trigger file creation input. first, create any intermediate directories
99-
const new_directories = create_directories(name, $files);
91+
const new_directories = create_directories(name, s.files);
10092
10193
if (new_directories.length > 0) {
102-
reset_files([...$files, ...new_directories]);
94+
reset_files([...s.files, ...new_directories]);
10395
}
10496
10597
// find the parent directory
10698
const parent = name.split('/').slice(0, -1).join('/');
10799
108-
creating.set({
100+
s.creating = {
109101
parent,
110102
type: 'file'
111-
});
103+
};
112104
113105
show_filetree = true;
114106
} else {
115107
show_filetree = false;
116-
selected_name.set(name);
108+
s.selected_name = name;
117109
}
118110
119111
show_editor = true;
120112
}
121113
122114
/** @param {string} name */
123115
function navigate_to_file(name) {
124-
if (name === $selected_name) return;
116+
if (name === s.selected_name) return;
125117
126118
select_file(name);
127119
128120
if (mobile) {
129-
const q = new URLSearchParams({ file: $selected_name || '' });
121+
const q = new URLSearchParams({ file: s.selected_name || '' });
130122
history.pushState({}, '', `?${q}`);
131123
}
132124
}
@@ -208,7 +200,7 @@
208200
<section class="navigator" slot="a">
209201
{#if mobile}
210202
<button class="file" on:click={() => (show_filetree = !show_filetree)}>
211-
{$selected_file?.name.replace(
203+
{s.selected_file?.name.replace(
212204
data.exercise.scope.prefix,
213205
data.exercise.scope.name + '/'
214206
) ?? 'Files'}
@@ -240,7 +232,7 @@
240232
241233
<section class="editor-container" slot="b">
242234
<Editor />
243-
<ImageViewer selected={$selected_file} />
235+
<ImageViewer selected={s.selected_file} />
244236
245237
{#if mobile && show_filetree}
246238
<div class="mobile-filetree">
@@ -273,7 +265,7 @@
273265
const url = new URL(location.origin + location.pathname);
274266
275267
if (show_editor) {
276-
url.searchParams.set('file', $selected_name ?? '');
268+
url.searchParams.set('file', s.selected_name ?? '');
277269
}
278270
279271
history.pushState({}, '', url);

0 commit comments

Comments
 (0)