@sparkstudio/storage-ui 1.0.7 → 1.0.8
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 +143 -19
- package/dist/index.d.cts +34 -17
- package/dist/index.d.ts +34 -17
- package/dist/index.js +142 -18
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
AWSPresignedUrlDTO: () => AWSPresignedUrlDTO,
|
|
24
24
|
Container: () => Container,
|
|
25
25
|
ContainerDTO: () => ContainerDTO,
|
|
26
26
|
ContainerType: () => ContainerType,
|
|
@@ -123,8 +123,8 @@ var Container = class {
|
|
|
123
123
|
if (!response.ok) throw new Error(await response.text());
|
|
124
124
|
return await response.json();
|
|
125
125
|
}
|
|
126
|
-
async
|
|
127
|
-
const url = `${this.baseUrl}/api/Container/
|
|
126
|
+
async GetPreSignedUrl(fileName, size, contentType) {
|
|
127
|
+
const url = `${this.baseUrl}/api/Container/GetPreSignedUrl/` + fileName + `/` + size + `/` + contentType;
|
|
128
128
|
const token = localStorage.getItem("auth_token");
|
|
129
129
|
const requestOptions = {
|
|
130
130
|
method: "GET",
|
|
@@ -157,19 +157,15 @@ var SparkStudioStorageSDK = class {
|
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
-
// src/api/DTOs/
|
|
161
|
-
var
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
BucketName;
|
|
166
|
-
Region;
|
|
160
|
+
// src/api/DTOs/AWSPresignedUrlDTO.ts
|
|
161
|
+
var AWSPresignedUrlDTO = class {
|
|
162
|
+
PresignedUrl;
|
|
163
|
+
PublicUrl;
|
|
164
|
+
Key;
|
|
167
165
|
constructor(init) {
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
171
|
-
this.BucketName = init.BucketName;
|
|
172
|
-
this.Region = init.Region;
|
|
166
|
+
this.PresignedUrl = init.PresignedUrl;
|
|
167
|
+
this.PublicUrl = init.PublicUrl;
|
|
168
|
+
this.Key = init.Key;
|
|
173
169
|
}
|
|
174
170
|
};
|
|
175
171
|
|
|
@@ -178,6 +174,7 @@ var ContainerDTO = class {
|
|
|
178
174
|
Id;
|
|
179
175
|
ContainerType;
|
|
180
176
|
Name;
|
|
177
|
+
ContentType;
|
|
181
178
|
CreatedDate;
|
|
182
179
|
FileSize;
|
|
183
180
|
UserId;
|
|
@@ -186,6 +183,7 @@ var ContainerDTO = class {
|
|
|
186
183
|
this.Id = init.Id;
|
|
187
184
|
this.ContainerType = init.ContainerType;
|
|
188
185
|
this.Name = init.Name;
|
|
186
|
+
this.ContentType = init.ContentType;
|
|
189
187
|
this.CreatedDate = init.CreatedDate;
|
|
190
188
|
this.FileSize = init.FileSize;
|
|
191
189
|
this.UserId = init.UserId;
|
|
@@ -209,10 +207,66 @@ var UploadContainer = ({
|
|
|
209
207
|
description = "Drag and drop files here, or click the button to browse.",
|
|
210
208
|
multiple = true,
|
|
211
209
|
accept,
|
|
212
|
-
onFilesSelected
|
|
210
|
+
onFilesSelected,
|
|
211
|
+
autoUpload = false,
|
|
212
|
+
getPresignedUrl,
|
|
213
|
+
onUploadComplete,
|
|
214
|
+
onUploadError
|
|
213
215
|
}) => {
|
|
214
216
|
const [isDragging, setIsDragging] = (0, import_react.useState)(false);
|
|
215
217
|
const [fileNames, setFileNames] = (0, import_react.useState)([]);
|
|
218
|
+
const [uploads, setUploads] = (0, import_react.useState)([]);
|
|
219
|
+
const startUploadsIfNeeded = (files) => {
|
|
220
|
+
if (!autoUpload || !getPresignedUrl) return;
|
|
221
|
+
const newUploads = Array.from(files).map((file) => ({
|
|
222
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Math.random()}`,
|
|
223
|
+
file,
|
|
224
|
+
progress: 0,
|
|
225
|
+
status: "pending"
|
|
226
|
+
}));
|
|
227
|
+
setUploads((prev) => [...prev, ...newUploads]);
|
|
228
|
+
newUploads.forEach((upload) => {
|
|
229
|
+
uploadFile(upload);
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
const uploadFile = async (upload) => {
|
|
233
|
+
setUploads(
|
|
234
|
+
(prev) => prev.map(
|
|
235
|
+
(u) => u.id === upload.id ? { ...u, status: "uploading", progress: 0 } : u
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
try {
|
|
239
|
+
if (!getPresignedUrl) {
|
|
240
|
+
throw new Error("getPresignedUrl is not provided.");
|
|
241
|
+
}
|
|
242
|
+
const presignedUrl = await getPresignedUrl(upload.file);
|
|
243
|
+
await uploadFileToS3(upload.file, presignedUrl, (progress) => {
|
|
244
|
+
setUploads(
|
|
245
|
+
(prev) => prev.map(
|
|
246
|
+
(u) => u.id === upload.id ? { ...u, progress } : u
|
|
247
|
+
)
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
const fileUrl = presignedUrl.split("?")[0];
|
|
251
|
+
setUploads(
|
|
252
|
+
(prev) => prev.map(
|
|
253
|
+
(u) => u.id === upload.id ? { ...u, status: "success", progress: 100, s3Url: fileUrl } : u
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
onUploadComplete?.(upload.file, fileUrl);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
let message = "Upload failed";
|
|
259
|
+
if (err instanceof Error) {
|
|
260
|
+
message = err.message;
|
|
261
|
+
}
|
|
262
|
+
setUploads(
|
|
263
|
+
(prev) => prev.map(
|
|
264
|
+
(u) => u.id === upload.id ? { ...u, status: "error", error: message } : u
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
onUploadError?.(upload.file, err instanceof Error ? err : new Error(message));
|
|
268
|
+
}
|
|
269
|
+
};
|
|
216
270
|
const handleDragOver = (e) => {
|
|
217
271
|
e.preventDefault();
|
|
218
272
|
setIsDragging(true);
|
|
@@ -228,12 +282,14 @@ var UploadContainer = ({
|
|
|
228
282
|
if (!files || files.length === 0) return;
|
|
229
283
|
setFileNames(Array.from(files).map((f) => f.name));
|
|
230
284
|
onFilesSelected?.(files);
|
|
285
|
+
startUploadsIfNeeded(files);
|
|
231
286
|
};
|
|
232
287
|
const handleFileChange = (e) => {
|
|
233
288
|
const files = e.target.files;
|
|
234
289
|
if (!files || files.length === 0) return;
|
|
235
290
|
setFileNames(Array.from(files).map((f) => f.name));
|
|
236
291
|
onFilesSelected?.(files);
|
|
292
|
+
startUploadsIfNeeded(files);
|
|
237
293
|
};
|
|
238
294
|
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: [
|
|
239
295
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h5", { className: "card-title mb-2", children: title }),
|
|
@@ -272,9 +328,56 @@ var UploadContainer = ({
|
|
|
272
328
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "small text-muted", children: "Selected:" }),
|
|
273
329
|
/* @__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)) })
|
|
274
330
|
] })
|
|
275
|
-
] })
|
|
331
|
+
] }),
|
|
332
|
+
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: [
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "d-flex justify-content-between small mb-1", children: [
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: u.file.name }),
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: u.status === "success" ? "Completed" : u.status === "error" ? "Error" : `${u.progress}%` })
|
|
336
|
+
] }),
|
|
337
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "progress", style: { height: "6px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
338
|
+
"div",
|
|
339
|
+
{
|
|
340
|
+
className: "progress-bar " + (u.status === "success" ? "bg-success" : u.status === "error" ? "bg-danger" : ""),
|
|
341
|
+
role: "progressbar",
|
|
342
|
+
style: { width: `${u.progress}%` },
|
|
343
|
+
"aria-valuenow": u.progress,
|
|
344
|
+
"aria-valuemin": 0,
|
|
345
|
+
"aria-valuemax": 100
|
|
346
|
+
}
|
|
347
|
+
) }),
|
|
348
|
+
u.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-danger small mt-1", children: u.error ?? "Upload failed" })
|
|
349
|
+
] }, u.id)) })
|
|
276
350
|
] }) }) });
|
|
277
351
|
};
|
|
352
|
+
async function uploadFileToS3(file, presignedUrl, onProgress) {
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
const xhr = new XMLHttpRequest();
|
|
355
|
+
xhr.open("PUT", presignedUrl);
|
|
356
|
+
xhr.upload.onprogress = (event) => {
|
|
357
|
+
if (!event.lengthComputable) return;
|
|
358
|
+
const percent = Math.round(event.loaded / event.total * 100);
|
|
359
|
+
onProgress(percent);
|
|
360
|
+
};
|
|
361
|
+
xhr.onload = () => {
|
|
362
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
363
|
+
onProgress(100);
|
|
364
|
+
resolve();
|
|
365
|
+
} else {
|
|
366
|
+
reject(
|
|
367
|
+
new Error(`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
xhr.onerror = () => {
|
|
372
|
+
reject(new Error("Network error while uploading to S3"));
|
|
373
|
+
};
|
|
374
|
+
xhr.setRequestHeader(
|
|
375
|
+
"Content-Type",
|
|
376
|
+
file.type || "application/octet-stream"
|
|
377
|
+
);
|
|
378
|
+
xhr.send(file);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
278
381
|
|
|
279
382
|
// src/views/HomeView.tsx
|
|
280
383
|
var import_authentication_ui = require("@sparkstudio/authentication-ui");
|
|
@@ -283,6 +386,11 @@ function HomeView() {
|
|
|
283
386
|
function handleOnLoginSuccess(user) {
|
|
284
387
|
alert(user?.Id);
|
|
285
388
|
}
|
|
389
|
+
const getPresignedUrl = async (file) => {
|
|
390
|
+
const sdk = new SparkStudioStorageSDK("https://localhost:5001");
|
|
391
|
+
const result = await sdk.container.GetPreSignedUrl(file.name, file.size, encodeURIComponent(file.type));
|
|
392
|
+
return result?.PresignedUrl ?? "";
|
|
393
|
+
};
|
|
286
394
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
287
395
|
import_authentication_ui.AuthenticatorProvider,
|
|
288
396
|
{
|
|
@@ -292,14 +400,30 @@ function HomeView() {
|
|
|
292
400
|
onLoginSuccess: handleOnLoginSuccess,
|
|
293
401
|
children: [
|
|
294
402
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_authentication_ui.UserInfoCard, {}),
|
|
295
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
404
|
+
UploadContainer,
|
|
405
|
+
{
|
|
406
|
+
title: "Upload your files to S3",
|
|
407
|
+
description: "Drag & drop or browse files. Each file will upload with its own progress bar.",
|
|
408
|
+
multiple: true,
|
|
409
|
+
accept: "*/*",
|
|
410
|
+
autoUpload: true,
|
|
411
|
+
getPresignedUrl,
|
|
412
|
+
onUploadComplete: (file, s3Url) => {
|
|
413
|
+
console.log("Uploaded", file.name, "to", s3Url);
|
|
414
|
+
},
|
|
415
|
+
onUploadError: (file, error) => {
|
|
416
|
+
console.error("Failed to upload", file.name, error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
)
|
|
296
420
|
]
|
|
297
421
|
}
|
|
298
422
|
);
|
|
299
423
|
}
|
|
300
424
|
// Annotate the CommonJS export names for ESM import in node:
|
|
301
425
|
0 && (module.exports = {
|
|
302
|
-
|
|
426
|
+
AWSPresignedUrlDTO,
|
|
303
427
|
Container,
|
|
304
428
|
ContainerDTO,
|
|
305
429
|
ContainerType,
|
package/dist/index.d.cts
CHANGED
|
@@ -14,6 +14,7 @@ interface IContainerDTO {
|
|
|
14
14
|
Id: string;
|
|
15
15
|
ContainerType: ContainerType;
|
|
16
16
|
Name?: string;
|
|
17
|
+
ContentType?: string;
|
|
17
18
|
CreatedDate: string;
|
|
18
19
|
FileSize: number;
|
|
19
20
|
UserId: string;
|
|
@@ -24,6 +25,7 @@ declare class ContainerDTO implements IContainerDTO {
|
|
|
24
25
|
Id: string;
|
|
25
26
|
ContainerType: ContainerType;
|
|
26
27
|
Name?: string;
|
|
28
|
+
ContentType?: string;
|
|
27
29
|
CreatedDate: string;
|
|
28
30
|
FileSize: number;
|
|
29
31
|
UserId: string;
|
|
@@ -32,23 +34,19 @@ declare class ContainerDTO implements IContainerDTO {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* Represents an Auto-generated model for
|
|
37
|
+
* Represents an Auto-generated model for AWSPresignedUrlDTO.
|
|
36
38
|
*/
|
|
37
|
-
interface
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
BucketName?: string;
|
|
42
|
-
Region?: string;
|
|
39
|
+
interface IAWSPresignedUrlDTO {
|
|
40
|
+
PresignedUrl?: string;
|
|
41
|
+
PublicUrl?: string;
|
|
42
|
+
Key?: string;
|
|
43
43
|
}
|
|
44
|
-
type
|
|
45
|
-
declare class
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Region?: string;
|
|
51
|
-
constructor(init: AWSCredentialsDTOInit);
|
|
44
|
+
type AWSPresignedUrlDTOInit = Partial<IAWSPresignedUrlDTO>;
|
|
45
|
+
declare class AWSPresignedUrlDTO implements IAWSPresignedUrlDTO {
|
|
46
|
+
PresignedUrl?: string;
|
|
47
|
+
PublicUrl?: string;
|
|
48
|
+
Key?: string;
|
|
49
|
+
constructor(init: AWSPresignedUrlDTOInit);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
/**
|
|
@@ -63,7 +61,7 @@ declare class Container {
|
|
|
63
61
|
Create(containerDTO: ContainerDTO): Promise<ContainerDTO>;
|
|
64
62
|
Update(containerDTO: ContainerDTO): Promise<ContainerDTO>;
|
|
65
63
|
Delete(id: string): Promise<ContainerDTO>;
|
|
66
|
-
|
|
64
|
+
GetPreSignedUrl(fileName: string, size: number, contentType: string): Promise<AWSPresignedUrlDTO>;
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
/**
|
|
@@ -89,9 +87,28 @@ interface UploadContainerProps {
|
|
|
89
87
|
multiple?: boolean;
|
|
90
88
|
accept?: string;
|
|
91
89
|
onFilesSelected?: (files: FileList) => void;
|
|
90
|
+
/**
|
|
91
|
+
* When true, the component will immediately start uploading to S3
|
|
92
|
+
* using getPresignedUrl for each file.
|
|
93
|
+
*/
|
|
94
|
+
autoUpload?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Your function that returns a pre-signed URL for a given file.
|
|
97
|
+
* Typically calls your backend: /api/uploads/presign
|
|
98
|
+
*/
|
|
99
|
+
getPresignedUrl?: (file: File) => Promise<string>;
|
|
100
|
+
/**
|
|
101
|
+
* Called when a file has successfully finished uploading.
|
|
102
|
+
* s3Url is usually the final S3 object URL or key (depends on your backend).
|
|
103
|
+
*/
|
|
104
|
+
onUploadComplete?: (file: File, s3Url: string) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Called when a file upload fails.
|
|
107
|
+
*/
|
|
108
|
+
onUploadError?: (file: File, error: Error) => void;
|
|
92
109
|
}
|
|
93
110
|
declare const UploadContainer: React.FC<UploadContainerProps>;
|
|
94
111
|
|
|
95
112
|
declare function HomeView(): react_jsx_runtime.JSX.Element;
|
|
96
113
|
|
|
97
|
-
export {
|
|
114
|
+
export { AWSPresignedUrlDTO, Container, ContainerDTO, ContainerType, Home, HomeView, type IAWSPresignedUrlDTO, type IContainerDTO, SparkStudioStorageSDK, UploadContainer, type UploadContainerProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ interface IContainerDTO {
|
|
|
14
14
|
Id: string;
|
|
15
15
|
ContainerType: ContainerType;
|
|
16
16
|
Name?: string;
|
|
17
|
+
ContentType?: string;
|
|
17
18
|
CreatedDate: string;
|
|
18
19
|
FileSize: number;
|
|
19
20
|
UserId: string;
|
|
@@ -24,6 +25,7 @@ declare class ContainerDTO implements IContainerDTO {
|
|
|
24
25
|
Id: string;
|
|
25
26
|
ContainerType: ContainerType;
|
|
26
27
|
Name?: string;
|
|
28
|
+
ContentType?: string;
|
|
27
29
|
CreatedDate: string;
|
|
28
30
|
FileSize: number;
|
|
29
31
|
UserId: string;
|
|
@@ -32,23 +34,19 @@ declare class ContainerDTO implements IContainerDTO {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* Represents an Auto-generated model for
|
|
37
|
+
* Represents an Auto-generated model for AWSPresignedUrlDTO.
|
|
36
38
|
*/
|
|
37
|
-
interface
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
BucketName?: string;
|
|
42
|
-
Region?: string;
|
|
39
|
+
interface IAWSPresignedUrlDTO {
|
|
40
|
+
PresignedUrl?: string;
|
|
41
|
+
PublicUrl?: string;
|
|
42
|
+
Key?: string;
|
|
43
43
|
}
|
|
44
|
-
type
|
|
45
|
-
declare class
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Region?: string;
|
|
51
|
-
constructor(init: AWSCredentialsDTOInit);
|
|
44
|
+
type AWSPresignedUrlDTOInit = Partial<IAWSPresignedUrlDTO>;
|
|
45
|
+
declare class AWSPresignedUrlDTO implements IAWSPresignedUrlDTO {
|
|
46
|
+
PresignedUrl?: string;
|
|
47
|
+
PublicUrl?: string;
|
|
48
|
+
Key?: string;
|
|
49
|
+
constructor(init: AWSPresignedUrlDTOInit);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
/**
|
|
@@ -63,7 +61,7 @@ declare class Container {
|
|
|
63
61
|
Create(containerDTO: ContainerDTO): Promise<ContainerDTO>;
|
|
64
62
|
Update(containerDTO: ContainerDTO): Promise<ContainerDTO>;
|
|
65
63
|
Delete(id: string): Promise<ContainerDTO>;
|
|
66
|
-
|
|
64
|
+
GetPreSignedUrl(fileName: string, size: number, contentType: string): Promise<AWSPresignedUrlDTO>;
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
/**
|
|
@@ -89,9 +87,28 @@ interface UploadContainerProps {
|
|
|
89
87
|
multiple?: boolean;
|
|
90
88
|
accept?: string;
|
|
91
89
|
onFilesSelected?: (files: FileList) => void;
|
|
90
|
+
/**
|
|
91
|
+
* When true, the component will immediately start uploading to S3
|
|
92
|
+
* using getPresignedUrl for each file.
|
|
93
|
+
*/
|
|
94
|
+
autoUpload?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Your function that returns a pre-signed URL for a given file.
|
|
97
|
+
* Typically calls your backend: /api/uploads/presign
|
|
98
|
+
*/
|
|
99
|
+
getPresignedUrl?: (file: File) => Promise<string>;
|
|
100
|
+
/**
|
|
101
|
+
* Called when a file has successfully finished uploading.
|
|
102
|
+
* s3Url is usually the final S3 object URL or key (depends on your backend).
|
|
103
|
+
*/
|
|
104
|
+
onUploadComplete?: (file: File, s3Url: string) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Called when a file upload fails.
|
|
107
|
+
*/
|
|
108
|
+
onUploadError?: (file: File, error: Error) => void;
|
|
92
109
|
}
|
|
93
110
|
declare const UploadContainer: React.FC<UploadContainerProps>;
|
|
94
111
|
|
|
95
112
|
declare function HomeView(): react_jsx_runtime.JSX.Element;
|
|
96
113
|
|
|
97
|
-
export {
|
|
114
|
+
export { AWSPresignedUrlDTO, Container, ContainerDTO, ContainerType, Home, HomeView, type IAWSPresignedUrlDTO, type IContainerDTO, SparkStudioStorageSDK, UploadContainer, type UploadContainerProps };
|
package/dist/index.js
CHANGED
|
@@ -90,8 +90,8 @@ var Container = class {
|
|
|
90
90
|
if (!response.ok) throw new Error(await response.text());
|
|
91
91
|
return await response.json();
|
|
92
92
|
}
|
|
93
|
-
async
|
|
94
|
-
const url = `${this.baseUrl}/api/Container/
|
|
93
|
+
async GetPreSignedUrl(fileName, size, contentType) {
|
|
94
|
+
const url = `${this.baseUrl}/api/Container/GetPreSignedUrl/` + fileName + `/` + size + `/` + contentType;
|
|
95
95
|
const token = localStorage.getItem("auth_token");
|
|
96
96
|
const requestOptions = {
|
|
97
97
|
method: "GET",
|
|
@@ -124,19 +124,15 @@ var SparkStudioStorageSDK = class {
|
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
-
// src/api/DTOs/
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
BucketName;
|
|
133
|
-
Region;
|
|
127
|
+
// src/api/DTOs/AWSPresignedUrlDTO.ts
|
|
128
|
+
var AWSPresignedUrlDTO = class {
|
|
129
|
+
PresignedUrl;
|
|
130
|
+
PublicUrl;
|
|
131
|
+
Key;
|
|
134
132
|
constructor(init) {
|
|
135
|
-
this.
|
|
136
|
-
this.
|
|
137
|
-
this.
|
|
138
|
-
this.BucketName = init.BucketName;
|
|
139
|
-
this.Region = init.Region;
|
|
133
|
+
this.PresignedUrl = init.PresignedUrl;
|
|
134
|
+
this.PublicUrl = init.PublicUrl;
|
|
135
|
+
this.Key = init.Key;
|
|
140
136
|
}
|
|
141
137
|
};
|
|
142
138
|
|
|
@@ -145,6 +141,7 @@ var ContainerDTO = class {
|
|
|
145
141
|
Id;
|
|
146
142
|
ContainerType;
|
|
147
143
|
Name;
|
|
144
|
+
ContentType;
|
|
148
145
|
CreatedDate;
|
|
149
146
|
FileSize;
|
|
150
147
|
UserId;
|
|
@@ -153,6 +150,7 @@ var ContainerDTO = class {
|
|
|
153
150
|
this.Id = init.Id;
|
|
154
151
|
this.ContainerType = init.ContainerType;
|
|
155
152
|
this.Name = init.Name;
|
|
153
|
+
this.ContentType = init.ContentType;
|
|
156
154
|
this.CreatedDate = init.CreatedDate;
|
|
157
155
|
this.FileSize = init.FileSize;
|
|
158
156
|
this.UserId = init.UserId;
|
|
@@ -176,10 +174,66 @@ var UploadContainer = ({
|
|
|
176
174
|
description = "Drag and drop files here, or click the button to browse.",
|
|
177
175
|
multiple = true,
|
|
178
176
|
accept,
|
|
179
|
-
onFilesSelected
|
|
177
|
+
onFilesSelected,
|
|
178
|
+
autoUpload = false,
|
|
179
|
+
getPresignedUrl,
|
|
180
|
+
onUploadComplete,
|
|
181
|
+
onUploadError
|
|
180
182
|
}) => {
|
|
181
183
|
const [isDragging, setIsDragging] = useState(false);
|
|
182
184
|
const [fileNames, setFileNames] = useState([]);
|
|
185
|
+
const [uploads, setUploads] = useState([]);
|
|
186
|
+
const startUploadsIfNeeded = (files) => {
|
|
187
|
+
if (!autoUpload || !getPresignedUrl) return;
|
|
188
|
+
const newUploads = Array.from(files).map((file) => ({
|
|
189
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Math.random()}`,
|
|
190
|
+
file,
|
|
191
|
+
progress: 0,
|
|
192
|
+
status: "pending"
|
|
193
|
+
}));
|
|
194
|
+
setUploads((prev) => [...prev, ...newUploads]);
|
|
195
|
+
newUploads.forEach((upload) => {
|
|
196
|
+
uploadFile(upload);
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const uploadFile = async (upload) => {
|
|
200
|
+
setUploads(
|
|
201
|
+
(prev) => prev.map(
|
|
202
|
+
(u) => u.id === upload.id ? { ...u, status: "uploading", progress: 0 } : u
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
try {
|
|
206
|
+
if (!getPresignedUrl) {
|
|
207
|
+
throw new Error("getPresignedUrl is not provided.");
|
|
208
|
+
}
|
|
209
|
+
const presignedUrl = await getPresignedUrl(upload.file);
|
|
210
|
+
await uploadFileToS3(upload.file, presignedUrl, (progress) => {
|
|
211
|
+
setUploads(
|
|
212
|
+
(prev) => prev.map(
|
|
213
|
+
(u) => u.id === upload.id ? { ...u, progress } : u
|
|
214
|
+
)
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
const fileUrl = presignedUrl.split("?")[0];
|
|
218
|
+
setUploads(
|
|
219
|
+
(prev) => prev.map(
|
|
220
|
+
(u) => u.id === upload.id ? { ...u, status: "success", progress: 100, s3Url: fileUrl } : u
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
onUploadComplete?.(upload.file, fileUrl);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
let message = "Upload failed";
|
|
226
|
+
if (err instanceof Error) {
|
|
227
|
+
message = err.message;
|
|
228
|
+
}
|
|
229
|
+
setUploads(
|
|
230
|
+
(prev) => prev.map(
|
|
231
|
+
(u) => u.id === upload.id ? { ...u, status: "error", error: message } : u
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
onUploadError?.(upload.file, err instanceof Error ? err : new Error(message));
|
|
235
|
+
}
|
|
236
|
+
};
|
|
183
237
|
const handleDragOver = (e) => {
|
|
184
238
|
e.preventDefault();
|
|
185
239
|
setIsDragging(true);
|
|
@@ -195,12 +249,14 @@ var UploadContainer = ({
|
|
|
195
249
|
if (!files || files.length === 0) return;
|
|
196
250
|
setFileNames(Array.from(files).map((f) => f.name));
|
|
197
251
|
onFilesSelected?.(files);
|
|
252
|
+
startUploadsIfNeeded(files);
|
|
198
253
|
};
|
|
199
254
|
const handleFileChange = (e) => {
|
|
200
255
|
const files = e.target.files;
|
|
201
256
|
if (!files || files.length === 0) return;
|
|
202
257
|
setFileNames(Array.from(files).map((f) => f.name));
|
|
203
258
|
onFilesSelected?.(files);
|
|
259
|
+
startUploadsIfNeeded(files);
|
|
204
260
|
};
|
|
205
261
|
return /* @__PURE__ */ jsx("div", { className: "container my-3", children: /* @__PURE__ */ jsx("div", { className: "card shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "card-body", children: [
|
|
206
262
|
/* @__PURE__ */ jsx("h5", { className: "card-title mb-2", children: title }),
|
|
@@ -239,9 +295,56 @@ var UploadContainer = ({
|
|
|
239
295
|
/* @__PURE__ */ jsx("div", { className: "small text-muted", children: "Selected:" }),
|
|
240
296
|
/* @__PURE__ */ jsx("ul", { className: "mb-0 small", children: fileNames.map((name) => /* @__PURE__ */ jsx("li", { children: name }, name)) })
|
|
241
297
|
] })
|
|
242
|
-
] })
|
|
298
|
+
] }),
|
|
299
|
+
uploads.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3", children: uploads.map((u) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
|
|
300
|
+
/* @__PURE__ */ jsxs("div", { className: "d-flex justify-content-between small mb-1", children: [
|
|
301
|
+
/* @__PURE__ */ jsx("span", { children: u.file.name }),
|
|
302
|
+
/* @__PURE__ */ jsx("span", { children: u.status === "success" ? "Completed" : u.status === "error" ? "Error" : `${u.progress}%` })
|
|
303
|
+
] }),
|
|
304
|
+
/* @__PURE__ */ jsx("div", { className: "progress", style: { height: "6px" }, children: /* @__PURE__ */ jsx(
|
|
305
|
+
"div",
|
|
306
|
+
{
|
|
307
|
+
className: "progress-bar " + (u.status === "success" ? "bg-success" : u.status === "error" ? "bg-danger" : ""),
|
|
308
|
+
role: "progressbar",
|
|
309
|
+
style: { width: `${u.progress}%` },
|
|
310
|
+
"aria-valuenow": u.progress,
|
|
311
|
+
"aria-valuemin": 0,
|
|
312
|
+
"aria-valuemax": 100
|
|
313
|
+
}
|
|
314
|
+
) }),
|
|
315
|
+
u.status === "error" && /* @__PURE__ */ jsx("div", { className: "text-danger small mt-1", children: u.error ?? "Upload failed" })
|
|
316
|
+
] }, u.id)) })
|
|
243
317
|
] }) }) });
|
|
244
318
|
};
|
|
319
|
+
async function uploadFileToS3(file, presignedUrl, onProgress) {
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
const xhr = new XMLHttpRequest();
|
|
322
|
+
xhr.open("PUT", presignedUrl);
|
|
323
|
+
xhr.upload.onprogress = (event) => {
|
|
324
|
+
if (!event.lengthComputable) return;
|
|
325
|
+
const percent = Math.round(event.loaded / event.total * 100);
|
|
326
|
+
onProgress(percent);
|
|
327
|
+
};
|
|
328
|
+
xhr.onload = () => {
|
|
329
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
330
|
+
onProgress(100);
|
|
331
|
+
resolve();
|
|
332
|
+
} else {
|
|
333
|
+
reject(
|
|
334
|
+
new Error(`S3 upload failed with status ${xhr.status}: ${xhr.statusText}`)
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
xhr.onerror = () => {
|
|
339
|
+
reject(new Error("Network error while uploading to S3"));
|
|
340
|
+
};
|
|
341
|
+
xhr.setRequestHeader(
|
|
342
|
+
"Content-Type",
|
|
343
|
+
file.type || "application/octet-stream"
|
|
344
|
+
);
|
|
345
|
+
xhr.send(file);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
245
348
|
|
|
246
349
|
// src/views/HomeView.tsx
|
|
247
350
|
import { AppSettings, AuthenticatorProvider, UserInfoCard } from "@sparkstudio/authentication-ui";
|
|
@@ -250,6 +353,11 @@ function HomeView() {
|
|
|
250
353
|
function handleOnLoginSuccess(user) {
|
|
251
354
|
alert(user?.Id);
|
|
252
355
|
}
|
|
356
|
+
const getPresignedUrl = async (file) => {
|
|
357
|
+
const sdk = new SparkStudioStorageSDK("https://localhost:5001");
|
|
358
|
+
const result = await sdk.container.GetPreSignedUrl(file.name, file.size, encodeURIComponent(file.type));
|
|
359
|
+
return result?.PresignedUrl ?? "";
|
|
360
|
+
};
|
|
253
361
|
return /* @__PURE__ */ jsxs2(
|
|
254
362
|
AuthenticatorProvider,
|
|
255
363
|
{
|
|
@@ -259,13 +367,29 @@ function HomeView() {
|
|
|
259
367
|
onLoginSuccess: handleOnLoginSuccess,
|
|
260
368
|
children: [
|
|
261
369
|
/* @__PURE__ */ jsx2(UserInfoCard, {}),
|
|
262
|
-
/* @__PURE__ */ jsx2(
|
|
370
|
+
/* @__PURE__ */ jsx2(
|
|
371
|
+
UploadContainer,
|
|
372
|
+
{
|
|
373
|
+
title: "Upload your files to S3",
|
|
374
|
+
description: "Drag & drop or browse files. Each file will upload with its own progress bar.",
|
|
375
|
+
multiple: true,
|
|
376
|
+
accept: "*/*",
|
|
377
|
+
autoUpload: true,
|
|
378
|
+
getPresignedUrl,
|
|
379
|
+
onUploadComplete: (file, s3Url) => {
|
|
380
|
+
console.log("Uploaded", file.name, "to", s3Url);
|
|
381
|
+
},
|
|
382
|
+
onUploadError: (file, error) => {
|
|
383
|
+
console.error("Failed to upload", file.name, error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
)
|
|
263
387
|
]
|
|
264
388
|
}
|
|
265
389
|
);
|
|
266
390
|
}
|
|
267
391
|
export {
|
|
268
|
-
|
|
392
|
+
AWSPresignedUrlDTO,
|
|
269
393
|
Container,
|
|
270
394
|
ContainerDTO,
|
|
271
395
|
ContainerType,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sparkstudio/storage-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -28,13 +28,15 @@
|
|
|
28
28
|
"build": "npm run generateIndex && tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.lib.json && sass --load-path=node_modules src/index.scss dist/index.css",
|
|
29
29
|
"lint": "eslint .",
|
|
30
30
|
"preview": "vite preview",
|
|
31
|
-
"start": "vite"
|
|
31
|
+
"start": "npm run generateIndex && vite"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": "18.3.1",
|
|
35
35
|
"react-dom": "18.3.1"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@aws-sdk/client-s3": "^3.958.0",
|
|
39
|
+
"@aws-sdk/s3-request-presigner": "^3.958.0",
|
|
38
40
|
"@sparkstudio/authentication-ui": "^1.0.29",
|
|
39
41
|
"@sparkstudio/common-ui": "^1.0.5",
|
|
40
42
|
"barrelsby": "^2.8.1"
|