|
12 | 12 | import { Icon } from '@sveltejs/site-kit';
|
13 | 13 | import Loading from './Loading.svelte';
|
14 | 14 | import { PUBLIC_USE_FILESYSTEM } from '$env/static/public';
|
| 15 | + import ContextMenu from './ContextMenu.svelte'; |
15 | 16 |
|
16 | 17 | /** @type {import('./$types').PageData} */
|
17 | 18 | export let data;
|
|
29 | 30 |
|
30 | 31 | /** @type {import('monaco-editor').editor.ITextModel} */
|
31 | 32 | let current_model;
|
| 33 | + /** @type {import('$lib/types').Stub[]}*/ |
| 34 | + let current_stubs = []; |
32 | 35 |
|
33 | 36 | /** @type {HTMLIFrameElement} */
|
34 | 37 | let iframe;
|
|
46 | 49 |
|
47 | 50 | $: b = { ...data.section.a, ...data.section.b };
|
48 | 51 |
|
| 52 | + /** @type {import('$lib/types').FileTreeContext} */ |
49 | 53 | const { select } = setContext('filetree', {
|
50 |
| - /** @param {import('$lib/types').FileStub} file */ |
51 | 54 | select: (file) => {
|
52 | 55 | $selected = file;
|
53 | 56 | current_model = /** @type {import('monaco-editor').editor.ITextModel} */ (models.get(file));
|
54 | 57 | },
|
55 | 58 |
|
| 59 | + add: async (stubs) => { |
| 60 | + current_stubs = [...current_stubs, ...stubs]; |
| 61 | +
|
| 62 | + const { monaco } = await import('$lib/client/monaco/monaco.js'); |
| 63 | + for (const stub of stubs) { |
| 64 | + create_monaco_file(stub, monaco); |
| 65 | + } |
| 66 | +
|
| 67 | + await load_files(current_stubs); |
| 68 | +
|
| 69 | + if (stubs[0].type === 'file') { |
| 70 | + select(stubs[0]); |
| 71 | + } |
| 72 | + }, |
| 73 | +
|
| 74 | + edit: async (to_rename, new_name) => { |
| 75 | + /** @type {Array<[import('$lib/types').Stub, import('$lib/types').Stub]>}*/ |
| 76 | + const changed = []; |
| 77 | + current_stubs = current_stubs.map((s) => { |
| 78 | + if (!s.name.startsWith(to_rename.name)) { |
| 79 | + return s; |
| 80 | + } |
| 81 | +
|
| 82 | + const name = |
| 83 | + s.name.slice(0, to_rename.name.length - to_rename.basename.length) + |
| 84 | + new_name + |
| 85 | + s.name.slice(to_rename.name.length); |
| 86 | + const basename = s === to_rename ? new_name : s.basename; |
| 87 | + const new_stub = { ...s, name, basename }; |
| 88 | +
|
| 89 | + changed.push([s, new_stub]); |
| 90 | + return new_stub; |
| 91 | + }); |
| 92 | +
|
| 93 | + const { monaco } = await import('$lib/client/monaco/monaco.js'); |
| 94 | + for (const [old_s, new_s] of changed) { |
| 95 | + if (old_s.type === 'file') { |
| 96 | + models.get(old_s)?.dispose(); |
| 97 | + models.delete(old_s); |
| 98 | + create_monaco_file(new_s, monaco); |
| 99 | + } |
| 100 | + } |
| 101 | +
|
| 102 | + await load_files(current_stubs); |
| 103 | +
|
| 104 | + if (to_rename.type === 'file') { |
| 105 | + select(/** @type {any} */ (changed.find(([old_s]) => old_s === to_rename))[1]); |
| 106 | + } |
| 107 | + }, |
| 108 | +
|
| 109 | + remove: async (stub) => { |
| 110 | + const out = current_stubs.filter((s) => s.name.startsWith(stub.name)); |
| 111 | + current_stubs = current_stubs.filter((s) => !out.includes(s)); |
| 112 | +
|
| 113 | + for (const s of out) { |
| 114 | + if (s.type === 'file') { |
| 115 | + models.get(s)?.dispose(); |
| 116 | + models.delete(s); |
| 117 | + } |
| 118 | + } |
| 119 | +
|
| 120 | + if ($selected && out.includes($selected)) { |
| 121 | + $selected = null; |
| 122 | + } |
| 123 | +
|
| 124 | + await load_files(current_stubs); |
| 125 | + }, |
| 126 | +
|
56 | 127 | selected
|
57 | 128 | });
|
58 | 129 |
|
|
84 | 155 | models.clear();
|
85 | 156 |
|
86 | 157 | complete_states = {};
|
87 |
| -
|
88 |
| - const stubs = Object.values(data.section.a); |
| 158 | + current_stubs = Object.values(data.section.a); |
89 | 159 |
|
90 | 160 | const { monaco } = await import('$lib/client/monaco/monaco.js');
|
91 | 161 |
|
92 |
| - stubs.forEach((stub) => { |
93 |
| - if (stub.type === 'file') { |
94 |
| - const type = /** @type {string} */ (stub.basename.split('.').pop()); |
95 |
| -
|
96 |
| - const model = monaco.editor.createModel( |
97 |
| - stub.contents, |
98 |
| - types[type] || type, |
99 |
| - new monaco.Uri().with({ path: stub.name }) |
100 |
| - ); |
101 |
| -
|
102 |
| - model.updateOptions({ tabSize: 2 }); |
103 |
| -
|
104 |
| - model.onDidChangeContent(() => { |
105 |
| - const contents = model.getValue(); |
106 |
| -
|
107 |
| - if (!completing) { |
108 |
| - adapter?.update([{ ...stub, contents }]); |
109 |
| - } |
110 |
| - }); |
111 |
| -
|
112 |
| - models.set(stub, model); |
113 |
| - } |
| 162 | + current_stubs.forEach((stub) => { |
| 163 | + create_monaco_file(stub, monaco); |
114 | 164 | });
|
115 | 165 |
|
116 | 166 | select(
|
117 | 167 | /** @type {import('$lib/types').FileStub} */ (
|
118 |
| - stubs.find((stub) => stub.name === data.section.focus) |
| 168 | + current_stubs.find((stub) => stub.name === data.section.focus) |
119 | 169 | )
|
120 | 170 | );
|
121 | 171 |
|
|
124 | 174 | load_exercise();
|
125 | 175 | });
|
126 | 176 |
|
| 177 | + /** |
| 178 | + * @param {import('$lib/types').Stub} stub |
| 179 | + * @param {import('monaco-editor')} monaco |
| 180 | + */ |
| 181 | + function create_monaco_file(stub, monaco) { |
| 182 | + if (stub.type === 'file') { |
| 183 | + const type = /** @type {string} */ (stub.basename.split('.').pop()); |
| 184 | +
|
| 185 | + const model = monaco.editor.createModel( |
| 186 | + stub.contents, |
| 187 | + types[type] || type, |
| 188 | + new monaco.Uri().with({ path: stub.name }) |
| 189 | + ); |
| 190 | +
|
| 191 | + model.updateOptions({ tabSize: 2 }); |
| 192 | +
|
| 193 | + model.onDidChangeContent(() => { |
| 194 | + const contents = model.getValue(); |
| 195 | +
|
| 196 | + if (!completing) { |
| 197 | + stub.contents = contents; |
| 198 | + adapter?.update([stub]); |
| 199 | + } |
| 200 | + }); |
| 201 | +
|
| 202 | + models.set(stub, model); |
| 203 | + } |
| 204 | + } |
| 205 | +
|
127 | 206 | /**
|
128 | 207 | * Loads the adapter initially or resets it. This method can throw.
|
129 | 208 | * @param {import('$lib/types').Stub[]} stubs
|
|
178 | 257 | loading = true;
|
179 | 258 |
|
180 | 259 | // Load expected output first so we can compare it to the actual output to determine when it's completed
|
181 |
| - let adapter = await reset_adapter(Object.values(b)); |
| 260 | + await reset_adapter(Object.values(b)); |
182 | 261 | expected = await get_transformed_modules(data.section.scope.prefix, Object.values(b));
|
183 | 262 |
|
184 | 263 | const stubs = Object.values(data.section.a);
|
185 |
| - adapter = await reset_adapter(stubs); |
186 |
| - const actual = await get_transformed_modules(data.section.scope.prefix, stubs); |
187 |
| -
|
188 |
| - for (const [name, transformed] of expected.entries()) { |
189 |
| - complete_states[name] = transformed === actual.get(name); |
190 |
| - } |
191 |
| -
|
192 |
| - set_iframe_src(adapter.base); |
| 264 | + await load_files(stubs); |
193 | 265 |
|
194 | 266 | loading = false;
|
195 | 267 | initial = false;
|
|
200 | 272 | }
|
201 | 273 | }
|
202 | 274 |
|
| 275 | + /** |
| 276 | + * @param {import('$lib/types').Stub[]} stubs |
| 277 | + */ |
| 278 | + async function load_files(stubs) { |
| 279 | + adapter = await reset_adapter(stubs); |
| 280 | + const actual = await get_transformed_modules(data.section.scope.prefix, stubs); |
| 281 | +
|
| 282 | + for (const [name, transformed] of expected.entries()) { |
| 283 | + complete_states[name] = transformed === actual.get(name); |
| 284 | + } |
| 285 | +
|
| 286 | + set_iframe_src(adapter.base); |
| 287 | + } |
| 288 | +
|
203 | 289 | /** @type {NodeJS.Timeout} */
|
204 | 290 | let timeout;
|
205 | 291 |
|
|
324 | 410 | <title>{data.section.chapter.title} / {data.section.title} • Svelte Tutorial</title>
|
325 | 411 | </svelte:head>
|
326 | 412 |
|
| 413 | +<ContextMenu /> |
| 414 | +
|
327 | 415 | <div class="container">
|
328 | 416 | <SplitPane type="horizontal" min="360px" max="50%" pos="33%">
|
329 | 417 | <section class="content" slot="a">
|
|
344 | 432 | <div class="filetree">
|
345 | 433 | <Folder
|
346 | 434 | {...data.section.scope}
|
347 |
| - files={Object.values(data.section.a).filter((stub) => !hidden.has(stub.basename))} |
| 435 | + files={current_stubs.filter((stub) => !hidden.has(stub.basename))} |
348 | 436 | expanded
|
349 | 437 | />
|
350 | 438 | </div>
|
|
0 commit comments