tangled
alpha
login
or
join now
backups.bunware.org
/
atbackup
18
fork
atom
One-click backups for AT Protocol
18
fork
atom
overview
issues
2
pulls
pipelines
feat: show download progress
Turtlepaw
7 months ago
4ccdf747
90bb5f3e
+112
-75
1 changed file
expand all
collapse all
unified
split
src
routes
Home.tsx
+112
-75
src/routes/Home.tsx
···
39
39
profile: ProfileViewDetailed;
40
40
onLogout: () => void;
41
41
}) {
42
42
-
const [isDirLoading, setDirLoading] = useState(false);
43
42
const [refreshTrigger, setRefreshTrigger] = useState(0);
44
43
const [showSettings, setShowSettings] = useState(false);
45
44
···
96
95
97
96
<div className="bg-card rounded-lg p-4 mb-4">
98
97
<p className="mb-2 text-white">Backups</p>
99
99
-
<div className="flex gap-2">
100
100
-
<Button
101
101
-
variant="outline"
102
102
-
className="cursor-pointer"
103
103
-
onClick={async () => {
104
104
-
try {
105
105
-
setDirLoading(true);
106
106
-
await createBackupDir();
107
107
-
const appDataDirPath = await getBackupDir();
108
108
-
openPath(appDataDirPath);
109
109
-
} finally {
110
110
-
setDirLoading(false);
111
111
-
}
112
112
-
}}
113
113
-
disabled={isDirLoading}
114
114
-
>
115
115
-
{isDirLoading ? (
116
116
-
<LoaderCircleIcon className="w-4 h-4 animate-spin mr-2" />
117
117
-
) : (
118
118
-
<FolderOpen className="w-4 h-4 mr-2" />
119
119
-
)}
120
120
-
Open backups
121
121
-
</Button>
122
122
-
123
123
-
<StartBackup onBackupComplete={handleBackupComplete} />
124
124
-
125
125
-
{/* <Button
126
126
-
variant="outline"
127
127
-
className="cursor-pointer"
128
128
-
onClick={async () => {
129
129
-
await BackgroundTestService.testBackgroundBackup();
130
130
-
}}
131
131
-
>
132
132
-
Test Background
133
133
-
</Button> */}
134
134
-
</div>
98
98
+
<StartBackup onBackupComplete={handleBackupComplete} />
135
99
</div>
136
100
137
101
<Backups refreshTrigger={refreshTrigger} />
···
142
106
function StartBackup({ onBackupComplete }: { onBackupComplete: () => void }) {
143
107
const [isLoading, setIsLoading] = useState(false);
144
108
const [stage, setStage] = useState<BackupStage | null>(null);
145
145
-
const [_, setProgress] = useState<number | undefined>();
109
109
+
const [progress, setProgress] = useState<number | undefined>();
110
110
+
const [isDirLoading, setDirLoading] = useState(false);
146
111
const { agent } = useAuth();
147
112
113
113
+
const formatStage = (stage: BackupStage | null): string => {
114
114
+
if (!stage) return "Initializing backup...";
115
115
+
116
116
+
switch (stage) {
117
117
+
case "blobs":
118
118
+
return "Downloading blobs...";
119
119
+
case "cleanup":
120
120
+
return "Cleaning up...";
121
121
+
case "complete":
122
122
+
return "Backup complete!";
123
123
+
case "fetching":
124
124
+
return "Downloading account archive...";
125
125
+
case "writing":
126
126
+
return "Downloading account archive...";
127
127
+
default:
128
128
+
return "Processing...";
129
129
+
}
130
130
+
};
131
131
+
148
132
return (
149
149
-
<Button
150
150
-
variant="outline"
151
151
-
className="cursor-pointer"
152
152
-
onClick={async () => {
153
153
-
try {
154
154
-
setIsLoading(true);
155
155
-
if (agent == null) {
156
156
-
toast("Agent not initialized, try to reload the app.");
157
157
-
return;
158
158
-
}
159
159
-
const manager = new BackupAgent(agent!!, {
160
160
-
onProgress: (progress) => {
161
161
-
setStage(progress.stage);
162
162
-
setProgress(progress.progress);
163
163
-
},
164
164
-
});
165
165
-
await manager.startBackup();
166
166
-
await settingsManager.setLastBackupDate(new Date().toISOString());
167
167
-
toast("Backup complete!");
168
168
-
onBackupComplete(); // Trigger refresh
169
169
-
} catch (err: any) {
170
170
-
toast(err.toString());
171
171
-
console.error(err);
172
172
-
} finally {
173
173
-
setIsLoading(false);
174
174
-
}
175
175
-
}}
176
176
-
disabled={isLoading}
177
177
-
>
178
178
-
{isLoading ? (
179
179
-
<>
180
180
-
<LoaderCircleIcon className="animate-spin text-white/80" />
181
181
-
<span className="capitalize">{stage}</span>
182
182
-
</>
183
183
-
) : (
184
184
-
<span>Backup now</span>
133
133
+
<div className="space-y-4">
134
134
+
<div className="flex gap-2">
135
135
+
<Button
136
136
+
variant="outline"
137
137
+
className="cursor-pointer"
138
138
+
onClick={async () => {
139
139
+
try {
140
140
+
setDirLoading(true);
141
141
+
await createBackupDir();
142
142
+
const appDataDirPath = await getBackupDir();
143
143
+
openPath(appDataDirPath);
144
144
+
} finally {
145
145
+
setDirLoading(false);
146
146
+
}
147
147
+
}}
148
148
+
disabled={isDirLoading}
149
149
+
>
150
150
+
{isDirLoading ? (
151
151
+
<LoaderCircleIcon className="w-4 h-4 animate-spin mr-2" />
152
152
+
) : (
153
153
+
<FolderOpen className="w-4 h-4 mr-2" />
154
154
+
)}
155
155
+
Open backups
156
156
+
</Button>
157
157
+
158
158
+
<Button
159
159
+
variant="outline"
160
160
+
className="cursor-pointer"
161
161
+
onClick={async () => {
162
162
+
try {
163
163
+
setIsLoading(true);
164
164
+
if (agent == null) {
165
165
+
toast("Agent not initialized, try to reload the app.");
166
166
+
return;
167
167
+
}
168
168
+
169
169
+
const manager = new BackupAgent(agent!!, {
170
170
+
onProgress: (progress) => {
171
171
+
setStage(progress.stage);
172
172
+
setProgress(progress.progress);
173
173
+
},
174
174
+
});
175
175
+
await manager.startBackup();
176
176
+
await settingsManager.setLastBackupDate(new Date().toISOString());
177
177
+
toast("Backup complete!");
178
178
+
onBackupComplete();
179
179
+
} catch (err: any) {
180
180
+
toast(err.toString());
181
181
+
console.error(err);
182
182
+
} finally {
183
183
+
setIsLoading(false);
184
184
+
setStage(null);
185
185
+
setProgress(undefined);
186
186
+
}
187
187
+
}}
188
188
+
disabled={isLoading}
189
189
+
>
190
190
+
{isLoading ? (
191
191
+
<>
192
192
+
<LoaderCircleIcon className="w-4 h-4 animate-spin mr-2" />
193
193
+
Backing up...
194
194
+
</>
195
195
+
) : (
196
196
+
"Backup now"
197
197
+
)}
198
198
+
</Button>
199
199
+
</div>
200
200
+
201
201
+
{/* Clean backup progress card with animations */}
202
202
+
{isLoading && (
203
203
+
<div className="bg-card border rounded-lg p-4 animate-in slide-in-from-top-2 duration-300">
204
204
+
<div className="flex items-center justify-between mb-3">
205
205
+
<div className="flex items-center gap-3">
206
206
+
<div className="w-8 h-8 rounded-md bg-primary/10 flex items-center justify-center">
207
207
+
<HardDrive className="w-4 h-4 text-primary animate-pulse" />
208
208
+
</div>
209
209
+
<div>
210
210
+
<h3 className="font-medium">{formatStage(stage)}</h3>
211
211
+
</div>
212
212
+
</div>
213
213
+
</div>
214
214
+
215
215
+
<div className="space-y-1.5">
216
216
+
<Progress
217
217
+
value={progress}
218
218
+
className="h-2 transition-all duration-500 ease-out"
219
219
+
/>
220
220
+
</div>
221
221
+
</div>
185
222
)}
186
186
-
</Button>
223
223
+
</div>
187
224
);
188
225
}
189
226