@plugable-io/react 0.0.2 → 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/README.md +33 -0
- package/dist/index.d.mts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +104 -13
- package/dist/index.mjs +119 -28
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -238,6 +238,37 @@ function ImageGallery() {
|
|
|
238
238
|
}
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
### Ordering Files
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
function SortedFileList() {
|
|
245
|
+
const [orderBy, setOrderBy] = useState<'created_at' | 'name' | 'byte_size'>('created_at');
|
|
246
|
+
const [orderDirection, setOrderDirection] = useState<'asc' | 'desc'>('desc');
|
|
247
|
+
|
|
248
|
+
const { files, isLoading } = useFiles({
|
|
249
|
+
orderBy,
|
|
250
|
+
orderDirection,
|
|
251
|
+
perPage: 20,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div>
|
|
256
|
+
<select value={orderBy} onChange={(e) => setOrderBy(e.target.value as any)}>
|
|
257
|
+
<option value="created_at">Date</option>
|
|
258
|
+
<option value="name">Name</option>
|
|
259
|
+
<option value="byte_size">Size</option>
|
|
260
|
+
</select>
|
|
261
|
+
<button onClick={() => setOrderDirection(orderDirection === 'asc' ? 'desc' : 'asc')}>
|
|
262
|
+
{orderDirection === 'asc' ? '↑' : '↓'}
|
|
263
|
+
</button>
|
|
264
|
+
{files.map((file) => (
|
|
265
|
+
<div key={file.id}>{file.name}</div>
|
|
266
|
+
))}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
241
272
|
### Manual Loading
|
|
242
273
|
|
|
243
274
|
```tsx
|
|
@@ -268,6 +299,8 @@ function LazyFileList() {
|
|
|
268
299
|
| `perPage` | `number` | `20` | Files per page |
|
|
269
300
|
| `startPage` | `number` | `1` | Initial page number |
|
|
270
301
|
| `autoLoad` | `boolean` | `true` | Fetch on mount |
|
|
302
|
+
| `orderBy` | `'created_at' \| 'name' \| 'byte_size'` | `'created_at'` | Field to order by |
|
|
303
|
+
| `orderDirection` | `'asc' \| 'desc'` | `'desc'` | Sort direction |
|
|
271
304
|
|
|
272
305
|
### Return Value
|
|
273
306
|
|
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 {
|
|
@@ -104,6 +114,9 @@ interface UseFilesOptions {
|
|
|
104
114
|
perPage?: number;
|
|
105
115
|
mediaType?: string;
|
|
106
116
|
autoLoad?: boolean;
|
|
117
|
+
orderBy?: 'created_at' | 'name' | 'byte_size';
|
|
118
|
+
orderDirection?: 'asc' | 'desc';
|
|
119
|
+
staleTime?: number;
|
|
107
120
|
}
|
|
108
121
|
interface UseFilesResult {
|
|
109
122
|
files: FileObject[];
|
|
@@ -118,6 +131,6 @@ interface UseFilesResult {
|
|
|
118
131
|
setPage: (page: number) => void;
|
|
119
132
|
refresh: () => Promise<void>;
|
|
120
133
|
}
|
|
121
|
-
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, }?: UseFilesOptions): UseFilesResult;
|
|
134
|
+
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, staleTime, }?: UseFilesOptions): UseFilesResult;
|
|
122
135
|
|
|
123
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 {
|
|
@@ -104,6 +114,9 @@ interface UseFilesOptions {
|
|
|
104
114
|
perPage?: number;
|
|
105
115
|
mediaType?: string;
|
|
106
116
|
autoLoad?: boolean;
|
|
117
|
+
orderBy?: 'created_at' | 'name' | 'byte_size';
|
|
118
|
+
orderDirection?: 'asc' | 'desc';
|
|
119
|
+
staleTime?: number;
|
|
107
120
|
}
|
|
108
121
|
interface UseFilesResult {
|
|
109
122
|
files: FileObject[];
|
|
@@ -118,6 +131,6 @@ interface UseFilesResult {
|
|
|
118
131
|
setPage: (page: number) => void;
|
|
119
132
|
refresh: () => Promise<void>;
|
|
120
133
|
}
|
|
121
|
-
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, }?: UseFilesOptions): UseFilesResult;
|
|
134
|
+
declare function useFiles({ metadata, startPage, perPage, mediaType, autoLoad, orderBy, orderDirection, staleTime, }?: UseFilesOptions): UseFilesResult;
|
|
122
135
|
|
|
123
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
|
}
|
|
@@ -369,16 +404,48 @@ function useFiles({
|
|
|
369
404
|
startPage = 1,
|
|
370
405
|
perPage = 20,
|
|
371
406
|
mediaType,
|
|
372
|
-
autoLoad = true
|
|
407
|
+
autoLoad = true,
|
|
408
|
+
orderBy,
|
|
409
|
+
orderDirection,
|
|
410
|
+
staleTime
|
|
373
411
|
} = {}) {
|
|
374
|
-
const { client, on } = usePlugable();
|
|
412
|
+
const { client, on, getCache, setCache, staleTime: providerStaleTime } = usePlugable();
|
|
375
413
|
const [files, setFiles] = (0, import_react3.useState)([]);
|
|
376
414
|
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
377
415
|
const [page, setPage] = (0, import_react3.useState)(startPage);
|
|
378
416
|
const [hasNext, setHasNext] = (0, import_react3.useState)(false);
|
|
417
|
+
const effectiveStaleTime = staleTime ?? providerStaleTime;
|
|
379
418
|
const metadataKey = JSON.stringify(metadata);
|
|
380
419
|
const stableMetadata = (0, import_react3.useMemo)(() => metadata, [metadataKey]);
|
|
381
|
-
const
|
|
420
|
+
const paramsKeyWithPage = (0, import_react3.useMemo)(() => JSON.stringify({
|
|
421
|
+
metadata: stableMetadata,
|
|
422
|
+
mediaType,
|
|
423
|
+
perPage,
|
|
424
|
+
orderBy,
|
|
425
|
+
orderDirection,
|
|
426
|
+
page
|
|
427
|
+
}), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
|
|
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
|
+
}
|
|
382
449
|
setIsLoading(true);
|
|
383
450
|
try {
|
|
384
451
|
const options = {
|
|
@@ -386,9 +453,24 @@ function useFiles({
|
|
|
386
453
|
media_type: mediaType,
|
|
387
454
|
page: pageNum,
|
|
388
455
|
per_page: perPage,
|
|
389
|
-
with_download_url: true
|
|
456
|
+
with_download_url: true,
|
|
457
|
+
...orderBy && { order_by: orderBy },
|
|
458
|
+
...orderDirection && { order_direction: orderDirection }
|
|
390
459
|
};
|
|
391
460
|
const response = await client.list(options);
|
|
461
|
+
const cacheKey = JSON.stringify({
|
|
462
|
+
metadata: stableMetadata,
|
|
463
|
+
mediaType,
|
|
464
|
+
perPage,
|
|
465
|
+
orderBy,
|
|
466
|
+
orderDirection,
|
|
467
|
+
page: pageNum
|
|
468
|
+
});
|
|
469
|
+
setCache(cacheKey, {
|
|
470
|
+
files: response.files,
|
|
471
|
+
paging: response.paging,
|
|
472
|
+
timestamp: Date.now()
|
|
473
|
+
});
|
|
392
474
|
setFiles(response.files);
|
|
393
475
|
setHasNext(response.paging.has_next_page);
|
|
394
476
|
} catch (err) {
|
|
@@ -398,15 +480,24 @@ function useFiles({
|
|
|
398
480
|
} finally {
|
|
399
481
|
setIsLoading(false);
|
|
400
482
|
}
|
|
401
|
-
}, [client, stableMetadata, mediaType, perPage]);
|
|
483
|
+
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
402
484
|
(0, import_react3.useEffect)(() => {
|
|
403
|
-
|
|
404
|
-
|
|
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);
|
|
405
496
|
}
|
|
406
|
-
}, [fetchFiles, page
|
|
497
|
+
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
407
498
|
(0, import_react3.useEffect)(() => {
|
|
408
499
|
const unsubscribe = on("file.uploaded", () => {
|
|
409
|
-
fetchFiles(page);
|
|
500
|
+
fetchFiles(page, true);
|
|
410
501
|
});
|
|
411
502
|
return unsubscribe;
|
|
412
503
|
}, [on, fetchFiles, page]);
|
|
@@ -419,7 +510,7 @@ function useFiles({
|
|
|
419
510
|
setPage((p) => Math.max(1, p - 1));
|
|
420
511
|
}, []);
|
|
421
512
|
const refresh = (0, import_react3.useCallback)(async () => {
|
|
422
|
-
await fetchFiles(page);
|
|
513
|
+
await fetchFiles(page, true);
|
|
423
514
|
}, [fetchFiles, page]);
|
|
424
515
|
return {
|
|
425
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,22 +355,54 @@ 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,
|
|
327
362
|
perPage = 20,
|
|
328
363
|
mediaType,
|
|
329
|
-
autoLoad = true
|
|
364
|
+
autoLoad = true,
|
|
365
|
+
orderBy,
|
|
366
|
+
orderDirection,
|
|
367
|
+
staleTime
|
|
330
368
|
} = {}) {
|
|
331
|
-
const { client, on } = usePlugable();
|
|
332
|
-
const [files, setFiles] =
|
|
333
|
-
const [isLoading, setIsLoading] =
|
|
334
|
-
const [page, setPage] =
|
|
335
|
-
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;
|
|
336
375
|
const metadataKey = JSON.stringify(metadata);
|
|
337
376
|
const stableMetadata = useMemo2(() => metadata, [metadataKey]);
|
|
338
|
-
const
|
|
377
|
+
const paramsKeyWithPage = useMemo2(() => JSON.stringify({
|
|
378
|
+
metadata: stableMetadata,
|
|
379
|
+
mediaType,
|
|
380
|
+
perPage,
|
|
381
|
+
orderBy,
|
|
382
|
+
orderDirection,
|
|
383
|
+
page
|
|
384
|
+
}), [stableMetadata, mediaType, perPage, orderBy, orderDirection, page]);
|
|
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
|
+
}
|
|
339
406
|
setIsLoading(true);
|
|
340
407
|
try {
|
|
341
408
|
const options = {
|
|
@@ -343,9 +410,24 @@ function useFiles({
|
|
|
343
410
|
media_type: mediaType,
|
|
344
411
|
page: pageNum,
|
|
345
412
|
per_page: perPage,
|
|
346
|
-
with_download_url: true
|
|
413
|
+
with_download_url: true,
|
|
414
|
+
...orderBy && { order_by: orderBy },
|
|
415
|
+
...orderDirection && { order_direction: orderDirection }
|
|
347
416
|
};
|
|
348
417
|
const response = await client.list(options);
|
|
418
|
+
const cacheKey = JSON.stringify({
|
|
419
|
+
metadata: stableMetadata,
|
|
420
|
+
mediaType,
|
|
421
|
+
perPage,
|
|
422
|
+
orderBy,
|
|
423
|
+
orderDirection,
|
|
424
|
+
page: pageNum
|
|
425
|
+
});
|
|
426
|
+
setCache(cacheKey, {
|
|
427
|
+
files: response.files,
|
|
428
|
+
paging: response.paging,
|
|
429
|
+
timestamp: Date.now()
|
|
430
|
+
});
|
|
349
431
|
setFiles(response.files);
|
|
350
432
|
setHasNext(response.paging.has_next_page);
|
|
351
433
|
} catch (err) {
|
|
@@ -355,15 +437,24 @@ function useFiles({
|
|
|
355
437
|
} finally {
|
|
356
438
|
setIsLoading(false);
|
|
357
439
|
}
|
|
358
|
-
}, [client, stableMetadata, mediaType, perPage]);
|
|
440
|
+
}, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
|
|
359
441
|
useEffect(() => {
|
|
360
|
-
|
|
361
|
-
|
|
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);
|
|
362
453
|
}
|
|
363
|
-
}, [fetchFiles, page
|
|
454
|
+
}, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
|
|
364
455
|
useEffect(() => {
|
|
365
456
|
const unsubscribe = on("file.uploaded", () => {
|
|
366
|
-
fetchFiles(page);
|
|
457
|
+
fetchFiles(page, true);
|
|
367
458
|
});
|
|
368
459
|
return unsubscribe;
|
|
369
460
|
}, [on, fetchFiles, page]);
|
|
@@ -376,7 +467,7 @@ function useFiles({
|
|
|
376
467
|
setPage((p) => Math.max(1, p - 1));
|
|
377
468
|
}, []);
|
|
378
469
|
const refresh = useCallback3(async () => {
|
|
379
|
-
await fetchFiles(page);
|
|
470
|
+
await fetchFiles(page, true);
|
|
380
471
|
}, [fetchFiles, page]);
|
|
381
472
|
return {
|
|
382
473
|
files,
|
|
@@ -423,7 +514,7 @@ function FileList({
|
|
|
423
514
|
}
|
|
424
515
|
|
|
425
516
|
// src/components/FileImage.tsx
|
|
426
|
-
import { useEffect as useEffect2, useState as
|
|
517
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
427
518
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
428
519
|
var imageCache = /* @__PURE__ */ new Map();
|
|
429
520
|
function FileImage({
|
|
@@ -438,9 +529,9 @@ function FileImage({
|
|
|
438
529
|
onLoad,
|
|
439
530
|
onError
|
|
440
531
|
}) {
|
|
441
|
-
const [imageSrc, setImageSrc] =
|
|
442
|
-
const [isLoading, setIsLoading] =
|
|
443
|
-
const [error, setError] =
|
|
532
|
+
const [imageSrc, setImageSrc] = useState4(null);
|
|
533
|
+
const [isLoading, setIsLoading] = useState4(true);
|
|
534
|
+
const [error, setError] = useState4(null);
|
|
444
535
|
useEffect2(() => {
|
|
445
536
|
let isMounted = true;
|
|
446
537
|
let objectUrl = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plugable-io/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "React components and hooks for Plugable File Management API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"react-dom": ">=16.8.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@plugable-io/js": "^0.0.
|
|
46
|
+
"@plugable-io/js": "^0.0.9"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.0.0",
|