@sparkstudio/storage-ui 1.0.11 → 1.0.13
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.cjs +437 -193
- package/dist/index.d.cts +101 -33
- package/dist/index.d.ts +101 -33
- package/dist/index.js +434 -193
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -76,23 +76,22 @@ var Container = class {
|
|
|
76
76
|
if (!response.ok) throw new Error(await response.text());
|
|
77
77
|
return await response.json();
|
|
78
78
|
}
|
|
79
|
-
async
|
|
80
|
-
const url = `${this.baseUrl}/api/Container/
|
|
79
|
+
async DeleteContainer(id) {
|
|
80
|
+
const url = `${this.baseUrl}/api/Container/DeleteContainer/` + id;
|
|
81
81
|
const token = localStorage.getItem("auth_token");
|
|
82
82
|
const requestOptions = {
|
|
83
|
-
method: "
|
|
83
|
+
method: "GET",
|
|
84
84
|
headers: {
|
|
85
85
|
"Content-Type": "application/json",
|
|
86
86
|
...token && { Authorization: `Bearer ${token}` }
|
|
87
|
-
}
|
|
88
|
-
body: JSON.stringify(containerDTO)
|
|
87
|
+
}
|
|
89
88
|
};
|
|
90
89
|
const response = await fetch(url, requestOptions);
|
|
91
90
|
if (!response.ok) throw new Error(await response.text());
|
|
92
91
|
return await response.json();
|
|
93
92
|
}
|
|
94
|
-
async
|
|
95
|
-
const url = `${this.baseUrl}/api/Container/
|
|
93
|
+
async CreateFileContainer(fileName, size, contentType) {
|
|
94
|
+
const url = `${this.baseUrl}/api/Container/CreateFileContainer/` + fileName + `/` + size + `/` + contentType;
|
|
96
95
|
const token = localStorage.getItem("auth_token");
|
|
97
96
|
const requestOptions = {
|
|
98
97
|
method: "GET",
|
|
@@ -105,22 +104,39 @@ var Container = class {
|
|
|
105
104
|
if (!response.ok) throw new Error(await response.text());
|
|
106
105
|
return await response.json();
|
|
107
106
|
}
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/api/Controllers/Home.ts
|
|
110
|
+
var Home = class {
|
|
111
|
+
baseUrl;
|
|
112
|
+
constructor(baseUrl) {
|
|
113
|
+
this.baseUrl = baseUrl;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/api/Controllers/S3.ts
|
|
118
|
+
var S3 = class {
|
|
119
|
+
baseUrl;
|
|
120
|
+
constructor(baseUrl) {
|
|
121
|
+
this.baseUrl = baseUrl;
|
|
122
|
+
}
|
|
123
|
+
async DeleteS3(containerDTO) {
|
|
124
|
+
const url = `${this.baseUrl}/api/S3/DeleteS3`;
|
|
110
125
|
const token = localStorage.getItem("auth_token");
|
|
111
126
|
const requestOptions = {
|
|
112
|
-
method: "
|
|
127
|
+
method: "POST",
|
|
113
128
|
headers: {
|
|
114
129
|
"Content-Type": "application/json",
|
|
115
130
|
...token && { Authorization: `Bearer ${token}` }
|
|
116
|
-
}
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(containerDTO)
|
|
117
133
|
};
|
|
118
134
|
const response = await fetch(url, requestOptions);
|
|
119
135
|
if (!response.ok) throw new Error(await response.text());
|
|
120
136
|
return await response.json();
|
|
121
137
|
}
|
|
122
138
|
async GetPreSignedUrl(containerDTO) {
|
|
123
|
-
const url = `${this.baseUrl}/api/
|
|
139
|
+
const url = `${this.baseUrl}/api/S3/GetPreSignedUrl`;
|
|
124
140
|
const token = localStorage.getItem("auth_token");
|
|
125
141
|
const requestOptions = {
|
|
126
142
|
method: "POST",
|
|
@@ -136,21 +152,15 @@ var Container = class {
|
|
|
136
152
|
}
|
|
137
153
|
};
|
|
138
154
|
|
|
139
|
-
// src/api/Controllers/Home.ts
|
|
140
|
-
var Home = class {
|
|
141
|
-
baseUrl;
|
|
142
|
-
constructor(baseUrl) {
|
|
143
|
-
this.baseUrl = baseUrl;
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
155
|
// src/api/SparkStudioStorageSDK.ts
|
|
148
156
|
var SparkStudioStorageSDK = class {
|
|
149
157
|
container;
|
|
150
158
|
home;
|
|
159
|
+
s3;
|
|
151
160
|
constructor(baseUrl) {
|
|
152
161
|
this.container = new Container(baseUrl);
|
|
153
162
|
this.home = new Home(baseUrl);
|
|
163
|
+
this.s3 = new S3(baseUrl);
|
|
154
164
|
}
|
|
155
165
|
};
|
|
156
166
|
|
|
@@ -196,75 +206,246 @@ var ContainerType = /* @__PURE__ */ ((ContainerType2) => {
|
|
|
196
206
|
return ContainerType2;
|
|
197
207
|
})(ContainerType || {});
|
|
198
208
|
|
|
209
|
+
// src/components/ContainerUploadPanel.tsx
|
|
210
|
+
import "react";
|
|
211
|
+
|
|
199
212
|
// src/components/UploadContainer.tsx
|
|
200
|
-
import { useState } from "react";
|
|
213
|
+
import { useState as useState2 } from "react";
|
|
214
|
+
|
|
215
|
+
// src/components/UploadDropzone.tsx
|
|
216
|
+
import "react";
|
|
201
217
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
202
|
-
var
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
218
|
+
var UploadDropzone = ({
|
|
219
|
+
isDragging,
|
|
220
|
+
onDragOver,
|
|
221
|
+
onDragLeave,
|
|
222
|
+
onDrop
|
|
223
|
+
}) => {
|
|
224
|
+
return /* @__PURE__ */ jsxs(
|
|
225
|
+
"div",
|
|
226
|
+
{
|
|
227
|
+
className: "border rounded-3 p-4 text-center mb-3 d-flex flex-column align-items-center justify-content-center " + (isDragging ? "bg-light border-primary" : "border-secondary border-dashed"),
|
|
228
|
+
style: { cursor: "pointer", minHeight: "140px" },
|
|
229
|
+
onDragOver,
|
|
230
|
+
onDragLeave,
|
|
231
|
+
onDrop,
|
|
232
|
+
children: [
|
|
233
|
+
/* @__PURE__ */ jsx("i", { className: "bi bi-cloud-arrow-up fs-1 mb-2" }),
|
|
234
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2", children: "Drop files here" }),
|
|
235
|
+
/* @__PURE__ */ jsx("small", { className: "text-muted", children: "or click the button below" })
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/components/UploadFilePicker.tsx
|
|
242
|
+
import "react";
|
|
243
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
244
|
+
var UploadFilePicker = ({
|
|
245
|
+
multiple,
|
|
206
246
|
accept,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
getPresignedUrl,
|
|
210
|
-
onUploadComplete,
|
|
211
|
-
onUploadError
|
|
247
|
+
fileNames,
|
|
248
|
+
onFileChange
|
|
212
249
|
}) => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
uploadFile(upload);
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
const uploadFile = async (upload) => {
|
|
230
|
-
setUploads(
|
|
231
|
-
(prev) => prev.map(
|
|
232
|
-
(u) => u.id === upload.id ? { ...u, status: "uploading", progress: 0 } : u
|
|
250
|
+
return /* @__PURE__ */ jsxs2("div", { className: "d-flex gap-2 align-items-center", children: [
|
|
251
|
+
/* @__PURE__ */ jsxs2("label", { className: "btn btn-primary mb-0", children: [
|
|
252
|
+
/* @__PURE__ */ jsx2("i", { className: "bi bi-folder2-open me-2" }),
|
|
253
|
+
"Browse files",
|
|
254
|
+
/* @__PURE__ */ jsx2(
|
|
255
|
+
"input",
|
|
256
|
+
{
|
|
257
|
+
type: "file",
|
|
258
|
+
className: "d-none",
|
|
259
|
+
multiple,
|
|
260
|
+
accept,
|
|
261
|
+
onChange: onFileChange
|
|
262
|
+
}
|
|
233
263
|
)
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
264
|
+
] }),
|
|
265
|
+
fileNames.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "flex-grow-1", children: [
|
|
266
|
+
/* @__PURE__ */ jsx2("div", { className: "small text-muted", children: "Selected:" }),
|
|
267
|
+
/* @__PURE__ */ jsx2("ul", { className: "mb-0 small", children: fileNames.map((name) => /* @__PURE__ */ jsx2("li", { children: name }, name)) })
|
|
268
|
+
] })
|
|
269
|
+
] });
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/components/UploadProgressList.tsx
|
|
273
|
+
import "react";
|
|
274
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
275
|
+
var UploadProgressList = ({
|
|
276
|
+
uploads
|
|
277
|
+
}) => {
|
|
278
|
+
if (uploads.length === 0) return null;
|
|
279
|
+
return /* @__PURE__ */ jsx3("div", { className: "mt-3", children: uploads.map((u) => /* @__PURE__ */ jsxs3("div", { className: "mb-2", children: [
|
|
280
|
+
/* @__PURE__ */ jsxs3("div", { className: "d-flex justify-content-between small mb-1", children: [
|
|
281
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
282
|
+
u.file.name,
|
|
283
|
+
" - ",
|
|
284
|
+
u?.publicUrl ?? ""
|
|
285
|
+
] }),
|
|
286
|
+
/* @__PURE__ */ jsx3("span", { children: u.status === "success" ? "Completed" : u.status === "error" ? "Error" : `${u.progress}%` })
|
|
287
|
+
] }),
|
|
288
|
+
/* @__PURE__ */ jsx3("div", { className: "progress", style: { height: "6px" }, children: /* @__PURE__ */ jsx3(
|
|
289
|
+
"div",
|
|
290
|
+
{
|
|
291
|
+
className: "progress-bar " + (u.status === "success" ? "bg-success" : u.status === "error" ? "bg-danger" : ""),
|
|
292
|
+
role: "progressbar",
|
|
293
|
+
style: { width: `${u.progress}%` },
|
|
294
|
+
"aria-valuenow": u.progress,
|
|
295
|
+
"aria-valuemin": 0,
|
|
296
|
+
"aria-valuemax": 100
|
|
238
297
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
298
|
+
) }),
|
|
299
|
+
u.status === "error" && /* @__PURE__ */ jsx3("div", { className: "text-danger small mt-1", children: u.error ?? "Upload failed" })
|
|
300
|
+
] }, u.id)) });
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/hooks/UseUploadManager.ts
|
|
304
|
+
import { useState, useCallback } from "react";
|
|
305
|
+
|
|
306
|
+
// src/engines/UploadFileToS3.ts
|
|
307
|
+
async function UploadFileToS3(file, presignedUrl, onProgress) {
|
|
308
|
+
return new Promise((resolve, reject) => {
|
|
309
|
+
const xhr = new XMLHttpRequest();
|
|
310
|
+
xhr.open("PUT", presignedUrl);
|
|
311
|
+
xhr.upload.onprogress = (event) => {
|
|
312
|
+
if (!event.lengthComputable) return;
|
|
313
|
+
const percent = Math.round(event.loaded / event.total * 100);
|
|
314
|
+
onProgress(percent);
|
|
315
|
+
};
|
|
316
|
+
xhr.onload = () => {
|
|
317
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
318
|
+
onProgress(100);
|
|
319
|
+
resolve();
|
|
320
|
+
} else {
|
|
321
|
+
reject(
|
|
322
|
+
new Error(
|
|
323
|
+
`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`
|
|
245
324
|
)
|
|
246
325
|
);
|
|
247
|
-
});
|
|
248
|
-
const fileUrl = url.split("?")[0];
|
|
249
|
-
setUploads(
|
|
250
|
-
(prev) => prev.map(
|
|
251
|
-
(u) => u.id === upload.id ? { ...u, status: "success", progress: 100, s3Url: fileUrl, publicUrl: presignedUrl.PublicUrl } : u
|
|
252
|
-
)
|
|
253
|
-
);
|
|
254
|
-
onUploadComplete?.(upload.file, fileUrl);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
let message = "Upload failed";
|
|
257
|
-
if (err instanceof Error) {
|
|
258
|
-
message = err.message;
|
|
259
326
|
}
|
|
327
|
+
};
|
|
328
|
+
xhr.onerror = () => {
|
|
329
|
+
reject(new Error("Network error while uploading to S3"));
|
|
330
|
+
};
|
|
331
|
+
xhr.setRequestHeader(
|
|
332
|
+
"Content-Type",
|
|
333
|
+
file.type || "application/octet-stream"
|
|
334
|
+
);
|
|
335
|
+
xhr.send(file);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/hooks/UseUploadManager.ts
|
|
340
|
+
function UseUploadManager({
|
|
341
|
+
autoUpload = false,
|
|
342
|
+
getPresignedUrl,
|
|
343
|
+
onUploadComplete,
|
|
344
|
+
onUploadError
|
|
345
|
+
}) {
|
|
346
|
+
const [uploads, setUploads] = useState([]);
|
|
347
|
+
const createUploadStates = (files) => Array.from(files).map((file) => ({
|
|
348
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Math.random()}`,
|
|
349
|
+
file,
|
|
350
|
+
progress: 0,
|
|
351
|
+
status: "pending"
|
|
352
|
+
}));
|
|
353
|
+
const uploadFile = useCallback(
|
|
354
|
+
async (upload) => {
|
|
260
355
|
setUploads(
|
|
261
356
|
(prev) => prev.map(
|
|
262
|
-
(u) => u.id === upload.id ? { ...u, status: "
|
|
357
|
+
(u) => u.id === upload.id ? { ...u, status: "uploading", progress: 0 } : u
|
|
263
358
|
)
|
|
264
359
|
);
|
|
265
|
-
|
|
266
|
-
|
|
360
|
+
try {
|
|
361
|
+
if (!getPresignedUrl) {
|
|
362
|
+
throw new Error("getPresignedUrl is not provided.");
|
|
363
|
+
}
|
|
364
|
+
const presignedUrl = await getPresignedUrl(
|
|
365
|
+
upload.file
|
|
366
|
+
);
|
|
367
|
+
const url = presignedUrl?.PresignedUrl ?? "";
|
|
368
|
+
await UploadFileToS3(upload.file, url, (progress) => {
|
|
369
|
+
setUploads(
|
|
370
|
+
(prev) => prev.map(
|
|
371
|
+
(u) => u.id === upload.id ? { ...u, progress } : u
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
const fileUrl = url.split("?")[0];
|
|
376
|
+
setUploads(
|
|
377
|
+
(prev) => prev.map(
|
|
378
|
+
(u) => u.id === upload.id ? {
|
|
379
|
+
...u,
|
|
380
|
+
status: "success",
|
|
381
|
+
progress: 100,
|
|
382
|
+
s3Url: fileUrl,
|
|
383
|
+
// assumes your DTO has PublicUrl
|
|
384
|
+
publicUrl: presignedUrl.PublicUrl
|
|
385
|
+
} : u
|
|
386
|
+
)
|
|
387
|
+
);
|
|
388
|
+
onUploadComplete?.(upload.file, fileUrl);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
let message = "Upload failed";
|
|
391
|
+
if (err instanceof Error) {
|
|
392
|
+
message = err.message;
|
|
393
|
+
}
|
|
394
|
+
setUploads(
|
|
395
|
+
(prev) => prev.map(
|
|
396
|
+
(u) => u.id === upload.id ? { ...u, status: "error", error: message } : u
|
|
397
|
+
)
|
|
398
|
+
);
|
|
399
|
+
onUploadError?.(
|
|
400
|
+
upload.file,
|
|
401
|
+
err instanceof Error ? err : new Error(message)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
[getPresignedUrl, onUploadComplete, onUploadError]
|
|
406
|
+
);
|
|
407
|
+
const startUploadsIfNeeded = useCallback(
|
|
408
|
+
(files) => {
|
|
409
|
+
if (!autoUpload || !getPresignedUrl) return;
|
|
410
|
+
const newUploads = createUploadStates(files);
|
|
411
|
+
setUploads((prev) => [...prev, ...newUploads]);
|
|
412
|
+
newUploads.forEach((upload) => {
|
|
413
|
+
void uploadFile(upload);
|
|
414
|
+
});
|
|
415
|
+
},
|
|
416
|
+
[autoUpload, getPresignedUrl, uploadFile]
|
|
417
|
+
);
|
|
418
|
+
const resetUploads = useCallback(() => setUploads([]), []);
|
|
419
|
+
return {
|
|
420
|
+
uploads,
|
|
421
|
+
startUploadsIfNeeded,
|
|
422
|
+
resetUploads
|
|
267
423
|
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/components/UploadContainer.tsx
|
|
427
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
428
|
+
var UploadContainer = ({
|
|
429
|
+
title = "Upload files",
|
|
430
|
+
description = "Drag and drop files here, or click the button to browse.",
|
|
431
|
+
multiple = true,
|
|
432
|
+
accept,
|
|
433
|
+
onFilesSelected,
|
|
434
|
+
existingFiles = [],
|
|
435
|
+
onExistingFileClick,
|
|
436
|
+
autoUpload = false,
|
|
437
|
+
getPresignedUrl,
|
|
438
|
+
onUploadComplete,
|
|
439
|
+
onUploadError
|
|
440
|
+
}) => {
|
|
441
|
+
const [isDragging, setIsDragging] = useState2(false);
|
|
442
|
+
const [fileNames, setFileNames] = useState2([]);
|
|
443
|
+
const { uploads, startUploadsIfNeeded } = UseUploadManager({
|
|
444
|
+
autoUpload,
|
|
445
|
+
getPresignedUrl,
|
|
446
|
+
onUploadComplete,
|
|
447
|
+
onUploadError
|
|
448
|
+
});
|
|
268
449
|
const handleDragOver = (e) => {
|
|
269
450
|
e.preventDefault();
|
|
270
451
|
setIsDragging(true);
|
|
@@ -289,149 +470,209 @@ var UploadContainer = ({
|
|
|
289
470
|
onFilesSelected?.(files);
|
|
290
471
|
startUploadsIfNeeded(files);
|
|
291
472
|
};
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
473
|
+
const handleExistingFileClickInternal = (file) => {
|
|
474
|
+
if (onExistingFileClick) {
|
|
475
|
+
onExistingFileClick(file);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const a = document.createElement("a");
|
|
479
|
+
a.download = file.Name ?? "";
|
|
480
|
+
a.target = "_blank";
|
|
481
|
+
a.rel = "noopener noreferrer";
|
|
482
|
+
document.body.appendChild(a);
|
|
483
|
+
a.click();
|
|
484
|
+
document.body.removeChild(a);
|
|
485
|
+
};
|
|
486
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
487
|
+
/* @__PURE__ */ jsx4("h5", { className: "card-title mb-2", children: title }),
|
|
488
|
+
/* @__PURE__ */ jsx4("p", { className: "card-text text-muted", children: description }),
|
|
489
|
+
existingFiles.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "mb-3", children: [
|
|
490
|
+
/* @__PURE__ */ jsx4("h6", { className: "mb-2", children: "Existing files" }),
|
|
491
|
+
/* @__PURE__ */ jsx4("ul", { className: "list-group", children: existingFiles.map((file) => /* @__PURE__ */ jsxs4(
|
|
492
|
+
"li",
|
|
493
|
+
{
|
|
494
|
+
className: "list-group-item d-flex justify-content-between align-items-center",
|
|
495
|
+
style: { cursor: "pointer" },
|
|
496
|
+
onClick: () => handleExistingFileClickInternal(file),
|
|
497
|
+
children: [
|
|
498
|
+
/* @__PURE__ */ jsx4("span", { children: file.Name }),
|
|
499
|
+
typeof file.FileSize === "number" && /* @__PURE__ */ jsxs4("small", { className: "text-muted", children: [
|
|
500
|
+
(file.FileSize / 1024).toFixed(1),
|
|
501
|
+
" KB"
|
|
502
|
+
] })
|
|
503
|
+
]
|
|
504
|
+
},
|
|
505
|
+
file.Id
|
|
506
|
+
)) })
|
|
507
|
+
] }),
|
|
508
|
+
/* @__PURE__ */ jsx4(
|
|
509
|
+
UploadDropzone,
|
|
297
510
|
{
|
|
298
|
-
|
|
299
|
-
style: { cursor: "pointer", minHeight: "140px" },
|
|
511
|
+
isDragging,
|
|
300
512
|
onDragOver: handleDragOver,
|
|
301
513
|
onDragLeave: handleDragLeave,
|
|
302
|
-
onDrop: handleDrop
|
|
303
|
-
children: [
|
|
304
|
-
/* @__PURE__ */ jsx("i", { className: "bi bi-cloud-arrow-up fs-1 mb-2" }),
|
|
305
|
-
/* @__PURE__ */ jsx("p", { className: "mb-2", children: "Drop files here" }),
|
|
306
|
-
/* @__PURE__ */ jsx("small", { className: "text-muted", children: "or click the button below" })
|
|
307
|
-
]
|
|
514
|
+
onDrop: handleDrop
|
|
308
515
|
}
|
|
309
516
|
),
|
|
310
|
-
/* @__PURE__ */
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
type: "file",
|
|
318
|
-
className: "d-none",
|
|
319
|
-
multiple,
|
|
320
|
-
accept,
|
|
321
|
-
onChange: handleFileChange
|
|
322
|
-
}
|
|
323
|
-
)
|
|
324
|
-
] }),
|
|
325
|
-
fileNames.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex-grow-1", children: [
|
|
326
|
-
/* @__PURE__ */ jsx("div", { className: "small text-muted", children: "Selected:" }),
|
|
327
|
-
/* @__PURE__ */ jsx("ul", { className: "mb-0 small", children: fileNames.map((name) => /* @__PURE__ */ jsx("li", { children: name }, name)) })
|
|
328
|
-
] })
|
|
329
|
-
] }),
|
|
330
|
-
uploads.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3", children: uploads.map((u) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
|
|
331
|
-
/* @__PURE__ */ jsxs("div", { className: "d-flex justify-content-between small mb-1", children: [
|
|
332
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
333
|
-
u.file.name,
|
|
334
|
-
" - ",
|
|
335
|
-
u?.publicUrl ?? ""
|
|
336
|
-
] }),
|
|
337
|
-
/* @__PURE__ */ jsx("span", { children: u.status === "success" ? "Completed" : u.status === "error" ? "Error" : `${u.progress}%` })
|
|
338
|
-
] }),
|
|
339
|
-
/* @__PURE__ */ jsx("div", { className: "progress", style: { height: "6px" }, children: /* @__PURE__ */ jsx(
|
|
340
|
-
"div",
|
|
341
|
-
{
|
|
342
|
-
className: "progress-bar " + (u.status === "success" ? "bg-success" : u.status === "error" ? "bg-danger" : ""),
|
|
343
|
-
role: "progressbar",
|
|
344
|
-
style: { width: `${u.progress}%` },
|
|
345
|
-
"aria-valuenow": u.progress,
|
|
346
|
-
"aria-valuemin": 0,
|
|
347
|
-
"aria-valuemax": 100
|
|
348
|
-
}
|
|
349
|
-
) }),
|
|
350
|
-
u.status === "error" && /* @__PURE__ */ jsx("div", { className: "text-danger small mt-1", children: u.error ?? "Upload failed" })
|
|
351
|
-
] }, u.id)) })
|
|
352
|
-
] }) }) });
|
|
353
|
-
};
|
|
354
|
-
async function uploadFileToS3(file, presignedUrl, onProgress) {
|
|
355
|
-
return new Promise((resolve, reject) => {
|
|
356
|
-
const xhr = new XMLHttpRequest();
|
|
357
|
-
xhr.open("PUT", presignedUrl);
|
|
358
|
-
xhr.upload.onprogress = (event) => {
|
|
359
|
-
if (!event.lengthComputable) return;
|
|
360
|
-
const percent = Math.round(event.loaded / event.total * 100);
|
|
361
|
-
onProgress(percent);
|
|
362
|
-
};
|
|
363
|
-
xhr.onload = () => {
|
|
364
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
365
|
-
onProgress(100);
|
|
366
|
-
resolve();
|
|
367
|
-
} else {
|
|
368
|
-
reject(
|
|
369
|
-
new Error(`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`)
|
|
370
|
-
);
|
|
517
|
+
/* @__PURE__ */ jsx4(
|
|
518
|
+
UploadFilePicker,
|
|
519
|
+
{
|
|
520
|
+
multiple,
|
|
521
|
+
accept,
|
|
522
|
+
fileNames,
|
|
523
|
+
onFileChange: handleFileChange
|
|
371
524
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
525
|
+
),
|
|
526
|
+
/* @__PURE__ */ jsx4(UploadProgressList, { uploads })
|
|
527
|
+
] });
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/hooks/UseContainers.ts
|
|
531
|
+
import { useEffect, useState as useState3 } from "react";
|
|
532
|
+
function UseContainers({ apiBaseUrl, parentId }) {
|
|
533
|
+
const [containers, setContainers] = useState3([]);
|
|
534
|
+
const [loading, setLoading] = useState3(false);
|
|
535
|
+
const [error, setError] = useState3(null);
|
|
536
|
+
const load = async () => {
|
|
537
|
+
setLoading(true);
|
|
538
|
+
setError(null);
|
|
539
|
+
try {
|
|
540
|
+
const sdkDb = new SparkStudioStorageSDK(
|
|
541
|
+
"https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod"
|
|
542
|
+
);
|
|
543
|
+
const result = await sdkDb.container.ReadRootContainers();
|
|
544
|
+
setContainers(result);
|
|
545
|
+
} catch (err) {
|
|
546
|
+
setError(err);
|
|
547
|
+
} finally {
|
|
548
|
+
setLoading(false);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
useEffect(() => {
|
|
552
|
+
void load();
|
|
553
|
+
}, [apiBaseUrl, parentId]);
|
|
554
|
+
return {
|
|
555
|
+
containers,
|
|
556
|
+
loading,
|
|
557
|
+
error,
|
|
558
|
+
reload: load
|
|
559
|
+
};
|
|
382
560
|
}
|
|
383
561
|
|
|
384
|
-
// src/
|
|
385
|
-
import {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
562
|
+
// src/components/ContainerUploadPanel.tsx
|
|
563
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
564
|
+
var ContainerUploadPanel = ({
|
|
565
|
+
containerApiBaseUrl,
|
|
566
|
+
parentContainerId,
|
|
567
|
+
title = "Upload files",
|
|
568
|
+
description = "Drop files to upload. Existing files are listed below."
|
|
569
|
+
}) => {
|
|
570
|
+
const { containers, loading, error, reload } = UseContainers({
|
|
571
|
+
apiBaseUrl: containerApiBaseUrl,
|
|
572
|
+
parentId: parentContainerId
|
|
573
|
+
});
|
|
391
574
|
const getPresignedUrl = async (file) => {
|
|
392
|
-
const sdkDb = new SparkStudioStorageSDK(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
575
|
+
const sdkDb = new SparkStudioStorageSDK(
|
|
576
|
+
"https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod"
|
|
577
|
+
);
|
|
578
|
+
const sdkS3 = new SparkStudioStorageSDK(
|
|
579
|
+
"https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
|
|
580
|
+
);
|
|
581
|
+
const containerDTO = await sdkDb.container.CreateFileContainer(
|
|
582
|
+
file.name,
|
|
583
|
+
file.size,
|
|
584
|
+
encodeURIComponent(file.type)
|
|
585
|
+
);
|
|
586
|
+
const resultS3 = await sdkS3.s3.GetPreSignedUrl(containerDTO);
|
|
396
587
|
return resultS3;
|
|
397
588
|
};
|
|
398
|
-
|
|
589
|
+
const handleUploadComplete = async (file, s3Url) => {
|
|
590
|
+
console.log("Upload complete:", file.name, s3Url);
|
|
591
|
+
await reload();
|
|
592
|
+
};
|
|
593
|
+
const handleUploadError = (file, err) => {
|
|
594
|
+
console.error("Upload failed:", file.name, err);
|
|
595
|
+
};
|
|
596
|
+
const handleExistingFileClick = (file) => {
|
|
597
|
+
const downloadUrl = `${containerApiBaseUrl}/api/Container/Download/${file.Id}`;
|
|
598
|
+
const a = document.createElement("a");
|
|
599
|
+
a.href = downloadUrl;
|
|
600
|
+
a.download = file.Name ?? "";
|
|
601
|
+
a.target = "_blank";
|
|
602
|
+
a.rel = "noopener noreferrer";
|
|
603
|
+
document.body.appendChild(a);
|
|
604
|
+
a.click();
|
|
605
|
+
document.body.removeChild(a);
|
|
606
|
+
};
|
|
607
|
+
return /* @__PURE__ */ jsxs5("div", { children: [
|
|
608
|
+
loading && /* @__PURE__ */ jsx5("p", { children: "Loading existing files\u2026" }),
|
|
609
|
+
error && /* @__PURE__ */ jsxs5("p", { className: "text-danger", children: [
|
|
610
|
+
"Failed to load containers: ",
|
|
611
|
+
error.message
|
|
612
|
+
] }),
|
|
613
|
+
/* @__PURE__ */ jsx5(
|
|
614
|
+
UploadContainer,
|
|
615
|
+
{
|
|
616
|
+
title,
|
|
617
|
+
description,
|
|
618
|
+
existingFiles: containers,
|
|
619
|
+
onExistingFileClick: handleExistingFileClick,
|
|
620
|
+
autoUpload: true,
|
|
621
|
+
getPresignedUrl,
|
|
622
|
+
onUploadComplete: handleUploadComplete,
|
|
623
|
+
onUploadError: handleUploadError
|
|
624
|
+
}
|
|
625
|
+
)
|
|
626
|
+
] });
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/views/HomeView.tsx
|
|
630
|
+
import {
|
|
631
|
+
AppSettings,
|
|
632
|
+
AuthenticatorProvider,
|
|
633
|
+
UserInfoCard,
|
|
634
|
+
useUser
|
|
635
|
+
} from "@sparkstudio/authentication-ui";
|
|
636
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
637
|
+
function HomeView() {
|
|
638
|
+
return /* @__PURE__ */ jsx6(
|
|
399
639
|
AuthenticatorProvider,
|
|
400
640
|
{
|
|
401
641
|
googleClientId: AppSettings.GoogleClientId,
|
|
402
642
|
authenticationUrl: AppSettings.AuthenticationUrl,
|
|
403
643
|
accountsUrl: AppSettings.AccountsUrl,
|
|
404
|
-
|
|
405
|
-
children: [
|
|
406
|
-
/* @__PURE__ */ jsx2(UserInfoCard, {}),
|
|
407
|
-
/* @__PURE__ */ jsx2(
|
|
408
|
-
UploadContainer,
|
|
409
|
-
{
|
|
410
|
-
title: "Upload your files to S3",
|
|
411
|
-
description: "Drag & drop or browse files. Each file will upload with its own progress bar.",
|
|
412
|
-
multiple: true,
|
|
413
|
-
accept: "*/*",
|
|
414
|
-
autoUpload: true,
|
|
415
|
-
getPresignedUrl,
|
|
416
|
-
onUploadComplete: (file, s3Url) => {
|
|
417
|
-
console.log("Uploaded", file.name, "to", s3Url);
|
|
418
|
-
},
|
|
419
|
-
onUploadError: (file, error) => {
|
|
420
|
-
console.error("Failed to upload", file.name, error);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
)
|
|
424
|
-
]
|
|
644
|
+
children: /* @__PURE__ */ jsx6(HomeContent, {})
|
|
425
645
|
}
|
|
426
646
|
);
|
|
427
647
|
}
|
|
648
|
+
function HomeContent() {
|
|
649
|
+
const { user } = useUser();
|
|
650
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
651
|
+
/* @__PURE__ */ jsx6(UserInfoCard, {}),
|
|
652
|
+
user && /* @__PURE__ */ jsx6(
|
|
653
|
+
ContainerUploadPanel,
|
|
654
|
+
{
|
|
655
|
+
containerApiBaseUrl: "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod",
|
|
656
|
+
storageApiBaseUrl: "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
] });
|
|
660
|
+
}
|
|
428
661
|
export {
|
|
429
662
|
AWSPresignedUrlDTO,
|
|
430
663
|
Container,
|
|
431
664
|
ContainerDTO,
|
|
432
665
|
ContainerType,
|
|
666
|
+
ContainerUploadPanel,
|
|
433
667
|
Home,
|
|
434
668
|
HomeView,
|
|
669
|
+
S3,
|
|
435
670
|
SparkStudioStorageSDK,
|
|
436
|
-
UploadContainer
|
|
671
|
+
UploadContainer,
|
|
672
|
+
UploadDropzone,
|
|
673
|
+
UploadFilePicker,
|
|
674
|
+
UploadFileToS3,
|
|
675
|
+
UploadProgressList,
|
|
676
|
+
UseContainers,
|
|
677
|
+
UseUploadManager
|
|
437
678
|
};
|