@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 CHANGED
@@ -24,10 +24,18 @@ __export(index_exports, {
24
24
  Container: () => Container,
25
25
  ContainerDTO: () => ContainerDTO,
26
26
  ContainerType: () => ContainerType,
27
+ ContainerUploadPanel: () => ContainerUploadPanel,
27
28
  Home: () => Home,
28
29
  HomeView: () => HomeView,
30
+ S3: () => S3,
29
31
  SparkStudioStorageSDK: () => SparkStudioStorageSDK,
30
- UploadContainer: () => UploadContainer
32
+ UploadContainer: () => UploadContainer,
33
+ UploadDropzone: () => UploadDropzone,
34
+ UploadFilePicker: () => UploadFilePicker,
35
+ UploadFileToS3: () => UploadFileToS3,
36
+ UploadProgressList: () => UploadProgressList,
37
+ UseContainers: () => UseContainers,
38
+ UseUploadManager: () => UseUploadManager
31
39
  });
32
40
  module.exports = __toCommonJS(index_exports);
33
41
 
@@ -109,23 +117,22 @@ var Container = class {
109
117
  if (!response.ok) throw new Error(await response.text());
110
118
  return await response.json();
111
119
  }
112
- async DeleteS3(containerDTO) {
113
- const url = `${this.baseUrl}/api/Container/DeleteS3`;
120
+ async DeleteContainer(id) {
121
+ const url = `${this.baseUrl}/api/Container/DeleteContainer/` + id;
114
122
  const token = localStorage.getItem("auth_token");
115
123
  const requestOptions = {
116
- method: "POST",
124
+ method: "GET",
117
125
  headers: {
118
126
  "Content-Type": "application/json",
119
127
  ...token && { Authorization: `Bearer ${token}` }
120
- },
121
- body: JSON.stringify(containerDTO)
128
+ }
122
129
  };
123
130
  const response = await fetch(url, requestOptions);
124
131
  if (!response.ok) throw new Error(await response.text());
125
132
  return await response.json();
126
133
  }
127
- async DeleteContainer(id) {
128
- const url = `${this.baseUrl}/api/Container/DeleteContainer/` + id;
134
+ async CreateFileContainer(fileName, size, contentType) {
135
+ const url = `${this.baseUrl}/api/Container/CreateFileContainer/` + fileName + `/` + size + `/` + contentType;
129
136
  const token = localStorage.getItem("auth_token");
130
137
  const requestOptions = {
131
138
  method: "GET",
@@ -138,22 +145,39 @@ var Container = class {
138
145
  if (!response.ok) throw new Error(await response.text());
139
146
  return await response.json();
140
147
  }
141
- async CreateFileContainer(fileName, size, contentType) {
142
- const url = `${this.baseUrl}/api/Container/CreateFileContainer/` + fileName + `/` + size + `/` + contentType;
148
+ };
149
+
150
+ // src/api/Controllers/Home.ts
151
+ var Home = class {
152
+ baseUrl;
153
+ constructor(baseUrl) {
154
+ this.baseUrl = baseUrl;
155
+ }
156
+ };
157
+
158
+ // src/api/Controllers/S3.ts
159
+ var S3 = class {
160
+ baseUrl;
161
+ constructor(baseUrl) {
162
+ this.baseUrl = baseUrl;
163
+ }
164
+ async DeleteS3(containerDTO) {
165
+ const url = `${this.baseUrl}/api/S3/DeleteS3`;
143
166
  const token = localStorage.getItem("auth_token");
144
167
  const requestOptions = {
145
- method: "GET",
168
+ method: "POST",
146
169
  headers: {
147
170
  "Content-Type": "application/json",
148
171
  ...token && { Authorization: `Bearer ${token}` }
149
- }
172
+ },
173
+ body: JSON.stringify(containerDTO)
150
174
  };
151
175
  const response = await fetch(url, requestOptions);
152
176
  if (!response.ok) throw new Error(await response.text());
153
177
  return await response.json();
154
178
  }
155
179
  async GetPreSignedUrl(containerDTO) {
156
- const url = `${this.baseUrl}/api/Container/GetPreSignedUrl`;
180
+ const url = `${this.baseUrl}/api/S3/GetPreSignedUrl`;
157
181
  const token = localStorage.getItem("auth_token");
158
182
  const requestOptions = {
159
183
  method: "POST",
@@ -169,21 +193,15 @@ var Container = class {
169
193
  }
170
194
  };
171
195
 
172
- // src/api/Controllers/Home.ts
173
- var Home = class {
174
- baseUrl;
175
- constructor(baseUrl) {
176
- this.baseUrl = baseUrl;
177
- }
178
- };
179
-
180
196
  // src/api/SparkStudioStorageSDK.ts
181
197
  var SparkStudioStorageSDK = class {
182
198
  container;
183
199
  home;
200
+ s3;
184
201
  constructor(baseUrl) {
185
202
  this.container = new Container(baseUrl);
186
203
  this.home = new Home(baseUrl);
204
+ this.s3 = new S3(baseUrl);
187
205
  }
188
206
  };
189
207
 
@@ -229,75 +247,246 @@ var ContainerType = /* @__PURE__ */ ((ContainerType2) => {
229
247
  return ContainerType2;
230
248
  })(ContainerType || {});
231
249
 
250
+ // src/components/ContainerUploadPanel.tsx
251
+ var import_react7 = require("react");
252
+
232
253
  // src/components/UploadContainer.tsx
254
+ var import_react5 = require("react");
255
+
256
+ // src/components/UploadDropzone.tsx
233
257
  var import_react = require("react");
234
258
  var import_jsx_runtime = require("react/jsx-runtime");
235
- var UploadContainer = ({
236
- title = "Upload files",
237
- description = "Drag and drop files here, or click the button to browse.",
238
- multiple = true,
259
+ var UploadDropzone = ({
260
+ isDragging,
261
+ onDragOver,
262
+ onDragLeave,
263
+ onDrop
264
+ }) => {
265
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
266
+ "div",
267
+ {
268
+ 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"),
269
+ style: { cursor: "pointer", minHeight: "140px" },
270
+ onDragOver,
271
+ onDragLeave,
272
+ onDrop,
273
+ children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: "bi bi-cloud-arrow-up fs-1 mb-2" }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mb-2", children: "Drop files here" }),
276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "text-muted", children: "or click the button below" })
277
+ ]
278
+ }
279
+ );
280
+ };
281
+
282
+ // src/components/UploadFilePicker.tsx
283
+ var import_react2 = require("react");
284
+ var import_jsx_runtime2 = require("react/jsx-runtime");
285
+ var UploadFilePicker = ({
286
+ multiple,
239
287
  accept,
240
- onFilesSelected,
241
- autoUpload = false,
242
- getPresignedUrl,
243
- onUploadComplete,
244
- onUploadError
288
+ fileNames,
289
+ onFileChange
245
290
  }) => {
246
- const [isDragging, setIsDragging] = (0, import_react.useState)(false);
247
- const [fileNames, setFileNames] = (0, import_react.useState)([]);
248
- const [uploads, setUploads] = (0, import_react.useState)([]);
249
- const startUploadsIfNeeded = (files) => {
250
- if (!autoUpload || !getPresignedUrl) return;
251
- const newUploads = Array.from(files).map((file) => ({
252
- id: `${file.name}-${file.size}-${file.lastModified}-${Math.random()}`,
253
- file,
254
- progress: 0,
255
- status: "pending"
256
- }));
257
- setUploads((prev) => [...prev, ...newUploads]);
258
- newUploads.forEach((upload) => {
259
- uploadFile(upload);
260
- });
261
- };
262
- const uploadFile = async (upload) => {
263
- setUploads(
264
- (prev) => prev.map(
265
- (u) => u.id === upload.id ? { ...u, status: "uploading", progress: 0 } : u
291
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "d-flex gap-2 align-items-center", children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "btn btn-primary mb-0", children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", { className: "bi bi-folder2-open me-2" }),
294
+ "Browse files",
295
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
296
+ "input",
297
+ {
298
+ type: "file",
299
+ className: "d-none",
300
+ multiple,
301
+ accept,
302
+ onChange: onFileChange
303
+ }
266
304
  )
267
- );
268
- try {
269
- if (!getPresignedUrl) {
270
- throw new Error("getPresignedUrl is not provided.");
305
+ ] }),
306
+ fileNames.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-grow-1", children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "small text-muted", children: "Selected:" }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "mb-0 small", children: fileNames.map((name) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { children: name }, name)) })
309
+ ] })
310
+ ] });
311
+ };
312
+
313
+ // src/components/UploadProgressList.tsx
314
+ var import_react3 = require("react");
315
+ var import_jsx_runtime3 = require("react/jsx-runtime");
316
+ var UploadProgressList = ({
317
+ uploads
318
+ }) => {
319
+ if (uploads.length === 0) return null;
320
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-3", children: uploads.map((u) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mb-2", children: [
321
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "d-flex justify-content-between small mb-1", children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
323
+ u.file.name,
324
+ " - ",
325
+ u?.publicUrl ?? ""
326
+ ] }),
327
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: u.status === "success" ? "Completed" : u.status === "error" ? "Error" : `${u.progress}%` })
328
+ ] }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "progress", style: { height: "6px" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
330
+ "div",
331
+ {
332
+ className: "progress-bar " + (u.status === "success" ? "bg-success" : u.status === "error" ? "bg-danger" : ""),
333
+ role: "progressbar",
334
+ style: { width: `${u.progress}%` },
335
+ "aria-valuenow": u.progress,
336
+ "aria-valuemin": 0,
337
+ "aria-valuemax": 100
271
338
  }
272
- const presignedUrl = await getPresignedUrl(upload.file);
273
- const url = presignedUrl?.PresignedUrl ?? "";
274
- await uploadFileToS3(upload.file, url, (progress) => {
275
- setUploads(
276
- (prev) => prev.map(
277
- (u) => u.id === upload.id ? { ...u, progress } : u
339
+ ) }),
340
+ u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-danger small mt-1", children: u.error ?? "Upload failed" })
341
+ ] }, u.id)) });
342
+ };
343
+
344
+ // src/hooks/UseUploadManager.ts
345
+ var import_react4 = require("react");
346
+
347
+ // src/engines/UploadFileToS3.ts
348
+ async function UploadFileToS3(file, presignedUrl, onProgress) {
349
+ return new Promise((resolve, reject) => {
350
+ const xhr = new XMLHttpRequest();
351
+ xhr.open("PUT", presignedUrl);
352
+ xhr.upload.onprogress = (event) => {
353
+ if (!event.lengthComputable) return;
354
+ const percent = Math.round(event.loaded / event.total * 100);
355
+ onProgress(percent);
356
+ };
357
+ xhr.onload = () => {
358
+ if (xhr.status >= 200 && xhr.status < 300) {
359
+ onProgress(100);
360
+ resolve();
361
+ } else {
362
+ reject(
363
+ new Error(
364
+ `S3 upload failed with status ${xhr.status}: ${xhr.statusText}`
278
365
  )
279
366
  );
280
- });
281
- const fileUrl = url.split("?")[0];
282
- setUploads(
283
- (prev) => prev.map(
284
- (u) => u.id === upload.id ? { ...u, status: "success", progress: 100, s3Url: fileUrl, publicUrl: presignedUrl.PublicUrl } : u
285
- )
286
- );
287
- onUploadComplete?.(upload.file, fileUrl);
288
- } catch (err) {
289
- let message = "Upload failed";
290
- if (err instanceof Error) {
291
- message = err.message;
292
367
  }
368
+ };
369
+ xhr.onerror = () => {
370
+ reject(new Error("Network error while uploading to S3"));
371
+ };
372
+ xhr.setRequestHeader(
373
+ "Content-Type",
374
+ file.type || "application/octet-stream"
375
+ );
376
+ xhr.send(file);
377
+ });
378
+ }
379
+
380
+ // src/hooks/UseUploadManager.ts
381
+ function UseUploadManager({
382
+ autoUpload = false,
383
+ getPresignedUrl,
384
+ onUploadComplete,
385
+ onUploadError
386
+ }) {
387
+ const [uploads, setUploads] = (0, import_react4.useState)([]);
388
+ const createUploadStates = (files) => Array.from(files).map((file) => ({
389
+ id: `${file.name}-${file.size}-${file.lastModified}-${Math.random()}`,
390
+ file,
391
+ progress: 0,
392
+ status: "pending"
393
+ }));
394
+ const uploadFile = (0, import_react4.useCallback)(
395
+ async (upload) => {
293
396
  setUploads(
294
397
  (prev) => prev.map(
295
- (u) => u.id === upload.id ? { ...u, status: "error", error: message } : u
398
+ (u) => u.id === upload.id ? { ...u, status: "uploading", progress: 0 } : u
296
399
  )
297
400
  );
298
- onUploadError?.(upload.file, err instanceof Error ? err : new Error(message));
299
- }
401
+ try {
402
+ if (!getPresignedUrl) {
403
+ throw new Error("getPresignedUrl is not provided.");
404
+ }
405
+ const presignedUrl = await getPresignedUrl(
406
+ upload.file
407
+ );
408
+ const url = presignedUrl?.PresignedUrl ?? "";
409
+ await UploadFileToS3(upload.file, url, (progress) => {
410
+ setUploads(
411
+ (prev) => prev.map(
412
+ (u) => u.id === upload.id ? { ...u, progress } : u
413
+ )
414
+ );
415
+ });
416
+ const fileUrl = url.split("?")[0];
417
+ setUploads(
418
+ (prev) => prev.map(
419
+ (u) => u.id === upload.id ? {
420
+ ...u,
421
+ status: "success",
422
+ progress: 100,
423
+ s3Url: fileUrl,
424
+ // assumes your DTO has PublicUrl
425
+ publicUrl: presignedUrl.PublicUrl
426
+ } : u
427
+ )
428
+ );
429
+ onUploadComplete?.(upload.file, fileUrl);
430
+ } catch (err) {
431
+ let message = "Upload failed";
432
+ if (err instanceof Error) {
433
+ message = err.message;
434
+ }
435
+ setUploads(
436
+ (prev) => prev.map(
437
+ (u) => u.id === upload.id ? { ...u, status: "error", error: message } : u
438
+ )
439
+ );
440
+ onUploadError?.(
441
+ upload.file,
442
+ err instanceof Error ? err : new Error(message)
443
+ );
444
+ }
445
+ },
446
+ [getPresignedUrl, onUploadComplete, onUploadError]
447
+ );
448
+ const startUploadsIfNeeded = (0, import_react4.useCallback)(
449
+ (files) => {
450
+ if (!autoUpload || !getPresignedUrl) return;
451
+ const newUploads = createUploadStates(files);
452
+ setUploads((prev) => [...prev, ...newUploads]);
453
+ newUploads.forEach((upload) => {
454
+ void uploadFile(upload);
455
+ });
456
+ },
457
+ [autoUpload, getPresignedUrl, uploadFile]
458
+ );
459
+ const resetUploads = (0, import_react4.useCallback)(() => setUploads([]), []);
460
+ return {
461
+ uploads,
462
+ startUploadsIfNeeded,
463
+ resetUploads
300
464
  };
465
+ }
466
+
467
+ // src/components/UploadContainer.tsx
468
+ var import_jsx_runtime4 = require("react/jsx-runtime");
469
+ var UploadContainer = ({
470
+ title = "Upload files",
471
+ description = "Drag and drop files here, or click the button to browse.",
472
+ multiple = true,
473
+ accept,
474
+ onFilesSelected,
475
+ existingFiles = [],
476
+ onExistingFileClick,
477
+ autoUpload = false,
478
+ getPresignedUrl,
479
+ onUploadComplete,
480
+ onUploadError
481
+ }) => {
482
+ const [isDragging, setIsDragging] = (0, import_react5.useState)(false);
483
+ const [fileNames, setFileNames] = (0, import_react5.useState)([]);
484
+ const { uploads, startUploadsIfNeeded } = UseUploadManager({
485
+ autoUpload,
486
+ getPresignedUrl,
487
+ onUploadComplete,
488
+ onUploadError
489
+ });
301
490
  const handleDragOver = (e) => {
302
491
  e.preventDefault();
303
492
  setIsDragging(true);
@@ -322,150 +511,205 @@ var UploadContainer = ({
322
511
  onFilesSelected?.(files);
323
512
  startUploadsIfNeeded(files);
324
513
  };
325
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "container my-3", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "card shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "card-body", children: [
326
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h5", { className: "card-title mb-2", children: title }),
327
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "card-text text-muted", children: description }),
328
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
329
- "div",
514
+ const handleExistingFileClickInternal = (file) => {
515
+ if (onExistingFileClick) {
516
+ onExistingFileClick(file);
517
+ return;
518
+ }
519
+ const a = document.createElement("a");
520
+ a.download = file.Name ?? "";
521
+ a.target = "_blank";
522
+ a.rel = "noopener noreferrer";
523
+ document.body.appendChild(a);
524
+ a.click();
525
+ document.body.removeChild(a);
526
+ };
527
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
528
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h5", { className: "card-title mb-2", children: title }),
529
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "card-text text-muted", children: description }),
530
+ existingFiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-3", children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h6", { className: "mb-2", children: "Existing files" }),
532
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "list-group", children: existingFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
533
+ "li",
534
+ {
535
+ className: "list-group-item d-flex justify-content-between align-items-center",
536
+ style: { cursor: "pointer" },
537
+ onClick: () => handleExistingFileClickInternal(file),
538
+ children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: file.Name }),
540
+ typeof file.FileSize === "number" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("small", { className: "text-muted", children: [
541
+ (file.FileSize / 1024).toFixed(1),
542
+ " KB"
543
+ ] })
544
+ ]
545
+ },
546
+ file.Id
547
+ )) })
548
+ ] }),
549
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
550
+ UploadDropzone,
330
551
  {
331
- 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"),
332
- style: { cursor: "pointer", minHeight: "140px" },
552
+ isDragging,
333
553
  onDragOver: handleDragOver,
334
554
  onDragLeave: handleDragLeave,
335
- onDrop: handleDrop,
336
- children: [
337
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: "bi bi-cloud-arrow-up fs-1 mb-2" }),
338
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mb-2", children: "Drop files here" }),
339
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "text-muted", children: "or click the button below" })
340
- ]
555
+ onDrop: handleDrop
341
556
  }
342
557
  ),
343
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "d-flex gap-2 align-items-center", children: [
344
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "btn btn-primary mb-0", children: [
345
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: "bi bi-folder2-open me-2" }),
346
- "Browse files",
347
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
348
- "input",
349
- {
350
- type: "file",
351
- className: "d-none",
352
- multiple,
353
- accept,
354
- onChange: handleFileChange
355
- }
356
- )
357
- ] }),
358
- fileNames.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-grow-1", children: [
359
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "small text-muted", children: "Selected:" }),
360
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "mb-0 small", children: fileNames.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: name }, name)) })
361
- ] })
362
- ] }),
363
- uploads.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-3", children: uploads.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-2", children: [
364
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "d-flex justify-content-between small mb-1", children: [
365
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
366
- u.file.name,
367
- " - ",
368
- u?.publicUrl ?? ""
369
- ] }),
370
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: u.status === "success" ? "Completed" : u.status === "error" ? "Error" : `${u.progress}%` })
371
- ] }),
372
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "progress", style: { height: "6px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
373
- "div",
374
- {
375
- className: "progress-bar " + (u.status === "success" ? "bg-success" : u.status === "error" ? "bg-danger" : ""),
376
- role: "progressbar",
377
- style: { width: `${u.progress}%` },
378
- "aria-valuenow": u.progress,
379
- "aria-valuemin": 0,
380
- "aria-valuemax": 100
381
- }
382
- ) }),
383
- u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-danger small mt-1", children: u.error ?? "Upload failed" })
384
- ] }, u.id)) })
385
- ] }) }) });
386
- };
387
- async function uploadFileToS3(file, presignedUrl, onProgress) {
388
- return new Promise((resolve, reject) => {
389
- const xhr = new XMLHttpRequest();
390
- xhr.open("PUT", presignedUrl);
391
- xhr.upload.onprogress = (event) => {
392
- if (!event.lengthComputable) return;
393
- const percent = Math.round(event.loaded / event.total * 100);
394
- onProgress(percent);
395
- };
396
- xhr.onload = () => {
397
- if (xhr.status >= 200 && xhr.status < 300) {
398
- onProgress(100);
399
- resolve();
400
- } else {
401
- reject(
402
- new Error(`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`)
403
- );
558
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
559
+ UploadFilePicker,
560
+ {
561
+ multiple,
562
+ accept,
563
+ fileNames,
564
+ onFileChange: handleFileChange
404
565
  }
405
- };
406
- xhr.onerror = () => {
407
- reject(new Error("Network error while uploading to S3"));
408
- };
409
- xhr.setRequestHeader(
410
- "Content-Type",
411
- file.type || "application/octet-stream"
412
- );
413
- xhr.send(file);
414
- });
566
+ ),
567
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UploadProgressList, { uploads })
568
+ ] });
569
+ };
570
+
571
+ // src/hooks/UseContainers.ts
572
+ var import_react6 = require("react");
573
+ function UseContainers({ apiBaseUrl, parentId }) {
574
+ const [containers, setContainers] = (0, import_react6.useState)([]);
575
+ const [loading, setLoading] = (0, import_react6.useState)(false);
576
+ const [error, setError] = (0, import_react6.useState)(null);
577
+ const load = async () => {
578
+ setLoading(true);
579
+ setError(null);
580
+ try {
581
+ const sdkDb = new SparkStudioStorageSDK(
582
+ "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod"
583
+ );
584
+ const result = await sdkDb.container.ReadRootContainers();
585
+ setContainers(result);
586
+ } catch (err) {
587
+ setError(err);
588
+ } finally {
589
+ setLoading(false);
590
+ }
591
+ };
592
+ (0, import_react6.useEffect)(() => {
593
+ void load();
594
+ }, [apiBaseUrl, parentId]);
595
+ return {
596
+ containers,
597
+ loading,
598
+ error,
599
+ reload: load
600
+ };
415
601
  }
416
602
 
417
- // src/views/HomeView.tsx
418
- var import_authentication_ui = require("@sparkstudio/authentication-ui");
419
- var import_jsx_runtime2 = require("react/jsx-runtime");
420
- function HomeView() {
421
- function handleOnLoginSuccess(user) {
422
- alert(user?.Id);
423
- }
603
+ // src/components/ContainerUploadPanel.tsx
604
+ var import_jsx_runtime5 = require("react/jsx-runtime");
605
+ var ContainerUploadPanel = ({
606
+ containerApiBaseUrl,
607
+ parentContainerId,
608
+ title = "Upload files",
609
+ description = "Drop files to upload. Existing files are listed below."
610
+ }) => {
611
+ const { containers, loading, error, reload } = UseContainers({
612
+ apiBaseUrl: containerApiBaseUrl,
613
+ parentId: parentContainerId
614
+ });
424
615
  const getPresignedUrl = async (file) => {
425
- const sdkDb = new SparkStudioStorageSDK("https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod");
426
- const sdkS3 = new SparkStudioStorageSDK("https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod");
427
- const containerDTO = await sdkDb.container.CreateFileContainer(file.name, file.size, encodeURIComponent(file.type));
428
- const resultS3 = await sdkS3.container.GetPreSignedUrl(containerDTO);
616
+ const sdkDb = new SparkStudioStorageSDK(
617
+ "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod"
618
+ );
619
+ const sdkS3 = new SparkStudioStorageSDK(
620
+ "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
621
+ );
622
+ const containerDTO = await sdkDb.container.CreateFileContainer(
623
+ file.name,
624
+ file.size,
625
+ encodeURIComponent(file.type)
626
+ );
627
+ const resultS3 = await sdkS3.s3.GetPreSignedUrl(containerDTO);
429
628
  return resultS3;
430
629
  };
431
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
630
+ const handleUploadComplete = async (file, s3Url) => {
631
+ console.log("Upload complete:", file.name, s3Url);
632
+ await reload();
633
+ };
634
+ const handleUploadError = (file, err) => {
635
+ console.error("Upload failed:", file.name, err);
636
+ };
637
+ const handleExistingFileClick = (file) => {
638
+ const downloadUrl = `${containerApiBaseUrl}/api/Container/Download/${file.Id}`;
639
+ const a = document.createElement("a");
640
+ a.href = downloadUrl;
641
+ a.download = file.Name ?? "";
642
+ a.target = "_blank";
643
+ a.rel = "noopener noreferrer";
644
+ document.body.appendChild(a);
645
+ a.click();
646
+ document.body.removeChild(a);
647
+ };
648
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
649
+ loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Loading existing files\u2026" }),
650
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-danger", children: [
651
+ "Failed to load containers: ",
652
+ error.message
653
+ ] }),
654
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
655
+ UploadContainer,
656
+ {
657
+ title,
658
+ description,
659
+ existingFiles: containers,
660
+ onExistingFileClick: handleExistingFileClick,
661
+ autoUpload: true,
662
+ getPresignedUrl,
663
+ onUploadComplete: handleUploadComplete,
664
+ onUploadError: handleUploadError
665
+ }
666
+ )
667
+ ] });
668
+ };
669
+
670
+ // src/views/HomeView.tsx
671
+ var import_authentication_ui = require("@sparkstudio/authentication-ui");
672
+ var import_jsx_runtime6 = require("react/jsx-runtime");
673
+ function HomeView() {
674
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
432
675
  import_authentication_ui.AuthenticatorProvider,
433
676
  {
434
677
  googleClientId: import_authentication_ui.AppSettings.GoogleClientId,
435
678
  authenticationUrl: import_authentication_ui.AppSettings.AuthenticationUrl,
436
679
  accountsUrl: import_authentication_ui.AppSettings.AccountsUrl,
437
- onLoginSuccess: handleOnLoginSuccess,
438
- children: [
439
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_authentication_ui.UserInfoCard, {}),
440
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
441
- UploadContainer,
442
- {
443
- title: "Upload your files to S3",
444
- description: "Drag & drop or browse files. Each file will upload with its own progress bar.",
445
- multiple: true,
446
- accept: "*/*",
447
- autoUpload: true,
448
- getPresignedUrl,
449
- onUploadComplete: (file, s3Url) => {
450
- console.log("Uploaded", file.name, "to", s3Url);
451
- },
452
- onUploadError: (file, error) => {
453
- console.error("Failed to upload", file.name, error);
454
- }
455
- }
456
- )
457
- ]
680
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HomeContent, {})
458
681
  }
459
682
  );
460
683
  }
684
+ function HomeContent() {
685
+ const { user } = (0, import_authentication_ui.useUser)();
686
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_authentication_ui.UserInfoCard, {}),
688
+ user && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
689
+ ContainerUploadPanel,
690
+ {
691
+ containerApiBaseUrl: "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod",
692
+ storageApiBaseUrl: "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod"
693
+ }
694
+ )
695
+ ] });
696
+ }
461
697
  // Annotate the CommonJS export names for ESM import in node:
462
698
  0 && (module.exports = {
463
699
  AWSPresignedUrlDTO,
464
700
  Container,
465
701
  ContainerDTO,
466
702
  ContainerType,
703
+ ContainerUploadPanel,
467
704
  Home,
468
705
  HomeView,
706
+ S3,
469
707
  SparkStudioStorageSDK,
470
- UploadContainer
708
+ UploadContainer,
709
+ UploadDropzone,
710
+ UploadFilePicker,
711
+ UploadFileToS3,
712
+ UploadProgressList,
713
+ UseContainers,
714
+ UseUploadManager
471
715
  });