@withvlibe/storage-sdk 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +246 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +472 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +445 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +68 -0
- package/dist/react.d.ts +68 -0
- package/dist/react.js +668 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +639 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +13 -5
package/dist/react.js
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
VlibeStorage: () => VlibeStorage,
|
|
24
|
+
useStorage: () => useStorage,
|
|
25
|
+
useStorageUsage: () => useStorageUsage
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_exports);
|
|
28
|
+
|
|
29
|
+
// src/VlibeStorage.ts
|
|
30
|
+
var DEFAULT_BASE_URL = "https://vlibe.app";
|
|
31
|
+
var VlibeStorage = class {
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this.authToken = null;
|
|
34
|
+
this.storageConfig = null;
|
|
35
|
+
this.appId = config.appId;
|
|
36
|
+
this.appSecret = config.appSecret;
|
|
37
|
+
this.baseUrl = config.baseUrl?.replace(/\/$/, "") || DEFAULT_BASE_URL;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Set the user's auth token (required for authenticated requests)
|
|
41
|
+
*/
|
|
42
|
+
setAuthToken(token) {
|
|
43
|
+
this.authToken = token;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Clear the auth token
|
|
47
|
+
*/
|
|
48
|
+
clearAuthToken() {
|
|
49
|
+
this.authToken = null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get storage configuration from the server
|
|
53
|
+
* This fetches the current storage provider and public URL base
|
|
54
|
+
*/
|
|
55
|
+
async getStorageConfig() {
|
|
56
|
+
if (this.storageConfig) {
|
|
57
|
+
return this.storageConfig;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const response = await this.request("/storage/config");
|
|
61
|
+
if (response.success && response.data) {
|
|
62
|
+
this.storageConfig = response.data;
|
|
63
|
+
return response.data;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
provider: "wasabi",
|
|
69
|
+
publicUrlBase: "https://s3.eu-central-2.wasabisys.com/vlibe.com"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Clear cached storage config (call if config might have changed)
|
|
74
|
+
*/
|
|
75
|
+
clearStorageConfigCache() {
|
|
76
|
+
this.storageConfig = null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Make an authenticated API request
|
|
80
|
+
*/
|
|
81
|
+
async request(path, options = {}) {
|
|
82
|
+
const url = `${this.baseUrl}/api${path}`;
|
|
83
|
+
const headers = {
|
|
84
|
+
"X-App-Id": this.appId,
|
|
85
|
+
"X-App-Secret": this.appSecret,
|
|
86
|
+
...options.headers || {}
|
|
87
|
+
};
|
|
88
|
+
if (this.authToken) {
|
|
89
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
90
|
+
}
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
...options,
|
|
93
|
+
headers
|
|
94
|
+
});
|
|
95
|
+
const data = await response.json();
|
|
96
|
+
if (!response.ok && !data.error) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
// ============================================
|
|
105
|
+
// UPLOAD METHODS
|
|
106
|
+
// ============================================
|
|
107
|
+
/**
|
|
108
|
+
* Upload a file directly
|
|
109
|
+
*/
|
|
110
|
+
async upload(file, options = {}) {
|
|
111
|
+
const formData = new FormData();
|
|
112
|
+
formData.append("file", file);
|
|
113
|
+
if (options.folder) {
|
|
114
|
+
formData.append("folder", options.folder);
|
|
115
|
+
}
|
|
116
|
+
if (options.isPublic) {
|
|
117
|
+
formData.append("isPublic", "true");
|
|
118
|
+
}
|
|
119
|
+
const response = await this.request("/storage", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: formData
|
|
122
|
+
});
|
|
123
|
+
if (!response.success || !response.data) {
|
|
124
|
+
throw new Error(response.error || "Upload failed");
|
|
125
|
+
}
|
|
126
|
+
return response.data;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get a presigned URL for direct upload to storage
|
|
130
|
+
* Useful for large files or when you want upload progress
|
|
131
|
+
*/
|
|
132
|
+
async getUploadUrl(filename, mimeType, size, options = {}) {
|
|
133
|
+
const response = await this.request("/storage/upload-url", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: {
|
|
136
|
+
"Content-Type": "application/json"
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
filename,
|
|
140
|
+
mimeType,
|
|
141
|
+
size,
|
|
142
|
+
folder: options.folder,
|
|
143
|
+
isPublic: options.isPublic
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
if (!response.success || !response.data) {
|
|
147
|
+
throw new Error(response.error || "Failed to get upload URL");
|
|
148
|
+
}
|
|
149
|
+
return response.data;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Upload a file using presigned URL (for progress tracking)
|
|
153
|
+
*/
|
|
154
|
+
async uploadWithProgress(file, options = {}) {
|
|
155
|
+
const { uploadUrl, key, expiresIn } = await this.getUploadUrl(
|
|
156
|
+
file.name,
|
|
157
|
+
file.type,
|
|
158
|
+
file.size,
|
|
159
|
+
options
|
|
160
|
+
);
|
|
161
|
+
await new Promise((resolve, reject) => {
|
|
162
|
+
const xhr = new XMLHttpRequest();
|
|
163
|
+
if (options.onProgress) {
|
|
164
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
165
|
+
if (e.lengthComputable) {
|
|
166
|
+
const percent = Math.round(e.loaded / e.total * 100);
|
|
167
|
+
options.onProgress(percent);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
xhr.addEventListener("load", () => {
|
|
172
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
173
|
+
resolve();
|
|
174
|
+
} else {
|
|
175
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
xhr.addEventListener("error", () => {
|
|
179
|
+
reject(new Error("Upload failed"));
|
|
180
|
+
});
|
|
181
|
+
xhr.open("PUT", uploadUrl);
|
|
182
|
+
xhr.setRequestHeader("Content-Type", file.type);
|
|
183
|
+
xhr.send(file);
|
|
184
|
+
});
|
|
185
|
+
const response = await this.request("/storage/upload-url", {
|
|
186
|
+
method: "PUT",
|
|
187
|
+
headers: {
|
|
188
|
+
"Content-Type": "application/json"
|
|
189
|
+
},
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
key,
|
|
192
|
+
filename: file.name,
|
|
193
|
+
mimeType: file.type,
|
|
194
|
+
size: file.size,
|
|
195
|
+
folder: options.folder,
|
|
196
|
+
isPublic: options.isPublic
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
if (!response.success || !response.data) {
|
|
200
|
+
throw new Error(response.error || "Failed to confirm upload");
|
|
201
|
+
}
|
|
202
|
+
return response.data;
|
|
203
|
+
}
|
|
204
|
+
// ============================================
|
|
205
|
+
// DOWNLOAD METHODS
|
|
206
|
+
// ============================================
|
|
207
|
+
/**
|
|
208
|
+
* Get a signed download URL for a file
|
|
209
|
+
*/
|
|
210
|
+
async getDownloadUrl(fileId, expiresIn) {
|
|
211
|
+
const params = new URLSearchParams({ download: "true" });
|
|
212
|
+
if (expiresIn) {
|
|
213
|
+
params.set("expiresIn", expiresIn.toString());
|
|
214
|
+
}
|
|
215
|
+
const response = await this.request(
|
|
216
|
+
`/storage/${fileId}?${params}`
|
|
217
|
+
);
|
|
218
|
+
if (!response.success || !response.data) {
|
|
219
|
+
throw new Error(response.error || "Failed to get download URL");
|
|
220
|
+
}
|
|
221
|
+
return response.data.url;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get a public URL for a file (only works for public files in the public path)
|
|
225
|
+
* Returns the direct URL based on the configured storage provider
|
|
226
|
+
*
|
|
227
|
+
* Note: This is a sync method that uses cached config. Call getStorageConfig()
|
|
228
|
+
* first to ensure the config is loaded, or use getPublicUrlAsync() for a
|
|
229
|
+
* guaranteed fresh URL.
|
|
230
|
+
*/
|
|
231
|
+
getPublicUrl(key) {
|
|
232
|
+
if (this.storageConfig) {
|
|
233
|
+
const base = this.storageConfig.publicUrlBase.replace(/\/$/, "");
|
|
234
|
+
return `${base}/${key}`;
|
|
235
|
+
}
|
|
236
|
+
const region = "eu-central-2";
|
|
237
|
+
const bucket = "vlibe.com";
|
|
238
|
+
return `https://s3.${region}.wasabisys.com/${bucket}/${key}`;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get a public URL for a file (async version that ensures fresh config)
|
|
242
|
+
* Returns the direct URL based on the configured storage provider
|
|
243
|
+
*/
|
|
244
|
+
async getPublicUrlAsync(key) {
|
|
245
|
+
const config = await this.getStorageConfig();
|
|
246
|
+
const base = config.publicUrlBase.replace(/\/$/, "");
|
|
247
|
+
return `${base}/${key}`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if a storage key is in the public path
|
|
251
|
+
*/
|
|
252
|
+
isPublicKey(key) {
|
|
253
|
+
return key.startsWith("vlibe-storage/public/");
|
|
254
|
+
}
|
|
255
|
+
// ============================================
|
|
256
|
+
// FILE MANAGEMENT
|
|
257
|
+
// ============================================
|
|
258
|
+
/**
|
|
259
|
+
* List files
|
|
260
|
+
*/
|
|
261
|
+
async list(options = {}) {
|
|
262
|
+
const params = new URLSearchParams();
|
|
263
|
+
if (options.folder !== void 0) {
|
|
264
|
+
params.set("folder", options.folder);
|
|
265
|
+
}
|
|
266
|
+
if (options.limit) {
|
|
267
|
+
params.set("limit", options.limit.toString());
|
|
268
|
+
}
|
|
269
|
+
if (options.offset) {
|
|
270
|
+
params.set("offset", options.offset.toString());
|
|
271
|
+
}
|
|
272
|
+
const response = await this.request(
|
|
273
|
+
`/storage?${params}`
|
|
274
|
+
);
|
|
275
|
+
if (!response.success || !response.data) {
|
|
276
|
+
throw new Error(response.error || "Failed to list files");
|
|
277
|
+
}
|
|
278
|
+
return response.data;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get a single file's metadata
|
|
282
|
+
*/
|
|
283
|
+
async get(fileId) {
|
|
284
|
+
const response = await this.request(`/storage/${fileId}`);
|
|
285
|
+
if (!response.success) {
|
|
286
|
+
if (response.error?.includes("not found")) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
throw new Error(response.error || "Failed to get file");
|
|
290
|
+
}
|
|
291
|
+
return response.data || null;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Delete a file
|
|
295
|
+
*/
|
|
296
|
+
async delete(fileId) {
|
|
297
|
+
const response = await this.request(`/storage/${fileId}`, {
|
|
298
|
+
method: "DELETE"
|
|
299
|
+
});
|
|
300
|
+
if (!response.success) {
|
|
301
|
+
if (response.error?.includes("not found")) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
throw new Error(response.error || "Failed to delete file");
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Delete multiple files
|
|
310
|
+
*/
|
|
311
|
+
async deleteMany(fileIds) {
|
|
312
|
+
const deleted = [];
|
|
313
|
+
const failed = [];
|
|
314
|
+
const batchSize = 10;
|
|
315
|
+
for (let i = 0; i < fileIds.length; i += batchSize) {
|
|
316
|
+
const batch = fileIds.slice(i, i + batchSize);
|
|
317
|
+
const results = await Promise.allSettled(
|
|
318
|
+
batch.map((id) => this.delete(id))
|
|
319
|
+
);
|
|
320
|
+
results.forEach((result, index) => {
|
|
321
|
+
const id = batch[index];
|
|
322
|
+
if (result.status === "fulfilled" && result.value) {
|
|
323
|
+
deleted.push(id);
|
|
324
|
+
} else {
|
|
325
|
+
failed.push(id);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return { deleted, failed };
|
|
330
|
+
}
|
|
331
|
+
// ============================================
|
|
332
|
+
// USAGE & LIMITS
|
|
333
|
+
// ============================================
|
|
334
|
+
/**
|
|
335
|
+
* Get storage usage statistics
|
|
336
|
+
*/
|
|
337
|
+
async getUsage() {
|
|
338
|
+
const response = await this.request("/storage/usage");
|
|
339
|
+
if (!response.success || !response.data) {
|
|
340
|
+
throw new Error(response.error || "Failed to get usage");
|
|
341
|
+
}
|
|
342
|
+
return response.data;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Check if a file of given size can be uploaded
|
|
346
|
+
*/
|
|
347
|
+
async canUpload(size) {
|
|
348
|
+
const response = await this.request("/storage/usage", {
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: {
|
|
351
|
+
"Content-Type": "application/json"
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify({ size })
|
|
354
|
+
});
|
|
355
|
+
if (!response.success || !response.data) {
|
|
356
|
+
throw new Error(response.error || "Failed to check upload");
|
|
357
|
+
}
|
|
358
|
+
return response.data;
|
|
359
|
+
}
|
|
360
|
+
// ============================================
|
|
361
|
+
// FOLDER MANAGEMENT
|
|
362
|
+
// ============================================
|
|
363
|
+
/**
|
|
364
|
+
* List folders
|
|
365
|
+
*/
|
|
366
|
+
async listFolders(options = {}) {
|
|
367
|
+
const params = new URLSearchParams();
|
|
368
|
+
if (options.parentId !== void 0) {
|
|
369
|
+
params.set("parentId", options.parentId === null ? "null" : options.parentId);
|
|
370
|
+
}
|
|
371
|
+
if (options.projectId) {
|
|
372
|
+
params.set("projectId", options.projectId);
|
|
373
|
+
}
|
|
374
|
+
const response = await this.request(`/storage/folders?${params}`);
|
|
375
|
+
if (!response.success || !response.data) {
|
|
376
|
+
throw new Error(response.error || "Failed to list folders");
|
|
377
|
+
}
|
|
378
|
+
return response.data;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Create a folder
|
|
382
|
+
*/
|
|
383
|
+
async createFolder(name, options = {}) {
|
|
384
|
+
const response = await this.request("/storage/folders", {
|
|
385
|
+
method: "POST",
|
|
386
|
+
headers: {
|
|
387
|
+
"Content-Type": "application/json"
|
|
388
|
+
},
|
|
389
|
+
body: JSON.stringify({
|
|
390
|
+
name,
|
|
391
|
+
parentId: options.parentId,
|
|
392
|
+
projectId: options.projectId,
|
|
393
|
+
isProjectRoot: options.isProjectRoot
|
|
394
|
+
})
|
|
395
|
+
});
|
|
396
|
+
if (!response.success || !response.data) {
|
|
397
|
+
throw new Error(response.error || "Failed to create folder");
|
|
398
|
+
}
|
|
399
|
+
return response.data;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Rename a folder
|
|
403
|
+
*/
|
|
404
|
+
async renameFolder(folderId, name) {
|
|
405
|
+
const response = await this.request(`/storage/folders/${folderId}`, {
|
|
406
|
+
method: "PATCH",
|
|
407
|
+
headers: {
|
|
408
|
+
"Content-Type": "application/json"
|
|
409
|
+
},
|
|
410
|
+
body: JSON.stringify({ name })
|
|
411
|
+
});
|
|
412
|
+
if (!response.success || !response.data) {
|
|
413
|
+
throw new Error(response.error || "Failed to rename folder");
|
|
414
|
+
}
|
|
415
|
+
return response.data;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Delete a folder (must be empty)
|
|
419
|
+
*/
|
|
420
|
+
async deleteFolder(folderId) {
|
|
421
|
+
const response = await this.request(`/storage/folders/${folderId}`, {
|
|
422
|
+
method: "DELETE"
|
|
423
|
+
});
|
|
424
|
+
if (!response.success) {
|
|
425
|
+
if (response.error?.includes("not found")) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
throw new Error(response.error || "Failed to delete folder");
|
|
429
|
+
}
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
// ============================================
|
|
433
|
+
// FILE COPY & REFERENCE
|
|
434
|
+
// ============================================
|
|
435
|
+
/**
|
|
436
|
+
* Copy a file to another project
|
|
437
|
+
* Creates a new database record pointing to the same S3 object
|
|
438
|
+
*/
|
|
439
|
+
async copyFileToProject(fileId, targetProjectId) {
|
|
440
|
+
const response = await this.request("/storage/files/copy", {
|
|
441
|
+
method: "POST",
|
|
442
|
+
headers: {
|
|
443
|
+
"Content-Type": "application/json"
|
|
444
|
+
},
|
|
445
|
+
body: JSON.stringify({ fileId, targetProjectId })
|
|
446
|
+
});
|
|
447
|
+
if (!response.success || !response.data) {
|
|
448
|
+
throw new Error(response.error || "Failed to copy file");
|
|
449
|
+
}
|
|
450
|
+
return response.data;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Create a file reference (link file to another project without copying)
|
|
454
|
+
* Does not count against storage quota
|
|
455
|
+
*/
|
|
456
|
+
async createFileReference(fileId, targetProjectId) {
|
|
457
|
+
const response = await this.request("/storage/files/reference", {
|
|
458
|
+
method: "POST",
|
|
459
|
+
headers: {
|
|
460
|
+
"Content-Type": "application/json"
|
|
461
|
+
},
|
|
462
|
+
body: JSON.stringify({ fileId, targetProjectId })
|
|
463
|
+
});
|
|
464
|
+
if (!response.success || !response.data) {
|
|
465
|
+
throw new Error(response.error || "Failed to create file reference");
|
|
466
|
+
}
|
|
467
|
+
return response.data;
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// src/hooks/useStorage.ts
|
|
472
|
+
var import_react = require("react");
|
|
473
|
+
function useStorage(client, options = {}) {
|
|
474
|
+
const { folder, autoFetch = true, limit = 50 } = options;
|
|
475
|
+
const [files, setFiles] = (0, import_react.useState)([]);
|
|
476
|
+
const [total, setTotal] = (0, import_react.useState)(0);
|
|
477
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
478
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
479
|
+
const [uploading, setUploading] = (0, import_react.useState)(false);
|
|
480
|
+
const [uploadProgress, setUploadProgress] = (0, import_react.useState)(0);
|
|
481
|
+
const [hasMore, setHasMore] = (0, import_react.useState)(false);
|
|
482
|
+
const offsetRef = (0, import_react.useRef)(0);
|
|
483
|
+
const fetchFiles = (0, import_react.useCallback)(
|
|
484
|
+
async (append = false) => {
|
|
485
|
+
setLoading(true);
|
|
486
|
+
setError(null);
|
|
487
|
+
try {
|
|
488
|
+
const listOptions = {
|
|
489
|
+
folder,
|
|
490
|
+
limit,
|
|
491
|
+
offset: append ? offsetRef.current : 0
|
|
492
|
+
};
|
|
493
|
+
const result = await client.list(listOptions);
|
|
494
|
+
if (append) {
|
|
495
|
+
setFiles((prev) => [...prev, ...result.files]);
|
|
496
|
+
} else {
|
|
497
|
+
setFiles(result.files);
|
|
498
|
+
offsetRef.current = 0;
|
|
499
|
+
}
|
|
500
|
+
setTotal(result.total);
|
|
501
|
+
setHasMore(result.hasMore);
|
|
502
|
+
offsetRef.current = (append ? offsetRef.current : 0) + result.files.length;
|
|
503
|
+
} catch (err) {
|
|
504
|
+
setError(err instanceof Error ? err.message : "Failed to fetch files");
|
|
505
|
+
} finally {
|
|
506
|
+
setLoading(false);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
[client, folder, limit]
|
|
510
|
+
);
|
|
511
|
+
(0, import_react.useEffect)(() => {
|
|
512
|
+
if (autoFetch) {
|
|
513
|
+
fetchFiles();
|
|
514
|
+
}
|
|
515
|
+
}, [autoFetch, fetchFiles]);
|
|
516
|
+
const upload = (0, import_react.useCallback)(
|
|
517
|
+
async (file, uploadOptions = {}) => {
|
|
518
|
+
setUploading(true);
|
|
519
|
+
setUploadProgress(0);
|
|
520
|
+
setError(null);
|
|
521
|
+
try {
|
|
522
|
+
const result = await client.upload(file, {
|
|
523
|
+
...uploadOptions,
|
|
524
|
+
folder: uploadOptions.folder ?? folder
|
|
525
|
+
});
|
|
526
|
+
await fetchFiles();
|
|
527
|
+
return result;
|
|
528
|
+
} catch (err) {
|
|
529
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
530
|
+
setError(message);
|
|
531
|
+
throw err;
|
|
532
|
+
} finally {
|
|
533
|
+
setUploading(false);
|
|
534
|
+
setUploadProgress(0);
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
[client, folder, fetchFiles]
|
|
538
|
+
);
|
|
539
|
+
const uploadWithProgress = (0, import_react.useCallback)(
|
|
540
|
+
async (file, uploadOptions = {}) => {
|
|
541
|
+
setUploading(true);
|
|
542
|
+
setUploadProgress(0);
|
|
543
|
+
setError(null);
|
|
544
|
+
try {
|
|
545
|
+
const result = await client.uploadWithProgress(file, {
|
|
546
|
+
...uploadOptions,
|
|
547
|
+
folder: uploadOptions.folder ?? folder,
|
|
548
|
+
onProgress: (progress) => {
|
|
549
|
+
setUploadProgress(progress);
|
|
550
|
+
uploadOptions.onProgress?.(progress);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
await fetchFiles();
|
|
554
|
+
return result;
|
|
555
|
+
} catch (err) {
|
|
556
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
557
|
+
setError(message);
|
|
558
|
+
throw err;
|
|
559
|
+
} finally {
|
|
560
|
+
setUploading(false);
|
|
561
|
+
setUploadProgress(0);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
[client, folder, fetchFiles]
|
|
565
|
+
);
|
|
566
|
+
const remove = (0, import_react.useCallback)(
|
|
567
|
+
async (fileId) => {
|
|
568
|
+
setError(null);
|
|
569
|
+
try {
|
|
570
|
+
const deleted = await client.delete(fileId);
|
|
571
|
+
if (deleted) {
|
|
572
|
+
setFiles((prev) => prev.filter((f) => f.id !== fileId));
|
|
573
|
+
setTotal((prev) => prev - 1);
|
|
574
|
+
}
|
|
575
|
+
return deleted;
|
|
576
|
+
} catch (err) {
|
|
577
|
+
const message = err instanceof Error ? err.message : "Delete failed";
|
|
578
|
+
setError(message);
|
|
579
|
+
throw err;
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
[client]
|
|
583
|
+
);
|
|
584
|
+
const refresh = (0, import_react.useCallback)(async () => {
|
|
585
|
+
await fetchFiles(false);
|
|
586
|
+
}, [fetchFiles]);
|
|
587
|
+
const loadMore = (0, import_react.useCallback)(async () => {
|
|
588
|
+
if (hasMore && !loading) {
|
|
589
|
+
await fetchFiles(true);
|
|
590
|
+
}
|
|
591
|
+
}, [hasMore, loading, fetchFiles]);
|
|
592
|
+
return {
|
|
593
|
+
files,
|
|
594
|
+
total,
|
|
595
|
+
loading,
|
|
596
|
+
error,
|
|
597
|
+
upload,
|
|
598
|
+
uploadWithProgress,
|
|
599
|
+
uploadProgress,
|
|
600
|
+
uploading,
|
|
601
|
+
remove,
|
|
602
|
+
refresh,
|
|
603
|
+
loadMore,
|
|
604
|
+
hasMore
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/hooks/useStorageUsage.ts
|
|
609
|
+
var import_react2 = require("react");
|
|
610
|
+
function useStorageUsage(client, options = {}) {
|
|
611
|
+
const { autoFetch = true, refreshInterval = 0 } = options;
|
|
612
|
+
const [usage, setUsage] = (0, import_react2.useState)(null);
|
|
613
|
+
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
614
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
615
|
+
const fetchUsage = (0, import_react2.useCallback)(async () => {
|
|
616
|
+
setLoading(true);
|
|
617
|
+
setError(null);
|
|
618
|
+
try {
|
|
619
|
+
const stats = await client.getUsage();
|
|
620
|
+
setUsage(stats);
|
|
621
|
+
} catch (err) {
|
|
622
|
+
setError(err instanceof Error ? err.message : "Failed to fetch usage");
|
|
623
|
+
} finally {
|
|
624
|
+
setLoading(false);
|
|
625
|
+
}
|
|
626
|
+
}, [client]);
|
|
627
|
+
(0, import_react2.useEffect)(() => {
|
|
628
|
+
if (autoFetch) {
|
|
629
|
+
fetchUsage();
|
|
630
|
+
}
|
|
631
|
+
}, [autoFetch, fetchUsage]);
|
|
632
|
+
(0, import_react2.useEffect)(() => {
|
|
633
|
+
if (refreshInterval > 0) {
|
|
634
|
+
const interval = setInterval(fetchUsage, refreshInterval);
|
|
635
|
+
return () => clearInterval(interval);
|
|
636
|
+
}
|
|
637
|
+
}, [refreshInterval, fetchUsage]);
|
|
638
|
+
const canUpload = (0, import_react2.useCallback)(
|
|
639
|
+
(size) => {
|
|
640
|
+
if (!usage) return true;
|
|
641
|
+
if (usage.storageLimit === 0) return true;
|
|
642
|
+
return usage.bytesUsed + size <= usage.storageLimit;
|
|
643
|
+
},
|
|
644
|
+
[usage]
|
|
645
|
+
);
|
|
646
|
+
const usagePercent = usage?.usagePercent ?? 0;
|
|
647
|
+
const isLimitReached = usagePercent >= 100;
|
|
648
|
+
const isNearLimit = usagePercent >= 80;
|
|
649
|
+
const usageFormatted = usage ? `${usage.bytesUsedFormatted} of ${usage.storageLimitFormatted}` : "Loading...";
|
|
650
|
+
return {
|
|
651
|
+
usage,
|
|
652
|
+
loading,
|
|
653
|
+
error,
|
|
654
|
+
canUpload,
|
|
655
|
+
refresh: fetchUsage,
|
|
656
|
+
usagePercent,
|
|
657
|
+
usageFormatted,
|
|
658
|
+
isLimitReached,
|
|
659
|
+
isNearLimit
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
663
|
+
0 && (module.exports = {
|
|
664
|
+
VlibeStorage,
|
|
665
|
+
useStorage,
|
|
666
|
+
useStorageUsage
|
|
667
|
+
});
|
|
668
|
+
//# sourceMappingURL=react.js.map
|