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
52 changes: 36 additions & 16 deletions apps/svelte.dev/src/routes/tutorial/[...slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import Controls from './Controls.svelte';
import type { Item } from 'editor';
import type { Snapshot } from './$types.js';
import { tick } from 'svelte';

interface Props {
data: any;
Expand All @@ -31,6 +32,7 @@
let w = $state(1000);

let editor: any; // TODO
let skip_set_files = true;

let previous_files: Item[] = [];

Expand Down Expand Up @@ -88,22 +90,6 @@
return files;
}

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

afterNavigate(async () => {
w = window.innerWidth;

const will_delete = previous_files.some((file) => !(file.name in a));

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

path = data.exercise.path;
paused = false;
});

function is_completed(files: Item[], solution: Record<string, Item> | null) {
if (!solution) return true;

Expand Down Expand Up @@ -213,6 +199,32 @@
workspace.selected_name = data.exercise.focus; // TODO this probably belongs in afterNavigate
});

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

afterNavigate(async () => {
skip_set_files = false;

editor.reset();

w = window.innerWidth;

const will_delete = previous_files.some((file) => !(file.name in a));

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

path = data.exercise.path;
paused = false;
});

$effect(() => {
const files = workspace.files; // capture the dependency. TODO don't use an effect here
if (!skip_set_files) editor.update_files(files);
});

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

Expand Down Expand Up @@ -306,6 +318,14 @@
bind:this={editor}
warnings={adapter.adapter_state.warnings}
{workspace}
onchange={async (file, contents) => {
skip_set_files = true;

workspace.update_file({ ...file, contents });

await tick();
skip_set_files = false;
}}
autocomplete_filter={(file) => {
return (
file.name.startsWith('/src') &&
Expand Down
55 changes: 16 additions & 39 deletions packages/editor/src/lib/Editor.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { BROWSER } from 'esm-env';
import { afterNavigate, beforeNavigate } from '$app/navigation';
import { acceptCompletion } from '@codemirror/autocomplete';
import { indentWithTab } from '@codemirror/commands';
import { html } from '@codemirror/lang-html';
Expand All @@ -12,7 +11,6 @@
import { svelte } from '@replit/codemirror-lang-svelte';
import { svelteTheme } from '@sveltejs/repl/theme';
import { basicSetup } from 'codemirror';
import { tick } from 'svelte';
import { autocomplete_for_svelte } from '@sveltejs/site-kit/codemirror';
import type { Diagnostic } from '@codemirror/lint';
import { Workspace, type Item, type File } from './Workspace.svelte.js';
Expand All @@ -22,15 +20,15 @@
interface Props {
warnings: Record<string, Warning[]>; // TODO this should include errors as well
workspace: Workspace;
onchange: (file: File, contents: string) => void;
autocomplete_filter?: (file: File) => boolean;
}

let { warnings, workspace, autocomplete_filter = () => true }: Props = $props();
let { warnings, workspace, onchange, autocomplete_filter = () => true }: Props = $props();

let container: HTMLDivElement;

let preserve_editor_focus = $state(false);
let skip_reset = true;

let remove_focus_timeout = $state<any>();

Expand All @@ -48,9 +46,7 @@

let installed_vim = false;

async function reset(files: Item[]) {
if (skip_reset) return;

export async function update_files(files: Item[]) {
let should_install_vim = localStorage.getItem('vim') === 'true';

const q = new URLSearchParams(location.search);
Expand Down Expand Up @@ -120,11 +116,21 @@
editor_states.set(file.name, state);
}
}

select_state(workspace.selected_name);
}

function select_state(selected_name: string | null) {
if (skip_reset) return;
/**
* Wipe the editor state clean, including all undo/redo history.
* Typically this only happens when navigating, not when
* updating files in-situ
*/
export async function reset() {
editor_states.clear();
await update_files(workspace.files);
}

function select_state(selected_name: string | null) {
const state =
(selected_name && editor_states.get(selected_name)) ||
EditorState.create({
Expand All @@ -142,19 +148,10 @@
editor_view.update([transaction]);

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

// 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()
});
onchange(workspace.selected_file, editor_view.state.doc.toString());

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

await tick();
skip_reset = false;
}
}
});
Expand All @@ -164,26 +161,6 @@
};
});

beforeNavigate(() => {
skip_reset = true;
});

afterNavigate(async () => {
skip_reset = false;

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

if (editor_view) {
// TODO is it possible to get here?
select_state(workspace.selected_name);
}
});

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

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