Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 20 additions & 27 deletions apps/svelte.dev/src/routes/tutorial/[...slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@
import Output from './Output.svelte';
import { ScreenToggle } from '@sveltejs/site-kit/components';
import Sidebar from './Sidebar.svelte';
import {
creating,
files,
reset_files,
selected_file,
selected_name,
solution
} from './state.svelte';
import { solution, workspace } from './state.svelte';
import { create_directories } from './utils';
import { needs_webcontainers, text_files } from './shared';
import OutputRollup from './OutputRollup.svelte';
Expand Down Expand Up @@ -93,10 +86,8 @@
return files;
}

// for the things we can't do with media queries

beforeNavigate(() => {
previous_files = $files;
previous_files = workspace.files;
});

afterNavigate(async () => {
Expand All @@ -105,7 +96,7 @@
const will_delete = previous_files.some((file) => !(file.name in a));

if (data.exercise.path !== path || will_delete) paused = true;
await reset($files);
await reset(workspace.files);

path = data.exercise.path;
paused = false;
Expand Down Expand Up @@ -137,40 +128,40 @@
}

function select_file(name: string | null) {
const file = name && $files.find((file) => file.name === name);
const file = name && workspace.files.find((file) => file.name === name);

if (!file && name) {
// trigger file creation input. first, create any intermediate directories
const new_directories = create_directories(name, $files);
const new_directories = create_directories(name, workspace.files);

if (new_directories.length > 0) {
reset_files([...$files, ...new_directories]);
workspace.reset_files([...workspace.files, ...new_directories]);
}

// find the parent directory
const parent = name.split('/').slice(0, -1).join('/');

creating.set({
workspace.creating = {
parent,
type: 'file'
});
};

show_filetree = true;
} else {
show_filetree = false;
selected_name.set(name);
workspace.selected_name = name;
}

show_editor = true;
}

function navigate_to_file(name: string) {
if (name === $selected_name) return;
if (name === workspace.selected_name) return;

select_file(name);

if (mobile) {
const q = new URLSearchParams({ file: $selected_name || '' });
const q = new URLSearchParams({ file: workspace.selected_name || '' });
history.pushState({}, '', `?${q}`);
}
}
Expand All @@ -191,21 +182,23 @@

let a = $derived(create_files(data.exercise.a));
let b = $derived(create_files({ ...data.exercise.a, ...data.exercise.b }));

// for the things we can't do with media queries
let mobile = $derived(w < 800);

$effect(() => {
files.set(Object.values(a));
workspace.files = Object.values(a);
});

$effect(() => {
solution.set(b);
});

$effect(() => {
selected_name.set(data.exercise.focus);
workspace.selected_name = data.exercise.focus;
});

let completed = $derived(is_completed($files, b));
let completed = $derived(is_completed(workspace.files, b));
</script>

<svelte:head>
Expand Down Expand Up @@ -248,7 +241,7 @@
exercise={data.exercise}
{completed}
toggle={() => {
reset_files(Object.values(completed ? a : b));
workspace.reset_files(Object.values(completed ? a : b));
}}
/>

Expand Down Expand Up @@ -277,7 +270,7 @@
<section slot="a" class="navigator">
{#if mobile}
<button class="file" onclick={() => (show_filetree = !show_filetree)}>
{$selected_file?.name.replace(
{workspace.selected_name?.replace(
data.exercise.scope.prefix,
data.exercise.scope.name + '/'
) ?? 'Files'}
Expand All @@ -294,7 +287,7 @@

<section slot="b" class="editor-container">
<Editor exercise={data.exercise} warnings={adapter_state.warnings} />
<ImageViewer selected={$selected_file} />
<ImageViewer selected={workspace.selected_file} />

{#if mobile && show_filetree}
<div class="mobile-filetree">
Expand Down Expand Up @@ -333,7 +326,7 @@
const url = new URL(location.origin + location.pathname);

if (show_editor) {
url.searchParams.set('file', $selected_name ?? '');
url.searchParams.set('file', workspace.selected_name ?? '');
}

history.pushState({}, '', url); // TODO use SvelteKit pushState
Expand Down
45 changes: 22 additions & 23 deletions apps/svelte.dev/src/routes/tutorial/[...slug]/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { svelteTheme } from '@sveltejs/repl/theme';
import { basicSetup } from 'codemirror';
import { onMount, tick } from 'svelte';
import { files, selected_file, selected_name, update_file } from './state.svelte';
import { workspace } from './state.svelte';
import { autocomplete_for_svelte } from '@sveltejs/site-kit/codemirror';
import type { Diagnostic } from '@codemirror/lint';
import type { Exercise, Stub } from '$lib/tutorial';
Expand Down Expand Up @@ -48,8 +48,7 @@

let installed_vim = false;

/** @param {import('$lib/tutorial').Stub[]} $files */
async function reset($files: Stub[]) {
async function reset(files: Stub[]) {
if (skip_reset) return;

let should_install_vim = localStorage.getItem('vim') === 'true';
Expand All @@ -66,7 +65,7 @@
extensions.push(vim());
}

for (const file of $files) {
for (const file of files) {
if (file.type !== 'file') continue;

let state = editor_states.get(file.name);
Expand All @@ -86,7 +85,7 @@
editor_states.set(file.name, transaction.state);
state = transaction.state;

if ($selected_name === file.name) {
if (workspace.selected_name === file.name) {
editor_view.setState(state);
}
}
Expand All @@ -101,9 +100,9 @@
lang = [
svelte(),
...autocomplete_for_svelte(
() => $selected_file!.name,
() => workspace.selected_name!,
() =>
$files
files
.filter(
(file) =>
file.type === 'file' &&
Expand All @@ -127,11 +126,11 @@
}
}

function select_state($selected_name: string | null) {
function select_state(selected_name: string | null) {
if (skip_reset) return;

const state =
($selected_name && editor_states.get($selected_name)) ||
(selected_name && editor_states.get(selected_name)) ||
EditorState.create({
doc: '',
extensions: [EditorState.readOnly.of(true)]
Expand All @@ -146,17 +145,17 @@
async dispatch(transaction) {
editor_view.update([transaction]);

if (transaction.docChanged && $selected_file) {
if (transaction.docChanged && workspace.selected_file) {
skip_reset = true;

// TODO do we even need to update `$files`? maintaining separate editor states is probably sufficient
update_file({
...$selected_file,
// TODO do we even need to update `workspace.files`? maintaining separate editor states is probably sufficient
workspace.update_file({
...workspace.selected_file,
contents: editor_view.state.doc.toString()
});

// keep `editor_states` updated so that undo/redo history is preserved for files independently
editor_states.set($selected_file.name, editor_view.state);
editor_states.set(workspace.selected_file.name, editor_view.state);

await tick();
skip_reset = false;
Expand All @@ -177,26 +176,26 @@
skip_reset = false;

editor_states.clear();
await reset($files);
await reset(workspace.files);

if (editor_view) {
// could be false if onMount returned early
select_state($selected_name);
select_state(workspace.selected_name);
}
});

$effect(() => {
reset($files);
reset(workspace.files);
});

$effect(() => {
select_state($selected_name);
select_state(workspace.selected_name);
});

$effect(() => {
if (editor_view) {
if ($selected_name) {
const current_warnings = warnings[$selected_name] || [];
if (workspace.selected_name) {
const current_warnings = warnings[workspace.selected_name] || [];
const diagnostics = current_warnings.map((warning) => {
/** @type {import('@codemirror/lint').Diagnostic} */
const diagnostic: Diagnostic = {
Expand Down Expand Up @@ -245,15 +244,15 @@
}, 200);
}}
>
{#if !browser && $selected_file}
{#if !browser && workspace.selected_file}
<div class="fake">
<div class="fake-gutter">
{#each $selected_file.contents.split('\n') as _, i}
{#each workspace.selected_file.contents.split('\n') as _, i}
<div class="fake-line">{i + 1}</div>
{/each}
</div>
<div class="fake-content">
{#each $selected_file.contents.split('\n') as line}
{#each workspace.selected_file.contents.split('\n') as line}
<pre>{line || ' '}</pre>
{/each}
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/svelte.dev/src/routes/tutorial/[...slug]/Loading.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { load_webcontainer, reset } from './adapter.svelte';
import { files } from './state.svelte';
import { workspace } from './state.svelte';

interface Props {
initial: boolean;
Expand Down Expand Up @@ -69,7 +69,7 @@
onclick={async () => {
error = null;
load_webcontainer(true);
await reset($files);
await reset(workspace.files);
}}>clicking here</button
>.
</p>
Expand Down
67 changes: 35 additions & 32 deletions apps/svelte.dev/src/routes/tutorial/[...slug]/filetree/File.svelte
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
<script>
<script lang="ts">
import * as context from './context.js';
import Item from './Item.svelte';
import file_icon from '$lib/icons/file.svg';
import { selected_name, solution } from '../state.svelte';
import { solution, workspace } from '../state.svelte';
import type { FileStub, MenuItem } from '$lib/tutorial';

/** @type {import('$lib/tutorial').FileStub} */
export let file;
interface Props {
file: FileStub;
depth: number;
}

/** @type {number} */
export let depth;
let { file, depth }: Props = $props();

const { rename, remove, select } = context.get();

let renaming = false;
let renaming = $state(false);

$: can_remove = !$solution[file.name];
let can_remove = $derived(!$solution[file.name]);

/** @type {import('$lib/tutorial').MenuItem[]} */
$: actions = can_remove
? [
{
icon: 'rename',
label: 'Rename',
fn: () => {
renaming = true;
let actions: MenuItem[] = $derived(
can_remove
? [
{
icon: 'rename',
label: 'Rename',
fn: () => {
renaming = true;
}
},
{
icon: 'delete',
label: 'Delete',
fn: () => {
remove(file);
}
}
},
{
icon: 'delete',
label: 'Delete',
fn: () => {
remove(file);
}
}
]
: [];
]
: []
);
</script>

<Item
Expand All @@ -43,16 +46,16 @@
{renaming}
basename={file.basename}
icon={file_icon}
selected={file.name === $selected_name}
selected={file.name === workspace.selected_name}
{actions}
on:click={() => select(file.name)}
on:edit={() => {
onclick={() => select(file.name)}
onedit={() => {
renaming = true;
}}
on:rename={(e) => {
rename(file, e.detail.basename);
onrename={(basename) => {
rename(file, basename);
}}
on:cancel={() => {
oncancel={() => {
renaming = false;
}}
/>
Loading
Loading