From 303cf180fcf28aa39f6814a70aae3fed738b41fc Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 13 Oct 2026 18:07:32 +0400 Subject: [PATCH] refac --- src/lib/components/workspace/Skills.svelte | 22 +- .../workspace/Skills/SkillImport.svelte | 249 ++++++++++++------ 2 files changed, 185 insertions(+), 86 deletions(-) diff --git a/src/lib/components/workspace/Skills.svelte b/src/lib/components/workspace/Skills.svelte index af68d29b61..9a2911f5b1 100644 --- a/src/lib/components/workspace/Skills.svelte +++ b/src/lib/components/workspace/Skills.svelte @@ -354,18 +354,16 @@ showImport = true; }} /> - {#key bundleFiles} - { - toast.success($i18n.t('Skill imported successfully')); - page = 1; - await loadSkillItems(); - _skills.set(await getSkills(localStorage.token)); - }} - /> - {/key} + { + toast.success($i18n.t('Skill imported successfully')); + page = 1; + await loadSkillItems(); + _skills.set(await getSkills(localStorage.token)); + }} + /> + import { getContext } from 'svelte'; import Modal from '$lib/components/common/Modal.svelte'; + import Spinner from '$lib/components/common/Spinner.svelte'; + import XMark from '$lib/components/icons/XMark.svelte'; + import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; import { toast } from 'svelte-sonner'; import { importSkillBundles, skillError } from '$lib/apis/skills'; + import { formatFileSize } from '$lib/utils'; + + const i18n = getContext('i18n'); export let show = false; export let files: File[] = []; export let onImported: () => Promise = async () => {}; + let loading = false; + let error = ''; let items: any[] = []; let results: any[] = []; let loadedFiles: File[] | null = null; + $: selectedCount = items.filter((item) => item.action !== 'skip').length; $: if (show && files !== loadedFiles) { loadedFiles = files; preview(); } + const preview = async () => { + const previewFiles = files; loading = true; + error = ''; results = []; items = []; try { - items = (await importSkillBundles(localStorage.token, files)).map((item: any) => ({ + const previewItems = await importSkillBundles(localStorage.token, previewFiles); + if (files !== previewFiles) return; + items = previewItems.map((item: any) => ({ ...item, + originalId: item.id, action: item.id_taken || item.name_taken ? 'skip' : 'create' })); - } catch (error) { - toast.error(skillError(error)); + } catch (err) { + if (files === previewFiles) error = skillError(err); } finally { - loading = false; + if (files === previewFiles) loading = false; } }; + const save = async () => { + if (loading || !selectedCount) return; + const importFiles = files; loading = true; try { - results = await importSkillBundles( + const importResults: any[] = await importSkillBundles( localStorage.token, - files, + importFiles, items.map(({ action, id, name, expected_version_id }) => ({ action, id, @@ -41,82 +60,164 @@ expected_version_id })) ); - results.forEach((result, index) => { - if (result.status === 'saved') items[index].action = 'skip'; - }); - items = [...items]; - if (results.some((r) => r.status === 'saved')) await onImported(); - } catch (error) { - toast.error(skillError(error)); + // A closed import can finish after a different bundle has opened. + if (files === importFiles) { + results = importResults.map((result, index) => + results[index]?.status === 'saved' ? results[index] : result + ); + results.forEach((result, index) => { + if (result.status === 'saved') items[index].action = 'skip'; + }); + items = [...items]; + } + if (importResults.some((result) => result.status === 'saved')) await onImported(); + } catch (err) { + toast.error(skillError(err)); } finally { - loading = false; + if (files === importFiles) loading = false; } }; - -
-
-

Import skills

- + +
+
+

{$i18n.t('Import skills')}

+
-

- New skills and copies are private. Replace saves a new version and preserves existing sharing. +

+ {$i18n.t( + 'New skills and copies are private. Replace saves a new version and preserves existing sharing.' + )}

-
- {#each items as item, index} -
-
- - -
- - {#if item.id_taken || item.name_taken}

- ID or name already exists. Choose a unique ID and name for a copy. -

{/if} -
- {item.files.length} files{#each item.files as file}
- {file.path} ({file.size} bytes) -
{/each} -
- {#if results[index]}

- {results[index].status}{results[index].error - ? `: ${skillError(results[index].error)}` - : ''} -

{/if} +
+ {#if loading && !items.length} +
+ + {$i18n.t('Loading...')}
- {/each} + {:else if error} +
+ + +
+ {:else if !items.length} +

+ {$i18n.t('No skills found')} +

+ {:else} +
+ {#each items as item, index} +
+
+
+ + +
+ +
+ {#if (item.id_taken || item.name_taken) && item.action !== 'replace' && results[index]?.status !== 'saved'} +

+ {$i18n.t('ID or name already exists. Choose a unique ID and name for a copy.')} +

+ {/if} +
+ + {$i18n.t('{{count}} files', { count: item.files.length })} + + +
+ {#each item.files as file} +
+ {file.path} + {formatFileSize(file.size)} +
+ {/each} +
+
+ {#if results[index]} +

+ {results[index].status === 'saved' + ? $i18n.t('Saved') + : results[index].status === 'skipped' + ? $i18n.t('Skipped') + : $i18n.t('Error')}{results[index].error + ? `: ${skillError(results[index].error)}` + : ''} +

+ {/if} +
+ {/each} +
+ {/if} +
+
+ {$i18n.t('{{count}} selected', { count: selectedCount })} +
-