@plugable-io/react 0.0.3 → 0.0.4
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 +88 -21
- package/dist/index.mjs +103 -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,17 @@ 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 effectiveStaleTime = staleTime ?? providerStaleTime;
|
|
381
418
|
const metadataKey = JSON.stringify(metadata);
|
|
382
419
|
const stableMetadata = (0, import_react3.useMemo)(() => metadata, [metadataKey]);
|
|
383
|
-
const loadedParamsRef = (0, import_react3.useRef)(null);
|
|
384
420
|
const paramsKeyWithPage = (0, import_react3.useMemo)(() => JSON.stringify({
|
|
385
421
|
metadata: stableMetadata,
|
|
386
422
|
mediaType,
|
|
@@ -389,7 +425,27 @@ function useFiles({
|
|
|
389
425
|
orderDirection,
|
|
390
426
|
page
|
|
391
427
|
}), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
|
|
392
|
-
const fetchFiles = (0, import_react3.useCallback)(async (pageNum) => {
|
|
428
|
+
const fetchFiles = (0, import_react3.useCallback)(async (pageNum, skipCache = false) => {
|
|
429
|
+
if (!skipCache) {
|
|
430
|
+
const cacheKey = JSON.stringify({
|
|
431
|
+
metadata: stableMetadata,
|
|
432
|
+
mediaType,
|
|
433
|
+
perPage,
|
|
434
|
+
orderBy,
|
|
435
|
+
orderDirection,
|
|
436
|
+
page: pageNum
|
|
437
|
+
});
|
|
438
|
+
const cachedEntry = getCache(cacheKey);
|
|
439
|
+
if (cachedEntry) {
|
|
440
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
441
|
+
const isStale = age > effectiveStaleTime;
|
|
442
|
+
if (!isStale) {
|
|
443
|
+
setFiles(cachedEntry.files);
|
|
444
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
393
449
|
setIsLoading(true);
|
|
394
450
|
try {
|
|
395
451
|
const options = {
|
|
@@ -398,13 +454,11 @@ function useFiles({
|
|
|
398
454
|
page: pageNum,
|
|
399
455
|
per_page: perPage,
|
|
400
456
|
with_download_url: true,
|
|
401
|
-
order_by: orderBy,
|
|
402
|
-
order_direction: orderDirection
|
|
457
|
+
...orderBy && { order_by: orderBy },
|
|
458
|
+
...orderDirection && { order_direction: orderDirection }
|
|
403
459
|
};
|
|
404
460
|
const response = await client.list(options);
|
|
405
|
-
|
|
406
|
-
setHasNext(response.paging.has_next_page);
|
|
407
|
-
const currentParamsKey = JSON.stringify({
|
|
461
|
+
const cacheKey = JSON.stringify({
|
|
408
462
|
metadata: stableMetadata,
|
|
409
463
|
mediaType,
|
|
410
464
|
perPage,
|
|
@@ -412,7 +466,13 @@ function useFiles({
|
|
|
412
466
|
orderDirection,
|
|
413
467
|
page: pageNum
|
|
414
468
|
});
|
|
415
|
-
|
|
469
|
+
setCache(cacheKey, {
|
|
470
|
+
files: response.files,
|
|
471
|
+
paging: response.paging,
|
|
472
|
+
timestamp: Date.now()
|
|
473
|
+
});
|
|
474
|
+
setFiles(response.files);
|
|
475
|
+
setHasNext(response.paging.has_next_page);
|
|
416
476
|
} catch (err) {
|
|
417
477
|
console.error("Failed to load files:", err);
|
|
418
478
|
setFiles([]);
|
|
@@ -420,17 +480,24 @@ function useFiles({
|
|
|
420
480
|
} finally {
|
|
421
481
|
setIsLoading(false);
|
|
422
482
|
}
|
|
423
|
-
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection]);
|
|
483
|
+
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
424
484
|
(0, import_react3.useEffect)(() => {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
485
|
+
const cachedEntry = getCache(paramsKeyWithPage);
|
|
486
|
+
if (cachedEntry) {
|
|
487
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
488
|
+
const isStale = age > effectiveStaleTime;
|
|
489
|
+
setFiles(cachedEntry.files);
|
|
490
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
491
|
+
if (isStale && autoLoad) {
|
|
492
|
+
fetchFiles(page, true);
|
|
493
|
+
}
|
|
494
|
+
} else if (autoLoad) {
|
|
495
|
+
fetchFiles(page, true);
|
|
429
496
|
}
|
|
430
|
-
}, [
|
|
497
|
+
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
431
498
|
(0, import_react3.useEffect)(() => {
|
|
432
499
|
const unsubscribe = on("file.uploaded", () => {
|
|
433
|
-
fetchFiles(page);
|
|
500
|
+
fetchFiles(page, true);
|
|
434
501
|
});
|
|
435
502
|
return unsubscribe;
|
|
436
503
|
}, [on, fetchFiles, page]);
|
|
@@ -443,7 +510,7 @@ function useFiles({
|
|
|
443
510
|
setPage((p) => Math.max(1, p - 1));
|
|
444
511
|
}, []);
|
|
445
512
|
const refresh = (0, import_react3.useCallback)(async () => {
|
|
446
|
-
await fetchFiles(page);
|
|
513
|
+
await fetchFiles(page, true);
|
|
447
514
|
}, [fetchFiles, page]);
|
|
448
515
|
return {
|
|
449
516
|
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 } from "react";
|
|
324
359
|
function useFiles({
|
|
325
360
|
metadata,
|
|
326
361
|
startPage = 1,
|
|
@@ -328,16 +363,17 @@ 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 effectiveStaleTime = staleTime ?? providerStaleTime;
|
|
338
375
|
const metadataKey = JSON.stringify(metadata);
|
|
339
376
|
const stableMetadata = useMemo2(() => metadata, [metadataKey]);
|
|
340
|
-
const loadedParamsRef = useRef2(null);
|
|
341
377
|
const paramsKeyWithPage = useMemo2(() => JSON.stringify({
|
|
342
378
|
metadata: stableMetadata,
|
|
343
379
|
mediaType,
|
|
@@ -346,7 +382,27 @@ function useFiles({
|
|
|
346
382
|
orderDirection,
|
|
347
383
|
page
|
|
348
384
|
}), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
|
|
349
|
-
const fetchFiles = useCallback3(async (pageNum) => {
|
|
385
|
+
const fetchFiles = useCallback3(async (pageNum, skipCache = false) => {
|
|
386
|
+
if (!skipCache) {
|
|
387
|
+
const cacheKey = JSON.stringify({
|
|
388
|
+
metadata: stableMetadata,
|
|
389
|
+
mediaType,
|
|
390
|
+
perPage,
|
|
391
|
+
orderBy,
|
|
392
|
+
orderDirection,
|
|
393
|
+
page: pageNum
|
|
394
|
+
});
|
|
395
|
+
const cachedEntry = getCache(cacheKey);
|
|
396
|
+
if (cachedEntry) {
|
|
397
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
398
|
+
const isStale = age > effectiveStaleTime;
|
|
399
|
+
if (!isStale) {
|
|
400
|
+
setFiles(cachedEntry.files);
|
|
401
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
350
406
|
setIsLoading(true);
|
|
351
407
|
try {
|
|
352
408
|
const options = {
|
|
@@ -355,13 +411,11 @@ function useFiles({
|
|
|
355
411
|
page: pageNum,
|
|
356
412
|
per_page: perPage,
|
|
357
413
|
with_download_url: true,
|
|
358
|
-
order_by: orderBy,
|
|
359
|
-
order_direction: orderDirection
|
|
414
|
+
...orderBy && { order_by: orderBy },
|
|
415
|
+
...orderDirection && { order_direction: orderDirection }
|
|
360
416
|
};
|
|
361
417
|
const response = await client.list(options);
|
|
362
|
-
|
|
363
|
-
setHasNext(response.paging.has_next_page);
|
|
364
|
-
const currentParamsKey = JSON.stringify({
|
|
418
|
+
const cacheKey = JSON.stringify({
|
|
365
419
|
metadata: stableMetadata,
|
|
366
420
|
mediaType,
|
|
367
421
|
perPage,
|
|
@@ -369,7 +423,13 @@ function useFiles({
|
|
|
369
423
|
orderDirection,
|
|
370
424
|
page: pageNum
|
|
371
425
|
});
|
|
372
|
-
|
|
426
|
+
setCache(cacheKey, {
|
|
427
|
+
files: response.files,
|
|
428
|
+
paging: response.paging,
|
|
429
|
+
timestamp: Date.now()
|
|
430
|
+
});
|
|
431
|
+
setFiles(response.files);
|
|
432
|
+
setHasNext(response.paging.has_next_page);
|
|
373
433
|
} catch (err) {
|
|
374
434
|
console.error("Failed to load files:", err);
|
|
375
435
|
setFiles([]);
|
|
@@ -377,17 +437,24 @@ function useFiles({
|
|
|
377
437
|
} finally {
|
|
378
438
|
setIsLoading(false);
|
|
379
439
|
}
|
|
380
|
-
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection]);
|
|
440
|
+
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
381
441
|
useEffect(() => {
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
442
|
+
const cachedEntry = getCache(paramsKeyWithPage);
|
|
443
|
+
if (cachedEntry) {
|
|
444
|
+
const age = Date.now() - cachedEntry.timestamp;
|
|
445
|
+
const isStale = age > effectiveStaleTime;
|
|
446
|
+
setFiles(cachedEntry.files);
|
|
447
|
+
setHasNext(cachedEntry.paging.has_next_page);
|
|
448
|
+
if (isStale && autoLoad) {
|
|
449
|
+
fetchFiles(page, true);
|
|
450
|
+
}
|
|
451
|
+
} else if (autoLoad) {
|
|
452
|
+
fetchFiles(page, true);
|
|
386
453
|
}
|
|
387
|
-
}, [
|
|
454
|
+
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
388
455
|
useEffect(() => {
|
|
389
456
|
const unsubscribe = on("file.uploaded", () => {
|
|
390
|
-
fetchFiles(page);
|
|
457
|
+
fetchFiles(page, true);
|
|
391
458
|
});
|
|
392
459
|
return unsubscribe;
|
|
393
460
|
}, [on, fetchFiles, page]);
|
|
@@ -400,7 +467,7 @@ function useFiles({
|
|
|
400
467
|
setPage((p) => Math.max(1, p - 1));
|
|
401
468
|
}, []);
|
|
402
469
|
const refresh = useCallback3(async () => {
|
|
403
|
-
await fetchFiles(page);
|
|
470
|
+
await fetchFiles(page, true);
|
|
404
471
|
}, [fetchFiles, page]);
|
|
405
472
|
return {
|
|
406
473
|
files,
|
|
@@ -447,7 +514,7 @@ function FileList({
|
|
|
447
514
|
}
|
|
448
515
|
|
|
449
516
|
// src/components/FileImage.tsx
|
|
450
|
-
import { useEffect as useEffect2, useState as
|
|
517
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
451
518
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
452
519
|
var imageCache = /* @__PURE__ */ new Map();
|
|
453
520
|
function FileImage({
|
|
@@ -462,9 +529,9 @@ function FileImage({
|
|
|
462
529
|
onLoad,
|
|
463
530
|
onError
|
|
464
531
|
}) {
|
|
465
|
-
const [imageSrc, setImageSrc] =
|
|
466
|
-
const [isLoading, setIsLoading] =
|
|
467
|
-
const [error, setError] =
|
|
532
|
+
const [imageSrc, setImageSrc] = useState4(null);
|
|
533
|
+
const [isLoading, setIsLoading] = useState4(true);
|
|
534
|
+
const [error, setError] = useState4(null);
|
|
468
535
|
useEffect2(() => {
|
|
469
536
|
let isMounted = true;
|
|
470
537
|
let objectUrl = null;
|