@plugable-io/react 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +123 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +684 -0
- package/dist/index.mjs +640 -0
- package/package.json +8 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Dropzone: () => Dropzone,
|
|
34
|
+
FileImage: () => FileImage,
|
|
35
|
+
FileList: () => FileList,
|
|
36
|
+
FilePreview: () => FilePreview,
|
|
37
|
+
PlugableProvider: () => PlugableProvider,
|
|
38
|
+
clearImageCache: () => clearImageCache,
|
|
39
|
+
useFiles: () => useFiles,
|
|
40
|
+
usePlugable: () => usePlugable
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/PlugableProvider.tsx
|
|
45
|
+
var import_react = require("react");
|
|
46
|
+
var import_js = require("@plugable-io/js");
|
|
47
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
48
|
+
var PlugableContext = (0, import_react.createContext)(null);
|
|
49
|
+
function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
|
|
50
|
+
switch (authProvider) {
|
|
51
|
+
case "clerk":
|
|
52
|
+
return async () => {
|
|
53
|
+
if (typeof window !== "undefined" && window.Clerk) {
|
|
54
|
+
const session = await window.Clerk.session;
|
|
55
|
+
if (!session) {
|
|
56
|
+
throw new Error("No active Clerk session found");
|
|
57
|
+
}
|
|
58
|
+
const template = clerkJWTTemplate || void 0;
|
|
59
|
+
return await session.getToken({ template });
|
|
60
|
+
}
|
|
61
|
+
throw new Error(
|
|
62
|
+
"Clerk not found. Please ensure @clerk/clerk-react is installed and ClerkProvider wraps your app, or provide a custom getToken function."
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
case "supabase":
|
|
66
|
+
return async () => {
|
|
67
|
+
if (typeof window !== "undefined" && window.supabase) {
|
|
68
|
+
const { data, error } = await window.supabase.auth.getSession();
|
|
69
|
+
if (error || !data.session) {
|
|
70
|
+
throw new Error("No active Supabase session found");
|
|
71
|
+
}
|
|
72
|
+
return data.session.access_token;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Supabase client not found. Please ensure @supabase/supabase-js is installed and initialized, or provide a custom getToken function."
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
case "firebase":
|
|
79
|
+
return async () => {
|
|
80
|
+
if (typeof window !== "undefined" && window.firebase?.auth) {
|
|
81
|
+
const user = window.firebase.auth().currentUser;
|
|
82
|
+
if (!user) {
|
|
83
|
+
throw new Error("No active Firebase user found");
|
|
84
|
+
}
|
|
85
|
+
return await user.getIdToken();
|
|
86
|
+
}
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Firebase not found. Please ensure firebase is installed and initialized, or provide a custom getToken function."
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Unknown auth provider: ${authProvider}. Please provide either a valid authProvider or a custom getToken function.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function PlugableProvider({
|
|
98
|
+
bucketId,
|
|
99
|
+
children,
|
|
100
|
+
getToken,
|
|
101
|
+
authProvider,
|
|
102
|
+
clerkJWTTemplate,
|
|
103
|
+
baseUrl
|
|
104
|
+
}) {
|
|
105
|
+
const listenersRef = (0, import_react.useRef)({});
|
|
106
|
+
const client = (0, import_react.useMemo)(() => {
|
|
107
|
+
if (!getToken && !authProvider) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Either getToken function or authProvider must be provided to PlugableProvider"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const tokenGetter = getToken || createAuthTokenGetter(authProvider, clerkJWTTemplate);
|
|
113
|
+
const client_ = new import_js.BucketClient({
|
|
114
|
+
bucketId,
|
|
115
|
+
getToken: tokenGetter,
|
|
116
|
+
baseUrl
|
|
117
|
+
});
|
|
118
|
+
return { client: client_, tokenGetter };
|
|
119
|
+
}, [bucketId, getToken, authProvider, clerkJWTTemplate, baseUrl]);
|
|
120
|
+
const on = (0, import_react.useCallback)((event, handler) => {
|
|
121
|
+
if (!listenersRef.current[event]) {
|
|
122
|
+
listenersRef.current[event] = /* @__PURE__ */ new Set();
|
|
123
|
+
}
|
|
124
|
+
listenersRef.current[event].add(handler);
|
|
125
|
+
return () => {
|
|
126
|
+
listenersRef.current[event]?.delete(handler);
|
|
127
|
+
};
|
|
128
|
+
}, []);
|
|
129
|
+
const emit = (0, import_react.useCallback)((event, data) => {
|
|
130
|
+
listenersRef.current[event]?.forEach((handler) => handler(data));
|
|
131
|
+
}, []);
|
|
132
|
+
const value = (0, import_react.useMemo)(
|
|
133
|
+
() => ({
|
|
134
|
+
client: client.client,
|
|
135
|
+
bucketId,
|
|
136
|
+
on,
|
|
137
|
+
emit,
|
|
138
|
+
getToken: client.tokenGetter,
|
|
139
|
+
baseUrl
|
|
140
|
+
}),
|
|
141
|
+
[client, bucketId, on, emit, baseUrl]
|
|
142
|
+
);
|
|
143
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlugableContext.Provider, { value, children });
|
|
144
|
+
}
|
|
145
|
+
function usePlugable() {
|
|
146
|
+
const context = (0, import_react.useContext)(PlugableContext);
|
|
147
|
+
if (!context) {
|
|
148
|
+
throw new Error("usePlugable must be used within a PlugableProvider");
|
|
149
|
+
}
|
|
150
|
+
return context;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/components/Dropzone.tsx
|
|
154
|
+
var import_react2 = __toESM(require("react"));
|
|
155
|
+
var import_js2 = require("@plugable-io/js");
|
|
156
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
157
|
+
function Dropzone({
|
|
158
|
+
bucketId: _bucketId,
|
|
159
|
+
metadata,
|
|
160
|
+
onUploadComplete,
|
|
161
|
+
onUploadError,
|
|
162
|
+
onProgressUpdate,
|
|
163
|
+
accept,
|
|
164
|
+
maxFiles,
|
|
165
|
+
children,
|
|
166
|
+
className,
|
|
167
|
+
style
|
|
168
|
+
}) {
|
|
169
|
+
const { client: defaultClient, emit, getToken, baseUrl } = usePlugable();
|
|
170
|
+
const client = import_react2.default.useMemo(() => {
|
|
171
|
+
if (_bucketId) {
|
|
172
|
+
return new import_js2.BucketClient({
|
|
173
|
+
bucketId: _bucketId,
|
|
174
|
+
getToken,
|
|
175
|
+
baseUrl
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return defaultClient;
|
|
179
|
+
}, [_bucketId, defaultClient, getToken, baseUrl]);
|
|
180
|
+
const [isDragActive, setIsDragActive] = (0, import_react2.useState)(false);
|
|
181
|
+
const [isUploading, setIsUploading] = (0, import_react2.useState)(false);
|
|
182
|
+
const [uploadProgress, setUploadProgress] = (0, import_react2.useState)({});
|
|
183
|
+
const [uploadedFiles, setUploadedFiles] = (0, import_react2.useState)([]);
|
|
184
|
+
const fileInputRef = import_react2.default.useRef(null);
|
|
185
|
+
const uploadFiles = (0, import_react2.useCallback)(
|
|
186
|
+
async (files) => {
|
|
187
|
+
const fileArray = Array.from(files);
|
|
188
|
+
const filesToUpload = maxFiles ? fileArray.slice(0, maxFiles) : fileArray;
|
|
189
|
+
setIsUploading(true);
|
|
190
|
+
setUploadProgress({});
|
|
191
|
+
const uploadPromises = filesToUpload.map(async (file) => {
|
|
192
|
+
const options = {
|
|
193
|
+
metadata,
|
|
194
|
+
onProgress: (progress) => {
|
|
195
|
+
setUploadProgress((prev) => ({
|
|
196
|
+
...prev,
|
|
197
|
+
[file.name]: progress
|
|
198
|
+
}));
|
|
199
|
+
onProgressUpdate?.(file.name, progress);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
try {
|
|
203
|
+
const uploadedFile = await client.upload(file, options);
|
|
204
|
+
return uploadedFile;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`Failed to upload ${file.name}:`, error);
|
|
207
|
+
onUploadError?.(error);
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const results = await Promise.all(uploadPromises);
|
|
212
|
+
const successfulUploads = results.filter((f) => f !== null);
|
|
213
|
+
setUploadedFiles((prev) => [...prev, ...successfulUploads]);
|
|
214
|
+
setIsUploading(false);
|
|
215
|
+
setUploadProgress({});
|
|
216
|
+
if (successfulUploads.length > 0) {
|
|
217
|
+
successfulUploads.forEach((file) => emit("file.uploaded", file));
|
|
218
|
+
onUploadComplete?.(successfulUploads);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
[client, metadata, maxFiles, onUploadComplete, onUploadError, onProgressUpdate, emit]
|
|
222
|
+
);
|
|
223
|
+
const handleDrop = (0, import_react2.useCallback)(
|
|
224
|
+
(e) => {
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
setIsDragActive(false);
|
|
228
|
+
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
229
|
+
uploadFiles(e.dataTransfer.files);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
[uploadFiles]
|
|
233
|
+
);
|
|
234
|
+
const handleDragOver = (0, import_react2.useCallback)((e) => {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
e.stopPropagation();
|
|
237
|
+
setIsDragActive(true);
|
|
238
|
+
}, []);
|
|
239
|
+
const handleDragLeave = (0, import_react2.useCallback)((e) => {
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
e.stopPropagation();
|
|
242
|
+
setIsDragActive(false);
|
|
243
|
+
}, []);
|
|
244
|
+
const handleFileInputChange = (0, import_react2.useCallback)(
|
|
245
|
+
(e) => {
|
|
246
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
247
|
+
uploadFiles(e.target.files);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
[uploadFiles]
|
|
251
|
+
);
|
|
252
|
+
const openFileDialog = (0, import_react2.useCallback)(() => {
|
|
253
|
+
fileInputRef.current?.click();
|
|
254
|
+
}, []);
|
|
255
|
+
const renderProps = {
|
|
256
|
+
isDragActive,
|
|
257
|
+
isUploading,
|
|
258
|
+
uploadProgress,
|
|
259
|
+
openFileDialog,
|
|
260
|
+
uploadedFiles
|
|
261
|
+
};
|
|
262
|
+
if (children) {
|
|
263
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
264
|
+
"div",
|
|
265
|
+
{
|
|
266
|
+
onDrop: handleDrop,
|
|
267
|
+
onDragOver: handleDragOver,
|
|
268
|
+
onDragLeave: handleDragLeave,
|
|
269
|
+
className,
|
|
270
|
+
style,
|
|
271
|
+
children: [
|
|
272
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
273
|
+
"input",
|
|
274
|
+
{
|
|
275
|
+
ref: fileInputRef,
|
|
276
|
+
type: "file",
|
|
277
|
+
multiple: !maxFiles || maxFiles > 1,
|
|
278
|
+
accept,
|
|
279
|
+
onChange: handleFileInputChange,
|
|
280
|
+
style: { display: "none" }
|
|
281
|
+
}
|
|
282
|
+
),
|
|
283
|
+
children(renderProps)
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
289
|
+
"div",
|
|
290
|
+
{
|
|
291
|
+
onDrop: handleDrop,
|
|
292
|
+
onDragOver: handleDragOver,
|
|
293
|
+
onDragLeave: handleDragLeave,
|
|
294
|
+
onClick: openFileDialog,
|
|
295
|
+
className,
|
|
296
|
+
style: {
|
|
297
|
+
border: `2px dashed ${isDragActive ? "#0070f3" : "#ccc"}`,
|
|
298
|
+
borderRadius: "8px",
|
|
299
|
+
padding: "40px 20px",
|
|
300
|
+
textAlign: "center",
|
|
301
|
+
cursor: "pointer",
|
|
302
|
+
backgroundColor: isDragActive ? "#f0f8ff" : "#fafafa",
|
|
303
|
+
transition: "all 0.2s ease",
|
|
304
|
+
...style
|
|
305
|
+
},
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
308
|
+
"input",
|
|
309
|
+
{
|
|
310
|
+
ref: fileInputRef,
|
|
311
|
+
type: "file",
|
|
312
|
+
multiple: !maxFiles || maxFiles > 1,
|
|
313
|
+
accept,
|
|
314
|
+
onChange: handleFileInputChange,
|
|
315
|
+
style: { display: "none" }
|
|
316
|
+
}
|
|
317
|
+
),
|
|
318
|
+
isUploading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
319
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { margin: 0, fontWeight: "bold", color: "#333" }, children: "Uploading..." }),
|
|
320
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "16px" }, children: Object.entries(uploadProgress).map(([fileName, progress]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "8px" }, children: [
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
|
|
322
|
+
fileName,
|
|
323
|
+
": ",
|
|
324
|
+
progress,
|
|
325
|
+
"%"
|
|
326
|
+
] }),
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
328
|
+
"div",
|
|
329
|
+
{
|
|
330
|
+
style: {
|
|
331
|
+
width: "100%",
|
|
332
|
+
height: "8px",
|
|
333
|
+
backgroundColor: "#e0e0e0",
|
|
334
|
+
borderRadius: "4px",
|
|
335
|
+
overflow: "hidden"
|
|
336
|
+
},
|
|
337
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
338
|
+
"div",
|
|
339
|
+
{
|
|
340
|
+
style: {
|
|
341
|
+
width: `${progress}%`,
|
|
342
|
+
height: "100%",
|
|
343
|
+
backgroundColor: "#0070f3",
|
|
344
|
+
transition: "width 0.3s ease"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
] }, fileName)) })
|
|
351
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { margin: 0, fontWeight: "bold", fontSize: "16px", color: "#333" }, children: isDragActive ? "Drop files here" : "Drag and drop files here" }),
|
|
353
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { margin: "8px 0 0 0", fontSize: "14px", color: "#666" }, children: "or click to select files" }),
|
|
354
|
+
maxFiles && maxFiles > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: { margin: "4px 0 0 0", fontSize: "12px", color: "#999" }, children: [
|
|
355
|
+
"(Maximum ",
|
|
356
|
+
maxFiles,
|
|
357
|
+
" files)"
|
|
358
|
+
] })
|
|
359
|
+
] })
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/hooks/useFiles.ts
|
|
366
|
+
var import_react3 = require("react");
|
|
367
|
+
function useFiles({
|
|
368
|
+
metadata,
|
|
369
|
+
startPage = 1,
|
|
370
|
+
perPage = 20,
|
|
371
|
+
mediaType,
|
|
372
|
+
autoLoad = true
|
|
373
|
+
} = {}) {
|
|
374
|
+
const { client, on } = usePlugable();
|
|
375
|
+
const [files, setFiles] = (0, import_react3.useState)([]);
|
|
376
|
+
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
377
|
+
const [page, setPage] = (0, import_react3.useState)(startPage);
|
|
378
|
+
const [hasNext, setHasNext] = (0, import_react3.useState)(false);
|
|
379
|
+
const metadataKey = JSON.stringify(metadata);
|
|
380
|
+
const stableMetadata = (0, import_react3.useMemo)(() => metadata, [metadataKey]);
|
|
381
|
+
const fetchFiles = (0, import_react3.useCallback)(async (pageNum) => {
|
|
382
|
+
setIsLoading(true);
|
|
383
|
+
try {
|
|
384
|
+
const options = {
|
|
385
|
+
metadata: stableMetadata,
|
|
386
|
+
media_type: mediaType,
|
|
387
|
+
page: pageNum,
|
|
388
|
+
per_page: perPage,
|
|
389
|
+
with_download_url: true
|
|
390
|
+
};
|
|
391
|
+
const response = await client.list(options);
|
|
392
|
+
setFiles(response.files);
|
|
393
|
+
setHasNext(response.paging.has_next_page);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.error("Failed to load files:", err);
|
|
396
|
+
setFiles([]);
|
|
397
|
+
setHasNext(false);
|
|
398
|
+
} finally {
|
|
399
|
+
setIsLoading(false);
|
|
400
|
+
}
|
|
401
|
+
}, [client, stableMetadata, mediaType, perPage]);
|
|
402
|
+
(0, import_react3.useEffect)(() => {
|
|
403
|
+
if (autoLoad) {
|
|
404
|
+
fetchFiles(page);
|
|
405
|
+
}
|
|
406
|
+
}, [fetchFiles, page, autoLoad]);
|
|
407
|
+
(0, import_react3.useEffect)(() => {
|
|
408
|
+
const unsubscribe = on("file.uploaded", () => {
|
|
409
|
+
fetchFiles(page);
|
|
410
|
+
});
|
|
411
|
+
return unsubscribe;
|
|
412
|
+
}, [on, fetchFiles, page]);
|
|
413
|
+
const loadNextPage = (0, import_react3.useCallback)(() => {
|
|
414
|
+
if (hasNext) {
|
|
415
|
+
setPage((p) => p + 1);
|
|
416
|
+
}
|
|
417
|
+
}, [hasNext]);
|
|
418
|
+
const loadPreviousPage = (0, import_react3.useCallback)(() => {
|
|
419
|
+
setPage((p) => Math.max(1, p - 1));
|
|
420
|
+
}, []);
|
|
421
|
+
const refresh = (0, import_react3.useCallback)(async () => {
|
|
422
|
+
await fetchFiles(page);
|
|
423
|
+
}, [fetchFiles, page]);
|
|
424
|
+
return {
|
|
425
|
+
files,
|
|
426
|
+
isLoading,
|
|
427
|
+
pagination: {
|
|
428
|
+
current: page,
|
|
429
|
+
hasNext,
|
|
430
|
+
hasPrevious: page > 1,
|
|
431
|
+
loadNextPage,
|
|
432
|
+
loadPreviousPage
|
|
433
|
+
},
|
|
434
|
+
setPage,
|
|
435
|
+
refresh
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/components/FileList.tsx
|
|
440
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
441
|
+
function FileList({
|
|
442
|
+
metadata,
|
|
443
|
+
mediaType,
|
|
444
|
+
perPage = 20,
|
|
445
|
+
autoLoad = true,
|
|
446
|
+
startPage = 1,
|
|
447
|
+
children
|
|
448
|
+
}) {
|
|
449
|
+
const { files, isLoading, pagination, refresh } = useFiles({
|
|
450
|
+
metadata,
|
|
451
|
+
mediaType,
|
|
452
|
+
perPage,
|
|
453
|
+
autoLoad,
|
|
454
|
+
startPage
|
|
455
|
+
});
|
|
456
|
+
const renderProps = {
|
|
457
|
+
files,
|
|
458
|
+
isLoading,
|
|
459
|
+
hasMore: pagination.hasNext,
|
|
460
|
+
loadMore: pagination.loadNextPage,
|
|
461
|
+
refresh,
|
|
462
|
+
error: null,
|
|
463
|
+
pagination
|
|
464
|
+
};
|
|
465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: children(renderProps) });
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/components/FileImage.tsx
|
|
469
|
+
var import_react4 = require("react");
|
|
470
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
471
|
+
var imageCache = /* @__PURE__ */ new Map();
|
|
472
|
+
function FileImage({
|
|
473
|
+
file,
|
|
474
|
+
width,
|
|
475
|
+
height,
|
|
476
|
+
objectFit = "cover",
|
|
477
|
+
borderRadius,
|
|
478
|
+
alt,
|
|
479
|
+
className,
|
|
480
|
+
style,
|
|
481
|
+
onLoad,
|
|
482
|
+
onError
|
|
483
|
+
}) {
|
|
484
|
+
const [imageSrc, setImageSrc] = (0, import_react4.useState)(null);
|
|
485
|
+
const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
|
|
486
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
487
|
+
(0, import_react4.useEffect)(() => {
|
|
488
|
+
let isMounted = true;
|
|
489
|
+
let objectUrl = null;
|
|
490
|
+
const loadImage = async () => {
|
|
491
|
+
try {
|
|
492
|
+
const cacheKey = `${file.id}-${file.checksum}`;
|
|
493
|
+
const cached = imageCache.get(cacheKey);
|
|
494
|
+
if (cached) {
|
|
495
|
+
if (isMounted) {
|
|
496
|
+
setImageSrc(cached);
|
|
497
|
+
setIsLoading(false);
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (file.download_url) {
|
|
502
|
+
const response = await fetch(file.download_url, {
|
|
503
|
+
headers: {
|
|
504
|
+
"Cache-Control": "private, max-age=31536000",
|
|
505
|
+
"If-None-Match": file.checksum
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
if (!response.ok) {
|
|
509
|
+
throw new Error(`Failed to fetch image: ${response.statusText}`);
|
|
510
|
+
}
|
|
511
|
+
const blob = await response.blob();
|
|
512
|
+
objectUrl = URL.createObjectURL(blob);
|
|
513
|
+
imageCache.set(cacheKey, objectUrl);
|
|
514
|
+
if (isMounted) {
|
|
515
|
+
setImageSrc(objectUrl);
|
|
516
|
+
setIsLoading(false);
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
throw new Error("No download URL available for file");
|
|
520
|
+
}
|
|
521
|
+
} catch (err) {
|
|
522
|
+
const error2 = err;
|
|
523
|
+
if (isMounted) {
|
|
524
|
+
setError(error2);
|
|
525
|
+
setIsLoading(false);
|
|
526
|
+
onError?.(error2);
|
|
527
|
+
}
|
|
528
|
+
console.error("Failed to load image:", err);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
loadImage();
|
|
532
|
+
return () => {
|
|
533
|
+
isMounted = false;
|
|
534
|
+
};
|
|
535
|
+
}, [file.id, file.checksum, file.download_url, onError]);
|
|
536
|
+
const handleLoad = () => {
|
|
537
|
+
setIsLoading(false);
|
|
538
|
+
onLoad?.();
|
|
539
|
+
};
|
|
540
|
+
const handleError = () => {
|
|
541
|
+
const err = new Error("Image failed to load");
|
|
542
|
+
setError(err);
|
|
543
|
+
setIsLoading(false);
|
|
544
|
+
onError?.(err);
|
|
545
|
+
};
|
|
546
|
+
const imageStyle = {
|
|
547
|
+
width: width || "100%",
|
|
548
|
+
height: height || "auto",
|
|
549
|
+
objectFit,
|
|
550
|
+
borderRadius: borderRadius || 0,
|
|
551
|
+
...style
|
|
552
|
+
};
|
|
553
|
+
if (error) {
|
|
554
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
555
|
+
"div",
|
|
556
|
+
{
|
|
557
|
+
className,
|
|
558
|
+
style: {
|
|
559
|
+
...imageStyle,
|
|
560
|
+
display: "flex",
|
|
561
|
+
alignItems: "center",
|
|
562
|
+
justifyContent: "center",
|
|
563
|
+
backgroundColor: "#f0f0f0",
|
|
564
|
+
color: "#999",
|
|
565
|
+
fontSize: "14px"
|
|
566
|
+
},
|
|
567
|
+
children: "Failed to load image"
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
if (isLoading || !imageSrc) {
|
|
572
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
573
|
+
"div",
|
|
574
|
+
{
|
|
575
|
+
className,
|
|
576
|
+
style: {
|
|
577
|
+
...imageStyle,
|
|
578
|
+
display: "flex",
|
|
579
|
+
alignItems: "center",
|
|
580
|
+
justifyContent: "center",
|
|
581
|
+
backgroundColor: "#f0f0f0"
|
|
582
|
+
},
|
|
583
|
+
children: [
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
585
|
+
"div",
|
|
586
|
+
{
|
|
587
|
+
style: {
|
|
588
|
+
width: "40px",
|
|
589
|
+
height: "40px",
|
|
590
|
+
border: "3px solid #e0e0e0",
|
|
591
|
+
borderTop: "3px solid #0070f3",
|
|
592
|
+
borderRadius: "50%",
|
|
593
|
+
animation: "spin 1s linear infinite"
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
|
|
598
|
+
@keyframes spin {
|
|
599
|
+
0% { transform: rotate(0deg); }
|
|
600
|
+
100% { transform: rotate(360deg); }
|
|
601
|
+
}
|
|
602
|
+
` })
|
|
603
|
+
]
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
608
|
+
"img",
|
|
609
|
+
{
|
|
610
|
+
src: imageSrc,
|
|
611
|
+
alt: alt || file.name,
|
|
612
|
+
className,
|
|
613
|
+
style: imageStyle,
|
|
614
|
+
onLoad: handleLoad,
|
|
615
|
+
onError: handleError
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
function clearImageCache() {
|
|
620
|
+
imageCache.forEach((url) => URL.revokeObjectURL(url));
|
|
621
|
+
imageCache.clear();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/components/FilePreview.tsx
|
|
625
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
626
|
+
function FilePreview({
|
|
627
|
+
file,
|
|
628
|
+
width = 80,
|
|
629
|
+
height = 80,
|
|
630
|
+
className,
|
|
631
|
+
style,
|
|
632
|
+
objectFit = "cover",
|
|
633
|
+
showExtension = true,
|
|
634
|
+
renderNonImage
|
|
635
|
+
}) {
|
|
636
|
+
const isImage = file.content_type.startsWith("image/");
|
|
637
|
+
const containerStyle = {
|
|
638
|
+
width,
|
|
639
|
+
height,
|
|
640
|
+
borderRadius: 4,
|
|
641
|
+
overflow: "hidden",
|
|
642
|
+
backgroundColor: "#f5f5f5",
|
|
643
|
+
display: "flex",
|
|
644
|
+
alignItems: "center",
|
|
645
|
+
justifyContent: "center",
|
|
646
|
+
border: "1px solid #eee",
|
|
647
|
+
...style
|
|
648
|
+
};
|
|
649
|
+
if (isImage) {
|
|
650
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
651
|
+
FileImage,
|
|
652
|
+
{
|
|
653
|
+
file,
|
|
654
|
+
width,
|
|
655
|
+
height,
|
|
656
|
+
objectFit,
|
|
657
|
+
className,
|
|
658
|
+
style,
|
|
659
|
+
borderRadius: 4
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
if (renderNonImage) {
|
|
664
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style: containerStyle, children: renderNonImage(file) });
|
|
665
|
+
}
|
|
666
|
+
const extension = file.name.split(".").pop()?.toUpperCase() || "FILE";
|
|
667
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style: containerStyle, children: showExtension && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: {
|
|
668
|
+
fontSize: "12px",
|
|
669
|
+
fontWeight: "bold",
|
|
670
|
+
color: "#666",
|
|
671
|
+
textTransform: "uppercase"
|
|
672
|
+
}, children: extension }) });
|
|
673
|
+
}
|
|
674
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
675
|
+
0 && (module.exports = {
|
|
676
|
+
Dropzone,
|
|
677
|
+
FileImage,
|
|
678
|
+
FileList,
|
|
679
|
+
FilePreview,
|
|
680
|
+
PlugableProvider,
|
|
681
|
+
clearImageCache,
|
|
682
|
+
useFiles,
|
|
683
|
+
usePlugable
|
|
684
|
+
});
|