From 617ffc4ec45c5e0152d0e71a2e8c8e5e6c50e8df Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Wed, 28 Jun 2023 00:25:07 +0530 Subject: [PATCH] fix(repl): ComponentSelector (#495) * Push * (fix) various issues about tab renaming (#497) * Add changeset --------- Co-authored-by: Paolo Ricciuti --- .changeset/bright-shoes-boil.md | 5 + packages/repl/package.json | 2 +- .../src/lib/Input/ComponentSelector.svelte | 175 +++++++++++------- packages/repl/src/lib/Repl.svelte | 69 ++++--- packages/repl/src/lib/types.d.ts | 6 +- .../repl/src/lib/workers/bundler/index.js | 5 +- 6 files changed, 151 insertions(+), 111 deletions(-) create mode 100644 .changeset/bright-shoes-boil.md diff --git a/.changeset/bright-shoes-boil.md b/.changeset/bright-shoes-boil.md new file mode 100644 index 00000000..30412975 --- /dev/null +++ b/.changeset/bright-shoes-boil.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/repl': patch +--- + +Fix ComponentSelector diff --git a/packages/repl/package.json b/packages/repl/package.json index ac36a29d..d75e2c08 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -39,7 +39,7 @@ "build": "vite build && npm run package", "preview": "vite preview", "package": "svelte-kit sync && svelte-package && publint", - "watch": "svelte-package --watch", + "package:watch": "svelte-package --watch", "prepublishOnly": "npm run package", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch" diff --git a/packages/repl/src/lib/Input/ComponentSelector.svelte b/packages/repl/src/lib/Input/ComponentSelector.svelte index c8721966..9501cba1 100644 --- a/packages/repl/src/lib/Input/ComponentSelector.svelte +++ b/packages/repl/src/lib/Input/ComponentSelector.svelte @@ -7,8 +7,8 @@ export let show_modified; /** @type {ReturnType>} */ const dispatch = createEventDispatcher(); @@ -18,33 +18,39 @@ module_editor, rebundle, selected, - selected_index, + selected_name, EDITOR_STATE_MAP } = get_repl_context(); - /** @type {number} */ - let editing_index = -1; + /** @type {string | null} */ + let editing_name = null; + + let input_value = ''; - /** @param {number} index */ - function select_file(index) { - if ($selected_index !== index) { - editing_index = -1; - handle_select(index); + /** @param {string} filename */ + function select_file(filename) { + if ($selected_name !== filename) { + editing_name = null; + handle_select(filename); } } - /** @param {number} index */ - function edit_tab(index) { - if ($selected_index === index) { - editing_index = $selected_index; + /** @param {import('$lib/types').File} file */ + function edit_tab(file) { + if ($selected_name === get_full_filename(file)) { + editing_name = get_full_filename(file); + input_value = file.name; } } async function close_edit() { - const match = /(.+)\.(svelte|js|json|md|css)$/.exec($selected?.name ?? ''); + const match = /(.+)\.(svelte|js|json|md|css)$/.exec(input_value ?? ''); + + const edited_file = $files.find((val) => get_full_filename(val) === editing_name); - const edited_file = $files[editing_index]; - edited_file.name = match ? match[1] : edited_file.name; + if (!edited_file) return; + + edited_file.name = match ? match[1] : input_value; if (!$selected) return; @@ -53,20 +59,43 @@ let name = $selected.name; do { - $files[$selected_index].name = `${name}_${i++}`; + const file = $files.find( + (val) => + get_full_filename(val) === get_full_filename(edited_file) && + // @ts-ignore + val.source === $selected.source + ); + + if (!file) break; + + file.name = `${name}_${i++}`; } while (is_file_name_used($selected)); - $files[$selected_index] = edited_file; + const idx = $files.findIndex( + (val) => get_full_filename(val) === get_full_filename(edited_file) + ); + $files[idx] = edited_file; } - if (match?.[2]) $files[$selected_index].type = match[2]; - - editing_index = -1; + const idx = $files.findIndex( + (val) => get_full_filename(val) === get_full_filename(edited_file) + ); + if (match?.[2]) $files[idx].type = match[2]; + + if (editing_name) { + const old_state = EDITOR_STATE_MAP.get(editing_name); + if (old_state) { + EDITOR_STATE_MAP.set(get_full_filename(edited_file), old_state); + EDITOR_STATE_MAP.delete(editing_name); + } + } - EDITOR_STATE_MAP.delete(get_full_filename($selected)); + editing_name = null; // re-select, in case the type changed - handle_select($selected_index); + handle_select(get_full_filename(edited_file)); + + $files = $files; // focus the editor, but wait a beat (so key events aren't misdirected) await tick(); @@ -77,25 +106,24 @@ } /** - * @param {number} index + * @param {string} filename */ - function remove(index) { - let result = confirm( - `Are you sure you want to delete ${$files[index].name}.${$files[index].type}?` - ); + function remove(filename) { + const file = $files.find((val) => get_full_filename(val) === filename); + const idx = $files.findIndex((val) => get_full_filename(val) === filename); + + if (!file) return; + + let result = confirm(`Are you sure you want to delete ${get_full_filename(file)}?`); if (!result) return; - if (index !== -1) { - $files = $files.filter((_, idx) => idx !== index); + $files = $files.filter((file) => get_full_filename(file) !== filename); - dispatch('remove', { files: $files }); - } else { - console.error(`Could not find component! That's... odd`); - } + dispatch('remove', { files: $files, diff: file }); - EDITOR_STATE_MAP.delete(get_full_filename($files[index])); - handle_select(($selected_index = index - 1)); + EDITOR_STATE_MAP.delete(get_full_filename(file)); + handle_select(($selected_name = idx === 1 ? 'App.svelte' : get_full_filename(file))); } /** @param {FocusEvent & { currentTarget: HTMLInputElement }} event */ @@ -117,11 +145,17 @@ $files = $files.concat(file); - editing_index = $files.length - 1; + editing_name = get_full_filename(file); + + input_value = file.name; + + handle_select(editing_name); - handle_select(editing_index); + rebundle(); + + dispatch('add', { files: $files, diff: file }); - dispatch('add', { files: $files }); + $files = $files; } /** @param {import('$lib/types').File} editing */ @@ -171,61 +205,64 @@
{#if $files.length}
- {#each $files as file, index (file)} + {#each $files as file, index (file.name)} + {@const filename = get_full_filename(file)}
select_file(index)} - on:keyup={(e) => e.key === ' ' && select_file(index)} + on:click={() => select_file(filename)} + on:keyup={(e) => e.key === ' ' && select_file(filename)} on:dblclick|stopPropagation={() => {}} - draggable={index !== editing_index} + draggable={filename !== editing_name} on:dragstart={dragStart} on:dragover|preventDefault={dragOver} on:dragleave={dragLeave} on:drop|preventDefault={dragEnd} > - {#if file.name === 'App' && index !== editing_index} + {#if file.name === 'App' && filename !== editing_name}
App.svelte{#if show_modified && file.modified}*{/if}
- {:else if index === editing_index} - {@const file = $files[editing_index]} - - - {file.name + (/\./.test(file.name) ? '' : `.${file.type}`)} - - - - - e.key === 'Enter' && !is_file_name_used(file) && e.currentTarget.blur()} - class:duplicate={is_file_name_used(file)} - /> + {:else if filename === editing_name} + {@const editing_file = $files.find((file) => get_full_filename(file) === editing_name)} + + {#if editing_file} + + {input_value + (/\./.test(input_value) ? '' : `.${editing_file.type}`)} + + + + + e.key === 'Enter' && !is_file_name_used(editing_file) && e.currentTarget.blur()} + class:duplicate={is_file_name_used(editing_file)} + /> + {/if} {:else}
edit_tab(index)} - on:keyup={(e) => e.key === ' ' && edit_tab(index)} + on:click={() => edit_tab(file)} + on:keyup={(e) => e.key === ' ' && edit_tab(file)} > {file.name}.{file.type}{#if show_modified && file.modified}*{/if}
remove(index)} - on:keyup={(e) => e.key === ' ' && remove(index)} + on:click={() => remove(filename)} + on:keyup={(e) => e.key === ' ' && remove(filename)} > diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index f2243c4e..1b5a9b60 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -39,7 +39,7 @@ */ export async function set(data) { $files = data.files; - $selected_index = 0; + $selected_name = 'App.svelte'; rebundle(); @@ -58,37 +58,33 @@ export function markSaved() { $files = $files.map((val) => ({ ...val, modified: false })); - if (!$selected) return; + // if (!$selected) return; - $files[$selected_index].modified = false; + // const current = $files.find(val => get_full_filename(val) === $selected_name).modified = false; } /** @param {{ files: import('./types').File[], css?: string }} data */ export function update(data) { - if (!$selected) return; - - const { name, type } = $selected; - $files = data.files; - const matched_component_index = data.files.findIndex( - (file) => file.name === name && file.type === type - ); + const matched_file = data.files.find((file) => get_full_filename(file) === $selected_name); - $selected_index = matched_component_index === -1 ? 0 : matched_component_index; + $selected_name = matched_file ? get_full_filename(matched_file) : 'App.svelte'; injectedCSS = data.css ?? ''; - if (matched_component_index) { + if (matched_file) { $module_editor?.update({ - code: $files[matched_component_index].source, - lang: $files[matched_component_index].type + code: matched_file.source, + lang: matched_file.type }); - $output?.update?.($files[matched_component_index], $compile_options); + $output?.update?.(matched_file, $compile_options); $module_editor?.clearEditorState(); } + + dispatch('change', { files: $files }); } /** @type {ReturnType>} */ @@ -115,12 +111,19 @@ /** @type {ReplContext['files']} */ const files = writable([]); - /** @type {ReplContext['selected_index']} */ - const selected_index = writable(-1); + /** @type {ReplContext['selected_name']} */ + const selected_name = writable('App.svelte'); /** @type {ReplContext['selected']} */ - const selected = derived([files, selected_index], ([$files, $selected_index]) => { - return $selected_index !== -1 ? $files?.[$selected_index] ?? null : null; + const selected = derived([files, selected_name], ([$files, $selected_name]) => { + return ( + $files.find((val) => get_full_filename(val) === $selected_name) ?? { + name: '', + type: '', + source: '', + modified: false + } + ); }); /** @type {ReplContext['bundle']} */ @@ -146,7 +149,7 @@ set_repl_context({ files, - selected_index, + selected_name, selected, bundle, bundler, @@ -176,24 +179,24 @@ let is_select_changing = false; /** - * @param {number} index + * @param {string} filename */ - async function handle_select(index) { + async function handle_select(filename) { is_select_changing = true; - $selected_index = index; + $selected_name = filename; if (!$selected) return; await $module_editor?.set({ code: $selected.source, lang: $selected.type }); - if (EDITOR_STATE_MAP.has(get_full_filename($selected))) { - $module_editor?.setEditorState(EDITOR_STATE_MAP.get(get_full_filename($selected))); + if (EDITOR_STATE_MAP.has(filename)) { + $module_editor?.setEditorState(EDITOR_STATE_MAP.get(filename)); } else { $module_editor?.clearEditorState(); } - EDITOR_STATE_MAP.set(get_full_filename($selected), $module_editor?.getEditorState()); + EDITOR_STATE_MAP.set(filename, $module_editor?.getEditorState()); $output?.set($selected, $compile_options); @@ -212,8 +215,10 @@ file.source = event.detail.value; file.modified = true; + const idx = $files.findIndex((val) => get_full_filename(val) === $selected_name); + // @ts-ignore - $files[$selected_index] = file; + $files[idx] = file; return $files; }); @@ -233,15 +238,7 @@ async function go_to_warning_pos(item) { if (!item) return; - const match = /^(.+)\.(\w+)$/.exec(item.filename); - if (!match) return; // ??? - - const [, name, type] = match; - const file_index = $files.findIndex((file) => file.name === name && file.type === type); - - if (file_index === -1) return; - - await handle_select(file_index); + await handle_select(item.filename); $module_editor?.focus(); $module_editor?.setCursor(item.start.character); diff --git a/packages/repl/src/lib/types.d.ts b/packages/repl/src/lib/types.d.ts index 1ac2a320..456c5dbb 100644 --- a/packages/repl/src/lib/types.d.ts +++ b/packages/repl/src/lib/types.d.ts @@ -51,7 +51,7 @@ export type File = { export type ReplState = { files: File[]; - selected_index: number; + selected_name: string; selected: File | null; bundle: Bundle | null; bundler: import('./Bundler').default | null; @@ -64,7 +64,7 @@ export type ReplState = { export type ReplContext = { files: Writable; - selected_index: Writable; + selected_name: Writable; selected: Readable; bundle: Writable; bundler: Writable; @@ -78,7 +78,7 @@ export type ReplContext = { // Methods rebundle(): Promise; - handle_select(index: number): Promise; + handle_select(filename: string): Promise; handle_change( event: CustomEvent<{ value: string; diff --git a/packages/repl/src/lib/workers/bundler/index.js b/packages/repl/src/lib/workers/bundler/index.js index 45447752..1c556091 100644 --- a/packages/repl/src/lib/workers/bundler/index.js +++ b/packages/repl/src/lib/workers/bundler/index.js @@ -275,7 +275,8 @@ async function get_bundle(uid, mode, cache, local_files_lookup) { if (importer && local_files_lookup.has(importer)) { // relative import in a REPL file // should've matched above otherwise importee doesn't exist - throw new Error(`Cannot find file "${importee}" imported by "${importer}" in the REPL`); + console.error(`Cannot find file "${importee}" imported by "${importer}" in the REPL`); + return; } else { // relative import in an external file const url = new URL(importee, importer).href; @@ -289,7 +290,7 @@ async function get_bundle(uid, mode, cache, local_files_lookup) { const match = /^((?:@[^/]+\/)?[^/]+)(\/.+)?$/.exec(importee); if (!match) { - throw new Error(`Invalid import "${importee}"`); + return console.error(`Invalid import "${importee}"`); } const pkg_name = match[1];