@plugable-io/react 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/dist/index.d.mts +32 -3
- package/dist/index.d.ts +32 -3
- package/dist/index.js +389 -99
- package/dist/index.mjs +395 -108
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,112 @@
|
|
|
1
1
|
// src/PlugableProvider.tsx
|
|
2
|
-
import { createContext, useContext, useMemo, useCallback, useRef, useState } from "react";
|
|
2
|
+
import { createContext, useContext, useMemo, useCallback, useRef, useState, useEffect } from "react";
|
|
3
3
|
import { BucketClient } from "@plugable-io/js";
|
|
4
|
+
|
|
5
|
+
// src/utils/theme.ts
|
|
6
|
+
var defaultLightTheme = {
|
|
7
|
+
// Purple to blue accent gradient
|
|
8
|
+
accentPrimary: "#9333ea",
|
|
9
|
+
// purple-600
|
|
10
|
+
accentSecondary: "#2563eb",
|
|
11
|
+
// blue-600
|
|
12
|
+
accentHover: "#7c3aed",
|
|
13
|
+
// purple-700
|
|
14
|
+
// Light backgrounds
|
|
15
|
+
baseBg: "#ffffff",
|
|
16
|
+
baseSurface: "#f8fafc",
|
|
17
|
+
// slate-50
|
|
18
|
+
baseBorder: "#e2e8f0",
|
|
19
|
+
// slate-200
|
|
20
|
+
// Dark text on light
|
|
21
|
+
textPrimary: "#0f172a",
|
|
22
|
+
// slate-900
|
|
23
|
+
textSecondary: "#475569",
|
|
24
|
+
// slate-600
|
|
25
|
+
textMuted: "#94a3b8",
|
|
26
|
+
// slate-400
|
|
27
|
+
// State colors
|
|
28
|
+
success: "#10b981",
|
|
29
|
+
// green-500
|
|
30
|
+
error: "#ef4444",
|
|
31
|
+
// red-500
|
|
32
|
+
warning: "#f59e0b",
|
|
33
|
+
// amber-500
|
|
34
|
+
// Overlay
|
|
35
|
+
overlay: "rgba(0, 0, 0, 0.05)",
|
|
36
|
+
backdropBlur: "blur(12px)"
|
|
37
|
+
};
|
|
38
|
+
var defaultDarkTheme = {
|
|
39
|
+
// Purple to blue accent gradient (same as light)
|
|
40
|
+
accentPrimary: "#9333ea",
|
|
41
|
+
accentSecondary: "#2563eb",
|
|
42
|
+
accentHover: "#a855f7",
|
|
43
|
+
// purple-500 (lighter for dark mode)
|
|
44
|
+
// Dark backgrounds
|
|
45
|
+
baseBg: "#0f172a",
|
|
46
|
+
// slate-900
|
|
47
|
+
baseSurface: "rgba(30, 41, 59, 0.5)",
|
|
48
|
+
// slate-800/50 with transparency
|
|
49
|
+
baseBorder: "rgba(255, 255, 255, 0.1)",
|
|
50
|
+
// Light text on dark
|
|
51
|
+
textPrimary: "#f1f5f9",
|
|
52
|
+
// slate-100
|
|
53
|
+
textSecondary: "#cbd5e1",
|
|
54
|
+
// slate-300
|
|
55
|
+
textMuted: "#64748b",
|
|
56
|
+
// slate-500
|
|
57
|
+
// State colors (slightly adjusted for dark mode)
|
|
58
|
+
success: "#34d399",
|
|
59
|
+
// green-400
|
|
60
|
+
error: "#f87171",
|
|
61
|
+
// red-400
|
|
62
|
+
warning: "#fbbf24",
|
|
63
|
+
// amber-400
|
|
64
|
+
// Overlay
|
|
65
|
+
overlay: "rgba(0, 0, 0, 0.3)",
|
|
66
|
+
backdropBlur: "blur(24px)"
|
|
67
|
+
};
|
|
68
|
+
function getThemeColors(config = {}) {
|
|
69
|
+
const { theme = "dark", accentColor, baseColor } = config;
|
|
70
|
+
const isDark = theme === "dark" || theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
71
|
+
const colors = isDark ? { ...defaultDarkTheme } : { ...defaultLightTheme };
|
|
72
|
+
if (accentColor) {
|
|
73
|
+
colors.accentPrimary = accentColor;
|
|
74
|
+
colors.accentSecondary = accentColor;
|
|
75
|
+
colors.accentHover = accentColor;
|
|
76
|
+
}
|
|
77
|
+
if (baseColor) {
|
|
78
|
+
colors.baseBg = baseColor;
|
|
79
|
+
colors.baseSurface = baseColor;
|
|
80
|
+
}
|
|
81
|
+
return colors;
|
|
82
|
+
}
|
|
83
|
+
function generateCSSVariables(colors) {
|
|
84
|
+
return {
|
|
85
|
+
"--plugable-accent-primary": colors.accentPrimary,
|
|
86
|
+
"--plugable-accent-secondary": colors.accentSecondary,
|
|
87
|
+
"--plugable-accent-hover": colors.accentHover,
|
|
88
|
+
"--plugable-base-bg": colors.baseBg,
|
|
89
|
+
"--plugable-base-surface": colors.baseSurface,
|
|
90
|
+
"--plugable-base-border": colors.baseBorder,
|
|
91
|
+
"--plugable-text-primary": colors.textPrimary,
|
|
92
|
+
"--plugable-text-secondary": colors.textSecondary,
|
|
93
|
+
"--plugable-text-muted": colors.textMuted,
|
|
94
|
+
"--plugable-success": colors.success,
|
|
95
|
+
"--plugable-error": colors.error,
|
|
96
|
+
"--plugable-warning": colors.warning,
|
|
97
|
+
"--plugable-overlay": colors.overlay,
|
|
98
|
+
"--plugable-backdrop-blur": colors.backdropBlur
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function applyCSSVariables(element, config = {}) {
|
|
102
|
+
const colors = getThemeColors(config);
|
|
103
|
+
const variables = generateCSSVariables(colors);
|
|
104
|
+
Object.entries(variables).forEach(([key, value]) => {
|
|
105
|
+
element.style.setProperty(key, value);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/PlugableProvider.tsx
|
|
4
110
|
import { jsx } from "react/jsx-runtime";
|
|
5
111
|
var PlugableContext = createContext(null);
|
|
6
112
|
function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
|
|
@@ -45,6 +151,13 @@ function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
|
|
|
45
151
|
"Firebase not found. Please ensure firebase is installed and initialized, or provide a custom getToken function."
|
|
46
152
|
);
|
|
47
153
|
};
|
|
154
|
+
case "generic_jwks":
|
|
155
|
+
case "generic_jwt":
|
|
156
|
+
return async () => {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Manual token required for ${authProvider}. Please provide a custom getToken function.`
|
|
159
|
+
);
|
|
160
|
+
};
|
|
48
161
|
default:
|
|
49
162
|
throw new Error(
|
|
50
163
|
`Unknown auth provider: ${authProvider}. Please provide either a valid authProvider or a custom getToken function.`
|
|
@@ -58,11 +171,15 @@ function PlugableProvider({
|
|
|
58
171
|
authProvider,
|
|
59
172
|
clerkJWTTemplate,
|
|
60
173
|
baseUrl,
|
|
61
|
-
staleTime = 5 * 60 * 1e3
|
|
174
|
+
staleTime = 5 * 60 * 1e3,
|
|
62
175
|
// Default 5 minutes
|
|
176
|
+
accentColor,
|
|
177
|
+
baseColor,
|
|
178
|
+
theme = "dark"
|
|
63
179
|
}) {
|
|
64
180
|
const listenersRef = useRef({});
|
|
65
181
|
const [cache, setCacheState] = useState(/* @__PURE__ */ new Map());
|
|
182
|
+
const containerRef = useRef(null);
|
|
66
183
|
const client = useMemo(() => {
|
|
67
184
|
if (!getToken && !authProvider) {
|
|
68
185
|
throw new Error(
|
|
@@ -132,7 +249,12 @@ function PlugableProvider({
|
|
|
132
249
|
}),
|
|
133
250
|
[client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
|
|
134
251
|
);
|
|
135
|
-
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (containerRef.current) {
|
|
254
|
+
applyCSSVariables(containerRef.current, { accentColor, baseColor, theme });
|
|
255
|
+
}
|
|
256
|
+
}, [accentColor, baseColor, theme]);
|
|
257
|
+
return /* @__PURE__ */ jsx(PlugableContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "plugable-root", children }) });
|
|
136
258
|
}
|
|
137
259
|
function usePlugable() {
|
|
138
260
|
const context = useContext(PlugableContext);
|
|
@@ -277,6 +399,74 @@ function Dropzone({
|
|
|
277
399
|
}
|
|
278
400
|
);
|
|
279
401
|
}
|
|
402
|
+
const containerStyle = {
|
|
403
|
+
position: "relative",
|
|
404
|
+
border: `2px dashed ${isDragActive ? "var(--plugable-accent-primary)" : "var(--plugable-base-border)"}`,
|
|
405
|
+
borderRadius: "12px",
|
|
406
|
+
padding: "48px 24px",
|
|
407
|
+
textAlign: "center",
|
|
408
|
+
cursor: "pointer",
|
|
409
|
+
background: isDragActive ? "var(--plugable-accent-primary)10" : "var(--plugable-base-surface)",
|
|
410
|
+
backdropFilter: "var(--plugable-backdrop-blur)",
|
|
411
|
+
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
412
|
+
...style
|
|
413
|
+
};
|
|
414
|
+
const iconContainerStyle = {
|
|
415
|
+
width: "48px",
|
|
416
|
+
height: "48px",
|
|
417
|
+
margin: "0 auto 16px",
|
|
418
|
+
borderRadius: "50%",
|
|
419
|
+
display: "flex",
|
|
420
|
+
alignItems: "center",
|
|
421
|
+
justifyContent: "center",
|
|
422
|
+
background: isDragActive ? `linear-gradient(135deg, var(--plugable-accent-primary), var(--plugable-accent-secondary))` : "var(--plugable-overlay)",
|
|
423
|
+
transition: "all 0.3s ease",
|
|
424
|
+
transform: isDragActive ? "scale(1.1)" : "scale(1)"
|
|
425
|
+
};
|
|
426
|
+
const cloudIconStyle = {
|
|
427
|
+
width: "24px",
|
|
428
|
+
height: "24px",
|
|
429
|
+
color: isDragActive ? "#fff" : "var(--plugable-accent-primary)"
|
|
430
|
+
};
|
|
431
|
+
const titleStyle = {
|
|
432
|
+
margin: 0,
|
|
433
|
+
fontWeight: 600,
|
|
434
|
+
fontSize: "16px",
|
|
435
|
+
color: "var(--plugable-text-primary)",
|
|
436
|
+
marginBottom: "8px"
|
|
437
|
+
};
|
|
438
|
+
const subtitleStyle = {
|
|
439
|
+
margin: 0,
|
|
440
|
+
fontSize: "14px",
|
|
441
|
+
color: "var(--plugable-text-secondary)"
|
|
442
|
+
};
|
|
443
|
+
const maxFilesStyle = {
|
|
444
|
+
margin: "4px 0 0 0",
|
|
445
|
+
fontSize: "12px",
|
|
446
|
+
color: "var(--plugable-text-muted)"
|
|
447
|
+
};
|
|
448
|
+
const progressContainerStyle = {
|
|
449
|
+
marginTop: "16px"
|
|
450
|
+
};
|
|
451
|
+
const progressItemStyle = {
|
|
452
|
+
marginBottom: "12px",
|
|
453
|
+
textAlign: "left"
|
|
454
|
+
};
|
|
455
|
+
const progressLabelStyle = {
|
|
456
|
+
fontSize: "14px",
|
|
457
|
+
color: "var(--plugable-text-secondary)",
|
|
458
|
+
marginBottom: "6px",
|
|
459
|
+
display: "flex",
|
|
460
|
+
justifyContent: "space-between",
|
|
461
|
+
alignItems: "center"
|
|
462
|
+
};
|
|
463
|
+
const progressBarBgStyle = {
|
|
464
|
+
width: "100%",
|
|
465
|
+
height: "6px",
|
|
466
|
+
backgroundColor: "var(--plugable-overlay)",
|
|
467
|
+
borderRadius: "3px",
|
|
468
|
+
overflow: "hidden"
|
|
469
|
+
};
|
|
280
470
|
return /* @__PURE__ */ jsxs(
|
|
281
471
|
"div",
|
|
282
472
|
{
|
|
@@ -285,16 +475,7 @@ function Dropzone({
|
|
|
285
475
|
onDragLeave: handleDragLeave,
|
|
286
476
|
onClick: openFileDialog,
|
|
287
477
|
className,
|
|
288
|
-
style:
|
|
289
|
-
border: `2px dashed ${isDragActive ? "#0070f3" : "#ccc"}`,
|
|
290
|
-
borderRadius: "8px",
|
|
291
|
-
padding: "40px 20px",
|
|
292
|
-
textAlign: "center",
|
|
293
|
-
cursor: "pointer",
|
|
294
|
-
backgroundColor: isDragActive ? "#f0f8ff" : "#fafafa",
|
|
295
|
-
transition: "all 0.2s ease",
|
|
296
|
-
...style
|
|
297
|
-
},
|
|
478
|
+
style: containerStyle,
|
|
298
479
|
children: [
|
|
299
480
|
/* @__PURE__ */ jsx2(
|
|
300
481
|
"input",
|
|
@@ -308,42 +489,34 @@ function Dropzone({
|
|
|
308
489
|
}
|
|
309
490
|
),
|
|
310
491
|
isUploading ? /* @__PURE__ */ jsxs("div", { children: [
|
|
311
|
-
/* @__PURE__ */ jsx2("
|
|
312
|
-
/* @__PURE__ */ jsx2("
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
":
|
|
316
|
-
|
|
317
|
-
|
|
492
|
+
/* @__PURE__ */ jsx2("div", { style: iconContainerStyle, children: /* @__PURE__ */ jsx2("svg", { style: cloudIconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }) }),
|
|
493
|
+
/* @__PURE__ */ jsx2("p", { style: titleStyle, children: "Uploading..." }),
|
|
494
|
+
/* @__PURE__ */ jsx2("div", { style: progressContainerStyle, children: Object.entries(uploadProgress).map(([fileName, progress]) => /* @__PURE__ */ jsxs("div", { style: progressItemStyle, children: [
|
|
495
|
+
/* @__PURE__ */ jsxs("div", { style: progressLabelStyle, children: [
|
|
496
|
+
/* @__PURE__ */ jsx2("span", { children: fileName }),
|
|
497
|
+
/* @__PURE__ */ jsxs("span", { style: { fontWeight: 600 }, children: [
|
|
498
|
+
progress,
|
|
499
|
+
"%"
|
|
500
|
+
] })
|
|
318
501
|
] }),
|
|
319
|
-
/* @__PURE__ */ jsx2(
|
|
502
|
+
/* @__PURE__ */ jsx2("div", { style: progressBarBgStyle, children: /* @__PURE__ */ jsx2(
|
|
320
503
|
"div",
|
|
321
504
|
{
|
|
322
505
|
style: {
|
|
323
|
-
width:
|
|
324
|
-
height: "
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
children: /* @__PURE__ */ jsx2(
|
|
330
|
-
"div",
|
|
331
|
-
{
|
|
332
|
-
style: {
|
|
333
|
-
width: `${progress}%`,
|
|
334
|
-
height: "100%",
|
|
335
|
-
backgroundColor: "#0070f3",
|
|
336
|
-
transition: "width 0.3s ease"
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
)
|
|
506
|
+
width: `${progress}%`,
|
|
507
|
+
height: "100%",
|
|
508
|
+
background: `linear-gradient(90deg, var(--plugable-accent-primary), var(--plugable-accent-secondary))`,
|
|
509
|
+
transition: "width 0.3s ease",
|
|
510
|
+
borderRadius: "3px"
|
|
511
|
+
}
|
|
340
512
|
}
|
|
341
|
-
)
|
|
513
|
+
) })
|
|
342
514
|
] }, fileName)) })
|
|
343
515
|
] }) : /* @__PURE__ */ jsxs("div", { children: [
|
|
344
|
-
/* @__PURE__ */ jsx2("
|
|
345
|
-
/* @__PURE__ */ jsx2("p", { style:
|
|
346
|
-
|
|
516
|
+
/* @__PURE__ */ jsx2("div", { style: iconContainerStyle, children: /* @__PURE__ */ jsx2("svg", { style: cloudIconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }) }),
|
|
517
|
+
/* @__PURE__ */ jsx2("p", { style: titleStyle, children: isDragActive ? "Drop files here" : "Click to upload or drag and drop" }),
|
|
518
|
+
/* @__PURE__ */ jsx2("p", { style: subtitleStyle, children: accept ? `Accepted: ${accept}` : "Any file type" }),
|
|
519
|
+
maxFiles && maxFiles > 1 && /* @__PURE__ */ jsxs("p", { style: maxFilesStyle, children: [
|
|
347
520
|
"(Maximum ",
|
|
348
521
|
maxFiles,
|
|
349
522
|
" files)"
|
|
@@ -355,7 +528,7 @@ function Dropzone({
|
|
|
355
528
|
}
|
|
356
529
|
|
|
357
530
|
// src/hooks/useFiles.ts
|
|
358
|
-
import { useState as useState3, useCallback as useCallback3, useEffect, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
531
|
+
import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
359
532
|
function useFiles({
|
|
360
533
|
metadata,
|
|
361
534
|
startPage = 1,
|
|
@@ -440,7 +613,7 @@ function useFiles({
|
|
|
440
613
|
setIsLoading(false);
|
|
441
614
|
}
|
|
442
615
|
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
443
|
-
|
|
616
|
+
useEffect2(() => {
|
|
444
617
|
const paramsChanged = previousParamsRef.current !== null && previousParamsRef.current !== paramsKeyWithPage;
|
|
445
618
|
const isInitialMount = isInitialMountRef.current;
|
|
446
619
|
previousParamsRef.current = paramsKeyWithPage;
|
|
@@ -461,7 +634,7 @@ function useFiles({
|
|
|
461
634
|
fetchFiles(page, true);
|
|
462
635
|
}
|
|
463
636
|
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
464
|
-
|
|
637
|
+
useEffect2(() => {
|
|
465
638
|
const unsubscribe = on("file.uploaded", () => {
|
|
466
639
|
fetchFiles(page, true);
|
|
467
640
|
});
|
|
@@ -523,7 +696,7 @@ function FileList({
|
|
|
523
696
|
}
|
|
524
697
|
|
|
525
698
|
// src/components/FileImage.tsx
|
|
526
|
-
import { useEffect as
|
|
699
|
+
import { useEffect as useEffect3, useState as useState4, useRef as useRef3 } from "react";
|
|
527
700
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
528
701
|
var imageCache = /* @__PURE__ */ new Map();
|
|
529
702
|
function FileImage({
|
|
@@ -543,7 +716,7 @@ function FileImage({
|
|
|
543
716
|
const [isLoading, setIsLoading] = useState4(true);
|
|
544
717
|
const [error, setError] = useState4(null);
|
|
545
718
|
const refetchAttemptedRef = useRef3(null);
|
|
546
|
-
|
|
719
|
+
useEffect3(() => {
|
|
547
720
|
let isMounted = true;
|
|
548
721
|
let objectUrl = null;
|
|
549
722
|
const loadImage = async () => {
|
|
@@ -558,29 +731,47 @@ function FileImage({
|
|
|
558
731
|
return;
|
|
559
732
|
}
|
|
560
733
|
if (file.download_url) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
734
|
+
let response = null;
|
|
735
|
+
try {
|
|
736
|
+
response = await fetch(file.download_url, {
|
|
737
|
+
headers: {
|
|
738
|
+
"Cache-Control": "private, max-age=31536000",
|
|
739
|
+
"If-None-Match": file.checksum
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
if (!response.ok) {
|
|
743
|
+
const downloadUrlKey = `${file.id}-${file.download_url}`;
|
|
744
|
+
if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
|
|
745
|
+
refetchAttemptedRef.current = downloadUrlKey;
|
|
746
|
+
imageCache.delete(cacheKey);
|
|
747
|
+
await onRefetchNeeded();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
throw new Error(`Failed to fetch image: ${response.statusText}`);
|
|
565
751
|
}
|
|
566
|
-
|
|
567
|
-
|
|
752
|
+
const blob = await response.blob();
|
|
753
|
+
objectUrl = URL.createObjectURL(blob);
|
|
754
|
+
imageCache.set(cacheKey, objectUrl);
|
|
755
|
+
if (isMounted) {
|
|
756
|
+
setImageSrc(objectUrl);
|
|
757
|
+
setIsLoading(false);
|
|
758
|
+
refetchAttemptedRef.current = null;
|
|
759
|
+
}
|
|
760
|
+
} catch (fetchError) {
|
|
568
761
|
const downloadUrlKey = `${file.id}-${file.download_url}`;
|
|
569
|
-
|
|
762
|
+
const isNetworkError = !response && (fetchError.message?.includes("Failed to fetch") || fetchError.message?.includes("CORS") || fetchError.name === "TypeError" || fetchError.message?.includes("network") || fetchError.message?.includes("ERR_FAILED"));
|
|
763
|
+
const is403Error = response?.status === 403;
|
|
764
|
+
if ((isNetworkError || is403Error) && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
|
|
570
765
|
refetchAttemptedRef.current = downloadUrlKey;
|
|
571
766
|
imageCache.delete(cacheKey);
|
|
572
|
-
|
|
573
|
-
|
|
767
|
+
try {
|
|
768
|
+
await onRefetchNeeded();
|
|
769
|
+
return;
|
|
770
|
+
} catch (refetchError) {
|
|
771
|
+
console.error("Failed to refetch file:", refetchError);
|
|
772
|
+
}
|
|
574
773
|
}
|
|
575
|
-
throw
|
|
576
|
-
}
|
|
577
|
-
const blob = await response.blob();
|
|
578
|
-
objectUrl = URL.createObjectURL(blob);
|
|
579
|
-
imageCache.set(cacheKey, objectUrl);
|
|
580
|
-
if (isMounted) {
|
|
581
|
-
setImageSrc(objectUrl);
|
|
582
|
-
setIsLoading(false);
|
|
583
|
-
refetchAttemptedRef.current = null;
|
|
774
|
+
throw fetchError;
|
|
584
775
|
}
|
|
585
776
|
} else {
|
|
586
777
|
throw new Error("No download URL available for file");
|
|
@@ -618,20 +809,25 @@ function FileImage({
|
|
|
618
809
|
...style
|
|
619
810
|
};
|
|
620
811
|
if (error) {
|
|
621
|
-
return /* @__PURE__ */
|
|
812
|
+
return /* @__PURE__ */ jsxs2(
|
|
622
813
|
"div",
|
|
623
814
|
{
|
|
624
815
|
className,
|
|
625
816
|
style: {
|
|
626
817
|
...imageStyle,
|
|
627
818
|
display: "flex",
|
|
819
|
+
flexDirection: "column",
|
|
628
820
|
alignItems: "center",
|
|
629
821
|
justifyContent: "center",
|
|
630
|
-
backgroundColor: "
|
|
631
|
-
color: "
|
|
632
|
-
fontSize: "14px"
|
|
822
|
+
backgroundColor: "var(--plugable-base-surface)",
|
|
823
|
+
color: "var(--plugable-text-muted)",
|
|
824
|
+
fontSize: "14px",
|
|
825
|
+
gap: "8px"
|
|
633
826
|
},
|
|
634
|
-
children:
|
|
827
|
+
children: [
|
|
828
|
+
/* @__PURE__ */ jsx4("svg", { style: { width: "32px", height: "32px" }, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
|
|
829
|
+
/* @__PURE__ */ jsx4("span", { children: "Failed to load" })
|
|
830
|
+
]
|
|
635
831
|
}
|
|
636
832
|
);
|
|
637
833
|
}
|
|
@@ -642,29 +838,29 @@ function FileImage({
|
|
|
642
838
|
className,
|
|
643
839
|
style: {
|
|
644
840
|
...imageStyle,
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
backgroundColor: "#f0f0f0"
|
|
841
|
+
position: "relative",
|
|
842
|
+
overflow: "hidden",
|
|
843
|
+
backgroundColor: "var(--plugable-base-surface)"
|
|
649
844
|
},
|
|
650
845
|
children: [
|
|
651
846
|
/* @__PURE__ */ jsx4(
|
|
652
847
|
"div",
|
|
653
848
|
{
|
|
654
849
|
style: {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
850
|
+
position: "absolute",
|
|
851
|
+
top: 0,
|
|
852
|
+
left: "-100%",
|
|
853
|
+
width: "100%",
|
|
854
|
+
height: "100%",
|
|
855
|
+
background: "linear-gradient(90deg, transparent, var(--plugable-overlay), transparent)",
|
|
856
|
+
animation: "shimmer 1.5s infinite"
|
|
661
857
|
}
|
|
662
858
|
}
|
|
663
859
|
),
|
|
664
860
|
/* @__PURE__ */ jsx4("style", { children: `
|
|
665
|
-
@keyframes
|
|
666
|
-
0% {
|
|
667
|
-
100% {
|
|
861
|
+
@keyframes shimmer {
|
|
862
|
+
0% { left: -100%; }
|
|
863
|
+
100% { left: 100%; }
|
|
668
864
|
}
|
|
669
865
|
` })
|
|
670
866
|
]
|
|
@@ -677,7 +873,11 @@ function FileImage({
|
|
|
677
873
|
src: imageSrc,
|
|
678
874
|
alt: alt || file.name,
|
|
679
875
|
className,
|
|
680
|
-
style:
|
|
876
|
+
style: {
|
|
877
|
+
...imageStyle,
|
|
878
|
+
opacity: isLoading ? 0 : 1,
|
|
879
|
+
transition: "opacity 0.3s ease-in"
|
|
880
|
+
},
|
|
681
881
|
onLoad: handleLoad,
|
|
682
882
|
onError: handleError
|
|
683
883
|
}
|
|
@@ -689,12 +889,35 @@ function clearImageCache() {
|
|
|
689
889
|
}
|
|
690
890
|
|
|
691
891
|
// src/components/FilePreview.tsx
|
|
692
|
-
import { useState as useState5, useCallback as useCallback4, useEffect as
|
|
693
|
-
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
892
|
+
import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4 } from "react";
|
|
893
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
894
|
+
var fileTypeColors = {
|
|
895
|
+
pdf: { bg: "transparent", text: "#94a3b8" },
|
|
896
|
+
// Slate-400
|
|
897
|
+
doc: { bg: "transparent", text: "#94a3b8" },
|
|
898
|
+
docx: { bg: "transparent", text: "#94a3b8" },
|
|
899
|
+
xls: { bg: "transparent", text: "#94a3b8" },
|
|
900
|
+
xlsx: { bg: "transparent", text: "#94a3b8" },
|
|
901
|
+
csv: { bg: "transparent", text: "#94a3b8" },
|
|
902
|
+
txt: { bg: "transparent", text: "#94a3b8" },
|
|
903
|
+
zip: { bg: "transparent", text: "#94a3b8" },
|
|
904
|
+
rar: { bg: "transparent", text: "#94a3b8" },
|
|
905
|
+
mp4: { bg: "transparent", text: "#94a3b8" },
|
|
906
|
+
mov: { bg: "transparent", text: "#94a3b8" },
|
|
907
|
+
avi: { bg: "transparent", text: "#94a3b8" },
|
|
908
|
+
mp3: { bg: "transparent", text: "#94a3b8" },
|
|
909
|
+
wav: { bg: "transparent", text: "#94a3b8" },
|
|
910
|
+
default: { bg: "transparent", text: "#64748b" }
|
|
911
|
+
// Slate-500
|
|
912
|
+
};
|
|
913
|
+
function getFileTypeColor(filename) {
|
|
914
|
+
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
915
|
+
return fileTypeColors[ext] || fileTypeColors.default;
|
|
916
|
+
}
|
|
694
917
|
function FilePreview({
|
|
695
918
|
file: initialFile,
|
|
696
|
-
width =
|
|
697
|
-
height =
|
|
919
|
+
width = 120,
|
|
920
|
+
height = 120,
|
|
698
921
|
className,
|
|
699
922
|
style,
|
|
700
923
|
objectFit = "cover",
|
|
@@ -704,7 +927,8 @@ function FilePreview({
|
|
|
704
927
|
const { client } = usePlugable();
|
|
705
928
|
const [file, setFile] = useState5(initialFile);
|
|
706
929
|
const [isRefetching, setIsRefetching] = useState5(false);
|
|
707
|
-
|
|
930
|
+
const [isHovered, setIsHovered] = useState5(false);
|
|
931
|
+
useEffect4(() => {
|
|
708
932
|
setFile(initialFile);
|
|
709
933
|
}, [initialFile.id, initialFile.download_url]);
|
|
710
934
|
const handleRefetch = useCallback4(async () => {
|
|
@@ -718,45 +942,105 @@ function FilePreview({
|
|
|
718
942
|
} finally {
|
|
719
943
|
setIsRefetching(false);
|
|
720
944
|
}
|
|
721
|
-
}, [file.id, client]);
|
|
945
|
+
}, [file.id, client, isRefetching]);
|
|
722
946
|
const isImage = file.content_type.startsWith("image/");
|
|
723
947
|
const containerStyle = {
|
|
724
948
|
width,
|
|
725
949
|
height,
|
|
726
|
-
borderRadius:
|
|
950
|
+
borderRadius: "8px",
|
|
727
951
|
overflow: "hidden",
|
|
728
|
-
|
|
952
|
+
position: "relative",
|
|
729
953
|
display: "flex",
|
|
730
954
|
alignItems: "center",
|
|
731
955
|
justifyContent: "center",
|
|
732
|
-
border: "1px solid
|
|
956
|
+
border: isHovered ? "1px solid var(--plugable-accent-primary)" : "1px solid var(--plugable-base-border)",
|
|
957
|
+
background: "var(--plugable-base-surface)",
|
|
958
|
+
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
959
|
+
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
|
|
960
|
+
cursor: "pointer",
|
|
733
961
|
...style
|
|
734
962
|
};
|
|
735
963
|
if (isImage) {
|
|
736
964
|
return /* @__PURE__ */ jsx5(
|
|
737
|
-
|
|
965
|
+
"div",
|
|
738
966
|
{
|
|
739
|
-
|
|
740
|
-
width,
|
|
741
|
-
height,
|
|
742
|
-
objectFit,
|
|
967
|
+
style: containerStyle,
|
|
743
968
|
className,
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
969
|
+
onMouseEnter: () => setIsHovered(true),
|
|
970
|
+
onMouseLeave: () => setIsHovered(false),
|
|
971
|
+
children: /* @__PURE__ */ jsx5(
|
|
972
|
+
FileImage,
|
|
973
|
+
{
|
|
974
|
+
file,
|
|
975
|
+
width,
|
|
976
|
+
height,
|
|
977
|
+
objectFit,
|
|
978
|
+
style: { borderRadius: "8px" },
|
|
979
|
+
onRefetchNeeded: handleRefetch
|
|
980
|
+
}
|
|
981
|
+
)
|
|
747
982
|
}
|
|
748
983
|
);
|
|
749
984
|
}
|
|
750
985
|
if (renderNonImage) {
|
|
751
|
-
return /* @__PURE__ */ jsx5(
|
|
986
|
+
return /* @__PURE__ */ jsx5(
|
|
987
|
+
"div",
|
|
988
|
+
{
|
|
989
|
+
className,
|
|
990
|
+
style: containerStyle,
|
|
991
|
+
onMouseEnter: () => setIsHovered(true),
|
|
992
|
+
onMouseLeave: () => setIsHovered(false),
|
|
993
|
+
children: renderNonImage(file)
|
|
994
|
+
}
|
|
995
|
+
);
|
|
752
996
|
}
|
|
753
997
|
const extension = file.name.split(".").pop()?.toUpperCase() || "FILE";
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
998
|
+
const colors = getFileTypeColor(file.name);
|
|
999
|
+
const badgeStyle = {
|
|
1000
|
+
padding: "2px 8px",
|
|
1001
|
+
borderRadius: "4px",
|
|
1002
|
+
border: "1px solid var(--plugable-base-border)",
|
|
1003
|
+
backgroundColor: "var(--plugable-overlay)",
|
|
1004
|
+
display: "inline-flex",
|
|
1005
|
+
alignItems: "center",
|
|
1006
|
+
justifyContent: "center"
|
|
1007
|
+
};
|
|
1008
|
+
const extensionStyle = {
|
|
1009
|
+
fontSize: "11px",
|
|
1010
|
+
fontWeight: 600,
|
|
1011
|
+
color: "var(--plugable-text-secondary)",
|
|
1012
|
+
textTransform: "uppercase",
|
|
1013
|
+
letterSpacing: "0.05em"
|
|
1014
|
+
};
|
|
1015
|
+
const iconStyle = {
|
|
1016
|
+
width: "28px",
|
|
1017
|
+
height: "28px",
|
|
1018
|
+
marginBottom: "6px",
|
|
1019
|
+
color: colors.text
|
|
1020
|
+
};
|
|
1021
|
+
const wrapperStyle = {
|
|
1022
|
+
textAlign: "center",
|
|
1023
|
+
padding: "12px",
|
|
1024
|
+
display: "flex",
|
|
1025
|
+
flexDirection: "column",
|
|
1026
|
+
alignItems: "center",
|
|
1027
|
+
justifyContent: "center",
|
|
1028
|
+
width: "100%",
|
|
1029
|
+
height: "100%"
|
|
1030
|
+
};
|
|
1031
|
+
return /* @__PURE__ */ jsx5(
|
|
1032
|
+
"div",
|
|
1033
|
+
{
|
|
1034
|
+
className,
|
|
1035
|
+
style: containerStyle,
|
|
1036
|
+
onMouseEnter: () => setIsHovered(true),
|
|
1037
|
+
onMouseLeave: () => setIsHovered(false),
|
|
1038
|
+
children: /* @__PURE__ */ jsxs3("div", { style: wrapperStyle, children: [
|
|
1039
|
+
/* @__PURE__ */ jsx5("svg", { style: iconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) }),
|
|
1040
|
+
showExtension && /* @__PURE__ */ jsx5("div", { style: badgeStyle, children: /* @__PURE__ */ jsx5("span", { style: extensionStyle, children: extension }) })
|
|
1041
|
+
] })
|
|
1042
|
+
}
|
|
1043
|
+
);
|
|
760
1044
|
}
|
|
761
1045
|
export {
|
|
762
1046
|
Dropzone,
|
|
@@ -764,7 +1048,10 @@ export {
|
|
|
764
1048
|
FileList,
|
|
765
1049
|
FilePreview,
|
|
766
1050
|
PlugableProvider,
|
|
1051
|
+
applyCSSVariables,
|
|
767
1052
|
clearImageCache,
|
|
1053
|
+
generateCSSVariables,
|
|
1054
|
+
getThemeColors,
|
|
768
1055
|
useFiles,
|
|
769
1056
|
usePlugable
|
|
770
1057
|
};
|