Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/lib/assets/examples/other.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print("hello world!!")
90 changes: 90 additions & 0 deletions src/lib/playground/App.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from "$app/state";
import CodeMirror from "$lib/playground/components/Editor.svelte";
import StatusBar from "$lib/playground/components/StatusBar.svelte";
import Terminal from "$lib/playground/components/Terminal.svelte";
import TerminalDragger from "$lib/playground/components/TerminalDragger.svelte";
import Toolbar from "$lib/playground/components/Toolbar.svelte";
import { PurePy } from "$lib/playground/lib/purepy";
import { url_with_params_from_state } from "$lib/playground/lib/share";
import { onMount } from "svelte";
import Tabs from "./components/Tabs.svelte";
import { State } from "./lib/app.svelte";

let { app }: { app: State } = $props();

let terminalHeight: number | undefined = $state(undefined);

// let stdout = new Stdout();
let purepy: PurePy | undefined = undefined;
let editor: CodeMirror;

const share = async () => {
try {
app.fs.save_all();
const state = app.serialise();
const url = await url_with_params_from_state(page.url, state);
// update the url without reloading page
const update_url = goto(url);
// TODO: add visual feedback
const copy_to_clipboard = navigator.clipboard.writeText(url.toString());

await Promise.all([update_url, copy_to_clipboard]);
} catch (error) {
console.error(error);
}
};

const setup_codemirror = async () => {
await editor.ready;
};

const setup_pyodide = async () => {
app.stdout.write("Loading PurePy...");
purepy = await PurePy.load();
purepy.attach_stdout(app.stdout);
app.register_purepy(purepy);
app.stdout.write("Ready");
app.stdout.write("Press Cmd/Ctrl + S or use the button below to run");
};

onMount(async () => {
await Promise.all([setup_codemirror(), setup_pyodide()]);
});

const onkeydown = (event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
event.preventDefault();
app.save_and_run();
}
};
</script>

<svelte:window {onkeydown} />

<div class="w-full h-full flex flex-col bg-zinc-900 overflow-clip">
<div class="shrink-0 grow-0 flex flex-col">
<Toolbar
{share}
check={app.save_and_check}
run={app.save_and_run}
is_busy={app.busy}
/>
<Tabs {app} />
</div>

<div class="h-auto grow shrink">
<CodeMirror bind:this={editor} {app} />
</div>

<div class="shrink-0 grow-0 flex flex-col">
<TerminalDragger setHeight={(height) => (terminalHeight = height)} />
<StatusBar
dirty={app.fs.dirty}
check_result={app.check_result}
eval_result={app.eval_result}
/>
<Terminal {terminalHeight} stdout={app.stdout} />
</div>
</div>
35 changes: 0 additions & 35 deletions src/lib/playground/StatusBar.svelte

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
<script lang="ts">
import type { EditorView } from "codemirror";
import { onMount } from "svelte";
import type { State } from "../lib/app.svelte";

type Props = {
initial_value: string;
onupdate: (value: string) => void;
};

let { initial_value, onupdate }: Props = $props();
let { app }: { app: State } = $props();

let element: HTMLDivElement;
let view: EditorView | undefined;
Expand All @@ -17,16 +13,6 @@
(resolve) => (ready_resolve = resolve),
);

export const set_doc = async (src: string) => {
await ready;
if (view === undefined) {
// never
return;
}
const tx = view.state.update({ changes: { from: 0, insert: src } });
view.dispatch(tx);
};

const setup = async () => {
const [{ basicSetup, EditorView }, { python }, { oneDark }] =
await Promise.all([
Expand All @@ -35,8 +21,8 @@
import("@codemirror/theme-one-dark"),
]);

view = new EditorView({
doc: initial_value,
const _view = new EditorView({
doc: app.buffer,
extensions: [
basicSetup,
oneDark,
Expand All @@ -52,7 +38,18 @@

dispatchTransactions: (txs, view) => {
view.update(txs);
onupdate(view.state.doc.toString());
app.buffer = view.state.doc.toString();
},
});

view = _view;

app.register_editor({
set_doc: (data: string) => {
const tx = _view.state.update({
changes: { from: 0, to: _view.state.doc.length, insert: data },
});
_view.dispatch(tx);
},
});

Expand All @@ -67,4 +64,8 @@
});
</script>

<div class="h-full w-full" id="editor" bind:this={element}></div>
<div
class="h-full w-full border-t-zinc-500"
id="editor"
bind:this={element}
></div>
31 changes: 31 additions & 0 deletions src/lib/playground/components/StatusBar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import StatusItem from "$lib/playground/components/statusbar/StatusItem.svelte";
import type { Result } from "../lib/app.svelte";

type Props = {
dirty: boolean;
check_result: Result | null;
eval_result: Result | null;
};

let { dirty, check_result, eval_result }: Props = $props();
</script>

<div
class="min-h-6 max-h-6 px-2 flex justify-between items-center bg-gray-950 text-blue-50 text-xs font-medium"
>
<div>
<p>
{#if dirty}
Unsaved changes
{:else}
Up to date
{/if}
</p>
</div>

<div class="flex gap-2">
<StatusItem type="Evaluate" result={eval_result} />
<StatusItem type="Check" result={check_result} />
</div>
</div>
117 changes: 117 additions & 0 deletions src/lib/playground/components/Tabs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script lang="ts">
import type { State } from "../lib/app.svelte";

let { app }: { app: State } = $props();

let new_file_prompt = $state(false);
let new_file_name = $state("");

let onfocusout = () => {
new_file_prompt = false;

if (new_file_name === "") {
return;
}

app.fs.new(new_file_name);
new_file_name = "";
};
</script>

<div class="h-8 flex w-full">
{#each app.fs.files as file, index (file.path)}
{#if app.fs.active_fd === index}
<div
class="px-1.5 gap-2 flex items-center text-sm rounded-t-md bg-[#282c34] text-white border border-t-zinc-500 border-x-zinc-500 border-b-transparent"
>
<div class="inline-flex items-baseline gap-x-1.5 pb-0.5">
<div class="size-2">
{#if file.dirty}
<div class="size-full rounded-full bg-white"></div>
{/if}
</div>

{file.path}
</div>

<div class="flex justify-end w-6">
{#if app.fs.files.length > 1}
<button
title="Delete file"
class="cursor-pointer"
onclick={() => app.fs.remove(index)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-3 text-zinc-500 hover:text-zinc-400"
>
<path
fill-rule="evenodd"
d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
clip-rule="evenodd"
/>
</svg>
</button>
{/if}
</div>
</div>
{:else}
<div
tabindex="-1"
onkeyup={() => {}}
role="button"
onclick={() => app.fs.open(index)}
class="px-1.5 gap-2 flex justify-center items-center text-sm cursor-pointer rounded-t-md bg-zinc-800 text-zinc-400 hover:bg-zinc-700 hover:text-zinc-300 border border-t-transparent border-x-transparent border-b-zinc-500"
>
<div class="inline-flex items-baseline gap-x-1.5 pb-0.5">
<div class="size-2">
{#if file.dirty}
<div class="size-full rounded-full bg-white"></div>
{/if}
</div>

{file.path}
</div>

<div class="flex items-center justify-end w-6"></div>
</div>
{/if}
{/each}

<div class="border-b border-zinc-500 w-full flex">
{#if new_file_prompt}
<div
class="px-4 flex justify-center items-center text-sm cursor-pointer rounded-t-md bg-zinc-800 text-zinc-200"
>
<form onsubmit={() => {}}>
<!-- svelte-ignore a11y_autofocus -->
<input
type="text"
bind:value={new_file_name}
{onfocusout}
autofocus={true}
/>
</form>
</div>
{:else}
<button
onclick={() => (new_file_prompt = true)}
title="New file"
class="ml-1 px-2 flex justify-center items-center cursor-pointer text-zinc-500 hover:text-zinc-400"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
</svg>
</button>
{/if}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
import Kbd from "./components/Kbd.svelte";
import ToolbarButton from "./components/ToolbarButton.svelte";
import ToolbarLink from "./components/ToolbarLink.svelte";
import Kbd from "$lib/playground/components/toolbar/Kbd.svelte";
import ToolbarButton from "$lib/playground/components/toolbar/ToolbarButton.svelte";
import ToolbarLink from "$lib/playground/components/toolbar/ToolbarLink.svelte";

let { share, check, run, is_busy } = $props();
</script>
Expand Down
Loading