@thetechfossil/upfiles 1.0.12 → 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/README.md +9 -9
- package/dist/index.d.mts +127 -11
- package/dist/index.d.ts +127 -11
- package/dist/index.js +752 -266
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +750 -268
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import * as React4 from 'react';
|
|
2
|
-
import React4__default, { useRef, useMemo, useState,
|
|
2
|
+
import React4__default, { useRef, useMemo, useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import * as Dialog2 from '@radix-ui/react-dialog';
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
|
+
var StorageQuotaError = class extends Error {
|
|
8
|
+
constructor(message, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "StorageQuotaError";
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
7
14
|
var UpfilesClient = class {
|
|
8
15
|
constructor(opts) {
|
|
9
16
|
this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
|
|
@@ -17,25 +24,66 @@ var UpfilesClient = class {
|
|
|
17
24
|
this.completionPath = opts.completionPath ?? "/api/plugin/files";
|
|
18
25
|
this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
|
|
19
26
|
this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
|
|
27
|
+
this.getToken = opts.getToken;
|
|
28
|
+
this.projectId = opts.projectId;
|
|
29
|
+
this.projectName = opts.projectName;
|
|
20
30
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
/** Build auth headers for API requests. Priority: getToken (JWT) > apiKey > static headers */
|
|
32
|
+
async buildAuthHeaders() {
|
|
33
|
+
const headers = { ...this.headers || {} };
|
|
34
|
+
if (this.getToken) {
|
|
35
|
+
const token = await this.getToken();
|
|
36
|
+
if (token) {
|
|
37
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
38
|
+
}
|
|
39
|
+
if (this.projectName) {
|
|
40
|
+
headers["x-project-name"] = this.projectName;
|
|
41
|
+
} else if (this.projectId) {
|
|
42
|
+
headers["x-project-id"] = this.projectId;
|
|
43
|
+
}
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
30
46
|
if (this.apiKey) {
|
|
31
47
|
if (this.apiKeyHeader === "authorization") {
|
|
32
|
-
headers["authorization"] = this.apiKey.startsWith("
|
|
48
|
+
headers["authorization"] = this.apiKey.startsWith("upf_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
33
49
|
} else if (this.apiKeyHeader === "x-api-key") {
|
|
34
50
|
headers["x-api-key"] = this.apiKey;
|
|
35
51
|
} else {
|
|
36
52
|
headers["x-up-api-key"] = this.apiKey;
|
|
37
53
|
}
|
|
38
54
|
}
|
|
55
|
+
return headers;
|
|
56
|
+
}
|
|
57
|
+
/** Get storage quota info for the project */
|
|
58
|
+
async getQuota() {
|
|
59
|
+
const basePath = this.presignPath.replace(/\/upload$/, "");
|
|
60
|
+
const target = this.baseUrl ? `${this.baseUrl}${basePath}/quota` : `${basePath}/quota`;
|
|
61
|
+
const headers = await this.buildAuthHeaders();
|
|
62
|
+
const res = await fetch(target, {
|
|
63
|
+
method: "GET",
|
|
64
|
+
headers,
|
|
65
|
+
credentials: this.withCredentials ? "include" : "same-origin"
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
let msg = "Failed to get storage quota";
|
|
69
|
+
try {
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
msg = data.message || data.error || msg;
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
throw new Error(msg);
|
|
75
|
+
}
|
|
76
|
+
return res.json();
|
|
77
|
+
}
|
|
78
|
+
async getPresignedUrl(params) {
|
|
79
|
+
if (!params.fileName) throw new Error("fileName is required");
|
|
80
|
+
if (!params.fileType) throw new Error("fileType is required");
|
|
81
|
+
if (!params.fileSize || params.fileSize <= 0) throw new Error("fileSize must be > 0");
|
|
82
|
+
const target = this.presignUrl ? this.presignUrl : this.baseUrl ? `${this.baseUrl}${this.presignPath}` : this.presignPath;
|
|
83
|
+
const headers = {
|
|
84
|
+
"content-type": "application/json",
|
|
85
|
+
...await this.buildAuthHeaders()
|
|
86
|
+
};
|
|
39
87
|
const res = await fetch(target, {
|
|
40
88
|
method: "POST",
|
|
41
89
|
headers,
|
|
@@ -46,8 +94,12 @@ var UpfilesClient = class {
|
|
|
46
94
|
let msg = "Failed to get upload URL";
|
|
47
95
|
try {
|
|
48
96
|
const data = await res.json();
|
|
49
|
-
|
|
50
|
-
|
|
97
|
+
if (res.status === 413 && data.error === "STORAGE_QUOTA_EXCEEDED") {
|
|
98
|
+
throw new StorageQuotaError(data.message || "Storage quota exceeded", data.details);
|
|
99
|
+
}
|
|
100
|
+
msg = data.message || data.error || msg;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
if (e instanceof StorageQuotaError) throw e;
|
|
51
103
|
}
|
|
52
104
|
throw new Error(msg);
|
|
53
105
|
}
|
|
@@ -83,7 +135,11 @@ var UpfilesClient = class {
|
|
|
83
135
|
originalName: file.name,
|
|
84
136
|
size: file.size,
|
|
85
137
|
contentType: file.type || "application/octet-stream",
|
|
86
|
-
projectId: extras?.projectId
|
|
138
|
+
projectId: extras?.projectId,
|
|
139
|
+
url: presign.publicUrl,
|
|
140
|
+
userId: extras?.userId
|
|
141
|
+
// userId might be passed in extras but not typed yet in this method signature, easy fix is to cast or update signature. Better to update signature but for now access via cast to keep change minimal if extras type isn't fully updated here locally. Wait, the plan said "Update upload method to pass...". I should probably check if I can update extras type locally or just cast. The `upload` method signature has `extras`. I should probably update `extras` type in the signature if I can.
|
|
142
|
+
// Actually, looking at the code, `extras` is defined inline: `extras?: { projectId?: string; folderPath?: string; fetchThumbnails?: boolean; callCompletionEndpoint?: boolean }`. I should update this type definition too.
|
|
87
143
|
});
|
|
88
144
|
} catch (error) {
|
|
89
145
|
throw new Error(`Upload verification failed: ${error.message}`);
|
|
@@ -112,22 +168,16 @@ var UpfilesClient = class {
|
|
|
112
168
|
const target = this.baseUrl ? `${this.baseUrl}${this.completionPath}` : this.completionPath;
|
|
113
169
|
const headers = {
|
|
114
170
|
"content-type": "application/json",
|
|
115
|
-
...this.
|
|
171
|
+
...await this.buildAuthHeaders()
|
|
116
172
|
};
|
|
117
|
-
if (this.apiKey) {
|
|
118
|
-
if (this.apiKeyHeader === "authorization") {
|
|
119
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
120
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
121
|
-
headers["x-api-key"] = this.apiKey;
|
|
122
|
-
} else {
|
|
123
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
173
|
const res = await fetch(target, {
|
|
127
174
|
method: "POST",
|
|
128
175
|
headers,
|
|
129
176
|
credentials: this.withCredentials ? "include" : "same-origin",
|
|
130
|
-
body: JSON.stringify(
|
|
177
|
+
body: JSON.stringify({
|
|
178
|
+
...params,
|
|
179
|
+
key: params.fileKey
|
|
180
|
+
})
|
|
131
181
|
});
|
|
132
182
|
if (!res.ok) {
|
|
133
183
|
let msg = "Failed to complete upload";
|
|
@@ -143,16 +193,7 @@ var UpfilesClient = class {
|
|
|
143
193
|
async getThumbnails(fileKey) {
|
|
144
194
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
145
195
|
const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
|
|
146
|
-
const headers =
|
|
147
|
-
if (this.apiKey) {
|
|
148
|
-
if (this.apiKeyHeader === "authorization") {
|
|
149
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
150
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
151
|
-
headers["x-api-key"] = this.apiKey;
|
|
152
|
-
} else {
|
|
153
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
196
|
+
const headers = await this.buildAuthHeaders();
|
|
156
197
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
157
198
|
if (!res.ok) {
|
|
158
199
|
let msg = "Failed to fetch thumbnails";
|
|
@@ -166,26 +207,19 @@ var UpfilesClient = class {
|
|
|
166
207
|
const data = await res.json();
|
|
167
208
|
return data?.thumbnails ?? [];
|
|
168
209
|
}
|
|
169
|
-
async generateThumbnails(fileKey) {
|
|
210
|
+
async generateThumbnails(fileKey, sizes) {
|
|
170
211
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
171
212
|
const headers = {
|
|
172
213
|
"content-type": "application/json",
|
|
173
|
-
...this.
|
|
214
|
+
...await this.buildAuthHeaders()
|
|
174
215
|
};
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
178
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
179
|
-
headers["x-api-key"] = this.apiKey;
|
|
180
|
-
} else {
|
|
181
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
216
|
+
const body = { fileKey };
|
|
217
|
+
if (sizes && sizes.length > 0) body.sizes = sizes;
|
|
184
218
|
const res = await fetch(target, {
|
|
185
219
|
method: "POST",
|
|
186
220
|
headers,
|
|
187
221
|
credentials: this.withCredentials ? "include" : "same-origin",
|
|
188
|
-
body: JSON.stringify(
|
|
222
|
+
body: JSON.stringify(body)
|
|
189
223
|
});
|
|
190
224
|
if (!res.ok) {
|
|
191
225
|
let msg = "Failed to generate thumbnails";
|
|
@@ -199,19 +233,53 @@ var UpfilesClient = class {
|
|
|
199
233
|
const data = await res.json();
|
|
200
234
|
return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
|
|
201
235
|
}
|
|
236
|
+
/** Get thumbnail settings for the project (requires API key) */
|
|
237
|
+
async getThumbnailSettings() {
|
|
238
|
+
const basePath = this.presignPath.replace(/\/upload$/, "");
|
|
239
|
+
const target = this.baseUrl ? `${this.baseUrl}${basePath}/settings/thumbnails` : `${basePath}/settings/thumbnails`;
|
|
240
|
+
const headers = await this.buildAuthHeaders();
|
|
241
|
+
const res = await fetch(target, {
|
|
242
|
+
method: "GET",
|
|
243
|
+
headers,
|
|
244
|
+
credentials: this.withCredentials ? "include" : "same-origin"
|
|
245
|
+
});
|
|
246
|
+
if (!res.ok) {
|
|
247
|
+
let msg = "Failed to get thumbnail settings";
|
|
248
|
+
try {
|
|
249
|
+
const d = await res.json();
|
|
250
|
+
msg = d.message || msg;
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
throw new Error(msg);
|
|
254
|
+
}
|
|
255
|
+
return res.json();
|
|
256
|
+
}
|
|
257
|
+
/** Update thumbnail settings for the project (requires API key with write scope) */
|
|
258
|
+
async updateThumbnailSettings(settings) {
|
|
259
|
+
const basePath = this.presignPath.replace(/\/upload$/, "");
|
|
260
|
+
const target = this.baseUrl ? `${this.baseUrl}${basePath}/settings/thumbnails` : `${basePath}/settings/thumbnails`;
|
|
261
|
+
const headers = { "content-type": "application/json", ...await this.buildAuthHeaders() };
|
|
262
|
+
const res = await fetch(target, {
|
|
263
|
+
method: "PATCH",
|
|
264
|
+
headers,
|
|
265
|
+
credentials: this.withCredentials ? "include" : "same-origin",
|
|
266
|
+
body: JSON.stringify(settings)
|
|
267
|
+
});
|
|
268
|
+
if (!res.ok) {
|
|
269
|
+
let msg = "Failed to update thumbnail settings";
|
|
270
|
+
try {
|
|
271
|
+
const d = await res.json();
|
|
272
|
+
msg = d.message || msg;
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
throw new Error(msg);
|
|
276
|
+
}
|
|
277
|
+
return res.json();
|
|
278
|
+
}
|
|
202
279
|
async getFolders(parentId) {
|
|
203
280
|
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
204
281
|
const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
|
|
205
|
-
const headers =
|
|
206
|
-
if (this.apiKey) {
|
|
207
|
-
if (this.apiKeyHeader === "authorization") {
|
|
208
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
209
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
210
|
-
headers["x-api-key"] = this.apiKey;
|
|
211
|
-
} else {
|
|
212
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
282
|
+
const headers = await this.buildAuthHeaders();
|
|
215
283
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
216
284
|
if (!res.ok) {
|
|
217
285
|
let msg = "Failed to fetch folders";
|
|
@@ -229,17 +297,8 @@ var UpfilesClient = class {
|
|
|
229
297
|
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
230
298
|
const headers = {
|
|
231
299
|
"content-type": "application/json",
|
|
232
|
-
...this.
|
|
300
|
+
...await this.buildAuthHeaders()
|
|
233
301
|
};
|
|
234
|
-
if (this.apiKey) {
|
|
235
|
-
if (this.apiKeyHeader === "authorization") {
|
|
236
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
237
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
238
|
-
headers["x-api-key"] = this.apiKey;
|
|
239
|
-
} else {
|
|
240
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
302
|
const res = await fetch(target, {
|
|
244
303
|
method: "POST",
|
|
245
304
|
headers,
|
|
@@ -263,16 +322,7 @@ var UpfilesClient = class {
|
|
|
263
322
|
const filesPath = `${basePath}/files`;
|
|
264
323
|
const target = this.baseUrl ? `${this.baseUrl}${filesPath}` : filesPath;
|
|
265
324
|
const url = params?.folderPath ? `${target}?folderPath=${encodeURIComponent(params.folderPath)}` : target;
|
|
266
|
-
const headers =
|
|
267
|
-
if (this.apiKey) {
|
|
268
|
-
if (this.apiKeyHeader === "authorization") {
|
|
269
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
270
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
271
|
-
headers["x-api-key"] = this.apiKey;
|
|
272
|
-
} else {
|
|
273
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
325
|
+
const headers = await this.buildAuthHeaders();
|
|
276
326
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
277
327
|
if (!res.ok) {
|
|
278
328
|
let msg = "Failed to fetch files";
|
|
@@ -290,24 +340,21 @@ var UpfilesClient = class {
|
|
|
290
340
|
};
|
|
291
341
|
}
|
|
292
342
|
getEventsUrl(projectId) {
|
|
293
|
-
this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
294
343
|
const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
|
|
295
344
|
const url = new URL(target, window.location.href);
|
|
296
|
-
if (this.apiKey) {
|
|
297
|
-
url.searchParams.set("
|
|
298
|
-
}
|
|
299
|
-
if (projectId) {
|
|
300
|
-
url.searchParams.set("projectId", projectId);
|
|
345
|
+
if (!this.getToken && !this.apiKey && (projectId || this.projectId)) {
|
|
346
|
+
url.searchParams.set("projectId", projectId || this.projectId);
|
|
301
347
|
}
|
|
302
348
|
return url.toString();
|
|
303
349
|
}
|
|
304
350
|
};
|
|
351
|
+
var THUMBNAIL_SIZE_OPTIONS = [64, 128, 256, 512];
|
|
305
352
|
var Uploader = ({
|
|
306
353
|
clientOptions,
|
|
307
354
|
multiple = true,
|
|
308
355
|
accept = ["image/*", "video/*", "audio/*", "application/pdf", "text/plain", "application/json"],
|
|
309
|
-
maxFileSize =
|
|
310
|
-
maxFiles =
|
|
356
|
+
maxFileSize = 0,
|
|
357
|
+
maxFiles = 0,
|
|
311
358
|
onComplete,
|
|
312
359
|
onError,
|
|
313
360
|
onUploadBegin,
|
|
@@ -318,36 +365,124 @@ var Uploader = ({
|
|
|
318
365
|
dropzoneClassName,
|
|
319
366
|
children,
|
|
320
367
|
projectId,
|
|
368
|
+
userId,
|
|
321
369
|
folderPath,
|
|
322
370
|
fetchThumbnails,
|
|
323
371
|
autoRecordToDb = true,
|
|
324
372
|
recordUrl = "/api/files",
|
|
325
|
-
autoUpload = false
|
|
373
|
+
autoUpload = false,
|
|
374
|
+
thumbnailEnabled,
|
|
375
|
+
thumbnailSizes
|
|
326
376
|
}) => {
|
|
327
377
|
const inputRef = useRef(null);
|
|
328
378
|
const client = useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
329
379
|
const [files, setFiles] = useState([]);
|
|
330
380
|
const [isUploading, setIsUploading] = useState(false);
|
|
331
381
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
382
|
+
const [quotaError, setQuotaError] = useState(null);
|
|
383
|
+
const showThumbnailUI = thumbnailEnabled !== void 0;
|
|
384
|
+
const [thumbEnabled, setThumbEnabled] = useState(thumbnailEnabled ?? true);
|
|
385
|
+
const [thumbSizes, setThumbSizes] = useState(thumbnailSizes ?? [256, 512]);
|
|
386
|
+
const [thumbFileIds, setThumbFileIds] = useState(/* @__PURE__ */ new Set());
|
|
387
|
+
const thumbEnabledRef = useRef(thumbnailEnabled ?? true);
|
|
388
|
+
const thumbSizesRef = useRef(thumbnailSizes ?? [256, 512]);
|
|
389
|
+
const thumbFileIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
thumbEnabledRef.current = thumbEnabled;
|
|
392
|
+
}, [thumbEnabled]);
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
thumbSizesRef.current = thumbSizes;
|
|
395
|
+
}, [thumbSizes]);
|
|
396
|
+
useEffect(() => {
|
|
397
|
+
thumbFileIdsRef.current = thumbFileIds;
|
|
398
|
+
}, [thumbFileIds]);
|
|
399
|
+
const pendingImages = useMemo(
|
|
400
|
+
() => files.filter((f2) => f2.type.startsWith("image/") && f2.status === "pending"),
|
|
401
|
+
[files]
|
|
402
|
+
);
|
|
403
|
+
const thumbSelectedCount = useMemo(
|
|
404
|
+
() => pendingImages.filter((f2) => thumbFileIds.has(f2.id)).length,
|
|
405
|
+
[pendingImages, thumbFileIds]
|
|
406
|
+
);
|
|
407
|
+
const handleThumbEnabledToggle = useCallback(() => {
|
|
408
|
+
const newVal = !thumbEnabledRef.current;
|
|
409
|
+
thumbEnabledRef.current = newVal;
|
|
410
|
+
setThumbEnabled(newVal);
|
|
411
|
+
if (newVal) {
|
|
412
|
+
setFiles((prev) => {
|
|
413
|
+
const ids = prev.filter((f2) => f2.type.startsWith("image/") && f2.status === "pending").map((f2) => f2.id);
|
|
414
|
+
if (ids.length > 0) {
|
|
415
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
416
|
+
ids.forEach((id) => next.add(id));
|
|
417
|
+
thumbFileIdsRef.current = next;
|
|
418
|
+
setThumbFileIds(next);
|
|
419
|
+
}
|
|
420
|
+
return prev;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}, []);
|
|
424
|
+
const toggleThumbSize = useCallback((size) => {
|
|
425
|
+
setThumbSizes((prev) => {
|
|
426
|
+
const next = prev.includes(size) ? prev.filter((s) => s !== size) : [...prev, size].sort((a, b) => a - b);
|
|
427
|
+
thumbSizesRef.current = next;
|
|
428
|
+
return next;
|
|
338
429
|
});
|
|
339
|
-
|
|
430
|
+
}, []);
|
|
431
|
+
const toggleFileThumb = useCallback((fileId) => {
|
|
432
|
+
setThumbFileIds((prev) => {
|
|
433
|
+
const next = new Set(prev);
|
|
434
|
+
if (next.has(fileId)) next.delete(fileId);
|
|
435
|
+
else next.add(fileId);
|
|
436
|
+
thumbFileIdsRef.current = next;
|
|
437
|
+
return next;
|
|
438
|
+
});
|
|
439
|
+
}, []);
|
|
440
|
+
const selectAllThumbs = useCallback(() => {
|
|
441
|
+
const ids = pendingImages.map((f2) => f2.id);
|
|
442
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
443
|
+
ids.forEach((id) => next.add(id));
|
|
444
|
+
thumbFileIdsRef.current = next;
|
|
445
|
+
setThumbFileIds(next);
|
|
446
|
+
}, [pendingImages]);
|
|
447
|
+
const deselectAllThumbs = useCallback(() => {
|
|
448
|
+
const pendingSet = new Set(pendingImages.map((f2) => f2.id));
|
|
449
|
+
const next = new Set([...thumbFileIdsRef.current].filter((id) => !pendingSet.has(id)));
|
|
450
|
+
thumbFileIdsRef.current = next;
|
|
451
|
+
setThumbFileIds(next);
|
|
452
|
+
}, [pendingImages]);
|
|
453
|
+
const addFiles = useCallback((incoming) => {
|
|
454
|
+
setQuotaError(null);
|
|
455
|
+
const filtered = maxFileSize > 0 ? incoming.filter((f2) => f2.size <= maxFileSize) : incoming;
|
|
456
|
+
const limited = maxFiles > 0 ? filtered.slice(0, Math.max(0, maxFiles - files.length)) : filtered;
|
|
340
457
|
const mapped = limited.map((f2) => ({
|
|
341
458
|
id: Math.random().toString(36).slice(2),
|
|
342
459
|
file: f2,
|
|
343
460
|
name: f2.name,
|
|
344
461
|
size: f2.size,
|
|
345
|
-
type: f2.type,
|
|
462
|
+
type: f2.type || "application/octet-stream",
|
|
346
463
|
progress: 0,
|
|
347
464
|
status: "pending"
|
|
348
465
|
}));
|
|
349
466
|
setFiles((prev) => [...prev, ...mapped]);
|
|
350
|
-
|
|
467
|
+
if (showThumbnailUI && thumbEnabledRef.current) {
|
|
468
|
+
const imageIds = mapped.filter((f2) => f2.type.startsWith("image/")).map((f2) => f2.id);
|
|
469
|
+
if (imageIds.length > 0) {
|
|
470
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
471
|
+
imageIds.forEach((id) => next.add(id));
|
|
472
|
+
thumbFileIdsRef.current = next;
|
|
473
|
+
setThumbFileIds(next);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}, [files.length, maxFileSize, maxFiles, showThumbnailUI]);
|
|
477
|
+
const removeFile = useCallback((fileId) => {
|
|
478
|
+
setFiles((prev) => prev.filter((f2) => f2.id !== fileId));
|
|
479
|
+
if (showThumbnailUI) {
|
|
480
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
481
|
+
next.delete(fileId);
|
|
482
|
+
thumbFileIdsRef.current = next;
|
|
483
|
+
setThumbFileIds(next);
|
|
484
|
+
}
|
|
485
|
+
}, [showThumbnailUI]);
|
|
351
486
|
const onInputChange = useCallback((e) => {
|
|
352
487
|
const fs = Array.from(e.target.files || []);
|
|
353
488
|
addFiles(fs);
|
|
@@ -368,7 +503,7 @@ var Uploader = ({
|
|
|
368
503
|
update({ status: "uploading", progress: 1 });
|
|
369
504
|
const presign = await client.getPresignedUrl({
|
|
370
505
|
fileName: item.name,
|
|
371
|
-
fileType: item.type,
|
|
506
|
+
fileType: item.type || "application/octet-stream",
|
|
372
507
|
fileSize: item.size,
|
|
373
508
|
projectId,
|
|
374
509
|
folderPath
|
|
@@ -395,14 +530,17 @@ var Uploader = ({
|
|
|
395
530
|
xhr.send(item.file);
|
|
396
531
|
});
|
|
397
532
|
let completionResult = null;
|
|
398
|
-
|
|
533
|
+
const canRecordToDb = autoRecordToDb && (projectId || clientOptions?.projectName || clientOptions?.projectId || clientOptions?.apiKey);
|
|
534
|
+
if (canRecordToDb) {
|
|
399
535
|
try {
|
|
400
536
|
completionResult = await client.completeUpload({
|
|
401
537
|
fileKey: presign.fileKey,
|
|
402
538
|
originalName: item.name,
|
|
403
539
|
size: item.size,
|
|
404
|
-
contentType: item.type,
|
|
405
|
-
projectId
|
|
540
|
+
contentType: item.type || "application/octet-stream",
|
|
541
|
+
projectId,
|
|
542
|
+
url: presign.publicUrl,
|
|
543
|
+
userId
|
|
406
544
|
});
|
|
407
545
|
} catch (e) {
|
|
408
546
|
console.error("Failed to complete upload:", e);
|
|
@@ -426,39 +564,86 @@ var Uploader = ({
|
|
|
426
564
|
}
|
|
427
565
|
const finalFileKey = completionResult?.file?.key || presign.fileKey;
|
|
428
566
|
const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
|
|
567
|
+
const fileThumbEnabled = showThumbnailUI && thumbEnabledRef.current && thumbFileIdsRef.current.has(item.id);
|
|
429
568
|
const result = {
|
|
430
569
|
...item,
|
|
431
570
|
status: "success",
|
|
432
571
|
progress: 100,
|
|
433
|
-
fileKey:
|
|
434
|
-
publicUrl:
|
|
572
|
+
fileKey: finalFileKey,
|
|
573
|
+
publicUrl: finalPublicUrl,
|
|
435
574
|
projectId: presign.projectId,
|
|
436
575
|
apiKeyId: presign.apiKeyId,
|
|
576
|
+
thumbnailEnabled: showThumbnailUI ? fileThumbEnabled : void 0,
|
|
577
|
+
thumbnailSizesSelected: showThumbnailUI ? fileThumbEnabled ? [...thumbSizesRef.current] : [] : void 0,
|
|
437
578
|
// @ts-ignore - allow downstream to access thumbs if present
|
|
438
579
|
thumbnails: thumbs,
|
|
439
580
|
// @ts-ignore - allow downstream to access masterWebp if present
|
|
440
581
|
masterWebp
|
|
441
582
|
};
|
|
442
|
-
update({ status: "success", progress: 100, fileKey:
|
|
583
|
+
update({ status: "success", progress: 100, fileKey: finalFileKey, publicUrl: finalPublicUrl });
|
|
443
584
|
onClientUploadComplete?.(result);
|
|
444
585
|
return result;
|
|
445
586
|
} catch (err) {
|
|
446
|
-
const message = err instanceof Error ? err.message : "Upload failed";
|
|
587
|
+
const message = err instanceof StorageQuotaError ? err.message : err instanceof Error ? err.message : "Upload failed";
|
|
447
588
|
update({ status: "error", error: message });
|
|
448
589
|
throw err;
|
|
449
590
|
}
|
|
450
|
-
}, [client, projectId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete]);
|
|
591
|
+
}, [client, projectId, userId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete, showThumbnailUI]);
|
|
451
592
|
const uploadAll = useCallback(async () => {
|
|
452
593
|
const targets = files.filter((f2) => f2.status === "pending");
|
|
453
594
|
if (!targets.length) return;
|
|
595
|
+
setQuotaError(null);
|
|
454
596
|
setIsUploading(true);
|
|
455
597
|
try {
|
|
598
|
+
try {
|
|
599
|
+
const quota = await client.getQuota();
|
|
600
|
+
if (quota.limitBytes > 0) {
|
|
601
|
+
const totalPendingSize = targets.reduce((sum, f2) => sum + f2.size, 0);
|
|
602
|
+
const remaining = quota.limitBytes - quota.usedBytes;
|
|
603
|
+
if (totalPendingSize > remaining) {
|
|
604
|
+
const fmt = (n) => {
|
|
605
|
+
if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
606
|
+
if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(2)} MB`;
|
|
607
|
+
if (n >= 1024) return `${(n / 1024).toFixed(2)} KB`;
|
|
608
|
+
return `${n} B`;
|
|
609
|
+
};
|
|
610
|
+
const errMsg = `Storage quota exceeded. Remaining: ${fmt(remaining)}, Upload size: ${fmt(totalPendingSize)}`;
|
|
611
|
+
setQuotaError(errMsg);
|
|
612
|
+
targets.forEach((t) => {
|
|
613
|
+
setFiles((prev) => prev.map((f2) => f2.id === t.id ? { ...f2, status: "error", error: errMsg } : f2));
|
|
614
|
+
});
|
|
615
|
+
onError?.(new StorageQuotaError(errMsg, {
|
|
616
|
+
limit: quota.limitBytes,
|
|
617
|
+
used: quota.usedBytes,
|
|
618
|
+
remaining,
|
|
619
|
+
fileSize: totalPendingSize
|
|
620
|
+
}));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
} catch (e) {
|
|
625
|
+
if (e instanceof StorageQuotaError) {
|
|
626
|
+
setQuotaError(e.message);
|
|
627
|
+
targets.forEach((t) => {
|
|
628
|
+
setFiles((prev) => prev.map((f2) => f2.id === t.id ? { ...f2, status: "error", error: e.message } : f2));
|
|
629
|
+
});
|
|
630
|
+
onError?.(e);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
456
634
|
const results = [];
|
|
457
635
|
for (const f2 of targets) {
|
|
458
636
|
try {
|
|
459
637
|
const done = await uploadOne(f2);
|
|
460
638
|
results.push(done);
|
|
461
639
|
} catch (e) {
|
|
640
|
+
if (e instanceof StorageQuotaError) {
|
|
641
|
+
setQuotaError(e.message);
|
|
642
|
+
setFiles((prev) => prev.map(
|
|
643
|
+
(file) => file.status === "pending" ? { ...file, status: "error", error: e.message } : file
|
|
644
|
+
));
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
462
647
|
}
|
|
463
648
|
}
|
|
464
649
|
onComplete?.(results.filter((f2) => f2.status === "success"));
|
|
@@ -467,7 +652,7 @@ var Uploader = ({
|
|
|
467
652
|
} finally {
|
|
468
653
|
setIsUploading(false);
|
|
469
654
|
}
|
|
470
|
-
}, [files, onComplete, onError, uploadOne]);
|
|
655
|
+
}, [files, client, onComplete, onError, uploadOne]);
|
|
471
656
|
React4__default.useEffect(() => {
|
|
472
657
|
if (autoUpload && files.some((f2) => f2.status === "pending") && !isUploading) {
|
|
473
658
|
const timer = setTimeout(() => {
|
|
@@ -515,15 +700,32 @@ var Uploader = ({
|
|
|
515
700
|
children ?? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
516
701
|
/* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
|
|
517
702
|
/* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
518
|
-
multiple ? `Up to ${maxFiles} files` : "
|
|
519
|
-
|
|
520
|
-
(maxFileSize / (1024 * 1024)).toFixed(0),
|
|
521
|
-
"MB"
|
|
703
|
+
!multiple ? "Single file" : maxFiles > 0 ? `Up to ${maxFiles} files` : "Multiple files",
|
|
704
|
+
maxFileSize > 0 ? ` \u2022 Max ${(maxFileSize / (1024 * 1024)).toFixed(0)}MB per file` : ""
|
|
522
705
|
] })
|
|
523
706
|
] })
|
|
524
707
|
]
|
|
525
708
|
}
|
|
526
709
|
),
|
|
710
|
+
quotaError && /* @__PURE__ */ jsx("div", { className: "mt-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
711
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-8 h-8 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-red-600 dark:text-red-400", children: [
|
|
712
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
713
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
714
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
715
|
+
] }) }),
|
|
716
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-300", children: quotaError }) }),
|
|
717
|
+
/* @__PURE__ */ jsx(
|
|
718
|
+
"button",
|
|
719
|
+
{
|
|
720
|
+
onClick: () => setQuotaError(null),
|
|
721
|
+
className: "text-red-400 hover:text-red-600 dark:hover:text-red-300 transition-colors",
|
|
722
|
+
children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
723
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
724
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
725
|
+
] })
|
|
726
|
+
}
|
|
727
|
+
)
|
|
728
|
+
] }) }),
|
|
527
729
|
files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-4", children: [
|
|
528
730
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
|
|
529
731
|
/* @__PURE__ */ jsx("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
|
|
@@ -548,12 +750,76 @@ var Uploader = ({
|
|
|
548
750
|
{
|
|
549
751
|
className: "px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm font-semibold rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 transition-colors",
|
|
550
752
|
disabled: isUploading,
|
|
551
|
-
onClick: () =>
|
|
753
|
+
onClick: () => {
|
|
754
|
+
setFiles([]);
|
|
755
|
+
if (showThumbnailUI) {
|
|
756
|
+
const empty = /* @__PURE__ */ new Set();
|
|
757
|
+
thumbFileIdsRef.current = empty;
|
|
758
|
+
setThumbFileIds(empty);
|
|
759
|
+
}
|
|
760
|
+
},
|
|
552
761
|
children: "Clear"
|
|
553
762
|
}
|
|
554
763
|
)
|
|
555
764
|
] })
|
|
556
765
|
] }),
|
|
766
|
+
showThumbnailUI && pendingImages.length > 0 && /* @__PURE__ */ jsxs("div", { className: "bg-purple-50 dark:bg-purple-900/10 border border-purple-200 dark:border-purple-800/50 rounded-xl p-4", children: [
|
|
767
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
768
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
769
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
770
|
+
/* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-purple-500", children: [
|
|
771
|
+
/* @__PURE__ */ jsx("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
|
|
772
|
+
/* @__PURE__ */ jsx("polyline", { points: "2 17 12 22 22 17" }),
|
|
773
|
+
/* @__PURE__ */ jsx("polyline", { points: "2 12 12 17 22 12" })
|
|
774
|
+
] }),
|
|
775
|
+
"Generate Thumbnails"
|
|
776
|
+
] }),
|
|
777
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: thumbEnabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
778
|
+
thumbSelectedCount,
|
|
779
|
+
" of ",
|
|
780
|
+
pendingImages.length,
|
|
781
|
+
" image",
|
|
782
|
+
pendingImages.length !== 1 ? "s" : "",
|
|
783
|
+
" selected",
|
|
784
|
+
" \xB7 ",
|
|
785
|
+
/* @__PURE__ */ jsx("button", { onClick: selectAllThumbs, className: "text-purple-600 hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-300", children: "Select all" }),
|
|
786
|
+
" \xB7 ",
|
|
787
|
+
/* @__PURE__ */ jsx("button", { onClick: deselectAllThumbs, className: "text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300", children: "Deselect all" })
|
|
788
|
+
] }) : "Disabled for all images" })
|
|
789
|
+
] }),
|
|
790
|
+
/* @__PURE__ */ jsx(
|
|
791
|
+
"button",
|
|
792
|
+
{
|
|
793
|
+
type: "button",
|
|
794
|
+
onClick: handleThumbEnabledToggle,
|
|
795
|
+
className: `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${thumbEnabled ? "bg-blue-600" : "bg-gray-200 dark:bg-gray-700"}`,
|
|
796
|
+
role: "switch",
|
|
797
|
+
"aria-checked": thumbEnabled,
|
|
798
|
+
children: /* @__PURE__ */ jsx("span", { className: `pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition ${thumbEnabled ? "translate-x-5" : "translate-x-0"}` })
|
|
799
|
+
}
|
|
800
|
+
)
|
|
801
|
+
] }),
|
|
802
|
+
thumbEnabled && /* @__PURE__ */ jsxs("div", { children: [
|
|
803
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-400 mb-2", children: "Sizes:" }),
|
|
804
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-2 flex-wrap", children: THUMBNAIL_SIZE_OPTIONS.map((size) => {
|
|
805
|
+
const active = thumbSizes.includes(size);
|
|
806
|
+
return /* @__PURE__ */ jsxs(
|
|
807
|
+
"button",
|
|
808
|
+
{
|
|
809
|
+
type: "button",
|
|
810
|
+
onClick: () => toggleThumbSize(size),
|
|
811
|
+
className: `px-3 py-1 rounded-full text-xs font-medium border transition-colors ${active ? "bg-purple-600 border-purple-600 text-white" : "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-purple-400 hover:text-purple-600"}`,
|
|
812
|
+
children: [
|
|
813
|
+
size,
|
|
814
|
+
"px"
|
|
815
|
+
]
|
|
816
|
+
},
|
|
817
|
+
size
|
|
818
|
+
);
|
|
819
|
+
}) }),
|
|
820
|
+
thumbSizes.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-600 dark:text-amber-400 mt-2", children: "Select at least one size." })
|
|
821
|
+
] })
|
|
822
|
+
] }),
|
|
557
823
|
/* @__PURE__ */ jsx("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ jsx("div", { className: "group relative bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-800 rounded-xl p-4 transition-all hover:shadow-md", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
|
|
558
824
|
/* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-lg bg-gray-50 dark:bg-gray-800 flex items-center justify-center flex-shrink-0", children: f2.type.startsWith("image/") ? /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-indigo-500", children: [
|
|
559
825
|
/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
@@ -577,7 +843,19 @@ var Uploader = ({
|
|
|
577
843
|
className: `h-full transition-all duration-300 ${f2.status === "success" ? "bg-green-500" : "bg-blue-500"}`,
|
|
578
844
|
style: { width: `${f2.progress}%` }
|
|
579
845
|
}
|
|
580
|
-
) })
|
|
846
|
+
) }),
|
|
847
|
+
showThumbnailUI && thumbEnabled && f2.type.startsWith("image/") && f2.status === "pending" && /* @__PURE__ */ jsxs("label", { className: "mt-1.5 inline-flex items-center gap-1.5 cursor-pointer select-none", children: [
|
|
848
|
+
/* @__PURE__ */ jsx(
|
|
849
|
+
"input",
|
|
850
|
+
{
|
|
851
|
+
type: "checkbox",
|
|
852
|
+
checked: thumbFileIds.has(f2.id),
|
|
853
|
+
onChange: () => toggleFileThumb(f2.id),
|
|
854
|
+
className: "w-3.5 h-3.5 rounded accent-purple-600 cursor-pointer"
|
|
855
|
+
}
|
|
856
|
+
),
|
|
857
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Generate thumbnail" })
|
|
858
|
+
] })
|
|
581
859
|
] }),
|
|
582
860
|
/* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 ml-4", children: [
|
|
583
861
|
f2.status === "success" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -611,7 +889,7 @@ var Uploader = ({
|
|
|
611
889
|
{
|
|
612
890
|
onClick: (e) => {
|
|
613
891
|
e.stopPropagation();
|
|
614
|
-
|
|
892
|
+
removeFile(f2.id);
|
|
615
893
|
},
|
|
616
894
|
className: "p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-colors",
|
|
617
895
|
title: "Remove",
|
|
@@ -695,10 +973,18 @@ var ProjectFilesWidget = ({
|
|
|
695
973
|
"content-type": "application/json",
|
|
696
974
|
...clientOptions?.headers || {}
|
|
697
975
|
};
|
|
698
|
-
|
|
976
|
+
const token = clientOptions?.getToken?.();
|
|
977
|
+
if (token) {
|
|
978
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
979
|
+
if (clientOptions.projectName) {
|
|
980
|
+
headers["x-project-name"] = clientOptions.projectName;
|
|
981
|
+
} else if (clientOptions.projectId) {
|
|
982
|
+
headers["x-project-id"] = clientOptions.projectId;
|
|
983
|
+
}
|
|
984
|
+
} else if (clientOptions?.apiKey) {
|
|
699
985
|
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
700
986
|
if (keyHeader === "authorization") {
|
|
701
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("
|
|
987
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
702
988
|
} else {
|
|
703
989
|
headers[keyHeader] = clientOptions.apiKey;
|
|
704
990
|
}
|
|
@@ -819,6 +1105,19 @@ var ProjectFilesWidget = ({
|
|
|
819
1105
|
|
|
820
1106
|
// src/projectConnect.ts
|
|
821
1107
|
var f = (globalThis.fetch || fetch).bind(globalThis);
|
|
1108
|
+
function buildInit(opts, extra) {
|
|
1109
|
+
const headers = {
|
|
1110
|
+
...extra?.headers || {}
|
|
1111
|
+
};
|
|
1112
|
+
if (opts.token) {
|
|
1113
|
+
headers["authorization"] = `Bearer ${opts.token}`;
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
...extra,
|
|
1117
|
+
headers,
|
|
1118
|
+
credentials: opts.token ? "same-origin" : "include"
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
822
1121
|
async function httpJson(input, init, useFetch) {
|
|
823
1122
|
const doFetch = useFetch ?? f;
|
|
824
1123
|
const res = await doFetch(input, init);
|
|
@@ -826,7 +1125,7 @@ async function httpJson(input, init, useFetch) {
|
|
|
826
1125
|
let msg = `Request failed (${res.status})`;
|
|
827
1126
|
try {
|
|
828
1127
|
const j = await res.json();
|
|
829
|
-
msg = j?.error || msg;
|
|
1128
|
+
msg = j?.error || j?.message || msg;
|
|
830
1129
|
} catch {
|
|
831
1130
|
}
|
|
832
1131
|
throw new Error(msg);
|
|
@@ -837,7 +1136,7 @@ async function listProjects(opts) {
|
|
|
837
1136
|
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
|
|
838
1137
|
const data = await httpJson(
|
|
839
1138
|
url,
|
|
840
|
-
{ method: "GET"
|
|
1139
|
+
buildInit(opts, { method: "GET" }),
|
|
841
1140
|
opts.fetch
|
|
842
1141
|
);
|
|
843
1142
|
return data?.projects ?? [];
|
|
@@ -846,53 +1145,74 @@ async function createProject(opts) {
|
|
|
846
1145
|
const base = opts.baseUrl.replace(/\/$/, "");
|
|
847
1146
|
const created = await httpJson(
|
|
848
1147
|
`${base}/api/projects`,
|
|
849
|
-
{
|
|
1148
|
+
buildInit(opts, {
|
|
850
1149
|
method: "POST",
|
|
851
|
-
credentials: "include",
|
|
852
1150
|
headers: { "content-type": "application/json" },
|
|
853
|
-
body: JSON.stringify({
|
|
854
|
-
|
|
1151
|
+
body: JSON.stringify({
|
|
1152
|
+
name: opts.name,
|
|
1153
|
+
isInHouse: opts.isInHouse,
|
|
1154
|
+
freeStorageLimitBytes: opts.freeStorageLimitBytes
|
|
1155
|
+
})
|
|
1156
|
+
}),
|
|
855
1157
|
opts.fetch
|
|
856
1158
|
);
|
|
857
1159
|
const projectId = created?.project?.id;
|
|
858
1160
|
if (!projectId) throw new Error("Failed to create project");
|
|
1161
|
+
return { projectId, project: created?.project };
|
|
1162
|
+
}
|
|
1163
|
+
async function createProjectWithApiKey(opts) {
|
|
1164
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
1165
|
+
const { projectId } = await createProject(opts);
|
|
859
1166
|
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
860
1167
|
const key = await httpJson(
|
|
861
1168
|
`${base}/api/projects/${projectId}/keys`,
|
|
862
|
-
{
|
|
1169
|
+
buildInit(opts, {
|
|
863
1170
|
method: "POST",
|
|
864
|
-
credentials: "include",
|
|
865
1171
|
headers: { "content-type": "application/json" },
|
|
866
1172
|
body: JSON.stringify({ name: keyLabel })
|
|
867
|
-
},
|
|
1173
|
+
}),
|
|
868
1174
|
opts.fetch
|
|
869
1175
|
);
|
|
870
|
-
const plaintext = key?.key?.plaintext;
|
|
1176
|
+
const plaintext = key?.key?.secret ?? key?.key?.plaintext;
|
|
871
1177
|
if (!plaintext) throw new Error("Failed to create API key");
|
|
872
1178
|
return { apiKey: plaintext, projectId };
|
|
873
1179
|
}
|
|
1180
|
+
async function updateProject(opts) {
|
|
1181
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
1182
|
+
const body = {};
|
|
1183
|
+
if (opts.isInHouse !== void 0) body.isInHouse = opts.isInHouse;
|
|
1184
|
+
if (opts.freeStorageLimitBytes !== void 0) body.freeStorageLimitBytes = opts.freeStorageLimitBytes;
|
|
1185
|
+
await httpJson(
|
|
1186
|
+
`${base}/api/projects/${opts.projectId}`,
|
|
1187
|
+
buildInit(opts, {
|
|
1188
|
+
method: "PUT",
|
|
1189
|
+
headers: { "content-type": "application/json" },
|
|
1190
|
+
body: JSON.stringify(body)
|
|
1191
|
+
}),
|
|
1192
|
+
opts.fetch
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
874
1195
|
async function connectProject(opts) {
|
|
875
1196
|
const base = opts.baseUrl.replace(/\/$/, "");
|
|
876
1197
|
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
877
1198
|
const key = await httpJson(
|
|
878
1199
|
`${base}/api/projects/${opts.projectId}/keys`,
|
|
879
|
-
{
|
|
1200
|
+
buildInit(opts, {
|
|
880
1201
|
method: "POST",
|
|
881
|
-
credentials: "include",
|
|
882
1202
|
headers: { "content-type": "application/json" },
|
|
883
1203
|
body: JSON.stringify({ name: keyLabel })
|
|
884
|
-
},
|
|
1204
|
+
}),
|
|
885
1205
|
opts.fetch
|
|
886
1206
|
);
|
|
887
|
-
const plaintext = key?.key?.plaintext;
|
|
1207
|
+
const plaintext = key?.key?.secret ?? key?.key?.plaintext;
|
|
888
1208
|
if (!plaintext) throw new Error("Failed to create API key");
|
|
889
1209
|
return { apiKey: plaintext };
|
|
890
1210
|
}
|
|
891
1211
|
function addApiKeyManually(apiKey) {
|
|
892
1212
|
const k = (apiKey || "").trim();
|
|
893
1213
|
if (!k) throw new Error("API key is required");
|
|
894
|
-
if (!/^
|
|
895
|
-
console.warn("Provided API key does not match expected format
|
|
1214
|
+
if (!/^upf_[A-Za-z0-9_-]+/.test(k)) {
|
|
1215
|
+
console.warn("Provided API key does not match expected format upf_*");
|
|
896
1216
|
}
|
|
897
1217
|
return { apiKey: k };
|
|
898
1218
|
}
|
|
@@ -976,7 +1296,7 @@ function ConnectProjectDialog(props) {
|
|
|
976
1296
|
setLoading(true);
|
|
977
1297
|
setError(null);
|
|
978
1298
|
if (!newProjectName.trim()) throw new Error("Project name is required");
|
|
979
|
-
const { apiKey, projectId } = await
|
|
1299
|
+
const { apiKey, projectId } = await createProjectWithApiKey({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
|
|
980
1300
|
onConnected(apiKey, { projectId, source: "new" });
|
|
981
1301
|
onOpenChange(false);
|
|
982
1302
|
resetState();
|
|
@@ -1199,6 +1519,23 @@ var IconLayers = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http:/
|
|
|
1199
1519
|
/* @__PURE__ */ jsx("polyline", { points: "2 17 12 22 22 17" }),
|
|
1200
1520
|
/* @__PURE__ */ jsx("polyline", { points: "2 12 12 17 22 12" })
|
|
1201
1521
|
] });
|
|
1522
|
+
var VisuallyHidden = ({ children }) => /* @__PURE__ */ jsx(
|
|
1523
|
+
"span",
|
|
1524
|
+
{
|
|
1525
|
+
style: {
|
|
1526
|
+
position: "absolute",
|
|
1527
|
+
width: "1px",
|
|
1528
|
+
height: "1px",
|
|
1529
|
+
padding: "0",
|
|
1530
|
+
margin: "-1px",
|
|
1531
|
+
overflow: "hidden",
|
|
1532
|
+
clip: "rect(0, 0, 0, 0)",
|
|
1533
|
+
whiteSpace: "nowrap",
|
|
1534
|
+
border: "0"
|
|
1535
|
+
},
|
|
1536
|
+
children
|
|
1537
|
+
}
|
|
1538
|
+
);
|
|
1202
1539
|
function ImageManager(props) {
|
|
1203
1540
|
const {
|
|
1204
1541
|
open,
|
|
@@ -1206,6 +1543,7 @@ function ImageManager(props) {
|
|
|
1206
1543
|
clientOptions,
|
|
1207
1544
|
projectId,
|
|
1208
1545
|
folderPath,
|
|
1546
|
+
userId,
|
|
1209
1547
|
title,
|
|
1210
1548
|
description,
|
|
1211
1549
|
className,
|
|
@@ -1219,7 +1557,9 @@ function ImageManager(props) {
|
|
|
1219
1557
|
maxFiles = 10,
|
|
1220
1558
|
mode = "full",
|
|
1221
1559
|
showDelete = true,
|
|
1222
|
-
refreshInterval = 5e3
|
|
1560
|
+
refreshInterval = 5e3,
|
|
1561
|
+
stayOnUploadAfterComplete = false,
|
|
1562
|
+
thumbnailConfig
|
|
1223
1563
|
} = props;
|
|
1224
1564
|
const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
|
|
1225
1565
|
const [loading, setLoading] = React4.useState(false);
|
|
@@ -1237,6 +1577,7 @@ function ImageManager(props) {
|
|
|
1237
1577
|
const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
|
|
1238
1578
|
const [selectedThumbnailForFile, setSelectedThumbnailForFile] = React4.useState(null);
|
|
1239
1579
|
const [previewFile, setPreviewFile] = React4.useState(null);
|
|
1580
|
+
const [generatingThumbnails, setGeneratingThumbnails] = React4.useState(false);
|
|
1240
1581
|
const lastUpdatedRef = React4.useRef(null);
|
|
1241
1582
|
const isFetchingRef = React4.useRef(false);
|
|
1242
1583
|
const deduplicateFiles = React4.useCallback((files2) => {
|
|
@@ -1259,10 +1600,11 @@ function ImageManager(props) {
|
|
|
1259
1600
|
const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1260
1601
|
const effectiveFolderPath = currentPathPrefix || folderPath;
|
|
1261
1602
|
const [filesData, foldersData] = await Promise.all([
|
|
1262
|
-
|
|
1603
|
+
fetchProjectFiles(clientOptions, effectiveFolderPath),
|
|
1263
1604
|
client.getFolders(currentFolderId)
|
|
1264
1605
|
]);
|
|
1265
|
-
const {
|
|
1606
|
+
const { files: rawFiles, updatedAt } = filesData;
|
|
1607
|
+
const list = rawFiles;
|
|
1266
1608
|
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
1267
1609
|
}
|
|
1268
1610
|
setFiles(deduplicateFiles(list));
|
|
@@ -1288,30 +1630,6 @@ function ImageManager(props) {
|
|
|
1288
1630
|
load();
|
|
1289
1631
|
}
|
|
1290
1632
|
}, [open, activeView, load, mode]);
|
|
1291
|
-
React4.useEffect(() => {
|
|
1292
|
-
if (!open) return;
|
|
1293
|
-
const client = new UpfilesClient(clientOptions);
|
|
1294
|
-
const url = client.getEventsUrl(projectId);
|
|
1295
|
-
const eventSource = new EventSource(url);
|
|
1296
|
-
eventSource.onmessage = (event) => {
|
|
1297
|
-
try {
|
|
1298
|
-
const payload = JSON.parse(event.data);
|
|
1299
|
-
if (payload.type === "file:uploaded") {
|
|
1300
|
-
const newFile = payload.data.file;
|
|
1301
|
-
setFiles((prev) => {
|
|
1302
|
-
if (prev.find((f2) => f2.key === newFile.key)) return prev;
|
|
1303
|
-
return deduplicateFiles([newFile, ...prev]);
|
|
1304
|
-
});
|
|
1305
|
-
} else if (payload.type === "file:deleted") {
|
|
1306
|
-
const deletedKey = payload.data.fileKey || payload.data.file?.key;
|
|
1307
|
-
setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
|
|
1308
|
-
}
|
|
1309
|
-
} catch (e) {
|
|
1310
|
-
console.error("SSE Error parsing message", e);
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
return () => eventSource.close();
|
|
1314
|
-
}, [open, clientOptions, projectId]);
|
|
1315
1633
|
const visibleFiles = React4.useMemo(() => {
|
|
1316
1634
|
const q = query.trim().toLowerCase();
|
|
1317
1635
|
return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
@@ -1335,10 +1653,18 @@ function ImageManager(props) {
|
|
|
1335
1653
|
"content-type": "application/json",
|
|
1336
1654
|
...clientOptions?.headers || {}
|
|
1337
1655
|
};
|
|
1338
|
-
|
|
1656
|
+
const token = clientOptions?.getToken?.();
|
|
1657
|
+
if (token) {
|
|
1658
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
1659
|
+
if (clientOptions.projectName) {
|
|
1660
|
+
headers["x-project-name"] = clientOptions.projectName;
|
|
1661
|
+
} else if (clientOptions.projectId) {
|
|
1662
|
+
headers["x-project-id"] = clientOptions.projectId;
|
|
1663
|
+
}
|
|
1664
|
+
} else if (clientOptions?.apiKey) {
|
|
1339
1665
|
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1340
1666
|
if (keyHeader === "authorization") {
|
|
1341
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("
|
|
1667
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1342
1668
|
} else {
|
|
1343
1669
|
headers[keyHeader] = clientOptions.apiKey;
|
|
1344
1670
|
}
|
|
@@ -1398,35 +1724,40 @@ function ImageManager(props) {
|
|
|
1398
1724
|
});
|
|
1399
1725
|
return options;
|
|
1400
1726
|
}, [breadcrumbs, currentFolderId, folders]);
|
|
1727
|
+
const handleThumbnailClick = React4.useCallback(async (f2) => {
|
|
1728
|
+
if (f2.thumbnails && f2.thumbnails.length > 0) {
|
|
1729
|
+
setThumbnailSelectorFile(f2);
|
|
1730
|
+
setSelectedThumbnailForFile(null);
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
setGeneratingThumbnails(true);
|
|
1734
|
+
setThumbnailSelectorFile(f2);
|
|
1735
|
+
setSelectedThumbnailForFile(null);
|
|
1736
|
+
try {
|
|
1737
|
+
const client = new UpfilesClient(clientOptions);
|
|
1738
|
+
const result = await client.generateThumbnails(f2.key);
|
|
1739
|
+
const updatedFile = { ...f2, thumbnails: result.thumbnails };
|
|
1740
|
+
setThumbnailSelectorFile(updatedFile);
|
|
1741
|
+
setFiles((prev) => prev.map((file) => file.key === f2.key ? updatedFile : file));
|
|
1742
|
+
} catch (e) {
|
|
1743
|
+
console.error("Failed to generate thumbnails:", e);
|
|
1744
|
+
} finally {
|
|
1745
|
+
setGeneratingThumbnails(false);
|
|
1746
|
+
}
|
|
1747
|
+
}, [clientOptions]);
|
|
1401
1748
|
const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
|
|
1402
1749
|
const activeBreadcrumbPath = React4.useMemo(() => {
|
|
1403
1750
|
return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1404
1751
|
}, [breadcrumbs]);
|
|
1405
1752
|
return /* @__PURE__ */ jsxs(Dialog2.Root, { open, onOpenChange, children: [
|
|
1406
1753
|
/* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1407
|
-
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/60
|
|
1754
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/60 z-[9999]" }),
|
|
1408
1755
|
/* @__PURE__ */ jsxs(
|
|
1409
1756
|
Dialog2.Content,
|
|
1410
1757
|
{
|
|
1411
|
-
className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] h-[90vh] max-w-7xl min-w-[800px] min-h-[600px] rounded-2xl bg-white dark:bg-gray-950 shadow-2xl z-[
|
|
1758
|
+
className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] h-[90vh] max-w-7xl min-w-[800px] min-h-[600px] rounded-2xl bg-white dark:bg-gray-950 shadow-2xl z-[10000] overflow-hidden flex flex-row ${className ?? ""}`,
|
|
1412
1759
|
children: [
|
|
1413
|
-
/* @__PURE__ */ jsx(
|
|
1414
|
-
Dialog2.Title,
|
|
1415
|
-
{
|
|
1416
|
-
style: {
|
|
1417
|
-
position: "absolute",
|
|
1418
|
-
width: "1px",
|
|
1419
|
-
height: "1px",
|
|
1420
|
-
padding: "0",
|
|
1421
|
-
margin: "-1px",
|
|
1422
|
-
overflow: "hidden",
|
|
1423
|
-
clip: "rect(0, 0, 0, 0)",
|
|
1424
|
-
whiteSpace: "nowrap",
|
|
1425
|
-
border: "0"
|
|
1426
|
-
},
|
|
1427
|
-
children: title ?? "Media Library"
|
|
1428
|
-
}
|
|
1429
|
-
),
|
|
1760
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx(Dialog2.Title, { children: title ?? "Media Library" }) }),
|
|
1430
1761
|
/* @__PURE__ */ jsxs("div", { className: "w-64 min-w-[256px] bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col flex-shrink-0", children: [
|
|
1431
1762
|
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
1432
1763
|
/* @__PURE__ */ jsxs("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
|
|
@@ -1533,16 +1864,27 @@ function ImageManager(props) {
|
|
|
1533
1864
|
folder.id
|
|
1534
1865
|
)),
|
|
1535
1866
|
visibleFiles.map((f2) => {
|
|
1536
|
-
const
|
|
1537
|
-
const
|
|
1538
|
-
const thumbUrl = bestThumb?.url || f2.url;
|
|
1867
|
+
const displayUrl = f2.url;
|
|
1868
|
+
const hasThumbnails = f2.thumbnails && f2.thumbnails.length > 0;
|
|
1539
1869
|
return /* @__PURE__ */ jsxs(
|
|
1540
1870
|
"div",
|
|
1541
1871
|
{
|
|
1542
1872
|
onClick: () => setPreviewFile(f2),
|
|
1543
1873
|
className: "group relative aspect-[4/3] bg-white rounded-xl border border-gray-200 overflow-hidden shadow-sm hover:shadow-xl hover:shadow-gray-200/50 hover:border-blue-300 transition-all duration-300 cursor-pointer",
|
|
1544
1874
|
children: [
|
|
1545
|
-
/* @__PURE__ */ jsx("img", { src:
|
|
1875
|
+
/* @__PURE__ */ jsx("img", { src: displayUrl, alt: f2.originalName, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
|
|
1876
|
+
f2.contentType?.startsWith("image/") && /* @__PURE__ */ jsx(
|
|
1877
|
+
"button",
|
|
1878
|
+
{
|
|
1879
|
+
onClick: (e) => {
|
|
1880
|
+
e.stopPropagation();
|
|
1881
|
+
handleThumbnailClick(f2);
|
|
1882
|
+
},
|
|
1883
|
+
className: `absolute top-2 left-2 p-1.5 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity shadow-lg ${hasThumbnails ? "bg-purple-600/90 hover:bg-purple-500" : "bg-gray-600/90 hover:bg-purple-500"}`,
|
|
1884
|
+
title: hasThumbnails ? "View Thumbnails" : "Generate Thumbnails",
|
|
1885
|
+
children: /* @__PURE__ */ jsx(IconLayers, { className: "w-4 h-4" })
|
|
1886
|
+
}
|
|
1887
|
+
),
|
|
1546
1888
|
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col justify-end p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
|
|
1547
1889
|
/* @__PURE__ */ jsx(
|
|
1548
1890
|
"button",
|
|
@@ -1563,19 +1905,6 @@ function ImageManager(props) {
|
|
|
1563
1905
|
children: "Select"
|
|
1564
1906
|
}
|
|
1565
1907
|
),
|
|
1566
|
-
fWithThumbs.thumbnails && fWithThumbs.thumbnails.length > 0 && /* @__PURE__ */ jsx(
|
|
1567
|
-
"button",
|
|
1568
|
-
{
|
|
1569
|
-
onClick: (e) => {
|
|
1570
|
-
e.stopPropagation();
|
|
1571
|
-
setThumbnailSelectorFile(f2);
|
|
1572
|
-
setSelectedThumbnailForFile(null);
|
|
1573
|
-
},
|
|
1574
|
-
className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-purple-500 backdrop-blur-md transition-colors",
|
|
1575
|
-
title: "Select Thumbnail",
|
|
1576
|
-
children: /* @__PURE__ */ jsx(IconLayers, { className: "w-4 h-4" })
|
|
1577
|
-
}
|
|
1578
|
-
),
|
|
1579
1908
|
showDelete && /* @__PURE__ */ jsx(
|
|
1580
1909
|
"button",
|
|
1581
1910
|
{
|
|
@@ -1601,7 +1930,7 @@ function ImageManager(props) {
|
|
|
1601
1930
|
/* @__PURE__ */ jsx("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
|
|
1602
1931
|
] })
|
|
1603
1932
|
] }),
|
|
1604
|
-
activeView === "upload" && /* @__PURE__ */ jsx("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ jsxs("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
|
|
1933
|
+
activeView === "upload" && /* @__PURE__ */ jsx("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto gap-4", children: /* @__PURE__ */ jsxs("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
|
|
1605
1934
|
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
|
|
1606
1935
|
/* @__PURE__ */ jsx(IconUploadCloud, { className: "text-blue-500" }),
|
|
1607
1936
|
"Upload Files"
|
|
@@ -1626,15 +1955,28 @@ function ImageManager(props) {
|
|
|
1626
1955
|
{
|
|
1627
1956
|
clientOptions,
|
|
1628
1957
|
projectId,
|
|
1958
|
+
userId,
|
|
1629
1959
|
folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
|
|
1630
1960
|
accept: ["image/*"],
|
|
1631
1961
|
maxFileSize,
|
|
1632
1962
|
maxFiles,
|
|
1633
1963
|
autoRecordToDb,
|
|
1634
|
-
fetchThumbnails,
|
|
1635
|
-
autoUpload:
|
|
1636
|
-
|
|
1964
|
+
fetchThumbnails: false,
|
|
1965
|
+
autoUpload: false,
|
|
1966
|
+
thumbnailEnabled: thumbnailConfig?.enabled ?? true,
|
|
1967
|
+
thumbnailSizes: thumbnailConfig?.sizes,
|
|
1968
|
+
onClientUploadComplete: async (file) => {
|
|
1637
1969
|
if (file.status === "success" && file.publicUrl && file.fileKey) {
|
|
1970
|
+
let thumbnails = file.thumbnails;
|
|
1971
|
+
if (file.thumbnailEnabled && file.thumbnailSizesSelected && file.thumbnailSizesSelected.length > 0 && file.type?.startsWith("image/")) {
|
|
1972
|
+
try {
|
|
1973
|
+
const client = new UpfilesClient(clientOptions);
|
|
1974
|
+
const result = await client.generateThumbnails(file.fileKey, file.thumbnailSizesSelected);
|
|
1975
|
+
thumbnails = result.thumbnails;
|
|
1976
|
+
} catch (e) {
|
|
1977
|
+
console.warn("Failed to generate thumbnails:", e);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1638
1980
|
const newItem = {
|
|
1639
1981
|
key: file.fileKey,
|
|
1640
1982
|
originalName: file.name,
|
|
@@ -1642,7 +1984,7 @@ function ImageManager(props) {
|
|
|
1642
1984
|
contentType: file.type,
|
|
1643
1985
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1644
1986
|
url: file.publicUrl,
|
|
1645
|
-
thumbnails
|
|
1987
|
+
thumbnails
|
|
1646
1988
|
};
|
|
1647
1989
|
setFiles((prev) => {
|
|
1648
1990
|
if (prev.find((f2) => f2.key === newItem.key)) return prev;
|
|
@@ -1651,11 +1993,13 @@ function ImageManager(props) {
|
|
|
1651
1993
|
}
|
|
1652
1994
|
},
|
|
1653
1995
|
onComplete: () => {
|
|
1654
|
-
if (mode === "full") {
|
|
1996
|
+
if (mode === "full" && !stayOnUploadAfterComplete) {
|
|
1655
1997
|
setTimeout(() => {
|
|
1656
1998
|
setActiveView("browse");
|
|
1657
1999
|
load();
|
|
1658
2000
|
}, 1e3);
|
|
2001
|
+
} else {
|
|
2002
|
+
load();
|
|
1659
2003
|
}
|
|
1660
2004
|
},
|
|
1661
2005
|
dropzoneClassName: "border-2 border-dashed border-gray-300 bg-gray-50 rounded-xl p-12 text-center cursor-pointer hover:border-blue-500 hover:bg-blue-50/50 transition-all",
|
|
@@ -1681,8 +2025,8 @@ function ImageManager(props) {
|
|
|
1681
2025
|
)
|
|
1682
2026
|
] }),
|
|
1683
2027
|
/* @__PURE__ */ jsx(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1684
|
-
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[
|
|
1685
|
-
/* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl z-[
|
|
2028
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[10010]" }),
|
|
2029
|
+
/* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl z-[10011] outline-none", "aria-describedby": "new-folder-description", children: [
|
|
1686
2030
|
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
|
|
1687
2031
|
/* @__PURE__ */ jsx(Dialog2.Description, { id: "new-folder-description", className: "sr-only", children: "Create a new folder to organize your files" }),
|
|
1688
2032
|
/* @__PURE__ */ jsx(
|
|
@@ -1711,8 +2055,8 @@ function ImageManager(props) {
|
|
|
1711
2055
|
] })
|
|
1712
2056
|
] }) }),
|
|
1713
2057
|
/* @__PURE__ */ jsx(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1714
|
-
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[
|
|
1715
|
-
/* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md rounded-xl bg-white p-6 shadow-2xl z-[
|
|
2058
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[10020]" }),
|
|
2059
|
+
/* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md rounded-xl bg-white p-6 shadow-2xl z-[10021]", "aria-describedby": "delete-file-description", children: [
|
|
1716
2060
|
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
|
|
1717
2061
|
/* @__PURE__ */ jsxs(Dialog2.Description, { id: "delete-file-description", className: "text-gray-600 mb-6", children: [
|
|
1718
2062
|
"Are you sure you want to delete ",
|
|
@@ -1745,8 +2089,8 @@ function ImageManager(props) {
|
|
|
1745
2089
|
] })
|
|
1746
2090
|
] }) }),
|
|
1747
2091
|
/* @__PURE__ */ jsx(Dialog2.Root, { open: !!thumbnailSelectorFile, onOpenChange: (open2) => !open2 && setThumbnailSelectorFile(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1748
|
-
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/50 z-[
|
|
1749
|
-
/* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-4xl max-h-[80vh] rounded-xl bg-white shadow-2xl z-[
|
|
2092
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/50 z-[10030]" }),
|
|
2093
|
+
/* @__PURE__ */ jsxs(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-4xl max-h-[80vh] rounded-xl bg-white shadow-2xl z-[10031] overflow-hidden", "aria-describedby": "thumbnail-selector-description", children: [
|
|
1750
2094
|
/* @__PURE__ */ jsxs("div", { className: "p-6 border-b border-gray-200", children: [
|
|
1751
2095
|
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Select Thumbnail" }),
|
|
1752
2096
|
/* @__PURE__ */ jsxs(Dialog2.Description, { id: "thumbnail-selector-description", className: "text-gray-600", children: [
|
|
@@ -1758,60 +2102,66 @@ function ImageManager(props) {
|
|
|
1758
2102
|
] })
|
|
1759
2103
|
] })
|
|
1760
2104
|
] }),
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__PURE__ */ jsxs(
|
|
1763
|
-
"
|
|
1764
|
-
{
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
]
|
|
1784
|
-
}
|
|
1785
|
-
),
|
|
1786
|
-
thumbnailSelectorFile?.thumbnails?.map((thumbnail) => /* @__PURE__ */ jsxs(
|
|
1787
|
-
"div",
|
|
1788
|
-
{
|
|
1789
|
-
onClick: () => setSelectedThumbnailForFile(thumbnail),
|
|
1790
|
-
className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile?.id === thumbnail.id ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
|
|
1791
|
-
children: [
|
|
1792
|
-
/* @__PURE__ */ jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
|
|
1793
|
-
/* @__PURE__ */ jsx(
|
|
1794
|
-
"img",
|
|
1795
|
-
{
|
|
1796
|
-
src: thumbnail.url,
|
|
1797
|
-
alt: `${thumbnail.size}px thumbnail`,
|
|
1798
|
-
className: "w-full h-full object-cover"
|
|
1799
|
-
}
|
|
1800
|
-
),
|
|
1801
|
-
selectedThumbnailForFile?.id === thumbnail.id && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
|
|
1802
|
-
] }),
|
|
1803
|
-
/* @__PURE__ */ jsxs("div", { className: "p-3 bg-white", children: [
|
|
1804
|
-
/* @__PURE__ */ jsxs("div", { className: "font-medium text-sm text-gray-900", children: [
|
|
1805
|
-
thumbnail.size,
|
|
1806
|
-
"px"
|
|
2105
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6 overflow-y-auto max-h-[60vh]", children: [
|
|
2106
|
+
generatingThumbnails && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 py-8 text-gray-500", children: [
|
|
2107
|
+
/* @__PURE__ */ jsx(IconLoader, { className: "w-5 h-5 animate-spin" }),
|
|
2108
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Generating thumbnails..." })
|
|
2109
|
+
] }),
|
|
2110
|
+
/* @__PURE__ */ jsxs("div", { className: `grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 ${generatingThumbnails ? "opacity-50 pointer-events-none" : ""}`, children: [
|
|
2111
|
+
/* @__PURE__ */ jsxs(
|
|
2112
|
+
"div",
|
|
2113
|
+
{
|
|
2114
|
+
onClick: () => setSelectedThumbnailForFile(null),
|
|
2115
|
+
className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile === null ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
|
|
2116
|
+
children: [
|
|
2117
|
+
/* @__PURE__ */ jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
|
|
2118
|
+
/* @__PURE__ */ jsx(
|
|
2119
|
+
"img",
|
|
2120
|
+
{
|
|
2121
|
+
src: thumbnailSelectorFile?.url,
|
|
2122
|
+
alt: "Original",
|
|
2123
|
+
className: "w-full h-full object-cover"
|
|
2124
|
+
}
|
|
2125
|
+
),
|
|
2126
|
+
selectedThumbnailForFile === null && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
|
|
1807
2127
|
] }),
|
|
1808
|
-
/* @__PURE__ */
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2128
|
+
/* @__PURE__ */ jsxs("div", { className: "p-3 bg-white", children: [
|
|
2129
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-sm text-gray-900", children: "Original" }),
|
|
2130
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: "Full size" })
|
|
2131
|
+
] })
|
|
2132
|
+
]
|
|
2133
|
+
}
|
|
2134
|
+
),
|
|
2135
|
+
thumbnailSelectorFile?.thumbnails?.map((thumbnail) => /* @__PURE__ */ jsxs(
|
|
2136
|
+
"div",
|
|
2137
|
+
{
|
|
2138
|
+
onClick: () => setSelectedThumbnailForFile(thumbnail),
|
|
2139
|
+
className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile?.id === thumbnail.id ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
|
|
2140
|
+
children: [
|
|
2141
|
+
/* @__PURE__ */ jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
|
|
2142
|
+
/* @__PURE__ */ jsx(
|
|
2143
|
+
"img",
|
|
2144
|
+
{
|
|
2145
|
+
src: thumbnail.url,
|
|
2146
|
+
alt: `${thumbnail.size}px thumbnail`,
|
|
2147
|
+
className: "w-full h-full object-cover"
|
|
2148
|
+
}
|
|
2149
|
+
),
|
|
2150
|
+
selectedThumbnailForFile?.id === thumbnail.id && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
|
|
2151
|
+
] }),
|
|
2152
|
+
/* @__PURE__ */ jsxs("div", { className: "p-3 bg-white", children: [
|
|
2153
|
+
/* @__PURE__ */ jsxs("div", { className: "font-medium text-sm text-gray-900", children: [
|
|
2154
|
+
thumbnail.size,
|
|
2155
|
+
"px"
|
|
2156
|
+
] }),
|
|
2157
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: thumbnail.sizeType || "Thumbnail" })
|
|
2158
|
+
] })
|
|
2159
|
+
]
|
|
2160
|
+
},
|
|
2161
|
+
thumbnail.id
|
|
2162
|
+
))
|
|
2163
|
+
] })
|
|
2164
|
+
] }),
|
|
1815
2165
|
/* @__PURE__ */ jsxs("div", { className: "p-6 border-t border-gray-200 flex justify-end gap-3", children: [
|
|
1816
2166
|
/* @__PURE__ */ jsx(
|
|
1817
2167
|
"button",
|
|
@@ -1850,8 +2200,8 @@ function ImageManager(props) {
|
|
|
1850
2200
|
] })
|
|
1851
2201
|
] }) }),
|
|
1852
2202
|
/* @__PURE__ */ jsx(Dialog2.Root, { open: !!previewFile, onOpenChange: (open2) => !open2 && setPreviewFile(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1853
|
-
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/90 z-[
|
|
1854
|
-
/* @__PURE__ */ jsx(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[90vh] z-[
|
|
2203
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/90 z-[10040]", onClick: () => setPreviewFile(null) }),
|
|
2204
|
+
/* @__PURE__ */ jsx(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[90vh] z-[10041] outline-none", "aria-describedby": "image-preview-description", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex flex-col", children: [
|
|
1855
2205
|
/* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 z-10 bg-gradient-to-b from-black/70 to-transparent p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
1856
2206
|
/* @__PURE__ */ jsxs("div", { className: "text-white", children: [
|
|
1857
2207
|
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold mb-1", children: previewFile?.originalName }),
|
|
@@ -1923,14 +2273,15 @@ function ImageManager(props) {
|
|
|
1923
2273
|
children: "Open in New Tab"
|
|
1924
2274
|
}
|
|
1925
2275
|
),
|
|
1926
|
-
previewFile?.
|
|
2276
|
+
previewFile?.contentType?.startsWith("image/") && /* @__PURE__ */ jsxs(
|
|
1927
2277
|
"button",
|
|
1928
2278
|
{
|
|
1929
2279
|
onClick: (e) => {
|
|
1930
2280
|
e.stopPropagation();
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2281
|
+
if (previewFile) {
|
|
2282
|
+
handleThumbnailClick(previewFile);
|
|
2283
|
+
setPreviewFile(null);
|
|
2284
|
+
}
|
|
1934
2285
|
},
|
|
1935
2286
|
className: "px-6 py-2.5 bg-purple-600 text-white font-semibold rounded-lg hover:bg-purple-500 shadow-lg flex items-center gap-2",
|
|
1936
2287
|
children: [
|
|
@@ -1944,7 +2295,138 @@ function ImageManager(props) {
|
|
|
1944
2295
|
] }) })
|
|
1945
2296
|
] });
|
|
1946
2297
|
}
|
|
2298
|
+
function useUpfilesInhouse(config) {
|
|
2299
|
+
const {
|
|
2300
|
+
baseUrl,
|
|
2301
|
+
getToken,
|
|
2302
|
+
userId,
|
|
2303
|
+
projectName = "default",
|
|
2304
|
+
enabled = true
|
|
2305
|
+
} = config;
|
|
2306
|
+
const [state, setState] = useState({ clientOptions: null, projectId: null, loading: false, error: null });
|
|
2307
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
2308
|
+
const [token, setToken] = useState(
|
|
2309
|
+
typeof getToken === "string" ? getToken : null
|
|
2310
|
+
);
|
|
2311
|
+
const getTokenFn = useCallback(() => {
|
|
2312
|
+
return typeof getToken === "function" ? getToken() : getToken;
|
|
2313
|
+
}, [getToken]);
|
|
2314
|
+
useEffect(() => {
|
|
2315
|
+
let cancelled = false;
|
|
2316
|
+
async function resolveToken() {
|
|
2317
|
+
const next = await getTokenFn();
|
|
2318
|
+
if (!cancelled) {
|
|
2319
|
+
setToken(next ?? null);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
resolveToken();
|
|
2323
|
+
window.addEventListener("focus", resolveToken);
|
|
2324
|
+
window.addEventListener("storage", resolveToken);
|
|
2325
|
+
return () => {
|
|
2326
|
+
cancelled = true;
|
|
2327
|
+
window.removeEventListener("focus", resolveToken);
|
|
2328
|
+
window.removeEventListener("storage", resolveToken);
|
|
2329
|
+
};
|
|
2330
|
+
}, [getTokenFn]);
|
|
2331
|
+
const mountedRef = useRef(true);
|
|
2332
|
+
useEffect(() => {
|
|
2333
|
+
mountedRef.current = true;
|
|
2334
|
+
return () => {
|
|
2335
|
+
mountedRef.current = false;
|
|
2336
|
+
};
|
|
2337
|
+
}, []);
|
|
2338
|
+
const buildClientOptions = useCallback(
|
|
2339
|
+
() => ({
|
|
2340
|
+
baseUrl,
|
|
2341
|
+
getToken: getTokenFn,
|
|
2342
|
+
projectName
|
|
2343
|
+
}),
|
|
2344
|
+
[baseUrl, getTokenFn, projectName]
|
|
2345
|
+
);
|
|
2346
|
+
useEffect(() => {
|
|
2347
|
+
if (!enabled || !token || !userId) {
|
|
2348
|
+
setState({ clientOptions: null, projectId: null, loading: false, error: null });
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
let cancelled = false;
|
|
2352
|
+
async function provision() {
|
|
2353
|
+
if (!mountedRef.current) return;
|
|
2354
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
2355
|
+
try {
|
|
2356
|
+
const connectOpts = { baseUrl, token };
|
|
2357
|
+
const projects = await listProjects(connectOpts);
|
|
2358
|
+
if (cancelled || !mountedRef.current) return;
|
|
2359
|
+
const existing = projects.find((p) => p.name === projectName);
|
|
2360
|
+
if (existing) {
|
|
2361
|
+
await updateProject({
|
|
2362
|
+
...connectOpts,
|
|
2363
|
+
projectId: existing.id,
|
|
2364
|
+
isInHouse: true,
|
|
2365
|
+
freeStorageLimitBytes: 2147483648
|
|
2366
|
+
// 2GB
|
|
2367
|
+
});
|
|
2368
|
+
} else {
|
|
2369
|
+
try {
|
|
2370
|
+
await createProject({
|
|
2371
|
+
...connectOpts,
|
|
2372
|
+
name: projectName,
|
|
2373
|
+
isInHouse: true,
|
|
2374
|
+
freeStorageLimitBytes: 2147483648
|
|
2375
|
+
// 2GB in bytes
|
|
2376
|
+
});
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
if (err?.message?.includes("409") || err?.message?.includes("duplicate") || err?.message?.includes("already exists")) {
|
|
2379
|
+
const retryProjects = await listProjects(connectOpts);
|
|
2380
|
+
const found = retryProjects.find((p) => p.name === projectName);
|
|
2381
|
+
if (!found) throw err;
|
|
2382
|
+
await updateProject({
|
|
2383
|
+
...connectOpts,
|
|
2384
|
+
projectId: found.id,
|
|
2385
|
+
isInHouse: true,
|
|
2386
|
+
freeStorageLimitBytes: 2147483648
|
|
2387
|
+
// 2GB
|
|
2388
|
+
});
|
|
2389
|
+
} else {
|
|
2390
|
+
throw err;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
if (cancelled || !mountedRef.current) return;
|
|
2395
|
+
setState({
|
|
2396
|
+
clientOptions: buildClientOptions(),
|
|
2397
|
+
projectId: null,
|
|
2398
|
+
loading: false,
|
|
2399
|
+
error: null
|
|
2400
|
+
});
|
|
2401
|
+
} catch (err) {
|
|
2402
|
+
if (cancelled || !mountedRef.current) return;
|
|
2403
|
+
setState({
|
|
2404
|
+
clientOptions: null,
|
|
2405
|
+
projectId: null,
|
|
2406
|
+
loading: false,
|
|
2407
|
+
error: err?.message || "Failed to auto-connect upfiles"
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
provision();
|
|
2412
|
+
return () => {
|
|
2413
|
+
cancelled = true;
|
|
2414
|
+
};
|
|
2415
|
+
}, [enabled, token, userId, baseUrl, projectName, retryCount, buildClientOptions]);
|
|
2416
|
+
const retry = useCallback(() => {
|
|
2417
|
+
setRetryCount((c) => c + 1);
|
|
2418
|
+
}, []);
|
|
2419
|
+
const clearCache = useCallback(() => {
|
|
2420
|
+
setState({ clientOptions: null, projectId: null, loading: false, error: null });
|
|
2421
|
+
}, []);
|
|
2422
|
+
return {
|
|
2423
|
+
...state,
|
|
2424
|
+
isReady: !!state.clientOptions,
|
|
2425
|
+
retry,
|
|
2426
|
+
clearCache
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
1947
2429
|
|
|
1948
|
-
export { ConnectProjectDialog, ImageManager, ProjectFilesWidget, UpfilesClient, Uploader, addApiKeyManually, connectProject, connectUsingApiKey, createClientWithKey, createProject, fetchFileThumbnails, fetchProjectFiles, fetchProjectImagesWithThumbs, listProjects };
|
|
2430
|
+
export { ConnectProjectDialog, ImageManager, ProjectFilesWidget, StorageQuotaError, UpfilesClient, Uploader, addApiKeyManually, connectProject, connectUsingApiKey, createClientWithKey, createProject, createProjectWithApiKey, fetchFileThumbnails, fetchProjectFiles, fetchProjectImagesWithThumbs, listProjects, updateProject, useUpfilesInhouse };
|
|
1949
2431
|
//# sourceMappingURL=index.mjs.map
|
|
1950
2432
|
//# sourceMappingURL=index.mjs.map
|