@thetechfossil/upfiles 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/index.d.mts +136 -13
- package/dist/index.d.ts +136 -13
- package/dist/index.js +1230 -536
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1110 -405
- package/dist/index.mjs.map +1 -0
- package/package.json +10 -10
package/dist/index.mjs
CHANGED
|
@@ -1,36 +1,89 @@
|
|
|
1
|
+
import * as React4 from 'react';
|
|
2
|
+
import React4__default, { useRef, useMemo, useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import * as Dialog2 from '@radix-ui/react-dialog';
|
|
5
|
+
|
|
1
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
|
+
};
|
|
2
14
|
var UpfilesClient = class {
|
|
3
15
|
constructor(opts) {
|
|
4
16
|
this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
|
|
5
|
-
this.presignPath = opts.presignPath ?? "/api/plugin/upload
|
|
17
|
+
this.presignPath = opts.presignPath ?? "/api/plugin/upload";
|
|
6
18
|
this.presignUrl = opts.presignUrl;
|
|
7
19
|
this.headers = opts.headers;
|
|
8
20
|
this.withCredentials = opts.withCredentials;
|
|
9
21
|
this.apiKey = opts.apiKey;
|
|
10
22
|
this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
|
|
11
23
|
this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
|
|
12
|
-
this.completionPath = opts.completionPath ?? "/api/plugin/
|
|
24
|
+
this.completionPath = opts.completionPath ?? "/api/plugin/files";
|
|
13
25
|
this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
|
|
14
26
|
this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
|
|
27
|
+
this.getToken = opts.getToken;
|
|
28
|
+
this.projectId = opts.projectId;
|
|
29
|
+
this.projectName = opts.projectName;
|
|
15
30
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
+
}
|
|
25
46
|
if (this.apiKey) {
|
|
26
47
|
if (this.apiKeyHeader === "authorization") {
|
|
27
|
-
headers["authorization"] = this.apiKey.startsWith("
|
|
48
|
+
headers["authorization"] = this.apiKey.startsWith("upf_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
28
49
|
} else if (this.apiKeyHeader === "x-api-key") {
|
|
29
50
|
headers["x-api-key"] = this.apiKey;
|
|
30
51
|
} else {
|
|
31
52
|
headers["x-up-api-key"] = this.apiKey;
|
|
32
53
|
}
|
|
33
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
|
+
};
|
|
34
87
|
const res = await fetch(target, {
|
|
35
88
|
method: "POST",
|
|
36
89
|
headers,
|
|
@@ -41,8 +94,12 @@ var UpfilesClient = class {
|
|
|
41
94
|
let msg = "Failed to get upload URL";
|
|
42
95
|
try {
|
|
43
96
|
const data = await res.json();
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
46
103
|
}
|
|
47
104
|
throw new Error(msg);
|
|
48
105
|
}
|
|
@@ -78,7 +135,11 @@ var UpfilesClient = class {
|
|
|
78
135
|
originalName: file.name,
|
|
79
136
|
size: file.size,
|
|
80
137
|
contentType: file.type || "application/octet-stream",
|
|
81
|
-
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.
|
|
82
143
|
});
|
|
83
144
|
} catch (error) {
|
|
84
145
|
throw new Error(`Upload verification failed: ${error.message}`);
|
|
@@ -107,22 +168,16 @@ var UpfilesClient = class {
|
|
|
107
168
|
const target = this.baseUrl ? `${this.baseUrl}${this.completionPath}` : this.completionPath;
|
|
108
169
|
const headers = {
|
|
109
170
|
"content-type": "application/json",
|
|
110
|
-
...this.
|
|
171
|
+
...await this.buildAuthHeaders()
|
|
111
172
|
};
|
|
112
|
-
if (this.apiKey) {
|
|
113
|
-
if (this.apiKeyHeader === "authorization") {
|
|
114
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
115
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
116
|
-
headers["x-api-key"] = this.apiKey;
|
|
117
|
-
} else {
|
|
118
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
173
|
const res = await fetch(target, {
|
|
122
174
|
method: "POST",
|
|
123
175
|
headers,
|
|
124
176
|
credentials: this.withCredentials ? "include" : "same-origin",
|
|
125
|
-
body: JSON.stringify(
|
|
177
|
+
body: JSON.stringify({
|
|
178
|
+
...params,
|
|
179
|
+
key: params.fileKey
|
|
180
|
+
})
|
|
126
181
|
});
|
|
127
182
|
if (!res.ok) {
|
|
128
183
|
let msg = "Failed to complete upload";
|
|
@@ -138,16 +193,7 @@ var UpfilesClient = class {
|
|
|
138
193
|
async getThumbnails(fileKey) {
|
|
139
194
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
140
195
|
const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
|
|
141
|
-
const headers =
|
|
142
|
-
if (this.apiKey) {
|
|
143
|
-
if (this.apiKeyHeader === "authorization") {
|
|
144
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
145
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
146
|
-
headers["x-api-key"] = this.apiKey;
|
|
147
|
-
} else {
|
|
148
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
196
|
+
const headers = await this.buildAuthHeaders();
|
|
151
197
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
152
198
|
if (!res.ok) {
|
|
153
199
|
let msg = "Failed to fetch thumbnails";
|
|
@@ -161,26 +207,19 @@ var UpfilesClient = class {
|
|
|
161
207
|
const data = await res.json();
|
|
162
208
|
return data?.thumbnails ?? [];
|
|
163
209
|
}
|
|
164
|
-
async generateThumbnails(fileKey) {
|
|
210
|
+
async generateThumbnails(fileKey, sizes) {
|
|
165
211
|
const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
|
|
166
212
|
const headers = {
|
|
167
213
|
"content-type": "application/json",
|
|
168
|
-
...this.
|
|
214
|
+
...await this.buildAuthHeaders()
|
|
169
215
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
173
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
174
|
-
headers["x-api-key"] = this.apiKey;
|
|
175
|
-
} else {
|
|
176
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
216
|
+
const body = { fileKey };
|
|
217
|
+
if (sizes && sizes.length > 0) body.sizes = sizes;
|
|
179
218
|
const res = await fetch(target, {
|
|
180
219
|
method: "POST",
|
|
181
220
|
headers,
|
|
182
221
|
credentials: this.withCredentials ? "include" : "same-origin",
|
|
183
|
-
body: JSON.stringify(
|
|
222
|
+
body: JSON.stringify(body)
|
|
184
223
|
});
|
|
185
224
|
if (!res.ok) {
|
|
186
225
|
let msg = "Failed to generate thumbnails";
|
|
@@ -194,19 +233,53 @@ var UpfilesClient = class {
|
|
|
194
233
|
const data = await res.json();
|
|
195
234
|
return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
|
|
196
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
|
+
}
|
|
197
279
|
async getFolders(parentId) {
|
|
198
280
|
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
199
281
|
const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
|
|
200
|
-
const headers =
|
|
201
|
-
if (this.apiKey) {
|
|
202
|
-
if (this.apiKeyHeader === "authorization") {
|
|
203
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
204
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
205
|
-
headers["x-api-key"] = this.apiKey;
|
|
206
|
-
} else {
|
|
207
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
282
|
+
const headers = await this.buildAuthHeaders();
|
|
210
283
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
211
284
|
if (!res.ok) {
|
|
212
285
|
let msg = "Failed to fetch folders";
|
|
@@ -224,17 +297,8 @@ var UpfilesClient = class {
|
|
|
224
297
|
const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
|
|
225
298
|
const headers = {
|
|
226
299
|
"content-type": "application/json",
|
|
227
|
-
...this.
|
|
300
|
+
...await this.buildAuthHeaders()
|
|
228
301
|
};
|
|
229
|
-
if (this.apiKey) {
|
|
230
|
-
if (this.apiKeyHeader === "authorization") {
|
|
231
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
232
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
233
|
-
headers["x-api-key"] = this.apiKey;
|
|
234
|
-
} else {
|
|
235
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
302
|
const res = await fetch(target, {
|
|
239
303
|
method: "POST",
|
|
240
304
|
headers,
|
|
@@ -258,16 +322,7 @@ var UpfilesClient = class {
|
|
|
258
322
|
const filesPath = `${basePath}/files`;
|
|
259
323
|
const target = this.baseUrl ? `${this.baseUrl}${filesPath}` : filesPath;
|
|
260
324
|
const url = params?.folderPath ? `${target}?folderPath=${encodeURIComponent(params.folderPath)}` : target;
|
|
261
|
-
const headers =
|
|
262
|
-
if (this.apiKey) {
|
|
263
|
-
if (this.apiKeyHeader === "authorization") {
|
|
264
|
-
headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
|
|
265
|
-
} else if (this.apiKeyHeader === "x-api-key") {
|
|
266
|
-
headers["x-api-key"] = this.apiKey;
|
|
267
|
-
} else {
|
|
268
|
-
headers["x-up-api-key"] = this.apiKey;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
325
|
+
const headers = await this.buildAuthHeaders();
|
|
271
326
|
const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
|
|
272
327
|
if (!res.ok) {
|
|
273
328
|
let msg = "Failed to fetch files";
|
|
@@ -285,29 +340,21 @@ var UpfilesClient = class {
|
|
|
285
340
|
};
|
|
286
341
|
}
|
|
287
342
|
getEventsUrl(projectId) {
|
|
288
|
-
const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
|
|
289
|
-
const eventsPath = `${basePath}/events`;
|
|
290
343
|
const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
|
|
291
344
|
const url = new URL(target, window.location.href);
|
|
292
|
-
if (this.apiKey) {
|
|
293
|
-
url.searchParams.set("
|
|
294
|
-
}
|
|
295
|
-
if (projectId) {
|
|
296
|
-
url.searchParams.set("projectId", projectId);
|
|
345
|
+
if (!this.getToken && !this.apiKey && (projectId || this.projectId)) {
|
|
346
|
+
url.searchParams.set("projectId", projectId || this.projectId);
|
|
297
347
|
}
|
|
298
348
|
return url.toString();
|
|
299
349
|
}
|
|
300
350
|
};
|
|
301
|
-
|
|
302
|
-
// src/Uploader.tsx
|
|
303
|
-
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
304
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
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;
|
|
429
|
+
});
|
|
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;
|
|
338
438
|
});
|
|
339
|
-
|
|
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,8 +652,8 @@ var Uploader = ({
|
|
|
467
652
|
} finally {
|
|
468
653
|
setIsUploading(false);
|
|
469
654
|
}
|
|
470
|
-
}, [files, onComplete, onError, uploadOne]);
|
|
471
|
-
|
|
655
|
+
}, [files, client, onComplete, onError, uploadOne]);
|
|
656
|
+
React4__default.useEffect(() => {
|
|
472
657
|
if (autoUpload && files.some((f2) => f2.status === "pending") && !isUploading) {
|
|
473
658
|
const timer = setTimeout(() => {
|
|
474
659
|
uploadAll();
|
|
@@ -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",
|
|
@@ -626,10 +904,6 @@ var Uploader = ({
|
|
|
626
904
|
] })
|
|
627
905
|
] });
|
|
628
906
|
};
|
|
629
|
-
|
|
630
|
-
// src/ProjectFilesWidget.tsx
|
|
631
|
-
import React2, { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
|
|
632
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
633
907
|
var ProjectFilesWidget = ({
|
|
634
908
|
clientOptions,
|
|
635
909
|
folderPath,
|
|
@@ -645,16 +919,16 @@ var ProjectFilesWidget = ({
|
|
|
645
919
|
showDelete = false,
|
|
646
920
|
refreshInterval = 5e3
|
|
647
921
|
}) => {
|
|
648
|
-
const client =
|
|
649
|
-
const [files, setFiles] =
|
|
650
|
-
const [loading, setLoading] =
|
|
651
|
-
const [error, setError] =
|
|
652
|
-
const [query, setQuery] =
|
|
653
|
-
const [savingKey, setSavingKey] =
|
|
654
|
-
const [deletingKey, setDeletingKey] =
|
|
655
|
-
const lastUpdatedRef =
|
|
656
|
-
const isFetchingRef =
|
|
657
|
-
const fetchFiles =
|
|
922
|
+
const client = useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
|
|
923
|
+
const [files, setFiles] = useState([]);
|
|
924
|
+
const [loading, setLoading] = useState(false);
|
|
925
|
+
const [error, setError] = useState(null);
|
|
926
|
+
const [query, setQuery] = useState("");
|
|
927
|
+
const [savingKey, setSavingKey] = useState(null);
|
|
928
|
+
const [deletingKey, setDeletingKey] = useState(null);
|
|
929
|
+
const lastUpdatedRef = React4__default.useRef(null);
|
|
930
|
+
const isFetchingRef = React4__default.useRef(false);
|
|
931
|
+
const fetchFiles = React4__default.useCallback(async (isPolling = false) => {
|
|
658
932
|
if (isFetchingRef.current) return;
|
|
659
933
|
isFetchingRef.current = true;
|
|
660
934
|
if (!isPolling) setLoading(true);
|
|
@@ -699,10 +973,18 @@ var ProjectFilesWidget = ({
|
|
|
699
973
|
"content-type": "application/json",
|
|
700
974
|
...clientOptions?.headers || {}
|
|
701
975
|
};
|
|
702
|
-
|
|
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) {
|
|
703
985
|
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
704
986
|
if (keyHeader === "authorization") {
|
|
705
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("
|
|
987
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
706
988
|
} else {
|
|
707
989
|
headers[keyHeader] = clientOptions.apiKey;
|
|
708
990
|
}
|
|
@@ -722,9 +1004,9 @@ var ProjectFilesWidget = ({
|
|
|
722
1004
|
setDeletingKey(null);
|
|
723
1005
|
}
|
|
724
1006
|
};
|
|
725
|
-
return /* @__PURE__ */
|
|
726
|
-
/* @__PURE__ */
|
|
727
|
-
/* @__PURE__ */
|
|
1007
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
1008
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
|
|
1009
|
+
/* @__PURE__ */ jsx(
|
|
728
1010
|
"input",
|
|
729
1011
|
{
|
|
730
1012
|
type: "text",
|
|
@@ -734,7 +1016,7 @@ var ProjectFilesWidget = ({
|
|
|
734
1016
|
style: { flex: 1, padding: 8, border: "1px solid #e5e7eb", borderRadius: 6 }
|
|
735
1017
|
}
|
|
736
1018
|
),
|
|
737
|
-
/* @__PURE__ */
|
|
1019
|
+
/* @__PURE__ */ jsx(
|
|
738
1020
|
"button",
|
|
739
1021
|
{
|
|
740
1022
|
onClick: () => {
|
|
@@ -746,26 +1028,26 @@ var ProjectFilesWidget = ({
|
|
|
746
1028
|
}
|
|
747
1029
|
)
|
|
748
1030
|
] }),
|
|
749
|
-
loading && /* @__PURE__ */
|
|
750
|
-
error && /* @__PURE__ */
|
|
751
|
-
!loading && !error && /* @__PURE__ */
|
|
752
|
-
visible.map((f2) => /* @__PURE__ */
|
|
1031
|
+
loading && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
|
|
1032
|
+
error && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
|
|
1033
|
+
!loading && !error && /* @__PURE__ */ jsxs("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
|
|
1034
|
+
visible.map((f2) => /* @__PURE__ */ jsxs(
|
|
753
1035
|
"li",
|
|
754
1036
|
{
|
|
755
1037
|
className: itemClassName,
|
|
756
1038
|
style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
|
|
757
1039
|
children: [
|
|
758
|
-
/* @__PURE__ */
|
|
759
|
-
/* @__PURE__ */
|
|
760
|
-
/* @__PURE__ */
|
|
1040
|
+
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
|
|
1041
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
|
|
1042
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
|
|
761
1043
|
(f2.size / (1024 * 1024)).toFixed(2),
|
|
762
1044
|
" MB \u2022 ",
|
|
763
1045
|
f2.contentType
|
|
764
1046
|
] })
|
|
765
1047
|
] }),
|
|
766
|
-
/* @__PURE__ */
|
|
767
|
-
/* @__PURE__ */
|
|
768
|
-
/* @__PURE__ */
|
|
1048
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
1049
|
+
/* @__PURE__ */ jsx("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
|
|
1050
|
+
/* @__PURE__ */ jsx(
|
|
769
1051
|
"button",
|
|
770
1052
|
{
|
|
771
1053
|
onClick: async () => {
|
|
@@ -802,7 +1084,7 @@ var ProjectFilesWidget = ({
|
|
|
802
1084
|
children: savingKey === f2.key ? "Saving\u2026" : "Use"
|
|
803
1085
|
}
|
|
804
1086
|
),
|
|
805
|
-
showDelete && /* @__PURE__ */
|
|
1087
|
+
showDelete && /* @__PURE__ */ jsx(
|
|
806
1088
|
"button",
|
|
807
1089
|
{
|
|
808
1090
|
onClick: () => handleDelete(f2.key),
|
|
@@ -816,17 +1098,26 @@ var ProjectFilesWidget = ({
|
|
|
816
1098
|
},
|
|
817
1099
|
f2.key
|
|
818
1100
|
)),
|
|
819
|
-
visible.length === 0 && /* @__PURE__ */
|
|
1101
|
+
visible.length === 0 && /* @__PURE__ */ jsx("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
|
|
820
1102
|
] })
|
|
821
1103
|
] });
|
|
822
1104
|
};
|
|
823
1105
|
|
|
824
|
-
// src/ConnectProjectDialog.tsx
|
|
825
|
-
import * as React3 from "react";
|
|
826
|
-
import * as Dialog from "@radix-ui/react-dialog";
|
|
827
|
-
|
|
828
1106
|
// src/projectConnect.ts
|
|
829
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
|
+
}
|
|
830
1121
|
async function httpJson(input, init, useFetch) {
|
|
831
1122
|
const doFetch = useFetch ?? f;
|
|
832
1123
|
const res = await doFetch(input, init);
|
|
@@ -834,7 +1125,7 @@ async function httpJson(input, init, useFetch) {
|
|
|
834
1125
|
let msg = `Request failed (${res.status})`;
|
|
835
1126
|
try {
|
|
836
1127
|
const j = await res.json();
|
|
837
|
-
msg = j?.error || msg;
|
|
1128
|
+
msg = j?.error || j?.message || msg;
|
|
838
1129
|
} catch {
|
|
839
1130
|
}
|
|
840
1131
|
throw new Error(msg);
|
|
@@ -845,7 +1136,7 @@ async function listProjects(opts) {
|
|
|
845
1136
|
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
|
|
846
1137
|
const data = await httpJson(
|
|
847
1138
|
url,
|
|
848
|
-
{ method: "GET"
|
|
1139
|
+
buildInit(opts, { method: "GET" }),
|
|
849
1140
|
opts.fetch
|
|
850
1141
|
);
|
|
851
1142
|
return data?.projects ?? [];
|
|
@@ -854,53 +1145,74 @@ async function createProject(opts) {
|
|
|
854
1145
|
const base = opts.baseUrl.replace(/\/$/, "");
|
|
855
1146
|
const created = await httpJson(
|
|
856
1147
|
`${base}/api/projects`,
|
|
857
|
-
{
|
|
1148
|
+
buildInit(opts, {
|
|
858
1149
|
method: "POST",
|
|
859
|
-
credentials: "include",
|
|
860
1150
|
headers: { "content-type": "application/json" },
|
|
861
|
-
body: JSON.stringify({
|
|
862
|
-
|
|
1151
|
+
body: JSON.stringify({
|
|
1152
|
+
name: opts.name,
|
|
1153
|
+
isInHouse: opts.isInHouse,
|
|
1154
|
+
freeStorageLimitBytes: opts.freeStorageLimitBytes
|
|
1155
|
+
})
|
|
1156
|
+
}),
|
|
863
1157
|
opts.fetch
|
|
864
1158
|
);
|
|
865
1159
|
const projectId = created?.project?.id;
|
|
866
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);
|
|
867
1166
|
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
868
1167
|
const key = await httpJson(
|
|
869
1168
|
`${base}/api/projects/${projectId}/keys`,
|
|
870
|
-
{
|
|
1169
|
+
buildInit(opts, {
|
|
871
1170
|
method: "POST",
|
|
872
|
-
credentials: "include",
|
|
873
1171
|
headers: { "content-type": "application/json" },
|
|
874
1172
|
body: JSON.stringify({ name: keyLabel })
|
|
875
|
-
},
|
|
1173
|
+
}),
|
|
876
1174
|
opts.fetch
|
|
877
1175
|
);
|
|
878
|
-
const plaintext = key?.key?.plaintext;
|
|
1176
|
+
const plaintext = key?.key?.secret ?? key?.key?.plaintext;
|
|
879
1177
|
if (!plaintext) throw new Error("Failed to create API key");
|
|
880
1178
|
return { apiKey: plaintext, projectId };
|
|
881
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
|
+
}
|
|
882
1195
|
async function connectProject(opts) {
|
|
883
1196
|
const base = opts.baseUrl.replace(/\/$/, "");
|
|
884
1197
|
const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
885
1198
|
const key = await httpJson(
|
|
886
1199
|
`${base}/api/projects/${opts.projectId}/keys`,
|
|
887
|
-
{
|
|
1200
|
+
buildInit(opts, {
|
|
888
1201
|
method: "POST",
|
|
889
|
-
credentials: "include",
|
|
890
1202
|
headers: { "content-type": "application/json" },
|
|
891
1203
|
body: JSON.stringify({ name: keyLabel })
|
|
892
|
-
},
|
|
1204
|
+
}),
|
|
893
1205
|
opts.fetch
|
|
894
1206
|
);
|
|
895
|
-
const plaintext = key?.key?.plaintext;
|
|
1207
|
+
const plaintext = key?.key?.secret ?? key?.key?.plaintext;
|
|
896
1208
|
if (!plaintext) throw new Error("Failed to create API key");
|
|
897
1209
|
return { apiKey: plaintext };
|
|
898
1210
|
}
|
|
899
1211
|
function addApiKeyManually(apiKey) {
|
|
900
1212
|
const k = (apiKey || "").trim();
|
|
901
1213
|
if (!k) throw new Error("API key is required");
|
|
902
|
-
if (!/^
|
|
903
|
-
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_*");
|
|
904
1216
|
}
|
|
905
1217
|
return { apiKey: k };
|
|
906
1218
|
}
|
|
@@ -914,19 +1226,16 @@ function connectUsingApiKey(opts) {
|
|
|
914
1226
|
const client = new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: opts.apiKeyHeader ?? "authorization" });
|
|
915
1227
|
return { apiKey, client };
|
|
916
1228
|
}
|
|
917
|
-
|
|
918
|
-
// src/ConnectProjectDialog.tsx
|
|
919
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
920
1229
|
function ConnectProjectDialog(props) {
|
|
921
1230
|
const { baseUrl, open, onOpenChange, onConnected, fetchImpl, title, description, className } = props;
|
|
922
|
-
const [mode, setMode] =
|
|
923
|
-
const [loading, setLoading] =
|
|
924
|
-
const [error, setError] =
|
|
925
|
-
const [projects, setProjects] =
|
|
926
|
-
const [selectedProjectId, setSelectedProjectId] =
|
|
927
|
-
const [manualKey, setManualKey] =
|
|
928
|
-
const [newProjectName, setNewProjectName] =
|
|
929
|
-
const resetState =
|
|
1231
|
+
const [mode, setMode] = React4.useState("existing");
|
|
1232
|
+
const [loading, setLoading] = React4.useState(false);
|
|
1233
|
+
const [error, setError] = React4.useState(null);
|
|
1234
|
+
const [projects, setProjects] = React4.useState([]);
|
|
1235
|
+
const [selectedProjectId, setSelectedProjectId] = React4.useState("");
|
|
1236
|
+
const [manualKey, setManualKey] = React4.useState("");
|
|
1237
|
+
const [newProjectName, setNewProjectName] = React4.useState("");
|
|
1238
|
+
const resetState = React4.useCallback(() => {
|
|
930
1239
|
setMode("existing");
|
|
931
1240
|
setLoading(false);
|
|
932
1241
|
setError(null);
|
|
@@ -935,7 +1244,7 @@ function ConnectProjectDialog(props) {
|
|
|
935
1244
|
setManualKey("");
|
|
936
1245
|
setNewProjectName("");
|
|
937
1246
|
}, []);
|
|
938
|
-
|
|
1247
|
+
React4.useEffect(() => {
|
|
939
1248
|
const run = async () => {
|
|
940
1249
|
if (!open || mode !== "existing") return;
|
|
941
1250
|
try {
|
|
@@ -987,7 +1296,7 @@ function ConnectProjectDialog(props) {
|
|
|
987
1296
|
setLoading(true);
|
|
988
1297
|
setError(null);
|
|
989
1298
|
if (!newProjectName.trim()) throw new Error("Project name is required");
|
|
990
|
-
const { apiKey, projectId } = await
|
|
1299
|
+
const { apiKey, projectId } = await createProjectWithApiKey({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
|
|
991
1300
|
onConnected(apiKey, { projectId, source: "new" });
|
|
992
1301
|
onOpenChange(false);
|
|
993
1302
|
resetState();
|
|
@@ -997,8 +1306,8 @@ function ConnectProjectDialog(props) {
|
|
|
997
1306
|
setLoading(false);
|
|
998
1307
|
}
|
|
999
1308
|
};
|
|
1000
|
-
const ModeSwitcher = /* @__PURE__ */
|
|
1001
|
-
/* @__PURE__ */
|
|
1309
|
+
const ModeSwitcher = /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx(
|
|
1002
1311
|
"button",
|
|
1003
1312
|
{
|
|
1004
1313
|
type: "button",
|
|
@@ -1007,7 +1316,7 @@ function ConnectProjectDialog(props) {
|
|
|
1007
1316
|
children: "Connect existing"
|
|
1008
1317
|
}
|
|
1009
1318
|
),
|
|
1010
|
-
/* @__PURE__ */
|
|
1319
|
+
/* @__PURE__ */ jsx(
|
|
1011
1320
|
"button",
|
|
1012
1321
|
{
|
|
1013
1322
|
type: "button",
|
|
@@ -1016,7 +1325,7 @@ function ConnectProjectDialog(props) {
|
|
|
1016
1325
|
children: "Add API key"
|
|
1017
1326
|
}
|
|
1018
1327
|
),
|
|
1019
|
-
/* @__PURE__ */
|
|
1328
|
+
/* @__PURE__ */ jsx(
|
|
1020
1329
|
"button",
|
|
1021
1330
|
{
|
|
1022
1331
|
type: "button",
|
|
@@ -1026,19 +1335,19 @@ function ConnectProjectDialog(props) {
|
|
|
1026
1335
|
}
|
|
1027
1336
|
)
|
|
1028
1337
|
] });
|
|
1029
|
-
return /* @__PURE__ */
|
|
1338
|
+
return /* @__PURE__ */ jsx(Dialog2.Root, { open, onOpenChange: (o) => {
|
|
1030
1339
|
if (!o) resetState();
|
|
1031
1340
|
onOpenChange(o);
|
|
1032
|
-
}, children: /* @__PURE__ */
|
|
1033
|
-
/* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
/* @__PURE__ */
|
|
1036
|
-
/* @__PURE__ */
|
|
1341
|
+
}, children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1342
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40" }),
|
|
1343
|
+
/* @__PURE__ */ jsx(Dialog2.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1344
|
+
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
|
|
1345
|
+
/* @__PURE__ */ jsx(Dialog2.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
|
|
1037
1346
|
ModeSwitcher,
|
|
1038
|
-
error && /* @__PURE__ */
|
|
1039
|
-
mode === "existing" && /* @__PURE__ */
|
|
1040
|
-
/* @__PURE__ */
|
|
1041
|
-
/* @__PURE__ */
|
|
1347
|
+
error && /* @__PURE__ */ jsx("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
|
|
1348
|
+
mode === "existing" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1349
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Project" }),
|
|
1350
|
+
/* @__PURE__ */ jsxs(
|
|
1042
1351
|
"select",
|
|
1043
1352
|
{
|
|
1044
1353
|
className: "w-full rounded border px-3 py-2 text-sm",
|
|
@@ -1046,14 +1355,14 @@ function ConnectProjectDialog(props) {
|
|
|
1046
1355
|
onChange: (e) => setSelectedProjectId(e.target.value),
|
|
1047
1356
|
disabled: loading,
|
|
1048
1357
|
children: [
|
|
1049
|
-
projects.length === 0 && /* @__PURE__ */
|
|
1050
|
-
projects.map((p) => /* @__PURE__ */
|
|
1358
|
+
projects.length === 0 && /* @__PURE__ */ jsx("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
|
|
1359
|
+
projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
|
|
1051
1360
|
]
|
|
1052
1361
|
}
|
|
1053
1362
|
),
|
|
1054
|
-
/* @__PURE__ */
|
|
1055
|
-
/* @__PURE__ */
|
|
1056
|
-
/* @__PURE__ */
|
|
1363
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
1364
|
+
/* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
1365
|
+
/* @__PURE__ */ jsx(
|
|
1057
1366
|
"button",
|
|
1058
1367
|
{
|
|
1059
1368
|
type: "button",
|
|
@@ -1065,9 +1374,9 @@ function ConnectProjectDialog(props) {
|
|
|
1065
1374
|
)
|
|
1066
1375
|
] })
|
|
1067
1376
|
] }),
|
|
1068
|
-
mode === "manual" && /* @__PURE__ */
|
|
1069
|
-
/* @__PURE__ */
|
|
1070
|
-
/* @__PURE__ */
|
|
1377
|
+
mode === "manual" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1378
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "API key" }),
|
|
1379
|
+
/* @__PURE__ */ jsx(
|
|
1071
1380
|
"input",
|
|
1072
1381
|
{
|
|
1073
1382
|
className: "w-full rounded border px-3 py-2 text-sm",
|
|
@@ -1077,9 +1386,9 @@ function ConnectProjectDialog(props) {
|
|
|
1077
1386
|
disabled: loading
|
|
1078
1387
|
}
|
|
1079
1388
|
),
|
|
1080
|
-
/* @__PURE__ */
|
|
1081
|
-
/* @__PURE__ */
|
|
1082
|
-
/* @__PURE__ */
|
|
1389
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
1390
|
+
/* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
1391
|
+
/* @__PURE__ */ jsx(
|
|
1083
1392
|
"button",
|
|
1084
1393
|
{
|
|
1085
1394
|
type: "button",
|
|
@@ -1091,9 +1400,9 @@ function ConnectProjectDialog(props) {
|
|
|
1091
1400
|
)
|
|
1092
1401
|
] })
|
|
1093
1402
|
] }),
|
|
1094
|
-
mode === "new" && /* @__PURE__ */
|
|
1095
|
-
/* @__PURE__ */
|
|
1096
|
-
/* @__PURE__ */
|
|
1403
|
+
mode === "new" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1404
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Project name" }),
|
|
1405
|
+
/* @__PURE__ */ jsx(
|
|
1097
1406
|
"input",
|
|
1098
1407
|
{
|
|
1099
1408
|
className: "w-full rounded border px-3 py-2 text-sm",
|
|
@@ -1103,10 +1412,10 @@ function ConnectProjectDialog(props) {
|
|
|
1103
1412
|
disabled: loading
|
|
1104
1413
|
}
|
|
1105
1414
|
),
|
|
1106
|
-
/* @__PURE__ */
|
|
1107
|
-
/* @__PURE__ */
|
|
1108
|
-
/* @__PURE__ */
|
|
1109
|
-
/* @__PURE__ */
|
|
1415
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
|
|
1416
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
|
|
1417
|
+
/* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
|
|
1418
|
+
/* @__PURE__ */ jsx(
|
|
1110
1419
|
"button",
|
|
1111
1420
|
{
|
|
1112
1421
|
type: "button",
|
|
@@ -1156,60 +1465,77 @@ async function fetchProjectImagesWithThumbs(clientOptions, opts) {
|
|
|
1156
1465
|
results.sort((a, b) => (keyToIndex.get(a.key) ?? 0) - (keyToIndex.get(b.key) ?? 0));
|
|
1157
1466
|
return { items: results, updatedAt };
|
|
1158
1467
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
var IconGrid = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1165
|
-
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1166
|
-
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1167
|
-
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1168
|
-
/* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1468
|
+
var IconGrid = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1469
|
+
/* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1470
|
+
/* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1471
|
+
/* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1472
|
+
/* @__PURE__ */ jsx("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1169
1473
|
] });
|
|
1170
|
-
var IconUploadCloud = ({ className }) => /* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1172
|
-
/* @__PURE__ */
|
|
1173
|
-
/* @__PURE__ */
|
|
1474
|
+
var IconUploadCloud = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1475
|
+
/* @__PURE__ */ jsx("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
|
|
1476
|
+
/* @__PURE__ */ jsx("path", { d: "M12 12v9" }),
|
|
1477
|
+
/* @__PURE__ */ jsx("path", { d: "m16 16-4-4-4 4" })
|
|
1174
1478
|
] });
|
|
1175
|
-
var IconFolder = ({ className }) => /* @__PURE__ */
|
|
1176
|
-
var IconFolderPlus = ({ className }) => /* @__PURE__ */
|
|
1177
|
-
/* @__PURE__ */
|
|
1178
|
-
/* @__PURE__ */
|
|
1179
|
-
/* @__PURE__ */
|
|
1479
|
+
var IconFolder = ({ className }) => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", stroke: "currentColor", strokeWidth: "0", className, children: /* @__PURE__ */ jsx("path", { d: "M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z" }) });
|
|
1480
|
+
var IconFolderPlus = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1481
|
+
/* @__PURE__ */ jsx("path", { d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 2H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2Z" }),
|
|
1482
|
+
/* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
|
|
1483
|
+
/* @__PURE__ */ jsx("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
|
|
1180
1484
|
] });
|
|
1181
|
-
var IconSearch = ({ className }) => /* @__PURE__ */
|
|
1182
|
-
/* @__PURE__ */
|
|
1183
|
-
/* @__PURE__ */
|
|
1485
|
+
var IconSearch = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1486
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1487
|
+
/* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
|
|
1184
1488
|
] });
|
|
1185
|
-
var IconRefresh = ({ className }) => /* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1188
|
-
/* @__PURE__ */
|
|
1189
|
-
/* @__PURE__ */
|
|
1489
|
+
var IconRefresh = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1490
|
+
/* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
1491
|
+
/* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" }),
|
|
1492
|
+
/* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
1493
|
+
/* @__PURE__ */ jsx("path", { d: "M8 16H3v5" })
|
|
1190
1494
|
] });
|
|
1191
|
-
var IconChevronRight = ({ className }) => /* @__PURE__ */
|
|
1192
|
-
var IconTrash = ({ className }) => /* @__PURE__ */
|
|
1193
|
-
/* @__PURE__ */
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
/* @__PURE__ */
|
|
1196
|
-
/* @__PURE__ */
|
|
1197
|
-
/* @__PURE__ */
|
|
1495
|
+
var IconChevronRight = ({ className }) => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
|
|
1496
|
+
var IconTrash = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1497
|
+
/* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
|
|
1498
|
+
/* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
|
|
1499
|
+
/* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
|
|
1500
|
+
/* @__PURE__ */ jsx("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
|
|
1501
|
+
/* @__PURE__ */ jsx("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
|
|
1198
1502
|
] });
|
|
1199
|
-
var IconX = ({ className }) => /* @__PURE__ */
|
|
1200
|
-
/* @__PURE__ */
|
|
1201
|
-
/* @__PURE__ */
|
|
1503
|
+
var IconX = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1504
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
1505
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
1202
1506
|
] });
|
|
1203
|
-
var IconHome = ({ className }) => /* @__PURE__ */
|
|
1204
|
-
/* @__PURE__ */
|
|
1205
|
-
/* @__PURE__ */
|
|
1507
|
+
var IconHome = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1508
|
+
/* @__PURE__ */ jsx("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
|
|
1509
|
+
/* @__PURE__ */ jsx("polyline", { points: "9 22 9 12 15 12 15 22" })
|
|
1206
1510
|
] });
|
|
1207
|
-
var IconImage = ({ className }) => /* @__PURE__ */
|
|
1208
|
-
/* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1210
|
-
/* @__PURE__ */
|
|
1511
|
+
var IconImage = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1512
|
+
/* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
|
|
1513
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" }),
|
|
1514
|
+
/* @__PURE__ */ jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
|
|
1211
1515
|
] });
|
|
1212
|
-
var IconLoader = ({ className }) => /* @__PURE__ */
|
|
1516
|
+
var IconLoader = ({ className }) => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
1517
|
+
var IconLayers = ({ className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
|
|
1518
|
+
/* @__PURE__ */ jsx("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
|
|
1519
|
+
/* @__PURE__ */ jsx("polyline", { points: "2 17 12 22 22 17" }),
|
|
1520
|
+
/* @__PURE__ */ jsx("polyline", { points: "2 12 12 17 22 12" })
|
|
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
|
+
);
|
|
1213
1539
|
function ImageManager(props) {
|
|
1214
1540
|
const {
|
|
1215
1541
|
open,
|
|
@@ -1217,6 +1543,7 @@ function ImageManager(props) {
|
|
|
1217
1543
|
clientOptions,
|
|
1218
1544
|
projectId,
|
|
1219
1545
|
folderPath,
|
|
1546
|
+
userId,
|
|
1220
1547
|
title,
|
|
1221
1548
|
description,
|
|
1222
1549
|
className,
|
|
@@ -1230,7 +1557,9 @@ function ImageManager(props) {
|
|
|
1230
1557
|
maxFiles = 10,
|
|
1231
1558
|
mode = "full",
|
|
1232
1559
|
showDelete = true,
|
|
1233
|
-
refreshInterval = 5e3
|
|
1560
|
+
refreshInterval = 5e3,
|
|
1561
|
+
stayOnUploadAfterComplete = false,
|
|
1562
|
+
thumbnailConfig
|
|
1234
1563
|
} = props;
|
|
1235
1564
|
const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
|
|
1236
1565
|
const [loading, setLoading] = React4.useState(false);
|
|
@@ -1246,8 +1575,21 @@ function ImageManager(props) {
|
|
|
1246
1575
|
const [creatingFolder, setCreatingFolder] = React4.useState(false);
|
|
1247
1576
|
const [fileToDelete, setFileToDelete] = React4.useState(null);
|
|
1248
1577
|
const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
|
|
1578
|
+
const [selectedThumbnailForFile, setSelectedThumbnailForFile] = React4.useState(null);
|
|
1579
|
+
const [previewFile, setPreviewFile] = React4.useState(null);
|
|
1580
|
+
const [generatingThumbnails, setGeneratingThumbnails] = React4.useState(false);
|
|
1249
1581
|
const lastUpdatedRef = React4.useRef(null);
|
|
1250
1582
|
const isFetchingRef = React4.useRef(false);
|
|
1583
|
+
const deduplicateFiles = React4.useCallback((files2) => {
|
|
1584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1585
|
+
return files2.filter((file) => {
|
|
1586
|
+
if (seen.has(file.key)) {
|
|
1587
|
+
return false;
|
|
1588
|
+
}
|
|
1589
|
+
seen.add(file.key);
|
|
1590
|
+
return true;
|
|
1591
|
+
});
|
|
1592
|
+
}, []);
|
|
1251
1593
|
const load = React4.useCallback(async (isPolling = false) => {
|
|
1252
1594
|
if (isFetchingRef.current) return;
|
|
1253
1595
|
isFetchingRef.current = true;
|
|
@@ -1258,13 +1600,14 @@ function ImageManager(props) {
|
|
|
1258
1600
|
const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1259
1601
|
const effectiveFolderPath = currentPathPrefix || folderPath;
|
|
1260
1602
|
const [filesData, foldersData] = await Promise.all([
|
|
1261
|
-
|
|
1603
|
+
fetchProjectFiles(clientOptions, effectiveFolderPath),
|
|
1262
1604
|
client.getFolders(currentFolderId)
|
|
1263
1605
|
]);
|
|
1264
|
-
const {
|
|
1606
|
+
const { files: rawFiles, updatedAt } = filesData;
|
|
1607
|
+
const list = rawFiles;
|
|
1265
1608
|
if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
|
|
1266
1609
|
}
|
|
1267
|
-
setFiles(list);
|
|
1610
|
+
setFiles(deduplicateFiles(list));
|
|
1268
1611
|
setFolders(foldersData);
|
|
1269
1612
|
lastUpdatedRef.current = updatedAt;
|
|
1270
1613
|
} catch (e) {
|
|
@@ -1287,30 +1630,6 @@ function ImageManager(props) {
|
|
|
1287
1630
|
load();
|
|
1288
1631
|
}
|
|
1289
1632
|
}, [open, activeView, load, mode]);
|
|
1290
|
-
React4.useEffect(() => {
|
|
1291
|
-
if (!open) return;
|
|
1292
|
-
const client = new UpfilesClient(clientOptions);
|
|
1293
|
-
const url = client.getEventsUrl(projectId);
|
|
1294
|
-
const eventSource = new EventSource(url);
|
|
1295
|
-
eventSource.onmessage = (event) => {
|
|
1296
|
-
try {
|
|
1297
|
-
const payload = JSON.parse(event.data);
|
|
1298
|
-
if (payload.type === "file:uploaded") {
|
|
1299
|
-
const newFile = payload.data.file;
|
|
1300
|
-
setFiles((prev) => {
|
|
1301
|
-
if (prev.find((f2) => f2.key === newFile.key)) return prev;
|
|
1302
|
-
return [newFile, ...prev];
|
|
1303
|
-
});
|
|
1304
|
-
} else if (payload.type === "file:deleted") {
|
|
1305
|
-
const deletedKey = payload.data.fileKey || payload.data.file?.key;
|
|
1306
|
-
setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
|
|
1307
|
-
}
|
|
1308
|
-
} catch (e) {
|
|
1309
|
-
console.error("SSE Error parsing message", e);
|
|
1310
|
-
}
|
|
1311
|
-
};
|
|
1312
|
-
return () => eventSource.close();
|
|
1313
|
-
}, [open, clientOptions, projectId]);
|
|
1314
1633
|
const visibleFiles = React4.useMemo(() => {
|
|
1315
1634
|
const q = query.trim().toLowerCase();
|
|
1316
1635
|
return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
|
|
@@ -1334,10 +1653,18 @@ function ImageManager(props) {
|
|
|
1334
1653
|
"content-type": "application/json",
|
|
1335
1654
|
...clientOptions?.headers || {}
|
|
1336
1655
|
};
|
|
1337
|
-
|
|
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) {
|
|
1338
1665
|
const keyHeader = clientOptions.apiKeyHeader || "authorization";
|
|
1339
1666
|
if (keyHeader === "authorization") {
|
|
1340
|
-
headers["authorization"] = clientOptions.apiKey.startsWith("
|
|
1667
|
+
headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
|
|
1341
1668
|
} else {
|
|
1342
1669
|
headers[keyHeader] = clientOptions.apiKey;
|
|
1343
1670
|
}
|
|
@@ -1397,65 +1724,87 @@ function ImageManager(props) {
|
|
|
1397
1724
|
});
|
|
1398
1725
|
return options;
|
|
1399
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]);
|
|
1400
1748
|
const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
|
|
1401
1749
|
const activeBreadcrumbPath = React4.useMemo(() => {
|
|
1402
1750
|
return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
|
|
1403
1751
|
}, [breadcrumbs]);
|
|
1404
|
-
return /* @__PURE__ */
|
|
1405
|
-
/* @__PURE__ */
|
|
1406
|
-
/* @__PURE__ */
|
|
1407
|
-
/* @__PURE__ */
|
|
1752
|
+
return /* @__PURE__ */ jsxs(Dialog2.Root, { open, onOpenChange, children: [
|
|
1753
|
+
/* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
1754
|
+
/* @__PURE__ */ jsx(Dialog2.Overlay, { className: "fixed inset-0 bg-black/60 z-[9999]" }),
|
|
1755
|
+
/* @__PURE__ */ jsxs(
|
|
1408
1756
|
Dialog2.Content,
|
|
1409
1757
|
{
|
|
1410
|
-
className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[
|
|
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 ?? ""}`,
|
|
1411
1759
|
children: [
|
|
1412
|
-
/* @__PURE__ */
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1760
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx(Dialog2.Title, { children: title ?? "Media Library" }) }),
|
|
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: [
|
|
1762
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
1763
|
+
/* @__PURE__ */ jsxs("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
|
|
1764
|
+
/* @__PURE__ */ jsx(IconImage, { className: "text-blue-600" }),
|
|
1416
1765
|
title ?? "Media Library"
|
|
1417
1766
|
] }),
|
|
1418
|
-
/* @__PURE__ */
|
|
1767
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
|
|
1419
1768
|
] }),
|
|
1420
|
-
/* @__PURE__ */
|
|
1421
|
-
/* @__PURE__ */
|
|
1769
|
+
/* @__PURE__ */ jsxs("nav", { className: "flex-1 px-3 space-y-1", children: [
|
|
1770
|
+
/* @__PURE__ */ jsxs(
|
|
1422
1771
|
"button",
|
|
1423
1772
|
{
|
|
1424
1773
|
onClick: () => setActiveView("browse"),
|
|
1425
1774
|
className: `w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-colors ${activeView === "browse" ? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`,
|
|
1426
1775
|
children: [
|
|
1427
|
-
/* @__PURE__ */
|
|
1776
|
+
/* @__PURE__ */ jsx(IconGrid, { className: "w-4 h-4" }),
|
|
1428
1777
|
"All Files"
|
|
1429
1778
|
]
|
|
1430
1779
|
}
|
|
1431
1780
|
),
|
|
1432
|
-
mode === "full" && /* @__PURE__ */
|
|
1781
|
+
mode === "full" && /* @__PURE__ */ jsxs(
|
|
1433
1782
|
"button",
|
|
1434
1783
|
{
|
|
1435
1784
|
onClick: () => setActiveView("upload"),
|
|
1436
1785
|
className: `w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-colors ${activeView === "upload" ? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`,
|
|
1437
1786
|
children: [
|
|
1438
|
-
/* @__PURE__ */
|
|
1787
|
+
/* @__PURE__ */ jsx(IconUploadCloud, { className: "w-4 h-4" }),
|
|
1439
1788
|
"Upload New"
|
|
1440
1789
|
]
|
|
1441
1790
|
}
|
|
1442
1791
|
)
|
|
1443
1792
|
] }),
|
|
1444
|
-
/* @__PURE__ */
|
|
1793
|
+
/* @__PURE__ */ jsx("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ jsxs("div", { className: "text-xs text-center text-gray-400", children: [
|
|
1445
1794
|
files.length,
|
|
1446
1795
|
" files \u2022 ",
|
|
1447
1796
|
folders.length,
|
|
1448
1797
|
" folders"
|
|
1449
1798
|
] }) })
|
|
1450
1799
|
] }),
|
|
1451
|
-
/* @__PURE__ */
|
|
1452
|
-
/* @__PURE__ */
|
|
1453
|
-
/* @__PURE__ */
|
|
1454
|
-
/* @__PURE__ */
|
|
1455
|
-
/* @__PURE__ */
|
|
1456
|
-
breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */
|
|
1457
|
-
/* @__PURE__ */
|
|
1458
|
-
/* @__PURE__ */
|
|
1800
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
|
|
1801
|
+
/* @__PURE__ */ jsxs("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
|
|
1802
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1803
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
|
|
1804
|
+
/* @__PURE__ */ jsx("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ jsx(IconHome, { className: "w-4 h-4" }) }),
|
|
1805
|
+
breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
|
|
1806
|
+
/* @__PURE__ */ jsx(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
|
|
1807
|
+
/* @__PURE__ */ jsx(
|
|
1459
1808
|
"button",
|
|
1460
1809
|
{
|
|
1461
1810
|
onClick: () => handleBreadcrumbClick(i + 1),
|
|
@@ -1463,23 +1812,23 @@ function ImageManager(props) {
|
|
|
1463
1812
|
children: b.name
|
|
1464
1813
|
}
|
|
1465
1814
|
)
|
|
1466
|
-
] }, b.id || i))
|
|
1815
|
+
] }, `${b.id || "breadcrumb"}-${i}`))
|
|
1467
1816
|
] }),
|
|
1468
|
-
/* @__PURE__ */
|
|
1469
|
-
/* @__PURE__ */
|
|
1817
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
|
|
1818
|
+
/* @__PURE__ */ jsx(
|
|
1470
1819
|
"button",
|
|
1471
1820
|
{
|
|
1472
1821
|
onClick: () => load(),
|
|
1473
1822
|
disabled: loading,
|
|
1474
1823
|
className: "p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-all disabled:opacity-50",
|
|
1475
1824
|
title: "Refresh",
|
|
1476
|
-
children: /* @__PURE__ */
|
|
1825
|
+
children: /* @__PURE__ */ jsx(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
|
|
1477
1826
|
}
|
|
1478
1827
|
),
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1481
|
-
/* @__PURE__ */
|
|
1482
|
-
/* @__PURE__ */
|
|
1828
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
|
|
1829
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex-1 max-w-sm group", children: [
|
|
1830
|
+
/* @__PURE__ */ jsx(IconSearch, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 group-focus-within:text-blue-500 transition-colors" }),
|
|
1831
|
+
/* @__PURE__ */ jsx(
|
|
1483
1832
|
"input",
|
|
1484
1833
|
{
|
|
1485
1834
|
value: query,
|
|
@@ -1490,45 +1839,58 @@ function ImageManager(props) {
|
|
|
1490
1839
|
)
|
|
1491
1840
|
] })
|
|
1492
1841
|
] }) }),
|
|
1493
|
-
/* @__PURE__ */
|
|
1494
|
-
activeView === "browse" && /* @__PURE__ */
|
|
1495
|
-
/* @__PURE__ */
|
|
1496
|
-
/* @__PURE__ */
|
|
1842
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1843
|
+
activeView === "browse" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1844
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowCreateFolder(true), className: "p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 rounded-lg transition-colors", title: "New Folder", children: /* @__PURE__ */ jsx(IconFolderPlus, { className: "w-5 h-5" }) }),
|
|
1845
|
+
/* @__PURE__ */ jsx("button", { onClick: () => load(), className: `p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 rounded-lg transition-colors ${loading ? "animate-spin text-blue-600" : ""}`, title: "Refresh", children: /* @__PURE__ */ jsx(IconLoader, { className: "w-5 h-5" }) })
|
|
1497
1846
|
] }),
|
|
1498
|
-
/* @__PURE__ */
|
|
1847
|
+
/* @__PURE__ */ jsx(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "ml-2 p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/10 rounded-lg transition-colors", children: /* @__PURE__ */ jsx(IconX, { className: "w-5 h-5" }) }) })
|
|
1499
1848
|
] })
|
|
1500
1849
|
] }),
|
|
1501
|
-
/* @__PURE__ */
|
|
1502
|
-
activeView === "browse" && /* @__PURE__ */
|
|
1503
|
-
error && /* @__PURE__ */
|
|
1504
|
-
/* @__PURE__ */
|
|
1505
|
-
visibleFolders.map((folder) => /* @__PURE__ */
|
|
1850
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
|
|
1851
|
+
activeView === "browse" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1852
|
+
error && /* @__PURE__ */ jsx("div", { className: "mb-6 bg-red-50 text-red-600 p-4 rounded-lg text-sm border border-red-100 flex items-center gap-2", children: /* @__PURE__ */ jsx("span", { children: error }) }),
|
|
1853
|
+
/* @__PURE__ */ jsxs("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
|
|
1854
|
+
visibleFolders.map((folder) => /* @__PURE__ */ jsxs(
|
|
1506
1855
|
"div",
|
|
1507
1856
|
{
|
|
1508
1857
|
onClick: () => handleNavigate(folder),
|
|
1509
1858
|
className: "group flex flex-col items-center gap-3 p-4 rounded-xl border border-gray-200/60 bg-white hover:border-blue-200 hover:shadow-lg hover:shadow-blue-500/5 hover:-translate-y-1 transition-all cursor-pointer",
|
|
1510
1859
|
children: [
|
|
1511
|
-
/* @__PURE__ */
|
|
1512
|
-
/* @__PURE__ */
|
|
1860
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 h-12 bg-blue-50 rounded-lg flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform", children: /* @__PURE__ */ jsx(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
|
|
1861
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
|
|
1513
1862
|
]
|
|
1514
1863
|
},
|
|
1515
1864
|
folder.id
|
|
1516
1865
|
)),
|
|
1517
1866
|
visibleFiles.map((f2) => {
|
|
1518
|
-
const
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
return /* @__PURE__ */ jsxs4(
|
|
1867
|
+
const displayUrl = f2.url;
|
|
1868
|
+
const hasThumbnails = f2.thumbnails && f2.thumbnails.length > 0;
|
|
1869
|
+
return /* @__PURE__ */ jsxs(
|
|
1522
1870
|
"div",
|
|
1523
1871
|
{
|
|
1524
|
-
|
|
1872
|
+
onClick: () => setPreviewFile(f2),
|
|
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",
|
|
1525
1874
|
children: [
|
|
1526
|
-
/* @__PURE__ */
|
|
1527
|
-
|
|
1528
|
-
|
|
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
|
+
),
|
|
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: [
|
|
1889
|
+
/* @__PURE__ */ jsx(
|
|
1529
1890
|
"button",
|
|
1530
1891
|
{
|
|
1531
|
-
onClick: () => {
|
|
1892
|
+
onClick: (e) => {
|
|
1893
|
+
e.stopPropagation();
|
|
1532
1894
|
onSelect({
|
|
1533
1895
|
url: f2.url,
|
|
1534
1896
|
key: f2.key,
|
|
@@ -1543,7 +1905,7 @@ function ImageManager(props) {
|
|
|
1543
1905
|
children: "Select"
|
|
1544
1906
|
}
|
|
1545
1907
|
),
|
|
1546
|
-
showDelete && /* @__PURE__ */
|
|
1908
|
+
showDelete && /* @__PURE__ */ jsx(
|
|
1547
1909
|
"button",
|
|
1548
1910
|
{
|
|
1549
1911
|
onClick: (e) => {
|
|
@@ -1551,57 +1913,70 @@ function ImageManager(props) {
|
|
|
1551
1913
|
setFileToDelete(f2);
|
|
1552
1914
|
},
|
|
1553
1915
|
className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-red-500 backdrop-blur-md transition-colors",
|
|
1554
|
-
children: /* @__PURE__ */
|
|
1916
|
+
children: /* @__PURE__ */ jsx(IconTrash, { className: "w-4 h-4" })
|
|
1555
1917
|
}
|
|
1556
1918
|
)
|
|
1557
1919
|
] }) }),
|
|
1558
|
-
/* @__PURE__ */
|
|
1920
|
+
/* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 bg-black/50 backdrop-blur-md text-white text-[10px] px-1.5 py-0.5 rounded opacity-0 group-hover:opacity-100 transition-opacity", children: f2.originalName.split(".").pop()?.toUpperCase() })
|
|
1559
1921
|
]
|
|
1560
1922
|
},
|
|
1561
1923
|
f2.key
|
|
1562
1924
|
);
|
|
1563
1925
|
})
|
|
1564
1926
|
] }),
|
|
1565
|
-
!loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */
|
|
1566
|
-
/* @__PURE__ */
|
|
1567
|
-
/* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
1927
|
+
!loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
|
|
1928
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
|
|
1929
|
+
/* @__PURE__ */ jsx("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
|
|
1930
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
|
|
1569
1931
|
] })
|
|
1570
1932
|
] }),
|
|
1571
|
-
activeView === "upload" && /* @__PURE__ */
|
|
1572
|
-
/* @__PURE__ */
|
|
1573
|
-
/* @__PURE__ */
|
|
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: [
|
|
1934
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
|
|
1935
|
+
/* @__PURE__ */ jsx(IconUploadCloud, { className: "text-blue-500" }),
|
|
1574
1936
|
"Upload Files"
|
|
1575
1937
|
] }),
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1579
|
-
/* @__PURE__ */
|
|
1938
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
|
|
1939
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
|
|
1940
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1941
|
+
/* @__PURE__ */ jsx(
|
|
1580
1942
|
"select",
|
|
1581
1943
|
{
|
|
1582
1944
|
value: selectedUploadFolder,
|
|
1583
1945
|
onChange: (e) => setSelectedUploadFolder(e.target.value),
|
|
1584
1946
|
className: "block w-full rounded-lg border-gray-200 bg-gray-50 text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 p-2.5",
|
|
1585
|
-
children: uploadFoldersList.map((opt) => /* @__PURE__ */
|
|
1947
|
+
children: uploadFoldersList.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.id, children: opt.name }, opt.id))
|
|
1586
1948
|
}
|
|
1587
1949
|
),
|
|
1588
|
-
/* @__PURE__ */
|
|
1950
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ jsx(IconFolderPlus, { className: "w-5 h-5" }) })
|
|
1589
1951
|
] })
|
|
1590
1952
|
] }),
|
|
1591
|
-
/* @__PURE__ */
|
|
1953
|
+
/* @__PURE__ */ jsx(
|
|
1592
1954
|
Uploader,
|
|
1593
1955
|
{
|
|
1594
1956
|
clientOptions,
|
|
1595
1957
|
projectId,
|
|
1958
|
+
userId,
|
|
1596
1959
|
folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
|
|
1597
1960
|
accept: ["image/*"],
|
|
1598
1961
|
maxFileSize,
|
|
1599
1962
|
maxFiles,
|
|
1600
1963
|
autoRecordToDb,
|
|
1601
|
-
fetchThumbnails,
|
|
1602
|
-
autoUpload:
|
|
1603
|
-
|
|
1964
|
+
fetchThumbnails: false,
|
|
1965
|
+
autoUpload: false,
|
|
1966
|
+
thumbnailEnabled: thumbnailConfig?.enabled ?? true,
|
|
1967
|
+
thumbnailSizes: thumbnailConfig?.sizes,
|
|
1968
|
+
onClientUploadComplete: async (file) => {
|
|
1604
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
|
+
}
|
|
1605
1980
|
const newItem = {
|
|
1606
1981
|
key: file.fileKey,
|
|
1607
1982
|
originalName: file.name,
|
|
@@ -1609,26 +1984,31 @@ function ImageManager(props) {
|
|
|
1609
1984
|
contentType: file.type,
|
|
1610
1985
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1611
1986
|
url: file.publicUrl,
|
|
1612
|
-
thumbnails
|
|
1987
|
+
thumbnails
|
|
1613
1988
|
};
|
|
1614
|
-
setFiles((prev) =>
|
|
1989
|
+
setFiles((prev) => {
|
|
1990
|
+
if (prev.find((f2) => f2.key === newItem.key)) return prev;
|
|
1991
|
+
return deduplicateFiles([newItem, ...prev]);
|
|
1992
|
+
});
|
|
1615
1993
|
}
|
|
1616
1994
|
},
|
|
1617
1995
|
onComplete: () => {
|
|
1618
|
-
if (mode === "full") {
|
|
1996
|
+
if (mode === "full" && !stayOnUploadAfterComplete) {
|
|
1619
1997
|
setTimeout(() => {
|
|
1620
1998
|
setActiveView("browse");
|
|
1621
1999
|
load();
|
|
1622
2000
|
}, 1e3);
|
|
2001
|
+
} else {
|
|
2002
|
+
load();
|
|
1623
2003
|
}
|
|
1624
2004
|
},
|
|
1625
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",
|
|
1626
2006
|
buttonClassName: "hidden",
|
|
1627
|
-
children: /* @__PURE__ */
|
|
1628
|
-
/* @__PURE__ */
|
|
1629
|
-
/* @__PURE__ */
|
|
1630
|
-
/* @__PURE__ */
|
|
1631
|
-
/* @__PURE__ */
|
|
2007
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
2008
|
+
/* @__PURE__ */ jsx("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx(IconUploadCloud, { className: "w-6 h-6" }) }),
|
|
2009
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2010
|
+
/* @__PURE__ */ jsx("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
|
|
2011
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mt-1", children: [
|
|
1632
2012
|
"Supports PNG, JPG, GIF up to ",
|
|
1633
2013
|
Math.round(maxFileSize / 1024 / 1024),
|
|
1634
2014
|
"MB"
|
|
@@ -1644,11 +2024,12 @@ function ImageManager(props) {
|
|
|
1644
2024
|
}
|
|
1645
2025
|
)
|
|
1646
2026
|
] }),
|
|
1647
|
-
/* @__PURE__ */
|
|
1648
|
-
/* @__PURE__ */
|
|
1649
|
-
/* @__PURE__ */
|
|
1650
|
-
/* @__PURE__ */
|
|
1651
|
-
/* @__PURE__ */
|
|
2027
|
+
/* @__PURE__ */ jsx(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
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: [
|
|
2030
|
+
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
|
|
2031
|
+
/* @__PURE__ */ jsx(Dialog2.Description, { id: "new-folder-description", className: "sr-only", children: "Create a new folder to organize your files" }),
|
|
2032
|
+
/* @__PURE__ */ jsx(
|
|
1652
2033
|
"input",
|
|
1653
2034
|
{
|
|
1654
2035
|
autoFocus: true,
|
|
@@ -1659,9 +2040,9 @@ function ImageManager(props) {
|
|
|
1659
2040
|
onKeyDown: (e) => e.key === "Enter" && handleCreateFolder()
|
|
1660
2041
|
}
|
|
1661
2042
|
),
|
|
1662
|
-
/* @__PURE__ */
|
|
1663
|
-
/* @__PURE__ */
|
|
1664
|
-
/* @__PURE__ */
|
|
2043
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
|
|
2044
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowCreateFolder(false), className: "px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg", children: "Cancel" }),
|
|
2045
|
+
/* @__PURE__ */ jsx(
|
|
1665
2046
|
"button",
|
|
1666
2047
|
{
|
|
1667
2048
|
onClick: handleCreateFolder,
|
|
@@ -1673,21 +2054,21 @@ function ImageManager(props) {
|
|
|
1673
2054
|
] })
|
|
1674
2055
|
] })
|
|
1675
2056
|
] }) }),
|
|
1676
|
-
/* @__PURE__ */
|
|
1677
|
-
/* @__PURE__ */
|
|
1678
|
-
/* @__PURE__ */
|
|
1679
|
-
/* @__PURE__ */
|
|
1680
|
-
/* @__PURE__ */
|
|
2057
|
+
/* @__PURE__ */ jsx(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
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: [
|
|
2060
|
+
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
|
|
2061
|
+
/* @__PURE__ */ jsxs(Dialog2.Description, { id: "delete-file-description", className: "text-gray-600 mb-6", children: [
|
|
1681
2062
|
"Are you sure you want to delete ",
|
|
1682
|
-
/* @__PURE__ */
|
|
2063
|
+
/* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-900", children: [
|
|
1683
2064
|
'"',
|
|
1684
2065
|
fileToDelete?.originalName,
|
|
1685
2066
|
'"'
|
|
1686
2067
|
] }),
|
|
1687
2068
|
"?"
|
|
1688
2069
|
] }),
|
|
1689
|
-
/* @__PURE__ */
|
|
1690
|
-
/* @__PURE__ */
|
|
2070
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
|
|
2071
|
+
/* @__PURE__ */ jsx(
|
|
1691
2072
|
"button",
|
|
1692
2073
|
{
|
|
1693
2074
|
onClick: () => setFileToDelete(null),
|
|
@@ -1695,7 +2076,7 @@ function ImageManager(props) {
|
|
|
1695
2076
|
children: "Cancel"
|
|
1696
2077
|
}
|
|
1697
2078
|
),
|
|
1698
|
-
/* @__PURE__ */
|
|
2079
|
+
/* @__PURE__ */ jsx(
|
|
1699
2080
|
"button",
|
|
1700
2081
|
{
|
|
1701
2082
|
onClick: handleDelete,
|
|
@@ -1706,22 +2087,346 @@ function ImageManager(props) {
|
|
|
1706
2087
|
)
|
|
1707
2088
|
] })
|
|
1708
2089
|
] })
|
|
2090
|
+
] }) }),
|
|
2091
|
+
/* @__PURE__ */ jsx(Dialog2.Root, { open: !!thumbnailSelectorFile, onOpenChange: (open2) => !open2 && setThumbnailSelectorFile(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
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: [
|
|
2094
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6 border-b border-gray-200", children: [
|
|
2095
|
+
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Select Thumbnail" }),
|
|
2096
|
+
/* @__PURE__ */ jsxs(Dialog2.Description, { id: "thumbnail-selector-description", className: "text-gray-600", children: [
|
|
2097
|
+
"Choose a thumbnail size for ",
|
|
2098
|
+
/* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-900", children: [
|
|
2099
|
+
'"',
|
|
2100
|
+
thumbnailSelectorFile?.originalName,
|
|
2101
|
+
'"'
|
|
2102
|
+
] })
|
|
2103
|
+
] })
|
|
2104
|
+
] }),
|
|
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" }) }) }) })
|
|
2127
|
+
] }),
|
|
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
|
+
] }),
|
|
2165
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6 border-t border-gray-200 flex justify-end gap-3", children: [
|
|
2166
|
+
/* @__PURE__ */ jsx(
|
|
2167
|
+
"button",
|
|
2168
|
+
{
|
|
2169
|
+
onClick: () => setThumbnailSelectorFile(null),
|
|
2170
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
|
|
2171
|
+
children: "Cancel"
|
|
2172
|
+
}
|
|
2173
|
+
),
|
|
2174
|
+
/* @__PURE__ */ jsxs(
|
|
2175
|
+
"button",
|
|
2176
|
+
{
|
|
2177
|
+
onClick: () => {
|
|
2178
|
+
if (thumbnailSelectorFile) {
|
|
2179
|
+
onSelect({
|
|
2180
|
+
url: selectedThumbnailForFile?.url || thumbnailSelectorFile.url,
|
|
2181
|
+
key: thumbnailSelectorFile.key,
|
|
2182
|
+
originalName: thumbnailSelectorFile.originalName,
|
|
2183
|
+
size: thumbnailSelectorFile.size,
|
|
2184
|
+
contentType: thumbnailSelectorFile.contentType,
|
|
2185
|
+
thumbnails: thumbnailSelectorFile.thumbnails,
|
|
2186
|
+
selectedThumbnail: selectedThumbnailForFile || void 0
|
|
2187
|
+
});
|
|
2188
|
+
onOpenChange(false);
|
|
2189
|
+
setThumbnailSelectorFile(null);
|
|
2190
|
+
}
|
|
2191
|
+
},
|
|
2192
|
+
className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700",
|
|
2193
|
+
children: [
|
|
2194
|
+
"Select ",
|
|
2195
|
+
selectedThumbnailForFile ? `${selectedThumbnailForFile.size}px Thumbnail` : "Original"
|
|
2196
|
+
]
|
|
2197
|
+
}
|
|
2198
|
+
)
|
|
2199
|
+
] })
|
|
2200
|
+
] })
|
|
2201
|
+
] }) }),
|
|
2202
|
+
/* @__PURE__ */ jsx(Dialog2.Root, { open: !!previewFile, onOpenChange: (open2) => !open2 && setPreviewFile(null), children: /* @__PURE__ */ jsxs(Dialog2.Portal, { children: [
|
|
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: [
|
|
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: [
|
|
2206
|
+
/* @__PURE__ */ jsxs("div", { className: "text-white", children: [
|
|
2207
|
+
/* @__PURE__ */ jsx(Dialog2.Title, { className: "text-xl font-semibold mb-1", children: previewFile?.originalName }),
|
|
2208
|
+
/* @__PURE__ */ jsxs(Dialog2.Description, { id: "image-preview-description", className: "text-sm text-gray-300", children: [
|
|
2209
|
+
previewFile?.size ? `${(previewFile.size / 1024).toFixed(1)} KB` : "",
|
|
2210
|
+
" \u2022 ",
|
|
2211
|
+
previewFile?.contentType
|
|
2212
|
+
] })
|
|
2213
|
+
] }),
|
|
2214
|
+
/* @__PURE__ */ jsx(
|
|
2215
|
+
"button",
|
|
2216
|
+
{
|
|
2217
|
+
onClick: () => setPreviewFile(null),
|
|
2218
|
+
className: "p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors",
|
|
2219
|
+
children: /* @__PURE__ */ jsx(IconX, { className: "w-6 h-6 text-white" })
|
|
2220
|
+
}
|
|
2221
|
+
)
|
|
2222
|
+
] }) }),
|
|
2223
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center p-6", children: /* @__PURE__ */ jsx(
|
|
2224
|
+
"img",
|
|
2225
|
+
{
|
|
2226
|
+
src: previewFile?.url,
|
|
2227
|
+
alt: previewFile?.originalName,
|
|
2228
|
+
className: "max-w-full max-h-full object-contain",
|
|
2229
|
+
onError: (e) => {
|
|
2230
|
+
const target = e.target;
|
|
2231
|
+
target.style.display = "none";
|
|
2232
|
+
const errorDiv = document.createElement("div");
|
|
2233
|
+
errorDiv.className = "text-white text-center";
|
|
2234
|
+
errorDiv.innerHTML = `
|
|
2235
|
+
<div class="text-red-400 mb-2">Failed to load image</div>
|
|
2236
|
+
<div class="text-sm text-gray-400">URL: ${previewFile?.url}</div>
|
|
2237
|
+
`;
|
|
2238
|
+
target.parentElement?.appendChild(errorDiv);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
) }),
|
|
2242
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 z-10 bg-gradient-to-t from-black/70 to-transparent p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-3 justify-center", children: [
|
|
2243
|
+
/* @__PURE__ */ jsx(
|
|
2244
|
+
"button",
|
|
2245
|
+
{
|
|
2246
|
+
onClick: (e) => {
|
|
2247
|
+
e.stopPropagation();
|
|
2248
|
+
if (previewFile) {
|
|
2249
|
+
onSelect({
|
|
2250
|
+
url: previewFile.url,
|
|
2251
|
+
key: previewFile.key,
|
|
2252
|
+
originalName: previewFile.originalName,
|
|
2253
|
+
size: previewFile.size,
|
|
2254
|
+
contentType: previewFile.contentType,
|
|
2255
|
+
thumbnails: previewFile.thumbnails
|
|
2256
|
+
});
|
|
2257
|
+
onOpenChange(false);
|
|
2258
|
+
setPreviewFile(null);
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
className: "px-6 py-2.5 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-500 shadow-lg",
|
|
2262
|
+
children: "Select Image"
|
|
2263
|
+
}
|
|
2264
|
+
),
|
|
2265
|
+
/* @__PURE__ */ jsx(
|
|
2266
|
+
"a",
|
|
2267
|
+
{
|
|
2268
|
+
href: previewFile?.url,
|
|
2269
|
+
target: "_blank",
|
|
2270
|
+
rel: "noopener noreferrer",
|
|
2271
|
+
onClick: (e) => e.stopPropagation(),
|
|
2272
|
+
className: "px-6 py-2.5 bg-white/10 text-white font-semibold rounded-lg hover:bg-white/20 backdrop-blur-md transition-colors",
|
|
2273
|
+
children: "Open in New Tab"
|
|
2274
|
+
}
|
|
2275
|
+
),
|
|
2276
|
+
previewFile?.contentType?.startsWith("image/") && /* @__PURE__ */ jsxs(
|
|
2277
|
+
"button",
|
|
2278
|
+
{
|
|
2279
|
+
onClick: (e) => {
|
|
2280
|
+
e.stopPropagation();
|
|
2281
|
+
if (previewFile) {
|
|
2282
|
+
handleThumbnailClick(previewFile);
|
|
2283
|
+
setPreviewFile(null);
|
|
2284
|
+
}
|
|
2285
|
+
},
|
|
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",
|
|
2287
|
+
children: [
|
|
2288
|
+
/* @__PURE__ */ jsx(IconLayers, { className: "w-5 h-5" }),
|
|
2289
|
+
"Select Thumbnail"
|
|
2290
|
+
]
|
|
2291
|
+
}
|
|
2292
|
+
)
|
|
2293
|
+
] }) })
|
|
2294
|
+
] }) })
|
|
1709
2295
|
] }) })
|
|
1710
2296
|
] });
|
|
1711
2297
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
};
|
|
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
|
+
}
|
|
2429
|
+
|
|
2430
|
+
export { ConnectProjectDialog, ImageManager, ProjectFilesWidget, StorageQuotaError, UpfilesClient, Uploader, addApiKeyManually, connectProject, connectUsingApiKey, createClientWithKey, createProject, createProjectWithApiKey, fetchFileThumbnails, fetchProjectFiles, fetchProjectImagesWithThumbs, listProjects, updateProject, useUpfilesInhouse };
|
|
2431
|
+
//# sourceMappingURL=index.mjs.map
|
|
2432
|
+
//# sourceMappingURL=index.mjs.map
|