@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.js
CHANGED
|
@@ -26,6 +26,13 @@ var React4__namespace = /*#__PURE__*/_interopNamespace(React4);
|
|
|
26
26
|
var Dialog2__namespace = /*#__PURE__*/_interopNamespace(Dialog2);
|
|
27
27
|
|
|
28
28
|
// src/client.ts
|
|
29
|
+
var StorageQuotaError = class extends Error {
|
|
30
|
+
constructor(message, details) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "StorageQuotaError";
|
|
33
|
+
this.details = details;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
29
36
|
var UpfilesClient = class {
|
|
30
37
|
constructor(opts) {
|
|
31
38
|
this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
|
|
@@ -39,25 +46,66 @@ var UpfilesClient = class {
|
|
|
39
46
|
this.completionPath = opts.completionPath ?? "/api/plugin/files";
|
|
40
47
|
this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
|
|
41
48
|
this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
|
|
49
|
+
this.getToken = opts.getToken;
|
|
50
|
+
this.projectId = opts.projectId;
|
|
51
|
+
this.projectName = opts.projectName;
|
|
42
52
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
/** Build auth headers for API requests. Priority: getToken (JWT) > apiKey > static headers */
|
|
54
|
+
async buildAuthHeaders() {
|
|
55
|
+
const headers = { ...this.headers || {} };
|
|
56
|
+
if (this.getToken) {
|
|
57
|
+
const token = await this.getToken();
|
|
58
|
+
if (token) {
|
|
59
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
60
|
+
}
|
|
61
|
+
if (this.projectName) {
|
|
62
|
+
headers["x-project-name"] = this.projectName;
|
|
63
|
+
} else if (this.projectId) {
|
|
64
|
+
headers["x-project-id"] = this.projectId;
|
|
65
|
+
}
|
|
66
|
+
return headers;
|
|
67
|
+
}
|
|
52
68
|
if (this.apiKey) {
|
|
53
69
|
if (this.apiKeyHeader === "authorization") {
|
|
54
|
-
headers["authorization"] = this.apiKey.startsWith("
|
|
70
|
+
headers["authorization"] = this.apiKey.startsWith("upf_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
55
71
|
} else if (this.apiKeyHeader === "x-api-key") {
|
|
56
72
|
headers["x-api-key"] = this.apiKey;
|
|
57
73
|
} else {
|
|
58
74
|
headers["x-up-api-key"] = this.apiKey;
|
|
59
75
|
}
|
|
60
76
|
}
|
|
77
|
+
return headers;
|
|
78
|
+
}
|
|
79
|
+
/** Get storage quota info for the project */
|
|
80
|
+
async getQuota() {
|
|
81
|
+
const basePath = this.presignPath.replace(/\/upload$/, "");
|
|
82
|
+
const target = this.baseUrl ? `${this.baseUrl}${basePath}/quota` : `${basePath}/quota`;
|
|
83
|
+
const headers = await this.buildAuthHeaders();
|
|
84
|
+
const res = await fetch(target, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
headers,
|
|
87
|
+
credentials: this.withCredentials ? "include" : "same-origin"
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
let msg = "Failed to get storage quota";
|
|
91
|
+
try {
|
|
92
|
+
const data = await res.json();
|
|
93
|
+
msg = data.message || data.error || msg;
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
throw new Error(msg);
|
|
97
|
+
}
|
|
98
|
+
return res.json();
|
|
99
|
+
}
|
|
100
|
+
async getPresignedUrl(params) {
|
|
101
|
+
if (!params.fileName) throw new Error("fileName is required");
|
|
102
|
+
if (!params.fileType) throw new Error("fileType is required");
|
|
103
|
+
if (!params.fileSize || params.fileSize <= 0) throw new Error("fileSize must be > 0");
|
|
104
|
+
const target = this.presignUrl ? this.presignUrl : this.baseUrl ? `${this.baseUrl}${this.presignPath}` : this.presignPath;
|
|
105
|
+
const headers = {
|
|
106
|
+
"content-type": "application/json",
|
|
107
|
+
...await this.buildAuthHeaders()
|
|
108
|
+
};
|
|
61
109
|
const res = await fetch(target, {
|
|
62
110
|
method: "POST",
|
|
63
111
|
headers,
|
|
@@ -68,8 +116,12 @@ var UpfilesClient = class {
|
|
|
68
116
|
let msg = "Failed to get upload URL";
|
|
69
117
|
try {
|
|
70
118
|
const data = await res.json();
|
|
71
|
-
|
|
72
|
-
|
|
119
|
+
if (res.status === 413 && data.error === "STORAGE_QUOTA_EXCEEDED") {
|
|
120
|
+
throw new StorageQuotaError(data.message || "Storage quota exceeded", data.details);
|
|
121
|
+
}
|
|
122
|
+
msg = data.message || data.error || msg;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
if (e instanceof StorageQuotaError) throw e;
|
|
73
125
|
}
|
|
74
126
|
throw new Error(msg);
|
|
75
127
|
}
|
|
@@ -105,7 +157,11 @@ var UpfilesClient = class {
|
|
|
105
157
|
originalName: file.name,
|
|
106
158
|
size: file.size,
|
|
107
159
|
contentType: file.type || "application/octet-stream",
|
|
108
|
-
projectId: extras?.projectId
|
|
160
|
+
projectId: extras?.projectId,
|
|
161
|
+
url: presign.publicUrl,
|
|
162
|
+
userId: extras?.userId
|
|
163
|
+
// 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.
|
|
164
|
+
// 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.
|
|
109
165
|
});
|
|
110
166
|
} catch (error) {
|
|
111
167
|
throw new Error(`Upload verification failed: ${error.message}`);
|
|
@@ -134,22 +190,16 @@ var UpfilesClient = class {
|
|
|
134
190
|
const target = this.baseUrl ? `${this.baseUrl}${this.completionPath}` : this.completionPath;
|
|
135
191
|
const headers = {
|
|
136
192
|
"content-type": "application/json",
|
|
137
|
-
...this.
|
|
193
|
+
...await this.buildAuthHeaders()
|
|
138
194
|
};
|
|
139
|
-
if (this.apiKey) {
|
|
140
|
-
if (this.apiKeyHeader === "authorization") {
|
|
141
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
142
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
143
|
-
headers["x-api-key"] = this.apiKey;
|
|
144
|
-
} else {
|
|
145
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
195
|
const res = await fetch(target, {
|
|
149
196
|
method: "POST",
|
|
150
197
|
headers,
|
|
151
198
|
credentials: this.withCredentials ? "include" : "same-origin",
|
|
152
|
-
body: JSON.stringify(
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
...params,
|
|
201
|
+
key: params.fileKey
|
|
202
|
+
})
|
|
153
203
|
});
|
|
154
204
|
if (!res.ok) {
|
|
155
205
|
let msg = "Failed to complete upload";
|
|
@@ -165,16 +215,7 @@ var UpfilesClient = class {
|
|
|
165
215
|
async getThumbnails(fileKey) {
|
|
166
216
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
167
217
|
const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
|
|
168
|
-
const headers =
|
|
169
|
-
if (this.apiKey) {
|
|
170
|
-
if (this.apiKeyHeader === "authorization") {
|
|
171
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
172
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
173
|
-
headers["x-api-key"] = this.apiKey;
|
|
174
|
-
} else {
|
|
175
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
218
|
+
const headers = await this.buildAuthHeaders();
|
|
178
219
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
179
220
|
if (!res.ok) {
|
|
180
221
|
let msg = "Failed to fetch thumbnails";
|
|
@@ -188,26 +229,19 @@ var UpfilesClient = class {
|
|
|
188
229
|
const data = await res.json();
|
|
189
230
|
return data?.thumbnails ?? [];
|
|
190
231
|
}
|
|
191
|
-
async generateThumbnails(fileKey) {
|
|
232
|
+
async generateThumbnails(fileKey, sizes) {
|
|
192
233
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
193
234
|
const headers = {
|
|
194
235
|
"content-type": "application/json",
|
|
195
|
-
...this.
|
|
236
|
+
...await this.buildAuthHeaders()
|
|
196
237
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
200
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
201
|
-
headers["x-api-key"] = this.apiKey;
|
|
202
|
-
} else {
|
|
203
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
238
|
+
const body = { fileKey };
|
|
239
|
+
if (sizes && sizes.length > 0) body.sizes = sizes;
|
|
206
240
|
const res = await fetch(target, {
|
|
207
241
|
method: "POST",
|
|
208
242
|
headers,
|
|
209
243
|
credentials: this.withCredentials ? "include" : "same-origin",
|
|
210
|
-
body: JSON.stringify(
|
|
244
|
+
body: JSON.stringify(body)
|
|
211
245
|
});
|
|
212
246
|
if (!res.ok) {
|
|
213
247
|
let msg = "Failed to generate thumbnails";
|
|
@@ -221,19 +255,53 @@ var UpfilesClient = class {
|
|
|
221
255
|
const data = await res.json();
|
|
222
256
|
return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
|
|
223
257
|
}
|
|
258
|
+
/** Get thumbnail settings for the project (requires API key) */
|
|
259
|
+
async getThumbnailSettings() {
|
|
260
|
+
const basePath = this.presignPath.replace(/\/upload$/, "");
|
|
261
|
+
const target = this.baseUrl ? `${this.baseUrl}${basePath}/settings/thumbnails` : `${basePath}/settings/thumbnails`;
|
|
262
|
+
const headers = await this.buildAuthHeaders();
|
|
263
|
+
const res = await fetch(target, {
|
|
264
|
+
method: "GET",
|
|
265
|
+
headers,
|
|
266
|
+
credentials: this.withCredentials ? "include" : "same-origin"
|
|
267
|
+
});
|
|
268
|
+
if (!res.ok) {
|
|
269
|
+
let msg = "Failed to get 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
|
+
}
|
|
279
|
+
/** Update thumbnail settings for the project (requires API key with write scope) */
|
|
280
|
+
async updateThumbnailSettings(settings) {
|
|
281
|
+
const basePath = this.presignPath.replace(/\/upload$/, "");
|
|
282
|
+
const target = this.baseUrl ? `${this.baseUrl}${basePath}/settings/thumbnails` : `${basePath}/settings/thumbnails`;
|
|
283
|
+
const headers = { "content-type": "application/json", ...await this.buildAuthHeaders() };
|
|
284
|
+
const res = await fetch(target, {
|
|
285
|
+
method: "PATCH",
|
|
286
|
+
headers,
|
|
287
|
+
credentials: this.withCredentials ? "include" : "same-origin",
|
|
288
|
+
body: JSON.stringify(settings)
|
|
289
|
+
});
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
let msg = "Failed to update thumbnail settings";
|
|
292
|
+
try {
|
|
293
|
+
const d = await res.json();
|
|
294
|
+
msg = d.message || msg;
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
throw new Error(msg);
|
|
298
|
+
}
|
|
299
|
+
return res.json();
|
|
300
|
+
}
|
|
224
301
|
async getFolders(parentId) {
|
|
225
302
|
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
226
303
|
const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
|
|
227
|
-
const headers =
|
|
228
|
-
if (this.apiKey) {
|
|
229
|
-
if (this.apiKeyHeader === "authorization") {
|
|
230
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
231
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
232
|
-
headers["x-api-key"] = this.apiKey;
|
|
233
|
-
} else {
|
|
234
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
304
|
+
const headers = await this.buildAuthHeaders();
|
|
237
305
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
238
306
|
if (!res.ok) {
|
|
239
307
|
let msg = "Failed to fetch folders";
|
|
@@ -251,17 +319,8 @@ var UpfilesClient = class {
|
|
|
251
319
|
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
252
320
|
const headers = {
|
|
253
321
|
"content-type": "application/json",
|
|
254
|
-
...this.
|
|
322
|
+
...await this.buildAuthHeaders()
|
|
255
323
|
};
|
|
256
|
-
if (this.apiKey) {
|
|
257
|
-
if (this.apiKeyHeader === "authorization") {
|
|
258
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
259
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
260
|
-
headers["x-api-key"] = this.apiKey;
|
|
261
|
-
} else {
|
|
262
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
324
|
const res = await fetch(target, {
|
|
266
325
|
method: "POST",
|
|
267
326
|
headers,
|
|
@@ -285,16 +344,7 @@ var UpfilesClient = class {
|
|
|
285
344
|
const filesPath = `${basePath}/files`;
|
|
286
345
|
const target = this.baseUrl ? `${this.baseUrl}${filesPath}` : filesPath;
|
|
287
346
|
const url = params?.folderPath ? `${target}?folderPath=${encodeURIComponent(params.folderPath)}` : target;
|
|
288
|
-
const headers =
|
|
289
|
-
if (this.apiKey) {
|
|
290
|
-
if (this.apiKeyHeader === "authorization") {
|
|
291
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
292
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
293
|
-
headers["x-api-key"] = this.apiKey;
|
|
294
|
-
} else {
|
|
295
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
347
|
+
const headers = await this.buildAuthHeaders();
|
|
298
348
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
299
349
|
if (!res.ok) {
|
|
300
350
|
let msg = "Failed to fetch files";
|
|
@@ -312,24 +362,21 @@ var UpfilesClient = class {
|
|
|
312
362
|
};
|
|
313
363
|
}
|
|
314
364
|
getEventsUrl(projectId) {
|
|
315
|
-
this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
316
365
|
const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
|
|
317
366
|
const url = new URL(target, window.location.href);
|
|
318
|
-
if (this.apiKey) {
|
|
319
|
-
url.searchParams.set("
|
|
320
|
-
}
|
|
321
|
-
if (projectId) {
|
|
322
|
-
url.searchParams.set("projectId", projectId);
|
|
367
|
+
if (!this.getToken && !this.apiKey && (projectId || this.projectId)) {
|
|
368
|
+
url.searchParams.set("projectId", projectId || this.projectId);
|
|
323
369
|
}
|
|
324
370
|
return url.toString();
|
|
325
371
|
}
|
|
326
372
|
};
|
|
373
|
+
var THUMBNAIL_SIZE_OPTIONS = [64, 128, 256, 512];
|
|
327
374
|
var Uploader = ({
|
|
328
375
|
clientOptions,
|
|
329
376
|
multiple = true,
|
|
330
377
|
accept = ["image/*", "video/*", "audio/*", "application/pdf", "text/plain", "application/json"],
|
|
331
|
-
maxFileSize =
|
|
332
|
-
maxFiles =
|
|
378
|
+
maxFileSize = 0,
|
|
379
|
+
maxFiles = 0,
|
|
333
380
|
onComplete,
|
|
334
381
|
onError,
|
|
335
382
|
onUploadBegin,
|
|
@@ -340,36 +387,124 @@ var Uploader = ({
|
|
|
340
387
|
dropzoneClassName,
|
|
341
388
|
children,
|
|
342
389
|
projectId,
|
|
390
|
+
userId,
|
|
343
391
|
folderPath,
|
|
344
392
|
fetchThumbnails,
|
|
345
393
|
autoRecordToDb = true,
|
|
346
394
|
recordUrl = "/api/files",
|
|
347
|
-
autoUpload = false
|
|
395
|
+
autoUpload = false,
|
|
396
|
+
thumbnailEnabled,
|
|
397
|
+
thumbnailSizes
|
|
348
398
|
}) => {
|
|
349
399
|
const inputRef = React4.useRef(null);
|
|
350
400
|
const client = React4.useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
351
401
|
const [files, setFiles] = React4.useState([]);
|
|
352
402
|
const [isUploading, setIsUploading] = React4.useState(false);
|
|
353
403
|
const [isDragOver, setIsDragOver] = React4.useState(false);
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
404
|
+
const [quotaError, setQuotaError] = React4.useState(null);
|
|
405
|
+
const showThumbnailUI = thumbnailEnabled !== void 0;
|
|
406
|
+
const [thumbEnabled, setThumbEnabled] = React4.useState(thumbnailEnabled ?? true);
|
|
407
|
+
const [thumbSizes, setThumbSizes] = React4.useState(thumbnailSizes ?? [256, 512]);
|
|
408
|
+
const [thumbFileIds, setThumbFileIds] = React4.useState(/* @__PURE__ */ new Set());
|
|
409
|
+
const thumbEnabledRef = React4.useRef(thumbnailEnabled ?? true);
|
|
410
|
+
const thumbSizesRef = React4.useRef(thumbnailSizes ?? [256, 512]);
|
|
411
|
+
const thumbFileIdsRef = React4.useRef(/* @__PURE__ */ new Set());
|
|
412
|
+
React4.useEffect(() => {
|
|
413
|
+
thumbEnabledRef.current = thumbEnabled;
|
|
414
|
+
}, [thumbEnabled]);
|
|
415
|
+
React4.useEffect(() => {
|
|
416
|
+
thumbSizesRef.current = thumbSizes;
|
|
417
|
+
}, [thumbSizes]);
|
|
418
|
+
React4.useEffect(() => {
|
|
419
|
+
thumbFileIdsRef.current = thumbFileIds;
|
|
420
|
+
}, [thumbFileIds]);
|
|
421
|
+
const pendingImages = React4.useMemo(
|
|
422
|
+
() => files.filter((f2) => f2.type.startsWith("image/") && f2.status === "pending"),
|
|
423
|
+
[files]
|
|
424
|
+
);
|
|
425
|
+
const thumbSelectedCount = React4.useMemo(
|
|
426
|
+
() => pendingImages.filter((f2) => thumbFileIds.has(f2.id)).length,
|
|
427
|
+
[pendingImages, thumbFileIds]
|
|
428
|
+
);
|
|
429
|
+
const handleThumbEnabledToggle = React4.useCallback(() => {
|
|
430
|
+
const newVal = !thumbEnabledRef.current;
|
|
431
|
+
thumbEnabledRef.current = newVal;
|
|
432
|
+
setThumbEnabled(newVal);
|
|
433
|
+
if (newVal) {
|
|
434
|
+
setFiles((prev) => {
|
|
435
|
+
const ids = prev.filter((f2) => f2.type.startsWith("image/") && f2.status === "pending").map((f2) => f2.id);
|
|
436
|
+
if (ids.length > 0) {
|
|
437
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
438
|
+
ids.forEach((id) => next.add(id));
|
|
439
|
+
thumbFileIdsRef.current = next;
|
|
440
|
+
setThumbFileIds(next);
|
|
441
|
+
}
|
|
442
|
+
return prev;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}, []);
|
|
446
|
+
const toggleThumbSize = React4.useCallback((size) => {
|
|
447
|
+
setThumbSizes((prev) => {
|
|
448
|
+
const next = prev.includes(size) ? prev.filter((s) => s !== size) : [...prev, size].sort((a, b) => a - b);
|
|
449
|
+
thumbSizesRef.current = next;
|
|
450
|
+
return next;
|
|
360
451
|
});
|
|
361
|
-
|
|
452
|
+
}, []);
|
|
453
|
+
const toggleFileThumb = React4.useCallback((fileId) => {
|
|
454
|
+
setThumbFileIds((prev) => {
|
|
455
|
+
const next = new Set(prev);
|
|
456
|
+
if (next.has(fileId)) next.delete(fileId);
|
|
457
|
+
else next.add(fileId);
|
|
458
|
+
thumbFileIdsRef.current = next;
|
|
459
|
+
return next;
|
|
460
|
+
});
|
|
461
|
+
}, []);
|
|
462
|
+
const selectAllThumbs = React4.useCallback(() => {
|
|
463
|
+
const ids = pendingImages.map((f2) => f2.id);
|
|
464
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
465
|
+
ids.forEach((id) => next.add(id));
|
|
466
|
+
thumbFileIdsRef.current = next;
|
|
467
|
+
setThumbFileIds(next);
|
|
468
|
+
}, [pendingImages]);
|
|
469
|
+
const deselectAllThumbs = React4.useCallback(() => {
|
|
470
|
+
const pendingSet = new Set(pendingImages.map((f2) => f2.id));
|
|
471
|
+
const next = new Set([...thumbFileIdsRef.current].filter((id) => !pendingSet.has(id)));
|
|
472
|
+
thumbFileIdsRef.current = next;
|
|
473
|
+
setThumbFileIds(next);
|
|
474
|
+
}, [pendingImages]);
|
|
475
|
+
const addFiles = React4.useCallback((incoming) => {
|
|
476
|
+
setQuotaError(null);
|
|
477
|
+
const filtered = maxFileSize > 0 ? incoming.filter((f2) => f2.size <= maxFileSize) : incoming;
|
|
478
|
+
const limited = maxFiles > 0 ? filtered.slice(0, Math.max(0, maxFiles - files.length)) : filtered;
|
|
362
479
|
const mapped = limited.map((f2) => ({
|
|
363
480
|
id: Math.random().toString(36).slice(2),
|
|
364
481
|
file: f2,
|
|
365
482
|
name: f2.name,
|
|
366
483
|
size: f2.size,
|
|
367
|
-
type: f2.type,
|
|
484
|
+
type: f2.type || "application/octet-stream",
|
|
368
485
|
progress: 0,
|
|
369
486
|
status: "pending"
|
|
370
487
|
}));
|
|
371
488
|
setFiles((prev) => [...prev, ...mapped]);
|
|
372
|
-
|
|
489
|
+
if (showThumbnailUI && thumbEnabledRef.current) {
|
|
490
|
+
const imageIds = mapped.filter((f2) => f2.type.startsWith("image/")).map((f2) => f2.id);
|
|
491
|
+
if (imageIds.length > 0) {
|
|
492
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
493
|
+
imageIds.forEach((id) => next.add(id));
|
|
494
|
+
thumbFileIdsRef.current = next;
|
|
495
|
+
setThumbFileIds(next);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}, [files.length, maxFileSize, maxFiles, showThumbnailUI]);
|
|
499
|
+
const removeFile = React4.useCallback((fileId) => {
|
|
500
|
+
setFiles((prev) => prev.filter((f2) => f2.id !== fileId));
|
|
501
|
+
if (showThumbnailUI) {
|
|
502
|
+
const next = new Set(thumbFileIdsRef.current);
|
|
503
|
+
next.delete(fileId);
|
|
504
|
+
thumbFileIdsRef.current = next;
|
|
505
|
+
setThumbFileIds(next);
|
|
506
|
+
}
|
|
507
|
+
}, [showThumbnailUI]);
|
|
373
508
|
const onInputChange = React4.useCallback((e) => {
|
|
374
509
|
const fs = Array.from(e.target.files || []);
|
|
375
510
|
addFiles(fs);
|
|
@@ -390,7 +525,7 @@ var Uploader = ({
|
|
|
390
525
|
update({ status: "uploading", progress: 1 });
|
|
391
526
|
const presign = await client.getPresignedUrl({
|
|
392
527
|
fileName: item.name,
|
|
393
|
-
fileType: item.type,
|
|
528
|
+
fileType: item.type || "application/octet-stream",
|
|
394
529
|
fileSize: item.size,
|
|
395
530
|
projectId,
|
|
396
531
|
folderPath
|
|
@@ -417,14 +552,17 @@ var Uploader = ({
|
|
|
417
552
|
xhr.send(item.file);
|
|
418
553
|
});
|
|
419
554
|
let completionResult = null;
|
|
420
|
-
|
|
555
|
+
const canRecordToDb = autoRecordToDb && (projectId || clientOptions?.projectName || clientOptions?.projectId || clientOptions?.apiKey);
|
|
556
|
+
if (canRecordToDb) {
|
|
421
557
|
try {
|
|
422
558
|
completionResult = await client.completeUpload({
|
|
423
559
|
fileKey: presign.fileKey,
|
|
424
560
|
originalName: item.name,
|
|
425
561
|
size: item.size,
|
|
426
|
-
contentType: item.type,
|
|
427
|
-
projectId
|
|
562
|
+
contentType: item.type || "application/octet-stream",
|
|
563
|
+
projectId,
|
|
564
|
+
url: presign.publicUrl,
|
|
565
|
+
userId
|
|
428
566
|
});
|
|
429
567
|
} catch (e) {
|
|
430
568
|
console.error("Failed to complete upload:", e);
|
|
@@ -448,39 +586,86 @@ var Uploader = ({
|
|
|
448
586
|
}
|
|
449
587
|
const finalFileKey = completionResult?.file?.key || presign.fileKey;
|
|
450
588
|
const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
|
|
589
|
+
const fileThumbEnabled = showThumbnailUI && thumbEnabledRef.current && thumbFileIdsRef.current.has(item.id);
|
|
451
590
|
const result = {
|
|
452
591
|
...item,
|
|
453
592
|
status: "success",
|
|
454
593
|
progress: 100,
|
|
455
|
-
fileKey:
|
|
456
|
-
publicUrl:
|
|
594
|
+
fileKey: finalFileKey,
|
|
595
|
+
publicUrl: finalPublicUrl,
|
|
457
596
|
projectId: presign.projectId,
|
|
458
597
|
apiKeyId: presign.apiKeyId,
|
|
598
|
+
thumbnailEnabled: showThumbnailUI ? fileThumbEnabled : void 0,
|
|
599
|
+
thumbnailSizesSelected: showThumbnailUI ? fileThumbEnabled ? [...thumbSizesRef.current] : [] : void 0,
|
|
459
600
|
// @ts-ignore - allow downstream to access thumbs if present
|
|
460
601
|
thumbnails: thumbs,
|
|
461
602
|
// @ts-ignore - allow downstream to access masterWebp if present
|
|
462
603
|
masterWebp
|
|
463
604
|
};
|
|
464
|
-
update({ status: "success", progress: 100, fileKey:
|
|
605
|
+
update({ status: "success", progress: 100, fileKey: finalFileKey, publicUrl: finalPublicUrl });
|
|
465
606
|
onClientUploadComplete?.(result);
|
|
466
607
|
return result;
|
|
467
608
|
} catch (err) {
|
|
468
|
-
const message = err instanceof Error ? err.message : "Upload failed";
|
|
609
|
+
const message = err instanceof StorageQuotaError ? err.message : err instanceof Error ? err.message : "Upload failed";
|
|
469
610
|
update({ status: "error", error: message });
|
|
470
611
|
throw err;
|
|
471
612
|
}
|
|
472
|
-
}, [client, projectId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete]);
|
|
613
|
+
}, [client, projectId, userId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete, showThumbnailUI]);
|
|
473
614
|
const uploadAll = React4.useCallback(async () => {
|
|
474
615
|
const targets = files.filter((f2) => f2.status === "pending");
|
|
475
616
|
if (!targets.length) return;
|
|
617
|
+
setQuotaError(null);
|
|
476
618
|
setIsUploading(true);
|
|
477
619
|
try {
|
|
620
|
+
try {
|
|
621
|
+
const quota = await client.getQuota();
|
|
622
|
+
if (quota.limitBytes > 0) {
|
|
623
|
+
const totalPendingSize = targets.reduce((sum, f2) => sum + f2.size, 0);
|
|
624
|
+
const remaining = quota.limitBytes - quota.usedBytes;
|
|
625
|
+
if (totalPendingSize > remaining) {
|
|
626
|
+
const fmt = (n) => {
|
|
627
|
+
if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
628
|
+
if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(2)} MB`;
|
|
629
|
+
if (n >= 1024) return `${(n / 1024).toFixed(2)} KB`;
|
|
630
|
+
return `${n} B`;
|
|
631
|
+
};
|
|
632
|
+
const errMsg = `Storage quota exceeded. Remaining: ${fmt(remaining)}, Upload size: ${fmt(totalPendingSize)}`;
|
|
633
|
+
setQuotaError(errMsg);
|
|
634
|
+
targets.forEach((t) => {
|
|
635
|
+
setFiles((prev) => prev.map((f2) => f2.id === t.id ? { ...f2, status: "error", error: errMsg } : f2));
|
|
636
|
+
});
|
|
637
|
+
onError?.(new StorageQuotaError(errMsg, {
|
|
638
|
+
limit: quota.limitBytes,
|
|
639
|
+
used: quota.usedBytes,
|
|
640
|
+
remaining,
|
|
641
|
+
fileSize: totalPendingSize
|
|
642
|
+
}));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
} catch (e) {
|
|
647
|
+
if (e instanceof StorageQuotaError) {
|
|
648
|
+
setQuotaError(e.message);
|
|
649
|
+
targets.forEach((t) => {
|
|
650
|
+
setFiles((prev) => prev.map((f2) => f2.id === t.id ? { ...f2, status: "error", error: e.message } : f2));
|
|
651
|
+
});
|
|
652
|
+
onError?.(e);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
478
656
|
const results = [];
|
|
479
657
|
for (const f2 of targets) {
|
|
480
658
|
try {
|
|
481
659
|
const done = await uploadOne(f2);
|
|
482
660
|
results.push(done);
|
|
483
661
|
} catch (e) {
|
|
662
|
+
if (e instanceof StorageQuotaError) {
|
|
663
|
+
setQuotaError(e.message);
|
|
664
|
+
setFiles((prev) => prev.map(
|
|
665
|
+
(file) => file.status === "pending" ? { ...file, status: "error", error: e.message } : file
|
|
666
|
+
));
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
484
669
|
}
|
|
485
670
|
}
|
|
486
671
|
onComplete?.(results.filter((f2) => f2.status === "success"));
|
|
@@ -489,7 +674,7 @@ var Uploader = ({
|
|
|
489
674
|
} finally {
|
|
490
675
|
setIsUploading(false);
|
|
491
676
|
}
|
|
492
|
-
}, [files, onComplete, onError, uploadOne]);
|
|
677
|
+
}, [files, client, onComplete, onError, uploadOne]);
|
|
493
678
|
React4__namespace.default.useEffect(() => {
|
|
494
679
|
if (autoUpload && files.some((f2) => f2.status === "pending") && !isUploading) {
|
|
495
680
|
const timer = setTimeout(() => {
|
|
@@ -537,15 +722,32 @@ var Uploader = ({
|
|
|
537
722
|
children ?? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
538
723
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
|
|
539
724
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
540
|
-
multiple ? `Up to ${maxFiles} files` : "
|
|
541
|
-
|
|
542
|
-
(maxFileSize / (1024 * 1024)).toFixed(0),
|
|
543
|
-
"MB"
|
|
725
|
+
!multiple ? "Single file" : maxFiles > 0 ? `Up to ${maxFiles} files` : "Multiple files",
|
|
726
|
+
maxFileSize > 0 ? ` \u2022 Max ${(maxFileSize / (1024 * 1024)).toFixed(0)}MB per file` : ""
|
|
544
727
|
] })
|
|
545
728
|
] })
|
|
546
729
|
]
|
|
547
730
|
}
|
|
548
731
|
),
|
|
732
|
+
quotaError && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
733
|
+
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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: [
|
|
734
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
735
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
736
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
737
|
+
] }) }),
|
|
738
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-300", children: quotaError }) }),
|
|
739
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
740
|
+
"button",
|
|
741
|
+
{
|
|
742
|
+
onClick: () => setQuotaError(null),
|
|
743
|
+
className: "text-red-400 hover:text-red-600 dark:hover:text-red-300 transition-colors",
|
|
744
|
+
children: /* @__PURE__ */ jsxRuntime.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: [
|
|
745
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
746
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
747
|
+
] })
|
|
748
|
+
}
|
|
749
|
+
)
|
|
750
|
+
] }) }),
|
|
549
751
|
files.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 space-y-4", children: [
|
|
550
752
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
|
|
551
753
|
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
|
|
@@ -570,12 +772,76 @@ var Uploader = ({
|
|
|
570
772
|
{
|
|
571
773
|
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",
|
|
572
774
|
disabled: isUploading,
|
|
573
|
-
onClick: () =>
|
|
775
|
+
onClick: () => {
|
|
776
|
+
setFiles([]);
|
|
777
|
+
if (showThumbnailUI) {
|
|
778
|
+
const empty = /* @__PURE__ */ new Set();
|
|
779
|
+
thumbFileIdsRef.current = empty;
|
|
780
|
+
setThumbFileIds(empty);
|
|
781
|
+
}
|
|
782
|
+
},
|
|
574
783
|
children: "Clear"
|
|
575
784
|
}
|
|
576
785
|
)
|
|
577
786
|
] })
|
|
578
787
|
] }),
|
|
788
|
+
showThumbnailUI && pendingImages.length > 0 && /* @__PURE__ */ jsxRuntime.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: [
|
|
789
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
790
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
791
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
792
|
+
/* @__PURE__ */ jsxRuntime.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: [
|
|
793
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
|
|
794
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 17 12 22 22 17" }),
|
|
795
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 12 12 17 22 12" })
|
|
796
|
+
] }),
|
|
797
|
+
"Generate Thumbnails"
|
|
798
|
+
] }),
|
|
799
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: thumbEnabled ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
800
|
+
thumbSelectedCount,
|
|
801
|
+
" of ",
|
|
802
|
+
pendingImages.length,
|
|
803
|
+
" image",
|
|
804
|
+
pendingImages.length !== 1 ? "s" : "",
|
|
805
|
+
" selected",
|
|
806
|
+
" \xB7 ",
|
|
807
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: selectAllThumbs, className: "text-purple-600 hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-300", children: "Select all" }),
|
|
808
|
+
" \xB7 ",
|
|
809
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: deselectAllThumbs, className: "text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300", children: "Deselect all" })
|
|
810
|
+
] }) : "Disabled for all images" })
|
|
811
|
+
] }),
|
|
812
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
813
|
+
"button",
|
|
814
|
+
{
|
|
815
|
+
type: "button",
|
|
816
|
+
onClick: handleThumbEnabledToggle,
|
|
817
|
+
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"}`,
|
|
818
|
+
role: "switch",
|
|
819
|
+
"aria-checked": thumbEnabled,
|
|
820
|
+
children: /* @__PURE__ */ jsxRuntime.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"}` })
|
|
821
|
+
}
|
|
822
|
+
)
|
|
823
|
+
] }),
|
|
824
|
+
thumbEnabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
825
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-400 mb-2", children: "Sizes:" }),
|
|
826
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 flex-wrap", children: THUMBNAIL_SIZE_OPTIONS.map((size) => {
|
|
827
|
+
const active = thumbSizes.includes(size);
|
|
828
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
829
|
+
"button",
|
|
830
|
+
{
|
|
831
|
+
type: "button",
|
|
832
|
+
onClick: () => toggleThumbSize(size),
|
|
833
|
+
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"}`,
|
|
834
|
+
children: [
|
|
835
|
+
size,
|
|
836
|
+
"px"
|
|
837
|
+
]
|
|
838
|
+
},
|
|
839
|
+
size
|
|
840
|
+
);
|
|
841
|
+
}) }),
|
|
842
|
+
thumbSizes.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-amber-600 dark:text-amber-400 mt-2", children: "Select at least one size." })
|
|
843
|
+
] })
|
|
844
|
+
] }),
|
|
579
845
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
|
|
580
846
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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: [
|
|
581
847
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
@@ -599,7 +865,19 @@ var Uploader = ({
|
|
|
599
865
|
className: `h-full transition-all duration-300 ${f2.status === "success" ? "bg-green-500" : "bg-blue-500"}`,
|
|
600
866
|
style: { width: `${f2.progress}%` }
|
|
601
867
|
}
|
|
602
|
-
) })
|
|
868
|
+
) }),
|
|
869
|
+
showThumbnailUI && thumbEnabled && f2.type.startsWith("image/") && f2.status === "pending" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mt-1.5 inline-flex items-center gap-1.5 cursor-pointer select-none", children: [
|
|
870
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
871
|
+
"input",
|
|
872
|
+
{
|
|
873
|
+
type: "checkbox",
|
|
874
|
+
checked: thumbFileIds.has(f2.id),
|
|
875
|
+
onChange: () => toggleFileThumb(f2.id),
|
|
876
|
+
className: "w-3.5 h-3.5 rounded accent-purple-600 cursor-pointer"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Generate thumbnail" })
|
|
880
|
+
] })
|
|
603
881
|
] }),
|
|
604
882
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 ml-4", children: [
|
|
605
883
|
f2.status === "success" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -633,7 +911,7 @@ var Uploader = ({
|
|
|
633
911
|
{
|
|
634
912
|
onClick: (e) => {
|
|
635
913
|
e.stopPropagation();
|
|
636
|
-
|
|
914
|
+
removeFile(f2.id);
|
|
637
915
|
},
|
|
638
916
|
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",
|
|
639
917
|
title: "Remove",
|
|
@@ -717,10 +995,18 @@ var ProjectFilesWidget = ({
|
|
|
717
995
|
"content-type": "application/json",
|
|
718
996
|
...clientOptions?.headers || {}
|
|
719
997
|
};
|
|
720
|
-
|
|
998
|
+
const token = clientOptions?.getToken?.();
|
|
999
|
+
if (token) {
|
|
1000
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
1001
|
+
if (clientOptions.projectName) {
|
|
1002
|
+
headers["x-project-name"] = clientOptions.projectName;
|
|
1003
|
+
} else if (clientOptions.projectId) {
|
|
1004
|
+
headers["x-project-id"] = clientOptions.projectId;
|
|
1005
|
+
}
|
|
1006
|
+
} else if (clientOptions?.apiKey) {
|
|
721
1007
|
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
722
1008
|
if (keyHeader === "authorization") {
|
|
723
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("
|
|
1009
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
724
1010
|
} else {
|
|
725
1011
|
headers[keyHeader] = clientOptions.apiKey;
|
|
726
1012
|
}
|
|
@@ -841,6 +1127,19 @@ var ProjectFilesWidget = ({
|
|
|
841
1127
|
|
|
842
1128
|
// src/projectConnect.ts
|
|
843
1129
|
var f = (globalThis.fetch || fetch).bind(globalThis);
|
|
1130
|
+
function buildInit(opts, extra) {
|
|
1131
|
+
const headers = {
|
|
1132
|
+
...extra?.headers || {}
|
|
1133
|
+
};
|
|
1134
|
+
if (opts.token) {
|
|
1135
|
+
headers["authorization"] = `Bearer ${opts.token}`;
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
...extra,
|
|
1139
|
+
headers,
|
|
1140
|
+
credentials: opts.token ? "same-origin" : "include"
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
844
1143
|
async function httpJson(input, init, useFetch) {
|
|
845
1144
|
const doFetch = useFetch ?? f;
|
|
846
1145
|
const res = await doFetch(input, init);
|
|
@@ -848,7 +1147,7 @@ async function httpJson(input, init, useFetch) {
|
|
|
848
1147
|
let msg = `Request failed (${res.status})`;
|
|
849
1148
|
try {
|
|
850
1149
|
const j = await res.json();
|
|
851
|
-
msg = j?.error || msg;
|
|
1150
|
+
msg = j?.error || j?.message || msg;
|
|
852
1151
|
} catch {
|
|
853
1152
|
}
|
|
854
1153
|
throw new Error(msg);
|
|
@@ -859,7 +1158,7 @@ async function listProjects(opts) {
|
|
|
859
1158
|
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
|
|
860
1159
|
const data = await httpJson(
|
|
861
1160
|
url,
|
|
862
|
-
{ method: "GET"
|
|
1161
|
+
buildInit(opts, { method: "GET" }),
|
|
863
1162
|
opts.fetch
|
|
864
1163
|
);
|
|
865
1164
|
return data?.projects ?? [];
|
|
@@ -868,53 +1167,74 @@ async function createProject(opts) {
|
|
|
868
1167
|
const base = opts.baseUrl.replace(/\/$/, "");
|
|
869
1168
|
const created = await httpJson(
|
|
870
1169
|
`${base}/api/projects`,
|
|
871
|
-
{
|
|
1170
|
+
buildInit(opts, {
|
|
872
1171
|
method: "POST",
|
|
873
|
-
credentials: "include",
|
|
874
1172
|
headers: { "content-type": "application/json" },
|
|
875
|
-
body: JSON.stringify({
|
|
876
|
-
|
|
1173
|
+
body: JSON.stringify({
|
|
1174
|
+
name: opts.name,
|
|
1175
|
+
isInHouse: opts.isInHouse,
|
|
1176
|
+
freeStorageLimitBytes: opts.freeStorageLimitBytes
|
|
1177
|
+
})
|
|
1178
|
+
}),
|
|
877
1179
|
opts.fetch
|
|
878
1180
|
);
|
|
879
1181
|
const projectId = created?.project?.id;
|
|
880
1182
|
if (!projectId) throw new Error("Failed to create project");
|
|
1183
|
+
return { projectId, project: created?.project };
|
|
1184
|
+
}
|
|
1185
|
+
async function createProjectWithApiKey(opts) {
|
|
1186
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
1187
|
+
const { projectId } = await createProject(opts);
|
|
881
1188
|
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
882
1189
|
const key = await httpJson(
|
|
883
1190
|
`${base}/api/projects/${projectId}/keys`,
|
|
884
|
-
{
|
|
1191
|
+
buildInit(opts, {
|
|
885
1192
|
method: "POST",
|
|
886
|
-
credentials: "include",
|
|
887
1193
|
headers: { "content-type": "application/json" },
|
|
888
1194
|
body: JSON.stringify({ name: keyLabel })
|
|
889
|
-
},
|
|
1195
|
+
}),
|
|
890
1196
|
opts.fetch
|
|
891
1197
|
);
|
|
892
|
-
const plaintext = key?.key?.plaintext;
|
|
1198
|
+
const plaintext = key?.key?.secret ?? key?.key?.plaintext;
|
|
893
1199
|
if (!plaintext) throw new Error("Failed to create API key");
|
|
894
1200
|
return { apiKey: plaintext, projectId };
|
|
895
1201
|
}
|
|
1202
|
+
async function updateProject(opts) {
|
|
1203
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
1204
|
+
const body = {};
|
|
1205
|
+
if (opts.isInHouse !== void 0) body.isInHouse = opts.isInHouse;
|
|
1206
|
+
if (opts.freeStorageLimitBytes !== void 0) body.freeStorageLimitBytes = opts.freeStorageLimitBytes;
|
|
1207
|
+
await httpJson(
|
|
1208
|
+
`${base}/api/projects/${opts.projectId}`,
|
|
1209
|
+
buildInit(opts, {
|
|
1210
|
+
method: "PUT",
|
|
1211
|
+
headers: { "content-type": "application/json" },
|
|
1212
|
+
body: JSON.stringify(body)
|
|
1213
|
+
}),
|
|
1214
|
+
opts.fetch
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
896
1217
|
async function connectProject(opts) {
|
|
897
1218
|
const base = opts.baseUrl.replace(/\/$/, "");
|
|
898
1219
|
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
899
1220
|
const key = await httpJson(
|
|
900
1221
|
`${base}/api/projects/${opts.projectId}/keys`,
|
|
901
|
-
{
|
|
1222
|
+
buildInit(opts, {
|
|
902
1223
|
method: "POST",
|
|
903
|
-
credentials: "include",
|
|
904
1224
|
headers: { "content-type": "application/json" },
|
|
905
1225
|
body: JSON.stringify({ name: keyLabel })
|
|
906
|
-
},
|
|
1226
|
+
}),
|
|
907
1227
|
opts.fetch
|
|
908
1228
|
);
|
|
909
|
-
const plaintext = key?.key?.plaintext;
|
|
1229
|
+
const plaintext = key?.key?.secret ?? key?.key?.plaintext;
|
|
910
1230
|
if (!plaintext) throw new Error("Failed to create API key");
|
|
911
1231
|
return { apiKey: plaintext };
|
|
912
1232
|
}
|
|
913
1233
|
function addApiKeyManually(apiKey) {
|
|
914
1234
|
const k = (apiKey || "").trim();
|
|
915
1235
|
if (!k) throw new Error("API key is required");
|
|
916
|
-
if (!/^
|
|
917
|
-
console.warn("Provided API key does not match expected format
|
|
1236
|
+
if (!/^upf_[A-Za-z0-9_-]+/.test(k)) {
|
|
1237
|
+
console.warn("Provided API key does not match expected format upf_*");
|
|
918
1238
|
}
|
|
919
1239
|
return { apiKey: k };
|
|
920
1240
|
}
|
|
@@ -998,7 +1318,7 @@ function ConnectProjectDialog(props) {
|
|
|
998
1318
|
setLoading(true);
|
|
999
1319
|
setError(null);
|
|
1000
1320
|
if (!newProjectName.trim()) throw new Error("Project name is required");
|
|
1001
|
-
const { apiKey, projectId } = await
|
|
1321
|
+
const { apiKey, projectId } = await createProjectWithApiKey({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
|
|
1002
1322
|
onConnected(apiKey, { projectId, source: "new" });
|
|
1003
1323
|
onOpenChange(false);
|
|
1004
1324
|
resetState();
|
|
@@ -1221,6 +1541,23 @@ var IconLayers = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { xml
|
|
|
1221
1541
|
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 17 12 22 22 17" }),
|
|
1222
1542
|
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 12 12 17 22 12" })
|
|
1223
1543
|
] });
|
|
1544
|
+
var VisuallyHidden = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1545
|
+
"span",
|
|
1546
|
+
{
|
|
1547
|
+
style: {
|
|
1548
|
+
position: "absolute",
|
|
1549
|
+
width: "1px",
|
|
1550
|
+
height: "1px",
|
|
1551
|
+
padding: "0",
|
|
1552
|
+
margin: "-1px",
|
|
1553
|
+
overflow: "hidden",
|
|
1554
|
+
clip: "rect(0, 0, 0, 0)",
|
|
1555
|
+
whiteSpace: "nowrap",
|
|
1556
|
+
border: "0"
|
|
1557
|
+
},
|
|
1558
|
+
children
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1224
1561
|
function ImageManager(props) {
|
|
1225
1562
|
const {
|
|
1226
1563
|
open,
|
|
@@ -1228,6 +1565,7 @@ function ImageManager(props) {
|
|
|
1228
1565
|
clientOptions,
|
|
1229
1566
|
projectId,
|
|
1230
1567
|
folderPath,
|
|
1568
|
+
userId,
|
|
1231
1569
|
title,
|
|
1232
1570
|
description,
|
|
1233
1571
|
className,
|
|
@@ -1241,7 +1579,9 @@ function ImageManager(props) {
|
|
|
1241
1579
|
maxFiles = 10,
|
|
1242
1580
|
mode = "full",
|
|
1243
1581
|
showDelete = true,
|
|
1244
|
-
refreshInterval = 5e3
|
|
1582
|
+
refreshInterval = 5e3,
|
|
1583
|
+
stayOnUploadAfterComplete = false,
|
|
1584
|
+
thumbnailConfig
|
|
1245
1585
|
} = props;
|
|
1246
1586
|
const [activeView, setActiveView] = React4__namespace.useState(mode === "upload" ? "upload" : "browse");
|
|
1247
1587
|
const [loading, setLoading] = React4__namespace.useState(false);
|
|
@@ -1259,6 +1599,7 @@ function ImageManager(props) {
|
|
|
1259
1599
|
const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4__namespace.useState(null);
|
|
1260
1600
|
const [selectedThumbnailForFile, setSelectedThumbnailForFile] = React4__namespace.useState(null);
|
|
1261
1601
|
const [previewFile, setPreviewFile] = React4__namespace.useState(null);
|
|
1602
|
+
const [generatingThumbnails, setGeneratingThumbnails] = React4__namespace.useState(false);
|
|
1262
1603
|
const lastUpdatedRef = React4__namespace.useRef(null);
|
|
1263
1604
|
const isFetchingRef = React4__namespace.useRef(false);
|
|
1264
1605
|
const deduplicateFiles = React4__namespace.useCallback((files2) => {
|
|
@@ -1281,10 +1622,11 @@ function ImageManager(props) {
|
|
|
1281
1622
|
const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1282
1623
|
const effectiveFolderPath = currentPathPrefix || folderPath;
|
|
1283
1624
|
const [filesData, foldersData] = await Promise.all([
|
|
1284
|
-
|
|
1625
|
+
fetchProjectFiles(clientOptions, effectiveFolderPath),
|
|
1285
1626
|
client.getFolders(currentFolderId)
|
|
1286
1627
|
]);
|
|
1287
|
-
const {
|
|
1628
|
+
const { files: rawFiles, updatedAt } = filesData;
|
|
1629
|
+
const list = rawFiles;
|
|
1288
1630
|
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
1289
1631
|
}
|
|
1290
1632
|
setFiles(deduplicateFiles(list));
|
|
@@ -1310,30 +1652,6 @@ function ImageManager(props) {
|
|
|
1310
1652
|
load();
|
|
1311
1653
|
}
|
|
1312
1654
|
}, [open, activeView, load, mode]);
|
|
1313
|
-
React4__namespace.useEffect(() => {
|
|
1314
|
-
if (!open) return;
|
|
1315
|
-
const client = new UpfilesClient(clientOptions);
|
|
1316
|
-
const url = client.getEventsUrl(projectId);
|
|
1317
|
-
const eventSource = new EventSource(url);
|
|
1318
|
-
eventSource.onmessage = (event) => {
|
|
1319
|
-
try {
|
|
1320
|
-
const payload = JSON.parse(event.data);
|
|
1321
|
-
if (payload.type === "file:uploaded") {
|
|
1322
|
-
const newFile = payload.data.file;
|
|
1323
|
-
setFiles((prev) => {
|
|
1324
|
-
if (prev.find((f2) => f2.key === newFile.key)) return prev;
|
|
1325
|
-
return deduplicateFiles([newFile, ...prev]);
|
|
1326
|
-
});
|
|
1327
|
-
} else if (payload.type === "file:deleted") {
|
|
1328
|
-
const deletedKey = payload.data.fileKey || payload.data.file?.key;
|
|
1329
|
-
setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
|
|
1330
|
-
}
|
|
1331
|
-
} catch (e) {
|
|
1332
|
-
console.error("SSE Error parsing message", e);
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
return () => eventSource.close();
|
|
1336
|
-
}, [open, clientOptions, projectId]);
|
|
1337
1655
|
const visibleFiles = React4__namespace.useMemo(() => {
|
|
1338
1656
|
const q = query.trim().toLowerCase();
|
|
1339
1657
|
return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
@@ -1357,10 +1675,18 @@ function ImageManager(props) {
|
|
|
1357
1675
|
"content-type": "application/json",
|
|
1358
1676
|
...clientOptions?.headers || {}
|
|
1359
1677
|
};
|
|
1360
|
-
|
|
1678
|
+
const token = clientOptions?.getToken?.();
|
|
1679
|
+
if (token) {
|
|
1680
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
1681
|
+
if (clientOptions.projectName) {
|
|
1682
|
+
headers["x-project-name"] = clientOptions.projectName;
|
|
1683
|
+
} else if (clientOptions.projectId) {
|
|
1684
|
+
headers["x-project-id"] = clientOptions.projectId;
|
|
1685
|
+
}
|
|
1686
|
+
} else if (clientOptions?.apiKey) {
|
|
1361
1687
|
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1362
1688
|
if (keyHeader === "authorization") {
|
|
1363
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("
|
|
1689
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1364
1690
|
} else {
|
|
1365
1691
|
headers[keyHeader] = clientOptions.apiKey;
|
|
1366
1692
|
}
|
|
@@ -1420,35 +1746,40 @@ function ImageManager(props) {
|
|
|
1420
1746
|
});
|
|
1421
1747
|
return options;
|
|
1422
1748
|
}, [breadcrumbs, currentFolderId, folders]);
|
|
1749
|
+
const handleThumbnailClick = React4__namespace.useCallback(async (f2) => {
|
|
1750
|
+
if (f2.thumbnails && f2.thumbnails.length > 0) {
|
|
1751
|
+
setThumbnailSelectorFile(f2);
|
|
1752
|
+
setSelectedThumbnailForFile(null);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
setGeneratingThumbnails(true);
|
|
1756
|
+
setThumbnailSelectorFile(f2);
|
|
1757
|
+
setSelectedThumbnailForFile(null);
|
|
1758
|
+
try {
|
|
1759
|
+
const client = new UpfilesClient(clientOptions);
|
|
1760
|
+
const result = await client.generateThumbnails(f2.key);
|
|
1761
|
+
const updatedFile = { ...f2, thumbnails: result.thumbnails };
|
|
1762
|
+
setThumbnailSelectorFile(updatedFile);
|
|
1763
|
+
setFiles((prev) => prev.map((file) => file.key === f2.key ? updatedFile : file));
|
|
1764
|
+
} catch (e) {
|
|
1765
|
+
console.error("Failed to generate thumbnails:", e);
|
|
1766
|
+
} finally {
|
|
1767
|
+
setGeneratingThumbnails(false);
|
|
1768
|
+
}
|
|
1769
|
+
}, [clientOptions]);
|
|
1423
1770
|
const [selectedUploadFolder, setSelectedUploadFolder] = React4__namespace.useState("current");
|
|
1424
1771
|
const activeBreadcrumbPath = React4__namespace.useMemo(() => {
|
|
1425
1772
|
return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1426
1773
|
}, [breadcrumbs]);
|
|
1427
1774
|
return /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Root, { open, onOpenChange, children: [
|
|
1428
1775
|
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
|
|
1429
|
-
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/60
|
|
1776
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/60 z-[9999]" }),
|
|
1430
1777
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1431
1778
|
Dialog2__namespace.Content,
|
|
1432
1779
|
{
|
|
1433
|
-
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-[
|
|
1780
|
+
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 ?? ""}`,
|
|
1434
1781
|
children: [
|
|
1435
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1436
|
-
Dialog2__namespace.Title,
|
|
1437
|
-
{
|
|
1438
|
-
style: {
|
|
1439
|
-
position: "absolute",
|
|
1440
|
-
width: "1px",
|
|
1441
|
-
height: "1px",
|
|
1442
|
-
padding: "0",
|
|
1443
|
-
margin: "-1px",
|
|
1444
|
-
overflow: "hidden",
|
|
1445
|
-
clip: "rect(0, 0, 0, 0)",
|
|
1446
|
-
whiteSpace: "nowrap",
|
|
1447
|
-
border: "0"
|
|
1448
|
-
},
|
|
1449
|
-
children: title ?? "Media Library"
|
|
1450
|
-
}
|
|
1451
|
-
),
|
|
1782
|
+
/* @__PURE__ */ jsxRuntime.jsx(VisuallyHidden, { children: /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { children: title ?? "Media Library" }) }),
|
|
1452
1783
|
/* @__PURE__ */ jsxRuntime.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: [
|
|
1453
1784
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6", children: [
|
|
1454
1785
|
/* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
|
|
@@ -1555,16 +1886,27 @@ function ImageManager(props) {
|
|
|
1555
1886
|
folder.id
|
|
1556
1887
|
)),
|
|
1557
1888
|
visibleFiles.map((f2) => {
|
|
1558
|
-
const
|
|
1559
|
-
const
|
|
1560
|
-
const thumbUrl = bestThumb?.url || f2.url;
|
|
1889
|
+
const displayUrl = f2.url;
|
|
1890
|
+
const hasThumbnails = f2.thumbnails && f2.thumbnails.length > 0;
|
|
1561
1891
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1562
1892
|
"div",
|
|
1563
1893
|
{
|
|
1564
1894
|
onClick: () => setPreviewFile(f2),
|
|
1565
1895
|
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",
|
|
1566
1896
|
children: [
|
|
1567
|
-
/* @__PURE__ */ jsxRuntime.jsx("img", { src:
|
|
1897
|
+
/* @__PURE__ */ jsxRuntime.jsx("img", { src: displayUrl, alt: f2.originalName, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
|
|
1898
|
+
f2.contentType?.startsWith("image/") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1899
|
+
"button",
|
|
1900
|
+
{
|
|
1901
|
+
onClick: (e) => {
|
|
1902
|
+
e.stopPropagation();
|
|
1903
|
+
handleThumbnailClick(f2);
|
|
1904
|
+
},
|
|
1905
|
+
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"}`,
|
|
1906
|
+
title: hasThumbnails ? "View Thumbnails" : "Generate Thumbnails",
|
|
1907
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(IconLayers, { className: "w-4 h-4" })
|
|
1908
|
+
}
|
|
1909
|
+
),
|
|
1568
1910
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
|
|
1569
1911
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1570
1912
|
"button",
|
|
@@ -1585,19 +1927,6 @@ function ImageManager(props) {
|
|
|
1585
1927
|
children: "Select"
|
|
1586
1928
|
}
|
|
1587
1929
|
),
|
|
1588
|
-
fWithThumbs.thumbnails && fWithThumbs.thumbnails.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1589
|
-
"button",
|
|
1590
|
-
{
|
|
1591
|
-
onClick: (e) => {
|
|
1592
|
-
e.stopPropagation();
|
|
1593
|
-
setThumbnailSelectorFile(f2);
|
|
1594
|
-
setSelectedThumbnailForFile(null);
|
|
1595
|
-
},
|
|
1596
|
-
className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-purple-500 backdrop-blur-md transition-colors",
|
|
1597
|
-
title: "Select Thumbnail",
|
|
1598
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(IconLayers, { className: "w-4 h-4" })
|
|
1599
|
-
}
|
|
1600
|
-
),
|
|
1601
1930
|
showDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1602
1931
|
"button",
|
|
1603
1932
|
{
|
|
@@ -1623,7 +1952,7 @@ function ImageManager(props) {
|
|
|
1623
1952
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
|
|
1624
1953
|
] })
|
|
1625
1954
|
] }),
|
|
1626
|
-
activeView === "upload" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
|
|
1955
|
+
activeView === "upload" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
|
|
1627
1956
|
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
|
|
1628
1957
|
/* @__PURE__ */ jsxRuntime.jsx(IconUploadCloud, { className: "text-blue-500" }),
|
|
1629
1958
|
"Upload Files"
|
|
@@ -1648,15 +1977,28 @@ function ImageManager(props) {
|
|
|
1648
1977
|
{
|
|
1649
1978
|
clientOptions,
|
|
1650
1979
|
projectId,
|
|
1980
|
+
userId,
|
|
1651
1981
|
folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
|
|
1652
1982
|
accept: ["image/*"],
|
|
1653
1983
|
maxFileSize,
|
|
1654
1984
|
maxFiles,
|
|
1655
1985
|
autoRecordToDb,
|
|
1656
|
-
fetchThumbnails,
|
|
1657
|
-
autoUpload:
|
|
1658
|
-
|
|
1986
|
+
fetchThumbnails: false,
|
|
1987
|
+
autoUpload: false,
|
|
1988
|
+
thumbnailEnabled: thumbnailConfig?.enabled ?? true,
|
|
1989
|
+
thumbnailSizes: thumbnailConfig?.sizes,
|
|
1990
|
+
onClientUploadComplete: async (file) => {
|
|
1659
1991
|
if (file.status === "success" && file.publicUrl && file.fileKey) {
|
|
1992
|
+
let thumbnails = file.thumbnails;
|
|
1993
|
+
if (file.thumbnailEnabled && file.thumbnailSizesSelected && file.thumbnailSizesSelected.length > 0 && file.type?.startsWith("image/")) {
|
|
1994
|
+
try {
|
|
1995
|
+
const client = new UpfilesClient(clientOptions);
|
|
1996
|
+
const result = await client.generateThumbnails(file.fileKey, file.thumbnailSizesSelected);
|
|
1997
|
+
thumbnails = result.thumbnails;
|
|
1998
|
+
} catch (e) {
|
|
1999
|
+
console.warn("Failed to generate thumbnails:", e);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1660
2002
|
const newItem = {
|
|
1661
2003
|
key: file.fileKey,
|
|
1662
2004
|
originalName: file.name,
|
|
@@ -1664,7 +2006,7 @@ function ImageManager(props) {
|
|
|
1664
2006
|
contentType: file.type,
|
|
1665
2007
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1666
2008
|
url: file.publicUrl,
|
|
1667
|
-
thumbnails
|
|
2009
|
+
thumbnails
|
|
1668
2010
|
};
|
|
1669
2011
|
setFiles((prev) => {
|
|
1670
2012
|
if (prev.find((f2) => f2.key === newItem.key)) return prev;
|
|
@@ -1673,11 +2015,13 @@ function ImageManager(props) {
|
|
|
1673
2015
|
}
|
|
1674
2016
|
},
|
|
1675
2017
|
onComplete: () => {
|
|
1676
|
-
if (mode === "full") {
|
|
2018
|
+
if (mode === "full" && !stayOnUploadAfterComplete) {
|
|
1677
2019
|
setTimeout(() => {
|
|
1678
2020
|
setActiveView("browse");
|
|
1679
2021
|
load();
|
|
1680
2022
|
}, 1e3);
|
|
2023
|
+
} else {
|
|
2024
|
+
load();
|
|
1681
2025
|
}
|
|
1682
2026
|
},
|
|
1683
2027
|
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",
|
|
@@ -1703,8 +2047,8 @@ function ImageManager(props) {
|
|
|
1703
2047
|
)
|
|
1704
2048
|
] }),
|
|
1705
2049
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
|
|
1706
|
-
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40 z-[
|
|
1707
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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-[
|
|
2050
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40 z-[10010]" }),
|
|
2051
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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: [
|
|
1708
2052
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
|
|
1709
2053
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Description, { id: "new-folder-description", className: "sr-only", children: "Create a new folder to organize your files" }),
|
|
1710
2054
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1733,8 +2077,8 @@ function ImageManager(props) {
|
|
|
1733
2077
|
] })
|
|
1734
2078
|
] }) }),
|
|
1735
2079
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
|
|
1736
|
-
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40 z-[
|
|
1737
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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-[
|
|
2080
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40 z-[10020]" }),
|
|
2081
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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: [
|
|
1738
2082
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
|
|
1739
2083
|
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Description, { id: "delete-file-description", className: "text-gray-600 mb-6", children: [
|
|
1740
2084
|
"Are you sure you want to delete ",
|
|
@@ -1767,8 +2111,8 @@ function ImageManager(props) {
|
|
|
1767
2111
|
] })
|
|
1768
2112
|
] }) }),
|
|
1769
2113
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: !!thumbnailSelectorFile, onOpenChange: (open2) => !open2 && setThumbnailSelectorFile(null), children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
|
|
1770
|
-
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/50 z-[
|
|
1771
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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-[
|
|
2114
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/50 z-[10030]" }),
|
|
2115
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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: [
|
|
1772
2116
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 border-b border-gray-200", children: [
|
|
1773
2117
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Select Thumbnail" }),
|
|
1774
2118
|
/* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Description, { id: "thumbnail-selector-description", className: "text-gray-600", children: [
|
|
@@ -1780,60 +2124,66 @@ function ImageManager(props) {
|
|
|
1780
2124
|
] })
|
|
1781
2125
|
] })
|
|
1782
2126
|
] }),
|
|
1783
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1784
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1785
|
-
"
|
|
1786
|
-
{
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
]
|
|
1806
|
-
}
|
|
1807
|
-
),
|
|
1808
|
-
thumbnailSelectorFile?.thumbnails?.map((thumbnail) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1809
|
-
"div",
|
|
1810
|
-
{
|
|
1811
|
-
onClick: () => setSelectedThumbnailForFile(thumbnail),
|
|
1812
|
-
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"}`,
|
|
1813
|
-
children: [
|
|
1814
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
|
|
1815
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1816
|
-
"img",
|
|
1817
|
-
{
|
|
1818
|
-
src: thumbnail.url,
|
|
1819
|
-
alt: `${thumbnail.size}px thumbnail`,
|
|
1820
|
-
className: "w-full h-full object-cover"
|
|
1821
|
-
}
|
|
1822
|
-
),
|
|
1823
|
-
selectedThumbnailForFile?.id === thumbnail.id && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.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" }) }) }) })
|
|
1824
|
-
] }),
|
|
1825
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 bg-white", children: [
|
|
1826
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium text-sm text-gray-900", children: [
|
|
1827
|
-
thumbnail.size,
|
|
1828
|
-
"px"
|
|
2127
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 overflow-y-auto max-h-[60vh]", children: [
|
|
2128
|
+
generatingThumbnails && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-3 py-8 text-gray-500", children: [
|
|
2129
|
+
/* @__PURE__ */ jsxRuntime.jsx(IconLoader, { className: "w-5 h-5 animate-spin" }),
|
|
2130
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Generating thumbnails..." })
|
|
2131
|
+
] }),
|
|
2132
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 ${generatingThumbnails ? "opacity-50 pointer-events-none" : ""}`, children: [
|
|
2133
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2134
|
+
"div",
|
|
2135
|
+
{
|
|
2136
|
+
onClick: () => setSelectedThumbnailForFile(null),
|
|
2137
|
+
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"}`,
|
|
2138
|
+
children: [
|
|
2139
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
|
|
2140
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2141
|
+
"img",
|
|
2142
|
+
{
|
|
2143
|
+
src: thumbnailSelectorFile?.url,
|
|
2144
|
+
alt: "Original",
|
|
2145
|
+
className: "w-full h-full object-cover"
|
|
2146
|
+
}
|
|
2147
|
+
),
|
|
2148
|
+
selectedThumbnailForFile === null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.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" }) }) }) })
|
|
1829
2149
|
] }),
|
|
1830
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2150
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 bg-white", children: [
|
|
2151
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-sm text-gray-900", children: "Original" }),
|
|
2152
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: "Full size" })
|
|
2153
|
+
] })
|
|
2154
|
+
]
|
|
2155
|
+
}
|
|
2156
|
+
),
|
|
2157
|
+
thumbnailSelectorFile?.thumbnails?.map((thumbnail) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2158
|
+
"div",
|
|
2159
|
+
{
|
|
2160
|
+
onClick: () => setSelectedThumbnailForFile(thumbnail),
|
|
2161
|
+
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"}`,
|
|
2162
|
+
children: [
|
|
2163
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
|
|
2164
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2165
|
+
"img",
|
|
2166
|
+
{
|
|
2167
|
+
src: thumbnail.url,
|
|
2168
|
+
alt: `${thumbnail.size}px thumbnail`,
|
|
2169
|
+
className: "w-full h-full object-cover"
|
|
2170
|
+
}
|
|
2171
|
+
),
|
|
2172
|
+
selectedThumbnailForFile?.id === thumbnail.id && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.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" }) }) }) })
|
|
2173
|
+
] }),
|
|
2174
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 bg-white", children: [
|
|
2175
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium text-sm text-gray-900", children: [
|
|
2176
|
+
thumbnail.size,
|
|
2177
|
+
"px"
|
|
2178
|
+
] }),
|
|
2179
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: thumbnail.sizeType || "Thumbnail" })
|
|
2180
|
+
] })
|
|
2181
|
+
]
|
|
2182
|
+
},
|
|
2183
|
+
thumbnail.id
|
|
2184
|
+
))
|
|
2185
|
+
] })
|
|
2186
|
+
] }),
|
|
1837
2187
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 border-t border-gray-200 flex justify-end gap-3", children: [
|
|
1838
2188
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1839
2189
|
"button",
|
|
@@ -1872,8 +2222,8 @@ function ImageManager(props) {
|
|
|
1872
2222
|
] })
|
|
1873
2223
|
] }) }),
|
|
1874
2224
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: !!previewFile, onOpenChange: (open2) => !open2 && setPreviewFile(null), children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
|
|
1875
|
-
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/90 z-[
|
|
1876
|
-
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[90vh] z-[
|
|
2225
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/90 z-[10040]", onClick: () => setPreviewFile(null) }),
|
|
2226
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.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__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full flex flex-col", children: [
|
|
1877
2227
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
|
|
1878
2228
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-white", children: [
|
|
1879
2229
|
/* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-xl font-semibold mb-1", children: previewFile?.originalName }),
|
|
@@ -1945,14 +2295,15 @@ function ImageManager(props) {
|
|
|
1945
2295
|
children: "Open in New Tab"
|
|
1946
2296
|
}
|
|
1947
2297
|
),
|
|
1948
|
-
previewFile?.
|
|
2298
|
+
previewFile?.contentType?.startsWith("image/") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1949
2299
|
"button",
|
|
1950
2300
|
{
|
|
1951
2301
|
onClick: (e) => {
|
|
1952
2302
|
e.stopPropagation();
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2303
|
+
if (previewFile) {
|
|
2304
|
+
handleThumbnailClick(previewFile);
|
|
2305
|
+
setPreviewFile(null);
|
|
2306
|
+
}
|
|
1956
2307
|
},
|
|
1957
2308
|
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",
|
|
1958
2309
|
children: [
|
|
@@ -1966,10 +2317,142 @@ function ImageManager(props) {
|
|
|
1966
2317
|
] }) })
|
|
1967
2318
|
] });
|
|
1968
2319
|
}
|
|
2320
|
+
function useUpfilesInhouse(config) {
|
|
2321
|
+
const {
|
|
2322
|
+
baseUrl,
|
|
2323
|
+
getToken,
|
|
2324
|
+
userId,
|
|
2325
|
+
projectName = "default",
|
|
2326
|
+
enabled = true
|
|
2327
|
+
} = config;
|
|
2328
|
+
const [state, setState] = React4.useState({ clientOptions: null, projectId: null, loading: false, error: null });
|
|
2329
|
+
const [retryCount, setRetryCount] = React4.useState(0);
|
|
2330
|
+
const [token, setToken] = React4.useState(
|
|
2331
|
+
typeof getToken === "string" ? getToken : null
|
|
2332
|
+
);
|
|
2333
|
+
const getTokenFn = React4.useCallback(() => {
|
|
2334
|
+
return typeof getToken === "function" ? getToken() : getToken;
|
|
2335
|
+
}, [getToken]);
|
|
2336
|
+
React4.useEffect(() => {
|
|
2337
|
+
let cancelled = false;
|
|
2338
|
+
async function resolveToken() {
|
|
2339
|
+
const next = await getTokenFn();
|
|
2340
|
+
if (!cancelled) {
|
|
2341
|
+
setToken(next ?? null);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
resolveToken();
|
|
2345
|
+
window.addEventListener("focus", resolveToken);
|
|
2346
|
+
window.addEventListener("storage", resolveToken);
|
|
2347
|
+
return () => {
|
|
2348
|
+
cancelled = true;
|
|
2349
|
+
window.removeEventListener("focus", resolveToken);
|
|
2350
|
+
window.removeEventListener("storage", resolveToken);
|
|
2351
|
+
};
|
|
2352
|
+
}, [getTokenFn]);
|
|
2353
|
+
const mountedRef = React4.useRef(true);
|
|
2354
|
+
React4.useEffect(() => {
|
|
2355
|
+
mountedRef.current = true;
|
|
2356
|
+
return () => {
|
|
2357
|
+
mountedRef.current = false;
|
|
2358
|
+
};
|
|
2359
|
+
}, []);
|
|
2360
|
+
const buildClientOptions = React4.useCallback(
|
|
2361
|
+
() => ({
|
|
2362
|
+
baseUrl,
|
|
2363
|
+
getToken: getTokenFn,
|
|
2364
|
+
projectName
|
|
2365
|
+
}),
|
|
2366
|
+
[baseUrl, getTokenFn, projectName]
|
|
2367
|
+
);
|
|
2368
|
+
React4.useEffect(() => {
|
|
2369
|
+
if (!enabled || !token || !userId) {
|
|
2370
|
+
setState({ clientOptions: null, projectId: null, loading: false, error: null });
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
let cancelled = false;
|
|
2374
|
+
async function provision() {
|
|
2375
|
+
if (!mountedRef.current) return;
|
|
2376
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
2377
|
+
try {
|
|
2378
|
+
const connectOpts = { baseUrl, token };
|
|
2379
|
+
const projects = await listProjects(connectOpts);
|
|
2380
|
+
if (cancelled || !mountedRef.current) return;
|
|
2381
|
+
const existing = projects.find((p) => p.name === projectName);
|
|
2382
|
+
if (existing) {
|
|
2383
|
+
await updateProject({
|
|
2384
|
+
...connectOpts,
|
|
2385
|
+
projectId: existing.id,
|
|
2386
|
+
isInHouse: true,
|
|
2387
|
+
freeStorageLimitBytes: 2147483648
|
|
2388
|
+
// 2GB
|
|
2389
|
+
});
|
|
2390
|
+
} else {
|
|
2391
|
+
try {
|
|
2392
|
+
await createProject({
|
|
2393
|
+
...connectOpts,
|
|
2394
|
+
name: projectName,
|
|
2395
|
+
isInHouse: true,
|
|
2396
|
+
freeStorageLimitBytes: 2147483648
|
|
2397
|
+
// 2GB in bytes
|
|
2398
|
+
});
|
|
2399
|
+
} catch (err) {
|
|
2400
|
+
if (err?.message?.includes("409") || err?.message?.includes("duplicate") || err?.message?.includes("already exists")) {
|
|
2401
|
+
const retryProjects = await listProjects(connectOpts);
|
|
2402
|
+
const found = retryProjects.find((p) => p.name === projectName);
|
|
2403
|
+
if (!found) throw err;
|
|
2404
|
+
await updateProject({
|
|
2405
|
+
...connectOpts,
|
|
2406
|
+
projectId: found.id,
|
|
2407
|
+
isInHouse: true,
|
|
2408
|
+
freeStorageLimitBytes: 2147483648
|
|
2409
|
+
// 2GB
|
|
2410
|
+
});
|
|
2411
|
+
} else {
|
|
2412
|
+
throw err;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
if (cancelled || !mountedRef.current) return;
|
|
2417
|
+
setState({
|
|
2418
|
+
clientOptions: buildClientOptions(),
|
|
2419
|
+
projectId: null,
|
|
2420
|
+
loading: false,
|
|
2421
|
+
error: null
|
|
2422
|
+
});
|
|
2423
|
+
} catch (err) {
|
|
2424
|
+
if (cancelled || !mountedRef.current) return;
|
|
2425
|
+
setState({
|
|
2426
|
+
clientOptions: null,
|
|
2427
|
+
projectId: null,
|
|
2428
|
+
loading: false,
|
|
2429
|
+
error: err?.message || "Failed to auto-connect upfiles"
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
provision();
|
|
2434
|
+
return () => {
|
|
2435
|
+
cancelled = true;
|
|
2436
|
+
};
|
|
2437
|
+
}, [enabled, token, userId, baseUrl, projectName, retryCount, buildClientOptions]);
|
|
2438
|
+
const retry = React4.useCallback(() => {
|
|
2439
|
+
setRetryCount((c) => c + 1);
|
|
2440
|
+
}, []);
|
|
2441
|
+
const clearCache = React4.useCallback(() => {
|
|
2442
|
+
setState({ clientOptions: null, projectId: null, loading: false, error: null });
|
|
2443
|
+
}, []);
|
|
2444
|
+
return {
|
|
2445
|
+
...state,
|
|
2446
|
+
isReady: !!state.clientOptions,
|
|
2447
|
+
retry,
|
|
2448
|
+
clearCache
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
1969
2451
|
|
|
1970
2452
|
exports.ConnectProjectDialog = ConnectProjectDialog;
|
|
1971
2453
|
exports.ImageManager = ImageManager;
|
|
1972
2454
|
exports.ProjectFilesWidget = ProjectFilesWidget;
|
|
2455
|
+
exports.StorageQuotaError = StorageQuotaError;
|
|
1973
2456
|
exports.UpfilesClient = UpfilesClient;
|
|
1974
2457
|
exports.Uploader = Uploader;
|
|
1975
2458
|
exports.addApiKeyManually = addApiKeyManually;
|
|
@@ -1977,9 +2460,12 @@ exports.connectProject = connectProject;
|
|
|
1977
2460
|
exports.connectUsingApiKey = connectUsingApiKey;
|
|
1978
2461
|
exports.createClientWithKey = createClientWithKey;
|
|
1979
2462
|
exports.createProject = createProject;
|
|
2463
|
+
exports.createProjectWithApiKey = createProjectWithApiKey;
|
|
1980
2464
|
exports.fetchFileThumbnails = fetchFileThumbnails;
|
|
1981
2465
|
exports.fetchProjectFiles = fetchProjectFiles;
|
|
1982
2466
|
exports.fetchProjectImagesWithThumbs = fetchProjectImagesWithThumbs;
|
|
1983
2467
|
exports.listProjects = listProjects;
|
|
2468
|
+
exports.updateProject = updateProject;
|
|
2469
|
+
exports.useUpfilesInhouse = useUpfilesInhouse;
|
|
1984
2470
|
//# sourceMappingURL=index.js.map
|
|
1985
2471
|
//# sourceMappingURL=index.js.map
|