1+ <script lang =" ts" >
2+ import { Download , Upload , FileText , Database } from ' lucide-svelte' ;
3+ import { DataExporter } from ' $lib/utils/data-export' ;
4+ import { petStore } from ' $lib/stores/pets' ;
5+ import type { PetPanelData } from ' $lib/types/Pet' ;
6+
7+ let isExporting = false ;
8+ let isImporting = false ;
9+ let importMessage = ' ' ;
10+ let importSuccess = false ;
11+ let fileInput: HTMLInputElement ;
12+ let pets: PetPanelData [] = [];
13+
14+ petStore .subscribe (value => { pets = value ; });
15+
16+ async function exportAllData() {
17+ isExporting = true ;
18+ try {
19+ await DataExporter .exportAllData ();
20+ } catch (error ) {
21+ alert (' Export failed: ' + (error as Error ).message );
22+ } finally {
23+ isExporting = false ;
24+ }
25+ }
26+
27+ async function exportSinglePet(pet : PetPanelData ) {
28+ isExporting = true ;
29+ try {
30+ await DataExporter .exportPet (pet );
31+ } catch (error ) {
32+ alert (' Export failed: ' + (error as Error ).message );
33+ } finally {
34+ isExporting = false ;
35+ }
36+ }
37+
38+ async function handleImport(event : Event ) {
39+ const target = event .target as HTMLInputElement ;
40+ const file = target .files ?.[0 ];
41+ if (! file ) return ;
42+
43+ isImporting = true ;
44+ importMessage = ' ' ;
45+
46+ try {
47+ const result = await DataExporter .importFromFile (file );
48+ importMessage = result .message ;
49+ importSuccess = result .success ;
50+
51+ if (result .success ) {
52+ // Refresh data after import
53+ setTimeout (() => {
54+ window .location .reload ();
55+ }, 2000 );
56+ }
57+ } catch (error ) {
58+ importMessage = ' Import failed: ' + (error as Error ).message ;
59+ importSuccess = false ;
60+ } finally {
61+ isImporting = false ;
62+ if (fileInput ) {
63+ fileInput .value = ' ' ;
64+ }
65+ }
66+ }
67+ </script >
68+
69+ <div class =" data-manager space-y-6" >
70+ <!-- Export Section -->
71+ <div class =" export-section" >
72+ <h3 class =" text-lg font-semibold mb-4 flex items-center" style =" color: var(--petalytics-text);" >
73+ <Download size ={20 } class =" mr-2" style =" color: var(--petalytics-accent);" />
74+ Export Data
75+ </h3 >
76+
77+ <div class =" space-y-3" >
78+ <!-- Export All -->
79+ <div class =" export-item p-4 rounded-lg border" style =" background: var(--petalytics-surface); border-color: var(--petalytics-border);" >
80+ <div class =" flex items-center justify-between" >
81+ <div >
82+ <h4 class =" font-medium" style =" color: var(--petalytics-text);" >Complete Backup</h4 >
83+ <p class =" text-sm" style =" color: var(--petalytics-subtle);" >
84+ Export all pets, settings, and journal entries
85+ </p >
86+ </div >
87+ <button
88+ on:click ={exportAllData }
89+ disabled ={isExporting || pets .length === 0 }
90+ class =" bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors"
91+ >
92+ {#if isExporting }
93+ <div class =" animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full" ></div >
94+ {:else }
95+ <Database size ={16 } />
96+ {/if }
97+ <span >Export All</span >
98+ </button >
99+ </div >
100+ </div >
101+
102+ <!-- Export Individual Pets -->
103+ {#if pets .length > 0 }
104+ <div class =" individual-exports" >
105+ <h4 class =" font-medium mb-2" style =" color: var(--petalytics-text);" >Export Individual Pets</h4 >
106+ <div class =" grid grid-cols-1 md:grid-cols-2 gap-3" >
107+ {#each pets as pet }
108+ <div class =" pet-export-item p-3 rounded-lg border" style =" background: var(--petalytics-overlay); border-color: var(--petalytics-border);" >
109+ <div class =" flex items-center justify-between" >
110+ <div class =" flex items-center space-x-2" >
111+ <img
112+ src ={pet .profileImageUrl || ' /images/default-pet.png' }
113+ alt ={pet .name }
114+ class =" w-8 h-8 rounded-full object-cover"
115+ on:error ={(e ) => {
116+ const target = e .target as HTMLImageElement ;
117+ target .src = ' data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iMTYiIGZpbGw9IiNGMzRGNEYiLz4KPHN2ZyB4PSI4IiB5PSI4IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9IndoaXRlIj4KICA8cGF0aCBkPSJNNS4yNSA0QzQuNTU5NjQgNCA0IDQuNTU5NjQgNCA1LjI1VjEwLjc1QzQgMTEuNDQwNCA0LjU1OTY0IDEyIDUuMjUgMTJIMTAuNzVDMTEuNDQwNCAxMiAxMiAxMS40NDA0IDEyIDEwLjc1VjUuMjVDMTIgNC41NTk2NCAxMS40NDA0IDQgMTAuNzUgNEg1LjI1WiIvPgo8L3N2Zz4KPC9zdmc+' ;
118+ }}
119+ />
120+ <div >
121+ <p class ="font-medium text-sm" style ="color: var(--petalytics-text);" >{pet .name }</p >
122+ <p class =" text-xs" style =" color: var(--petalytics-subtle);" >
123+ {pet .journalEntries ?.length || 0 } entries
124+ </p >
125+ </div >
126+ </div >
127+ <button
128+ on:click ={() => exportSinglePet (pet )}
129+ disabled ={isExporting }
130+ class =" bg-gray-100 hover:bg-gray-200 disabled:bg-gray-50 disabled:cursor-not-allowed text-gray-700 text-xs px-2 py-1 rounded flex items-center space-x-1 transition-colors"
131+ >
132+ <FileText size ={12 } />
133+ <span >Export</span >
134+ </button >
135+ </div >
136+ </div >
137+ {/each }
138+ </div >
139+ </div >
140+ {/if }
141+ </div >
142+ </div >
143+
144+ <!-- Import Section -->
145+ <div class =" import-section" >
146+ <h3 class =" text-lg font-semibold mb-4 flex items-center" style =" color: var(--petalytics-text);" >
147+ <Upload size ={20 } class =" mr-2" style =" color: var(--petalytics-accent);" />
148+ Import Data
149+ </h3 >
150+
151+ <div class =" import-area p-6 rounded-lg border-2 border-dashed text-center"
152+ style =" border-color: var(--petalytics-border);" >
153+ <Upload size ={32 } style =" color: var(--petalytics-subtle);" class =" mx-auto mb-3" />
154+ <p class =" mb-3" style =" color: var(--petalytics-text);" >
155+ Select a JSONL backup file to import
156+ </p >
157+ <input
158+ bind:this ={fileInput }
159+ type =" file"
160+ accept =" .jsonl"
161+ on:change ={handleImport }
162+ class =" hidden"
163+ />
164+ <button
165+ on:click ={() => fileInput ?.click ()}
166+ disabled ={isImporting }
167+ class =" bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white px-4 py-2 rounded-lg flex items-center space-x-2 mx-auto transition-colors"
168+ >
169+ {#if isImporting }
170+ <div class =" animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full" ></div >
171+ <span >Importing...</span >
172+ {:else }
173+ <Upload size ={16 } />
174+ <span >Choose File</span >
175+ {/if }
176+ </button >
177+
178+ {#if importMessage }
179+ <div class =" mt-4 p-3 rounded"
180+ class:bg-green- 100={importSuccess }
181+ class:bg-red- 100={!importSuccess }>
182+ <p class =" text-sm"
183+ class:text-green- 800={importSuccess }
184+ class:text-red- 800={!importSuccess }>
185+ {importMessage }
186+ </p >
187+ </div >
188+ {/if }
189+ </div >
190+
191+ <!-- Data Format Info -->
192+ <div class =" format-info mt-4 p-3 rounded" style =" background: var(--petalytics-overlay);" >
193+ <h4 class =" font-medium text-sm mb-2" style =" color: var(--petalytics-text);" >Data Format</h4 >
194+ <ul class =" text-xs space-y-1" style =" color: var(--petalytics-subtle);" >
195+ <li >• JSONL files exported from Petalytics</li >
196+ <li >• Individual pet files or complete backups</li >
197+ <li >• All journal entries and AI analyses included</li >
198+ <li >• Import will merge with existing data</li >
199+ </ul >
200+ </div >
201+ </div >
202+ </div >
0 commit comments