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
2 changes: 1 addition & 1 deletion apps/svelte.dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@supabase/supabase-js": "^2.43.4",
"@sveltejs/adapter-vercel": "^5.4.3",
"@sveltejs/enhanced-img": "^0.3.4",
"@sveltejs/kit": "^2.5.25",
"@sveltejs/kit": "^2.6.3",
"@sveltejs/site-kit": "workspace:*",
"@sveltejs/vite-plugin-svelte": "4.0.0-next.6",
"@types/cookie": "^0.6.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export async function load({ fetch, params, url }) {

return {
gist,
version: url.searchParams.get('version') || 'next'
version: url.searchParams.get('version') || 'next' // TODO replace with 'latest' when 5.0 is released
};
}
121 changes: 83 additions & 38 deletions apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,66 +1,95 @@
<script lang="ts">
import { browser } from '$app/environment';
import { afterNavigate, goto, replaceState } from '$app/navigation';
import { Repl } from '@sveltejs/repl';
import type { Gist } from '$lib/db/types';
import { Repl, type File } from '@sveltejs/repl';
import { theme } from '@sveltejs/site-kit/stores';
import { onMount } from 'svelte';
import { mapbox_setup } from '../../../../config.js';
import AppControls from './AppControls.svelte';
import { compress_and_encode_text, decode_and_decompress_text } from './gzip.js';
let { data } = $props();
let version = $state(data.version);
let repl = $state() as Repl;
let name = $state(data.gist.name);
let zen_mode = $state(false);
let modified_count = $state(0);
$effect(() => {
const params = [];
if (version !== 'latest') {
params.push(`version=${version}`);
}
const url =
params.length > 0
? `/playground/${data.gist.id}?${params.join('&')}`
: `/playground/${data.gist.id}`;
history.replaceState({}, 'x', url);
});
let version = data.version;
let setting_hash: any = null;
onMount(() => {
if (data.version !== 'local') {
fetch(`https://unpkg.com/svelte@${data.version || 'next'}/package.json`)
if (version !== 'local') {
fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then((r) => r.json())
.then((pkg) => {
version = pkg.version;
if (pkg.version !== version) {
version = pkg.version;
let url = `/playground/${data.gist.id}?version=${version}`;
if (location.hash) {
url += location.hash;
}
replaceState(url, {});
}
});
}
});
afterNavigate(() => {
repl?.set({
// TODO move the snapshotting elsewhere (but also... this shouldn't really be necessary?)
files: $state.snapshot(data.gist.components)
});
});
afterNavigate(set_files);
async function set_files() {
const hash = location.hash.slice(1);
if (!hash) {
repl?.set({
files: data.gist.components
});
function handle_fork(event: CustomEvent) {
console.log('> handle_fork', event);
goto(`/playground/${event.detail.gist.id}?version=${version}`);
return;
}
try {
const files = JSON.parse(await decode_and_decompress_text(hash)).files;
repl.set({ files });
} catch {
alert(`Couldn't load the code from the URL. Make sure you copied the link correctly.`);
}
}
function handle_change(event: CustomEvent) {
modified_count = event.detail.files.filter((c: any) => c.modified).length;
function handle_fork({ gist }: { gist: Gist }) {
goto(`/playground/${gist.id}?version=${version}`);
}
const svelteUrl = $derived(
function handle_save() {
// Hide hash from URL
const hash = location.hash.slice(1);
if (hash) {
change_hash();
}
}
async function change_hash(hash?: string) {
let url = `${location.pathname}${location.search}`;
if (hash) {
url += `#${await compress_and_encode_text(hash)}`;
}
clearTimeout(setting_hash);
replaceState(url, {});
setting_hash = setTimeout(() => {
setting_hash = null;
}, 500);
}
function handle_change({ files }: { files: File[] }) {
modified_count = files.filter((c) => c.modified).length;
}
const svelteUrl =
browser && version === 'local'
? `${location.origin}/playground/local`
: `https://unpkg.com/svelte@${version}`
);
: `https://unpkg.com/svelte@${version}`;
const relaxed = $derived(data.gist.relaxed || (data.user && data.user.id === data.gist.owner));
</script>
Expand All @@ -73,15 +102,24 @@
<meta name="Description" content="Interactive Svelte playground" />
</svelte:head>

<svelte:window
on:hashchange={() => {
if (!setting_hash) {
set_files();
}
}}
/>

<div class="repl-outer {zen_mode ? 'zen-mode' : ''}">
<AppControls
user={data.user}
gist={data.gist}
forked={handle_fork}
saved={handle_save}
{repl}
bind:name
bind:zen_mode
bind:modified_count
on:forked={handle_fork}
/>

{#if browser}
Expand All @@ -93,9 +131,16 @@
injectedJS={mapbox_setup}
showModified
showAst
on:change={handle_change}
on:add={handle_change}
on:remove={handle_change}
change={handle_change}
add={handle_change}
remove={handle_change}
blur={() => {
// Only change hash on editor blur to not pollute everyone's browser history
if (modified_count !== 0) {
const json = JSON.stringify({ files: repl.toJSON().files });
change_hash(json);
}
}}
previewTheme={$theme.current}
/>
{/if}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import UserMenu from './UserMenu.svelte';
import { Icon } from '@sveltejs/site-kit/components';
import * as doNotZip from 'do-not-zip';
Expand All @@ -18,6 +17,8 @@
name: string;
zen_mode: boolean;
modified_count: number;
forked: (value: { gist: Gist }) => void;
saved: () => void;
}
let {
Expand All @@ -26,10 +27,11 @@
modified_count = $bindable(),
user,
repl,
gist
gist,
forked,
saved
}: Props = $props();
const dispatch = createEventDispatcher();
const { login } = get_app_context();
let saving = $state(false);
Expand Down Expand Up @@ -77,7 +79,7 @@
}
const gist = await r.json();
dispatch('forked', { gist });
forked({ gist });
modified_count = 0;
repl.markSaved();
Expand Down Expand Up @@ -143,6 +145,7 @@
modified_count = 0;
repl.markSaved();
saved();
justSaved = true;
await wait(600);
justSaved = false;
Expand Down
29 changes: 29 additions & 0 deletions apps/svelte.dev/src/routes/(authed)/playground/[id]/gzip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** @param {string} input */
export async function compress_and_encode_text(input) {
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
let buffer = '';
for (;;) {
const { done, value } = await reader.read();
if (done) {
reader.releaseLock();
return btoa(buffer).replaceAll('+', '-').replaceAll('/', '_');
} else {
for (let i = 0; i < value.length; i++) {
// decoding as utf-8 will make btoa reject the string
buffer += String.fromCharCode(value[i]);
}
}
}
}

/** @param {string} input */
export async function decode_and_decompress_text(input) {
const decoded = atob(input.replaceAll('-', '+').replaceAll('_', '/'));
// putting it directly into the blob gives a corrupted file
const u8 = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; i++) {
u8[i] = decoded.charCodeAt(i);
}
const stream = new Blob([u8]).stream().pipeThrough(new DecompressionStream('gzip'));
return new Response(stream).text();
}
2 changes: 1 addition & 1 deletion packages/repl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"devDependencies": {
"@lezer/common": "^1.2.1",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/kit": "^2.6.3",
"@sveltejs/package": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "4.0.0-next.6",
"@types/estree": "^1.0.5",
Expand Down
21 changes: 8 additions & 13 deletions packages/repl/src/lib/Input/ComponentSelector.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
<script lang="ts">
import { get_repl_context } from '../context';
import { get_full_filename } from '../utils';
import { createEventDispatcher, tick } from 'svelte';
import { tick } from 'svelte';
import RunesInfo from './RunesInfo.svelte';
import Migrate from './Migrate.svelte';
import type { File } from '../types';
export let show_modified: boolean;
export let runes: boolean;
const dispatch: ReturnType<
typeof createEventDispatcher<{
remove: { files: File[]; diff: File };
add: { files: File[]; diff: File };
}>
> = createEventDispatcher();
export let remove: (value: { files: File[]; diff: File }) => void;
export let add: (value: { files: File[]; diff: File }) => void;
const {
files,
Expand Down Expand Up @@ -105,7 +100,7 @@
rebundle();
}
function remove(filename: string) {
function remove_file(filename: string) {
const file = $files.find((val) => get_full_filename(val) === filename);
const idx = $files.findIndex((val) => get_full_filename(val) === filename);
Expand All @@ -117,7 +112,7 @@
$files = $files.filter((file) => get_full_filename(file) !== filename);
dispatch('remove', { files: $files, diff: file });
remove({ files: $files, diff: file });
EDITOR_STATE_MAP.delete(get_full_filename(file));
Expand Down Expand Up @@ -151,7 +146,7 @@
rebundle();
dispatch('add', { files: $files, diff: file });
add({ files: $files, diff: file });
$files = $files;
}
Expand Down Expand Up @@ -262,8 +257,8 @@

<span
class="remove"
on:click={() => remove(filename)}
on:keyup={(e) => e.key === ' ' && remove(filename)}
on:click={() => remove_file(filename)}
on:keyup={(e) => e.key === ' ' && remove_file(filename)}
>
<svg width="12" height="12" viewBox="0 0 24 24">
<line stroke="#999" x1="18" y1="6" x2="6" y2="18" />
Expand Down
3 changes: 2 additions & 1 deletion packages/repl/src/lib/Input/ModuleEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export let error: CompileError | undefined;
export let warnings: Warning[];
export let vim: boolean;
export let blur: () => void;
export function focus() {
$module_editor?.focus();
Expand All @@ -14,7 +15,7 @@
const { handle_change, module_editor } = get_repl_context();
</script>

<div class="editor-wrapper">
<div class="editor-wrapper" onblurcapture={blur}>
<div class="editor notranslate" translate="no">
<CodeMirror
bind:this={$module_editor}
Expand Down
Loading
Loading