@plugable-io/react 0.0.3 → 0.0.5
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 +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +97 -21
- package/dist/index.mjs +112 -36
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React, { ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { BucketClient, FileObject } from '@plugable-io/js';
|
|
3
|
+
import { BucketClient, FileObject, ListResponse } from '@plugable-io/js';
|
|
4
4
|
export { FileObject, SearchOptions, UpdateOptions } from '@plugable-io/js';
|
|
5
5
|
|
|
6
6
|
type AuthProvider = 'clerk' | 'supabase' | 'firebase';
|
|
@@ -11,9 +11,15 @@ interface PlugableProviderProps {
|
|
|
11
11
|
authProvider?: AuthProvider;
|
|
12
12
|
clerkJWTTemplate?: string;
|
|
13
13
|
baseUrl?: string;
|
|
14
|
+
staleTime?: number;
|
|
14
15
|
}
|
|
15
16
|
type PlugableEventType = 'file.uploaded' | 'file.deleted';
|
|
16
17
|
type EventHandler = (data?: any) => void;
|
|
18
|
+
interface CacheEntry {
|
|
19
|
+
files: FileObject[];
|
|
20
|
+
paging: ListResponse['paging'];
|
|
21
|
+
timestamp: number;
|
|
22
|
+
}
|
|
17
23
|
interface PlugableContextValue {
|
|
18
24
|
client: BucketClient;
|
|
19
25
|
bucketId: string;
|
|
@@ -21,8 +27,12 @@ interface PlugableContextValue {
|
|
|
21
27
|
emit: (event: PlugableEventType, data?: any) => void;
|
|
22
28
|
getToken: () => Promise<string> | string;
|
|
23
29
|
baseUrl?: string;
|
|
30
|
+
staleTime: number;
|
|
31
|
+
getCache: (key: string) => CacheEntry | undefined;
|
|
32
|
+
setCache: (key: string, entry: CacheEntry) => void;
|
|
33
|
+
invalidateCache: (pattern?: string) => void;
|
|
24
34
|
}
|
|
25
|
-
declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
|
|
35
|
+
declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, staleTime, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
|
|
26
36
|
declare function usePlugable(): PlugableContextValue;
|
|
27
37
|
|
|
28
38
|
interface DropzoneProps {
|
|
@@ -106,6 +116,7 @@ interface UseFilesOptions {
|
|
|
106
116
|
autoLoad?: boolean;
|
|
107
117
|
orderBy?: 'created_at' | 'name' | 'byte_size';
|
|
108
118
|
orderDirection?: 'asc' | 'desc';
|
|
119
|
+
staleTime?: number;
|
|
109
120
|
}
|
|
110
121
|
interface UseFilesResult {
|
|
111
122
|
files: FileObject[];
|
|
@@ -120,6 +131,6 @@ interface UseFilesResult {
|
|
|
120
131
|
setPage: (page: number) => void;
|
|
121
132
|
refresh: () => Promise<void>;
|
|
122
133
|
}
|
|
123
|
-
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, }?: UseFilesOptions): UseFilesResult;
|
|
134
|
+
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, staleTime, }?: UseFilesOptions): UseFilesResult;
|
|
124
135
|
|
|
125
136
|
export { type AuthProvider, Dropzone, type DropzoneProps, type DropzoneRenderProps, FileImage, type FileImageProps, FileList, type FileListProps, type FileListRenderProps, FilePreview, type FilePreviewProps, PlugableProvider, type PlugableProviderProps, type UseFilesOptions, type UseFilesResult, clearImageCache, useFiles, usePlugable };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React, { ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { BucketClient, FileObject } from '@plugable-io/js';
|
|
3
|
+
import { BucketClient, FileObject, ListResponse } from '@plugable-io/js';
|
|
4
4
|
export { FileObject, SearchOptions, UpdateOptions } from '@plugable-io/js';
|
|
5
5
|
|
|
6
6
|
type AuthProvider = 'clerk' | 'supabase' | 'firebase';
|
|
@@ -11,9 +11,15 @@ interface PlugableProviderProps {
|
|
|
11
11
|
authProvider?: AuthProvider;
|
|
12
12
|
clerkJWTTemplate?: string;
|
|
13
13
|
baseUrl?: string;
|
|
14
|
+
staleTime?: number;
|
|
14
15
|
}
|
|
15
16
|
type PlugableEventType = 'file.uploaded' | 'file.deleted';
|
|
16
17
|
type EventHandler = (data?: any) => void;
|
|
18
|
+
interface CacheEntry {
|
|
19
|
+
files: FileObject[];
|
|
20
|
+
paging: ListResponse['paging'];
|
|
21
|
+
timestamp: number;
|
|
22
|
+
}
|
|
17
23
|
interface PlugableContextValue {
|
|
18
24
|
client: BucketClient;
|
|
19
25
|
bucketId: string;
|
|
@@ -21,8 +27,12 @@ interface PlugableContextValue {
|
|
|
21
27
|
emit: (event: PlugableEventType, data?: any) => void;
|
|
22
28
|
getToken: () => Promise<string> | string;
|
|
23
29
|
baseUrl?: string;
|
|
30
|
+
staleTime: number;
|
|
31
|
+
getCache: (key: string) => CacheEntry | undefined;
|
|
32
|
+
setCache: (key: string, entry: CacheEntry) => void;
|
|
33
|
+
invalidateCache: (pattern?: string) => void;
|
|
24
34
|
}
|
|
25
|
-
declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
|
|
35
|
+
declare function PlugableProvider({ bucketId, children, getToken, authProvider, clerkJWTTemplate, baseUrl, staleTime, }: PlugableProviderProps): react_jsx_runtime.JSX.Element;
|
|
26
36
|
declare function usePlugable(): PlugableContextValue;
|
|
27
37
|
|
|
28
38
|
interface DropzoneProps {
|
|
@@ -106,6 +116,7 @@ interface UseFilesOptions {
|
|
|
106
116
|
autoLoad?: boolean;
|
|
107
117
|
orderBy?: 'created_at' | 'name' | 'byte_size';
|
|
108
118
|
orderDirection?: 'asc' | 'desc';
|
|
119
|
+
staleTime?: number;
|
|
109
120
|
}
|
|
110
121
|
interface UseFilesResult {
|
|
111
122
|
files: FileObject[];
|
|
@@ -120,6 +131,6 @@ interface UseFilesResult {
|
|
|
120
131
|
setPage: (page: number) => void;
|
|
121
132
|
refresh: () => Promise<void>;
|
|
122
133
|
}
|
|
123
|
-
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, }?: UseFilesOptions): UseFilesResult;
|
|
134
|
+
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, staleTime, }?: UseFilesOptions): UseFilesResult;
|
|
124
135
|
|
|
125
136
|
export { type AuthProvider, Dropzone, type DropzoneProps, type DropzoneRenderProps, FileImage, type FileImageProps, FileList, type FileListProps, type FileListRenderProps, FilePreview, type FilePreviewProps, PlugableProvider, type PlugableProviderProps, type UseFilesOptions, type UseFilesResult, clearImageCache, useFiles, usePlugable };
|
package/dist/index.js
CHANGED
|
@@ -100,9 +100,12 @@ function PlugableProvider({
|
|
|
100
100
|
getToken,
|
|
101
101
|
authProvider,
|
|
102
102
|
clerkJWTTemplate,
|
|
103
|
-
baseUrl
|
|
103
|
+
baseUrl,
|
|
104
|
+
staleTime = 5 * 60 * 1e3
|
|
105
|
+
// Default 5 minutes
|
|
104
106
|
}) {
|
|
105
107
|
const listenersRef = (0, import_react.useRef)({});
|
|
108
|
+
const [cache, setCacheState] = (0, import_react.useState)(/* @__PURE__ */ new Map());
|
|
106
109
|
const client = (0, import_react.useMemo)(() => {
|
|
107
110
|
if (!getToken && !authProvider) {
|
|
108
111
|
throw new Error(
|
|
@@ -128,6 +131,34 @@ function PlugableProvider({
|
|
|
128
131
|
}, []);
|
|
129
132
|
const emit = (0, import_react.useCallback)((event, data) => {
|
|
130
133
|
listenersRef.current[event]?.forEach((handler) => handler(data));
|
|
134
|
+
if (event === "file.uploaded" || event === "file.deleted") {
|
|
135
|
+
setCacheState(/* @__PURE__ */ new Map());
|
|
136
|
+
}
|
|
137
|
+
}, []);
|
|
138
|
+
const getCache = (0, import_react.useCallback)((key) => {
|
|
139
|
+
return cache.get(key);
|
|
140
|
+
}, [cache]);
|
|
141
|
+
const setCache = (0, import_react.useCallback)((key, entry) => {
|
|
142
|
+
setCacheState((prev) => {
|
|
143
|
+
const next = new Map(prev);
|
|
144
|
+
next.set(key, entry);
|
|
145
|
+
return next;
|
|
146
|
+
});
|
|
147
|
+
}, []);
|
|
148
|
+
const invalidateCache = (0, import_react.useCallback)((pattern) => {
|
|
149
|
+
if (!pattern) {
|
|
150
|
+
setCacheState(/* @__PURE__ */ new Map());
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
setCacheState((prev) => {
|
|
154
|
+
const next = new Map(prev);
|
|
155
|
+
for (const key of prev.keys()) {
|
|
156
|
+
if (key.includes(pattern)) {
|
|
157
|
+
next.delete(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return next;
|
|
161
|
+
});
|
|
131
162
|
}, []);
|
|
132
163
|
const value = (0, import_react.useMemo)(
|
|
133
164
|
() => ({
|
|
@@ -136,9 +167,13 @@ function PlugableProvider({
|
|
|
136
167
|
on,
|
|
137
168
|
emit,
|
|
138
169
|
getToken: client.tokenGetter,
|
|
139
|
-
baseUrl
|
|
170
|
+
baseUrl,
|
|
171
|
+
staleTime,
|
|
172
|
+
getCache,
|
|
173
|
+
setCache,
|
|
174
|
+
invalidateCache
|
|
140
175
|
}),
|
|
141
|
-
[client, bucketId, on, emit, baseUrl]
|
|
176
|
+
[client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
|
|
142
177
|
);
|
|
143
178
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlugableContext.Provider, { value, children });
|
|
144
179
|
}
|
|
@@ -371,16 +406,19 @@ function useFiles({
|
|
|
371
406
|
mediaType,
|
|
372
407
|
autoLoad = true,
|
|
373
408
|
orderBy,
|
|
374
|
-
orderDirection
|
|
409
|
+
orderDirection,
|
|
410
|
+
staleTime
|
|
375
411
|
} = {}) {
|
|
376
|
-
const { client, on } = usePlugable();
|
|
412
|
+
const { client, on, getCache, setCache, staleTime: providerStaleTime } = usePlugable();
|
|
377
413
|
const [files, setFiles] = (0, import_react3.useState)([]);
|
|
378
414
|
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
379
415
|
const [page, setPage] = (0, import_react3.useState)(startPage);
|
|
380
416
|
const [hasNext, setHasNext] = (0, import_react3.useState)(false);
|
|
417
|
+
const previousParamsRef = (0, import_react3.useRef)(null);
|
|
418
|
+
const isInitialMountRef = (0, import_react3.useRef)(true);
|
|
419
|
+
const effectiveStaleTime = staleTime ?? providerStaleTime;
|
|
381
420
|
const metadataKey = JSON.stringify(metadata);
|
|
382
421
|
const stableMetadata = (0, import_react3.useMemo)(() => metadata, [metadataKey]);
|
|
383
|
-
const loadedParamsRef = (0, import_react3.useRef)(null);
|
|
384
422
|
const paramsKeyWithPage = (0, import_react3.useMemo)(() => JSON.stringify({
|
|
385
423
|
metadata: stableMetadata,
|
|
386
424
|
mediaType,
|
|
@@ -389,7 +427,27 @@ function useFiles({
|
|
|
389
427
|
orderDirection,
|
|
390
428
|
page
|
|
391
429
|
}), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
|
|
392
|
-
const fetchFiles = (0, import_react3.useCallback)(async (pageNum) => {
|
|
430
|
+
const fetchFiles = (0, import_react3.useCallback)(async (pageNum, skipCache = false) => {
|
|
431
|
+
if (!skipCache) {
|
|
432
|
+
const cacheKey = JSON.stringify({
|
|
433
|
+
metadata: stableMetadata,
|
|
434
|
+
mediaType,
|
|
435
|
+
perPage,
|
|
436
|
+
orderBy,
|
|
437
|
+
orderDirection,
|
|
438
|
+
page: pageNum
|
|
439
|
+
});
|
|
440
|
+
const cachedEntry = getCache(cacheKey);
|
|
441
|
+
if (cachedEntry) {
|
|
442
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
443
|
+
const isStale = age > effectiveStaleTime;
|
|
444
|
+
if (!isStale) {
|
|
445
|
+
setFiles(cachedEntry.files);
|
|
446
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
393
451
|
setIsLoading(true);
|
|
394
452
|
try {
|
|
395
453
|
const options = {
|
|
@@ -398,13 +456,11 @@ function useFiles({
|
|
|
398
456
|
page: pageNum,
|
|
399
457
|
per_page: perPage,
|
|
400
458
|
with_download_url: true,
|
|
401
|
-
order_by: orderBy,
|
|
402
|
-
order_direction: orderDirection
|
|
459
|
+
...orderBy && { order_by: orderBy },
|
|
460
|
+
...orderDirection && { order_direction: orderDirection }
|
|
403
461
|
};
|
|
404
462
|
const response = await client.list(options);
|
|
405
|
-
|
|
406
|
-
setHasNext(response.paging.has_next_page);
|
|
407
|
-
const currentParamsKey = JSON.stringify({
|
|
463
|
+
const cacheKey = JSON.stringify({
|
|
408
464
|
metadata: stableMetadata,
|
|
409
465
|
mediaType,
|
|
410
466
|
perPage,
|
|
@@ -412,7 +468,13 @@ function useFiles({
|
|
|
412
468
|
orderDirection,
|
|
413
469
|
page: pageNum
|
|
414
470
|
});
|
|
415
|
-
|
|
471
|
+
setCache(cacheKey, {
|
|
472
|
+
files: response.files,
|
|
473
|
+
paging: response.paging,
|
|
474
|
+
timestamp: Date.now()
|
|
475
|
+
});
|
|
476
|
+
setFiles(response.files);
|
|
477
|
+
setHasNext(response.paging.has_next_page);
|
|
416
478
|
} catch (err) {
|
|
417
479
|
console.error("Failed to load files:", err);
|
|
418
480
|
setFiles([]);
|
|
@@ -420,17 +482,31 @@ function useFiles({
|
|
|
420
482
|
} finally {
|
|
421
483
|
setIsLoading(false);
|
|
422
484
|
}
|
|
423
|
-
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection]);
|
|
485
|
+
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
424
486
|
(0, import_react3.useEffect)(() => {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
487
|
+
const paramsChanged = previousParamsRef.current !== null && previousParamsRef.current !== paramsKeyWithPage;
|
|
488
|
+
const isInitialMount = isInitialMountRef.current;
|
|
489
|
+
previousParamsRef.current = paramsKeyWithPage;
|
|
490
|
+
if (isInitialMount) {
|
|
491
|
+
isInitialMountRef.current = false;
|
|
492
|
+
}
|
|
493
|
+
const cachedEntry = getCache(paramsKeyWithPage);
|
|
494
|
+
const shouldFetch = paramsChanged || isInitialMount && autoLoad;
|
|
495
|
+
if (cachedEntry) {
|
|
496
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
497
|
+
const isStale = age > effectiveStaleTime;
|
|
498
|
+
setFiles(cachedEntry.files);
|
|
499
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
500
|
+
if (paramsChanged || isStale && shouldFetch) {
|
|
501
|
+
fetchFiles(page, true);
|
|
502
|
+
}
|
|
503
|
+
} else if (shouldFetch) {
|
|
504
|
+
fetchFiles(page, true);
|
|
429
505
|
}
|
|
430
|
-
}, [
|
|
506
|
+
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
431
507
|
(0, import_react3.useEffect)(() => {
|
|
432
508
|
const unsubscribe = on("file.uploaded", () => {
|
|
433
|
-
fetchFiles(page);
|
|
509
|
+
fetchFiles(page, true);
|
|
434
510
|
});
|
|
435
511
|
return unsubscribe;
|
|
436
512
|
}, [on, fetchFiles, page]);
|
|
@@ -443,7 +519,7 @@ function useFiles({
|
|
|
443
519
|
setPage((p) => Math.max(1, p - 1));
|
|
444
520
|
}, []);
|
|
445
521
|
const refresh = (0, import_react3.useCallback)(async () => {
|
|
446
|
-
await fetchFiles(page);
|
|
522
|
+
await fetchFiles(page, true);
|
|
447
523
|
}, [fetchFiles, page]);
|
|
448
524
|
return {
|
|
449
525
|
files,
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/PlugableProvider.tsx
|
|
2
|
-
import { createContext, useContext, useMemo, useCallback, useRef } from "react";
|
|
2
|
+
import { createContext, useContext, useMemo, useCallback, useRef, useState } from "react";
|
|
3
3
|
import { BucketClient } from "@plugable-io/js";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
var PlugableContext = createContext(null);
|
|
@@ -57,9 +57,12 @@ function PlugableProvider({
|
|
|
57
57
|
getToken,
|
|
58
58
|
authProvider,
|
|
59
59
|
clerkJWTTemplate,
|
|
60
|
-
baseUrl
|
|
60
|
+
baseUrl,
|
|
61
|
+
staleTime = 5 * 60 * 1e3
|
|
62
|
+
// Default 5 minutes
|
|
61
63
|
}) {
|
|
62
64
|
const listenersRef = useRef({});
|
|
65
|
+
const [cache, setCacheState] = useState(/* @__PURE__ */ new Map());
|
|
63
66
|
const client = useMemo(() => {
|
|
64
67
|
if (!getToken && !authProvider) {
|
|
65
68
|
throw new Error(
|
|
@@ -85,6 +88,34 @@ function PlugableProvider({
|
|
|
85
88
|
}, []);
|
|
86
89
|
const emit = useCallback((event, data) => {
|
|
87
90
|
listenersRef.current[event]?.forEach((handler) => handler(data));
|
|
91
|
+
if (event === "file.uploaded" || event === "file.deleted") {
|
|
92
|
+
setCacheState(/* @__PURE__ */ new Map());
|
|
93
|
+
}
|
|
94
|
+
}, []);
|
|
95
|
+
const getCache = useCallback((key) => {
|
|
96
|
+
return cache.get(key);
|
|
97
|
+
}, [cache]);
|
|
98
|
+
const setCache = useCallback((key, entry) => {
|
|
99
|
+
setCacheState((prev) => {
|
|
100
|
+
const next = new Map(prev);
|
|
101
|
+
next.set(key, entry);
|
|
102
|
+
return next;
|
|
103
|
+
});
|
|
104
|
+
}, []);
|
|
105
|
+
const invalidateCache = useCallback((pattern) => {
|
|
106
|
+
if (!pattern) {
|
|
107
|
+
setCacheState(/* @__PURE__ */ new Map());
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
setCacheState((prev) => {
|
|
111
|
+
const next = new Map(prev);
|
|
112
|
+
for (const key of prev.keys()) {
|
|
113
|
+
if (key.includes(pattern)) {
|
|
114
|
+
next.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return next;
|
|
118
|
+
});
|
|
88
119
|
}, []);
|
|
89
120
|
const value = useMemo(
|
|
90
121
|
() => ({
|
|
@@ -93,9 +124,13 @@ function PlugableProvider({
|
|
|
93
124
|
on,
|
|
94
125
|
emit,
|
|
95
126
|
getToken: client.tokenGetter,
|
|
96
|
-
baseUrl
|
|
127
|
+
baseUrl,
|
|
128
|
+
staleTime,
|
|
129
|
+
getCache,
|
|
130
|
+
setCache,
|
|
131
|
+
invalidateCache
|
|
97
132
|
}),
|
|
98
|
-
[client, bucketId, on, emit, baseUrl]
|
|
133
|
+
[client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
|
|
99
134
|
);
|
|
100
135
|
return /* @__PURE__ */ jsx(PlugableContext.Provider, { value, children });
|
|
101
136
|
}
|
|
@@ -108,7 +143,7 @@ function usePlugable() {
|
|
|
108
143
|
}
|
|
109
144
|
|
|
110
145
|
// src/components/Dropzone.tsx
|
|
111
|
-
import React, { useCallback as useCallback2, useState } from "react";
|
|
146
|
+
import React, { useCallback as useCallback2, useState as useState2 } from "react";
|
|
112
147
|
import { BucketClient as BucketClient2 } from "@plugable-io/js";
|
|
113
148
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
114
149
|
function Dropzone({
|
|
@@ -134,10 +169,10 @@ function Dropzone({
|
|
|
134
169
|
}
|
|
135
170
|
return defaultClient;
|
|
136
171
|
}, [_bucketId, defaultClient, getToken, baseUrl]);
|
|
137
|
-
const [isDragActive, setIsDragActive] =
|
|
138
|
-
const [isUploading, setIsUploading] =
|
|
139
|
-
const [uploadProgress, setUploadProgress] =
|
|
140
|
-
const [uploadedFiles, setUploadedFiles] =
|
|
172
|
+
const [isDragActive, setIsDragActive] = useState2(false);
|
|
173
|
+
const [isUploading, setIsUploading] = useState2(false);
|
|
174
|
+
const [uploadProgress, setUploadProgress] = useState2({});
|
|
175
|
+
const [uploadedFiles, setUploadedFiles] = useState2([]);
|
|
141
176
|
const fileInputRef = React.useRef(null);
|
|
142
177
|
const uploadFiles = useCallback2(
|
|
143
178
|
async (files) => {
|
|
@@ -320,7 +355,7 @@ function Dropzone({
|
|
|
320
355
|
}
|
|
321
356
|
|
|
322
357
|
// src/hooks/useFiles.ts
|
|
323
|
-
import { useState as
|
|
358
|
+
import { useState as useState3, useCallback as useCallback3, useEffect, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
324
359
|
function useFiles({
|
|
325
360
|
metadata,
|
|
326
361
|
startPage = 1,
|
|
@@ -328,16 +363,19 @@ function useFiles({
|
|
|
328
363
|
mediaType,
|
|
329
364
|
autoLoad = true,
|
|
330
365
|
orderBy,
|
|
331
|
-
orderDirection
|
|
366
|
+
orderDirection,
|
|
367
|
+
staleTime
|
|
332
368
|
} = {}) {
|
|
333
|
-
const { client, on } = usePlugable();
|
|
334
|
-
const [files, setFiles] =
|
|
335
|
-
const [isLoading, setIsLoading] =
|
|
336
|
-
const [page, setPage] =
|
|
337
|
-
const [hasNext, setHasNext] =
|
|
369
|
+
const { client, on, getCache, setCache, staleTime: providerStaleTime } = usePlugable();
|
|
370
|
+
const [files, setFiles] = useState3([]);
|
|
371
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
372
|
+
const [page, setPage] = useState3(startPage);
|
|
373
|
+
const [hasNext, setHasNext] = useState3(false);
|
|
374
|
+
const previousParamsRef = useRef2(null);
|
|
375
|
+
const isInitialMountRef = useRef2(true);
|
|
376
|
+
const effectiveStaleTime = staleTime ?? providerStaleTime;
|
|
338
377
|
const metadataKey = JSON.stringify(metadata);
|
|
339
378
|
const stableMetadata = useMemo2(() => metadata, [metadataKey]);
|
|
340
|
-
const loadedParamsRef = useRef2(null);
|
|
341
379
|
const paramsKeyWithPage = useMemo2(() => JSON.stringify({
|
|
342
380
|
metadata: stableMetadata,
|
|
343
381
|
mediaType,
|
|
@@ -346,7 +384,27 @@ function useFiles({
|
|
|
346
384
|
orderDirection,
|
|
347
385
|
page
|
|
348
386
|
}), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
|
|
349
|
-
const fetchFiles = useCallback3(async (pageNum) => {
|
|
387
|
+
const fetchFiles = useCallback3(async (pageNum, skipCache = false) => {
|
|
388
|
+
if (!skipCache) {
|
|
389
|
+
const cacheKey = JSON.stringify({
|
|
390
|
+
metadata: stableMetadata,
|
|
391
|
+
mediaType,
|
|
392
|
+
perPage,
|
|
393
|
+
orderBy,
|
|
394
|
+
orderDirection,
|
|
395
|
+
page: pageNum
|
|
396
|
+
});
|
|
397
|
+
const cachedEntry = getCache(cacheKey);
|
|
398
|
+
if (cachedEntry) {
|
|
399
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
400
|
+
const isStale = age > effectiveStaleTime;
|
|
401
|
+
if (!isStale) {
|
|
402
|
+
setFiles(cachedEntry.files);
|
|
403
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
350
408
|
setIsLoading(true);
|
|
351
409
|
try {
|
|
352
410
|
const options = {
|
|
@@ -355,13 +413,11 @@ function useFiles({
|
|
|
355
413
|
page: pageNum,
|
|
356
414
|
per_page: perPage,
|
|
357
415
|
with_download_url: true,
|
|
358
|
-
order_by: orderBy,
|
|
359
|
-
order_direction: orderDirection
|
|
416
|
+
...orderBy && { order_by: orderBy },
|
|
417
|
+
...orderDirection && { order_direction: orderDirection }
|
|
360
418
|
};
|
|
361
419
|
const response = await client.list(options);
|
|
362
|
-
|
|
363
|
-
setHasNext(response.paging.has_next_page);
|
|
364
|
-
const currentParamsKey = JSON.stringify({
|
|
420
|
+
const cacheKey = JSON.stringify({
|
|
365
421
|
metadata: stableMetadata,
|
|
366
422
|
mediaType,
|
|
367
423
|
perPage,
|
|
@@ -369,7 +425,13 @@ function useFiles({
|
|
|
369
425
|
orderDirection,
|
|
370
426
|
page: pageNum
|
|
371
427
|
});
|
|
372
|
-
|
|
428
|
+
setCache(cacheKey, {
|
|
429
|
+
files: response.files,
|
|
430
|
+
paging: response.paging,
|
|
431
|
+
timestamp: Date.now()
|
|
432
|
+
});
|
|
433
|
+
setFiles(response.files);
|
|
434
|
+
setHasNext(response.paging.has_next_page);
|
|
373
435
|
} catch (err) {
|
|
374
436
|
console.error("Failed to load files:", err);
|
|
375
437
|
setFiles([]);
|
|
@@ -377,17 +439,31 @@ function useFiles({
|
|
|
377
439
|
} finally {
|
|
378
440
|
setIsLoading(false);
|
|
379
441
|
}
|
|
380
|
-
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection]);
|
|
442
|
+
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
381
443
|
useEffect(() => {
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
444
|
+
const paramsChanged = previousParamsRef.current !== null && previousParamsRef.current !== paramsKeyWithPage;
|
|
445
|
+
const isInitialMount = isInitialMountRef.current;
|
|
446
|
+
previousParamsRef.current = paramsKeyWithPage;
|
|
447
|
+
if (isInitialMount) {
|
|
448
|
+
isInitialMountRef.current = false;
|
|
449
|
+
}
|
|
450
|
+
const cachedEntry = getCache(paramsKeyWithPage);
|
|
451
|
+
const shouldFetch = paramsChanged || isInitialMount && autoLoad;
|
|
452
|
+
if (cachedEntry) {
|
|
453
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
454
|
+
const isStale = age > effectiveStaleTime;
|
|
455
|
+
setFiles(cachedEntry.files);
|
|
456
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
457
|
+
if (paramsChanged || isStale && shouldFetch) {
|
|
458
|
+
fetchFiles(page, true);
|
|
459
|
+
}
|
|
460
|
+
} else if (shouldFetch) {
|
|
461
|
+
fetchFiles(page, true);
|
|
386
462
|
}
|
|
387
|
-
}, [
|
|
463
|
+
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
388
464
|
useEffect(() => {
|
|
389
465
|
const unsubscribe = on("file.uploaded", () => {
|
|
390
|
-
fetchFiles(page);
|
|
466
|
+
fetchFiles(page, true);
|
|
391
467
|
});
|
|
392
468
|
return unsubscribe;
|
|
393
469
|
}, [on, fetchFiles, page]);
|
|
@@ -400,7 +476,7 @@ function useFiles({
|
|
|
400
476
|
setPage((p) => Math.max(1, p - 1));
|
|
401
477
|
}, []);
|
|
402
478
|
const refresh = useCallback3(async () => {
|
|
403
|
-
await fetchFiles(page);
|
|
479
|
+
await fetchFiles(page, true);
|
|
404
480
|
}, [fetchFiles, page]);
|
|
405
481
|
return {
|
|
406
482
|
files,
|
|
@@ -447,7 +523,7 @@ function FileList({
|
|
|
447
523
|
}
|
|
448
524
|
|
|
449
525
|
// src/components/FileImage.tsx
|
|
450
|
-
import { useEffect as useEffect2, useState as
|
|
526
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
451
527
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
452
528
|
var imageCache = /* @__PURE__ */ new Map();
|
|
453
529
|
function FileImage({
|
|
@@ -462,9 +538,9 @@ function FileImage({
|
|
|
462
538
|
onLoad,
|
|
463
539
|
onError
|
|
464
540
|
}) {
|
|
465
|
-
const [imageSrc, setImageSrc] =
|
|
466
|
-
const [isLoading, setIsLoading] =
|
|
467
|
-
const [error, setError] =
|
|
541
|
+
const [imageSrc, setImageSrc] = useState4(null);
|
|
542
|
+
const [isLoading, setIsLoading] = useState4(true);
|
|
543
|
+
const [error, setError] = useState4(null);
|
|
468
544
|
useEffect2(() => {
|
|
469
545
|
let isMounted = true;
|
|
470
546
|
let objectUrl = null;
|