@plugable-io/react 0.0.4 → 0.0.6

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 CHANGED
@@ -92,8 +92,9 @@ interface FileImageProps {
92
92
  style?: CSSProperties;
93
93
  onLoad?: () => void;
94
94
  onError?: (error: Error) => void;
95
+ onRefetchNeeded?: () => Promise<void>;
95
96
  }
96
- declare function FileImage({ file, width, height, objectFit, borderRadius, alt, className, style, onLoad, onError, }: FileImageProps): react_jsx_runtime.JSX.Element;
97
+ declare function FileImage({ file, width, height, objectFit, borderRadius, alt, className, style, onLoad, onError, onRefetchNeeded, }: FileImageProps): react_jsx_runtime.JSX.Element;
97
98
  declare function clearImageCache(): void;
98
99
 
99
100
  interface FilePreviewProps {
@@ -106,7 +107,7 @@ interface FilePreviewProps {
106
107
  showExtension?: boolean;
107
108
  renderNonImage?: (file: FileObject) => React.ReactNode;
108
109
  }
109
- declare function FilePreview({ file, width, height, className, style, objectFit, showExtension, renderNonImage, }: FilePreviewProps): react_jsx_runtime.JSX.Element;
110
+ declare function FilePreview({ file: initialFile, width, height, className, style, objectFit, showExtension, renderNonImage, }: FilePreviewProps): react_jsx_runtime.JSX.Element;
110
111
 
111
112
  interface UseFilesOptions {
112
113
  metadata?: Record<string, any>;
package/dist/index.d.ts CHANGED
@@ -92,8 +92,9 @@ interface FileImageProps {
92
92
  style?: CSSProperties;
93
93
  onLoad?: () => void;
94
94
  onError?: (error: Error) => void;
95
+ onRefetchNeeded?: () => Promise<void>;
95
96
  }
96
- declare function FileImage({ file, width, height, objectFit, borderRadius, alt, className, style, onLoad, onError, }: FileImageProps): react_jsx_runtime.JSX.Element;
97
+ declare function FileImage({ file, width, height, objectFit, borderRadius, alt, className, style, onLoad, onError, onRefetchNeeded, }: FileImageProps): react_jsx_runtime.JSX.Element;
97
98
  declare function clearImageCache(): void;
98
99
 
99
100
  interface FilePreviewProps {
@@ -106,7 +107,7 @@ interface FilePreviewProps {
106
107
  showExtension?: boolean;
107
108
  renderNonImage?: (file: FileObject) => React.ReactNode;
108
109
  }
109
- declare function FilePreview({ file, width, height, className, style, objectFit, showExtension, renderNonImage, }: FilePreviewProps): react_jsx_runtime.JSX.Element;
110
+ declare function FilePreview({ file: initialFile, width, height, className, style, objectFit, showExtension, renderNonImage, }: FilePreviewProps): react_jsx_runtime.JSX.Element;
110
111
 
111
112
  interface UseFilesOptions {
112
113
  metadata?: Record<string, any>;
package/dist/index.js CHANGED
@@ -414,6 +414,8 @@ function useFiles({
414
414
  const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
415
415
  const [page, setPage] = (0, import_react3.useState)(startPage);
416
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);
417
419
  const effectiveStaleTime = staleTime ?? providerStaleTime;
418
420
  const metadataKey = JSON.stringify(metadata);
419
421
  const stableMetadata = (0, import_react3.useMemo)(() => metadata, [metadataKey]);
@@ -482,16 +484,23 @@ function useFiles({
482
484
  }
483
485
  }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
484
486
  (0, import_react3.useEffect)(() => {
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
+ }
485
493
  const cachedEntry = getCache(paramsKeyWithPage);
494
+ const shouldFetch = paramsChanged || isInitialMount && autoLoad;
486
495
  if (cachedEntry) {
487
496
  const age = Date.now() - cachedEntry.timestamp;
488
497
  const isStale = age > effectiveStaleTime;
489
498
  setFiles(cachedEntry.files);
490
499
  setHasNext(cachedEntry.paging.has_next_page);
491
- if (isStale && autoLoad) {
500
+ if (paramsChanged || isStale && shouldFetch) {
492
501
  fetchFiles(page, true);
493
502
  }
494
- } else if (autoLoad) {
503
+ } else if (shouldFetch) {
495
504
  fetchFiles(page, true);
496
505
  }
497
506
  }, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
@@ -570,11 +579,13 @@ function FileImage({
570
579
  className,
571
580
  style,
572
581
  onLoad,
573
- onError
582
+ onError,
583
+ onRefetchNeeded
574
584
  }) {
575
585
  const [imageSrc, setImageSrc] = (0, import_react4.useState)(null);
576
586
  const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
577
587
  const [error, setError] = (0, import_react4.useState)(null);
588
+ const refetchAttemptedRef = (0, import_react4.useRef)(null);
578
589
  (0, import_react4.useEffect)(() => {
579
590
  let isMounted = true;
580
591
  let objectUrl = null;
@@ -597,6 +608,13 @@ function FileImage({
597
608
  }
598
609
  });
599
610
  if (!response.ok) {
611
+ const downloadUrlKey = `${file.id}-${file.download_url}`;
612
+ if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
613
+ refetchAttemptedRef.current = downloadUrlKey;
614
+ imageCache.delete(cacheKey);
615
+ await onRefetchNeeded();
616
+ return;
617
+ }
600
618
  throw new Error(`Failed to fetch image: ${response.statusText}`);
601
619
  }
602
620
  const blob = await response.blob();
@@ -605,6 +623,7 @@ function FileImage({
605
623
  if (isMounted) {
606
624
  setImageSrc(objectUrl);
607
625
  setIsLoading(false);
626
+ refetchAttemptedRef.current = null;
608
627
  }
609
628
  } else {
610
629
  throw new Error("No download URL available for file");
@@ -623,7 +642,7 @@ function FileImage({
623
642
  return () => {
624
643
  isMounted = false;
625
644
  };
626
- }, [file.id, file.checksum, file.download_url, onError]);
645
+ }, [file.id, file.checksum, file.download_url, onError, onRefetchNeeded]);
627
646
  const handleLoad = () => {
628
647
  setIsLoading(false);
629
648
  onLoad?.();
@@ -713,9 +732,10 @@ function clearImageCache() {
713
732
  }
714
733
 
715
734
  // src/components/FilePreview.tsx
735
+ var import_react5 = require("react");
716
736
  var import_jsx_runtime5 = require("react/jsx-runtime");
717
737
  function FilePreview({
718
- file,
738
+ file: initialFile,
719
739
  width = 80,
720
740
  height = 80,
721
741
  className,
@@ -724,6 +744,24 @@ function FilePreview({
724
744
  showExtension = true,
725
745
  renderNonImage
726
746
  }) {
747
+ const { client } = usePlugable();
748
+ const [file, setFile] = (0, import_react5.useState)(initialFile);
749
+ const [isRefetching, setIsRefetching] = (0, import_react5.useState)(false);
750
+ (0, import_react5.useEffect)(() => {
751
+ setFile(initialFile);
752
+ }, [initialFile.id, initialFile.download_url]);
753
+ const handleRefetch = (0, import_react5.useCallback)(async () => {
754
+ if (isRefetching) return;
755
+ try {
756
+ setIsRefetching(true);
757
+ const refreshedFile = await client.get(file.id);
758
+ setFile(refreshedFile);
759
+ } catch (err) {
760
+ console.error("Failed to refetch file:", err);
761
+ } finally {
762
+ setIsRefetching(false);
763
+ }
764
+ }, [file.id, client]);
727
765
  const isImage = file.content_type.startsWith("image/");
728
766
  const containerStyle = {
729
767
  width,
@@ -747,7 +785,8 @@ function FilePreview({
747
785
  objectFit,
748
786
  className,
749
787
  style,
750
- borderRadius: 4
788
+ borderRadius: 4,
789
+ onRefetchNeeded: handleRefetch
751
790
  }
752
791
  );
753
792
  }
package/dist/index.mjs CHANGED
@@ -355,7 +355,7 @@ function Dropzone({
355
355
  }
356
356
 
357
357
  // src/hooks/useFiles.ts
358
- import { useState as useState3, useCallback as useCallback3, useEffect, useMemo as useMemo2 } from "react";
358
+ import { useState as useState3, useCallback as useCallback3, useEffect, useMemo as useMemo2, useRef as useRef2 } from "react";
359
359
  function useFiles({
360
360
  metadata,
361
361
  startPage = 1,
@@ -371,6 +371,8 @@ function useFiles({
371
371
  const [isLoading, setIsLoading] = useState3(false);
372
372
  const [page, setPage] = useState3(startPage);
373
373
  const [hasNext, setHasNext] = useState3(false);
374
+ const previousParamsRef = useRef2(null);
375
+ const isInitialMountRef = useRef2(true);
374
376
  const effectiveStaleTime = staleTime ?? providerStaleTime;
375
377
  const metadataKey = JSON.stringify(metadata);
376
378
  const stableMetadata = useMemo2(() => metadata, [metadataKey]);
@@ -439,16 +441,23 @@ function useFiles({
439
441
  }
440
442
  }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
441
443
  useEffect(() => {
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
+ }
442
450
  const cachedEntry = getCache(paramsKeyWithPage);
451
+ const shouldFetch = paramsChanged || isInitialMount && autoLoad;
443
452
  if (cachedEntry) {
444
453
  const age = Date.now() - cachedEntry.timestamp;
445
454
  const isStale = age > effectiveStaleTime;
446
455
  setFiles(cachedEntry.files);
447
456
  setHasNext(cachedEntry.paging.has_next_page);
448
- if (isStale && autoLoad) {
457
+ if (paramsChanged || isStale && shouldFetch) {
449
458
  fetchFiles(page, true);
450
459
  }
451
- } else if (autoLoad) {
460
+ } else if (shouldFetch) {
452
461
  fetchFiles(page, true);
453
462
  }
454
463
  }, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
@@ -514,7 +523,7 @@ function FileList({
514
523
  }
515
524
 
516
525
  // src/components/FileImage.tsx
517
- import { useEffect as useEffect2, useState as useState4 } from "react";
526
+ import { useEffect as useEffect2, useState as useState4, useRef as useRef3 } from "react";
518
527
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
519
528
  var imageCache = /* @__PURE__ */ new Map();
520
529
  function FileImage({
@@ -527,11 +536,13 @@ function FileImage({
527
536
  className,
528
537
  style,
529
538
  onLoad,
530
- onError
539
+ onError,
540
+ onRefetchNeeded
531
541
  }) {
532
542
  const [imageSrc, setImageSrc] = useState4(null);
533
543
  const [isLoading, setIsLoading] = useState4(true);
534
544
  const [error, setError] = useState4(null);
545
+ const refetchAttemptedRef = useRef3(null);
535
546
  useEffect2(() => {
536
547
  let isMounted = true;
537
548
  let objectUrl = null;
@@ -554,6 +565,13 @@ function FileImage({
554
565
  }
555
566
  });
556
567
  if (!response.ok) {
568
+ const downloadUrlKey = `${file.id}-${file.download_url}`;
569
+ if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
570
+ refetchAttemptedRef.current = downloadUrlKey;
571
+ imageCache.delete(cacheKey);
572
+ await onRefetchNeeded();
573
+ return;
574
+ }
557
575
  throw new Error(`Failed to fetch image: ${response.statusText}`);
558
576
  }
559
577
  const blob = await response.blob();
@@ -562,6 +580,7 @@ function FileImage({
562
580
  if (isMounted) {
563
581
  setImageSrc(objectUrl);
564
582
  setIsLoading(false);
583
+ refetchAttemptedRef.current = null;
565
584
  }
566
585
  } else {
567
586
  throw new Error("No download URL available for file");
@@ -580,7 +599,7 @@ function FileImage({
580
599
  return () => {
581
600
  isMounted = false;
582
601
  };
583
- }, [file.id, file.checksum, file.download_url, onError]);
602
+ }, [file.id, file.checksum, file.download_url, onError, onRefetchNeeded]);
584
603
  const handleLoad = () => {
585
604
  setIsLoading(false);
586
605
  onLoad?.();
@@ -670,9 +689,10 @@ function clearImageCache() {
670
689
  }
671
690
 
672
691
  // src/components/FilePreview.tsx
692
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect3 } from "react";
673
693
  import { jsx as jsx5 } from "react/jsx-runtime";
674
694
  function FilePreview({
675
- file,
695
+ file: initialFile,
676
696
  width = 80,
677
697
  height = 80,
678
698
  className,
@@ -681,6 +701,24 @@ function FilePreview({
681
701
  showExtension = true,
682
702
  renderNonImage
683
703
  }) {
704
+ const { client } = usePlugable();
705
+ const [file, setFile] = useState5(initialFile);
706
+ const [isRefetching, setIsRefetching] = useState5(false);
707
+ useEffect3(() => {
708
+ setFile(initialFile);
709
+ }, [initialFile.id, initialFile.download_url]);
710
+ const handleRefetch = useCallback4(async () => {
711
+ if (isRefetching) return;
712
+ try {
713
+ setIsRefetching(true);
714
+ const refreshedFile = await client.get(file.id);
715
+ setFile(refreshedFile);
716
+ } catch (err) {
717
+ console.error("Failed to refetch file:", err);
718
+ } finally {
719
+ setIsRefetching(false);
720
+ }
721
+ }, [file.id, client]);
684
722
  const isImage = file.content_type.startsWith("image/");
685
723
  const containerStyle = {
686
724
  width,
@@ -704,7 +742,8 @@ function FilePreview({
704
742
  objectFit,
705
743
  className,
706
744
  style,
707
- borderRadius: 4
745
+ borderRadius: 4,
746
+ onRefetchNeeded: handleRefetch
708
747
  }
709
748
  );
710
749
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plugable-io/react",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "React components and hooks for Plugable File Management API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",