@fluid-app/portal-sdk 0.1.228 → 0.1.229

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.
@@ -46,12 +46,13 @@ function useRepContext() {
46
46
  const shareablesKeys = {
47
47
  media: {
48
48
  all: ["media"],
49
- list: (search, sortDesc, repContext) => [
49
+ list: (search, sortDesc, repContext, ownership) => [
50
50
  "media",
51
51
  "list",
52
52
  search,
53
53
  sortDesc,
54
- repContext
54
+ repContext,
55
+ ownership
55
56
  ],
56
57
  detail: (id, repContext) => [
57
58
  "media",
@@ -691,6 +692,37 @@ function ToggleButton({ active, label, onClick, children }) {
691
692
  });
692
693
  }
693
694
  //#endregion
695
+ //#region ../../shareables/ui/src/hooks/use-infinite-list-sentinel.ts
696
+ /**
697
+ * Wires a sentinel `<div>` to TanStack Query infinite-pagination state.
698
+ *
699
+ * Returns a ref the caller attaches to a 1px sentinel element rendered below
700
+ * the list. When the sentinel scrolls into view (with a 200px root margin)
701
+ * and the query has a next page, isn't already fetching, and hasn't errored,
702
+ * `fetchNextPage()` is invoked.
703
+ */
704
+ function useInfiniteListSentinel({ hasNextPage, isFetchingNextPage, fetchNextPage, error }) {
705
+ const sentinelRef = (0, react.useRef)(null);
706
+ (0, react.useEffect)(() => {
707
+ const target = sentinelRef.current;
708
+ if (!target) return;
709
+ const observer = new IntersectionObserver((entries) => {
710
+ if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage && !error) fetchNextPage();
711
+ }, {
712
+ threshold: 0,
713
+ rootMargin: "200px"
714
+ });
715
+ observer.observe(target);
716
+ return () => observer.disconnect();
717
+ }, [
718
+ fetchNextPage,
719
+ hasNextPage,
720
+ isFetchingNextPage,
721
+ error
722
+ ]);
723
+ return sentinelRef;
724
+ }
725
+ //#endregion
694
726
  //#region ../../shareables/ui/src/components/screens/ProductsScreen.tsx
695
727
  const PAGE_SIZE$5 = 24;
696
728
  const SORT_OPTIONS$1 = [
@@ -733,7 +765,6 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
733
765
  const [debouncedSearch, setDebouncedSearch] = (0, react.useState)("");
734
766
  const [sortValue, setSortValue] = (0, react.useState)("created_at_desc");
735
767
  const [viewMode, setViewMode] = (0, react.useState)("grid");
736
- const observerTarget = (0, react.useRef)(null);
737
768
  (0, react.useEffect)(() => {
738
769
  const timer = setTimeout(() => {
739
770
  setDebouncedSearch(searchTerm);
@@ -782,23 +813,12 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
782
813
  });
783
814
  const usePortal = !!fetchPortalProducts;
784
815
  const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage, error } = usePortal ? portalQuery : legacyQuery;
785
- const handleIntersect = (0, react.useCallback)((entries) => {
786
- if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage) fetchNextPage();
787
- }, [
816
+ const sentinelRef = useInfiniteListSentinel({
788
817
  hasNextPage,
789
818
  isFetchingNextPage,
790
- fetchNextPage
791
- ]);
792
- (0, react.useEffect)(() => {
793
- const target = observerTarget.current;
794
- if (!target) return;
795
- const observer = new IntersectionObserver(handleIntersect, {
796
- threshold: .1,
797
- rootMargin: "200px"
798
- });
799
- observer.observe(target);
800
- return () => observer.disconnect();
801
- }, [handleIntersect]);
819
+ fetchNextPage,
820
+ error
821
+ });
802
822
  const products = (0, react.useMemo)(() => {
803
823
  if (!data) return [];
804
824
  if (usePortal) return data?.pages.flatMap((page) => (page.products ?? []).map((p) => ({
@@ -814,11 +834,16 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
814
834
  media_count: void 0
815
835
  })) ?? [];
816
836
  }, [data, usePortal]);
837
+ const footer = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
838
+ className: "flex justify-center py-4",
839
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
840
+ }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
841
+ className: "bg-destructive/10 text-destructive rounded-lg px-3 py-2",
842
+ children: "Failed to load products. Please try again."
843
+ })] });
817
844
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListLayout, {
818
845
  isLoading,
819
- error,
820
- errorMessage: "Failed to load products. Please try again.",
821
- isEmpty: products.length === 0,
846
+ isEmpty: !error && products.length === 0 && !isFetchingNextPage && !hasNextPage,
822
847
  emptyMessage: debouncedSearch ? "No products match your search." : "No products available.",
823
848
  filters: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
824
849
  className: "flex items-center justify-end gap-2",
@@ -838,11 +863,8 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
838
863
  })]
839
864
  }),
840
865
  loadingFilterShape: "search-view",
841
- footer: isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
842
- className: "flex justify-center py-4",
843
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
844
- }),
845
- sentinelRef: observerTarget,
866
+ footer,
867
+ sentinelRef,
846
868
  children: viewMode === "grid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
847
869
  className: SHAREABLE_GRID_CLASS,
848
870
  children: products.map((product) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareProductCard, {
@@ -1617,37 +1639,6 @@ function ProductDetailScreen({ productId, countryCode, fetchProduct: fetchPortal
1617
1639
  });
1618
1640
  }
1619
1641
  //#endregion
1620
- //#region ../../shareables/ui/src/hooks/use-infinite-list-sentinel.ts
1621
- /**
1622
- * Wires a sentinel `<div>` to TanStack Query infinite-pagination state.
1623
- *
1624
- * Returns a ref the caller attaches to a 1px sentinel element rendered below
1625
- * the list. When the sentinel scrolls into view (with a 200px root margin)
1626
- * and the query has a next page, isn't already fetching, and hasn't errored,
1627
- * `fetchNextPage()` is invoked.
1628
- */
1629
- function useInfiniteListSentinel({ hasNextPage, isFetchingNextPage, fetchNextPage, error }) {
1630
- const sentinelRef = (0, react.useRef)(null);
1631
- (0, react.useEffect)(() => {
1632
- const target = sentinelRef.current;
1633
- if (!target) return;
1634
- const observer = new IntersectionObserver((entries) => {
1635
- if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage && !error) fetchNextPage();
1636
- }, {
1637
- threshold: 0,
1638
- rootMargin: "200px"
1639
- });
1640
- observer.observe(target);
1641
- return () => observer.disconnect();
1642
- }, [
1643
- fetchNextPage,
1644
- hasNextPage,
1645
- isFetchingNextPage,
1646
- error
1647
- ]);
1648
- return sentinelRef;
1649
- }
1650
- //#endregion
1651
1642
  //#region ../../shareables/ui/src/components/OwnerFilterTabs.tsx
1652
1643
  function getFilteredEmptyMessage({ searchTerm, ownerFilter, hasOtherFilters = false, entityName = "items", defaultMessage = "Nothing available at the moment." }) {
1653
1644
  const isFiltered = ownerFilter !== "all" || hasOtherFilters;
@@ -1666,7 +1657,7 @@ function OwnerFilterTabs({ value, onValueChange, myLabel = "Mine" }) {
1666
1657
  children: "All"
1667
1658
  }),
1668
1659
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.TabsTrigger, {
1669
- value: "my",
1660
+ value: "mine",
1670
1661
  children: myLabel
1671
1662
  }),
1672
1663
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.TabsTrigger, {
@@ -1749,7 +1740,7 @@ function MediaListingScreen(_props) {
1749
1740
  title: "Media deleted",
1750
1741
  type: "success"
1751
1742
  });
1752
- queryClient.invalidateQueries({ queryKey: shareablesKeys.media.list(debouncedSearch, sortValue === "title_desc", repContext) });
1743
+ queryClient.invalidateQueries({ queryKey: shareablesKeys.media.all });
1753
1744
  setPendingDeleteId(null);
1754
1745
  },
1755
1746
  onError: (error) => {
@@ -1768,13 +1759,14 @@ function MediaListingScreen(_props) {
1768
1759
  const bffKind = kindFilter === "all" ? void 0 : kindFilter;
1769
1760
  const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage, error } = (0, _tanstack_react_query.useInfiniteQuery)({
1770
1761
  queryKey: [
1771
- ...shareablesKeys.media.list(debouncedSearch, sortValue === "title_desc", repContext),
1762
+ ...shareablesKeys.media.list(debouncedSearch, sortValue === "title_desc", repContext, ownerFilter),
1772
1763
  sortValue,
1773
1764
  kindFilter
1774
1765
  ],
1775
1766
  queryFn: async ({ pageParam }) => {
1776
1767
  return await api.media.list({
1777
1768
  "filter[title]": debouncedSearch || void 0,
1769
+ "filter[ownership]": ownerFilter,
1778
1770
  "filter[content_format]": bffKind,
1779
1771
  "page[cursor]": pageParam,
1780
1772
  "page[limit]": PAGE_SIZE$4,
@@ -1785,8 +1777,7 @@ function MediaListingScreen(_props) {
1785
1777
  initialPageParam: void 0,
1786
1778
  placeholderData: _tanstack_react_query.keepPreviousData
1787
1779
  });
1788
- const allItems = (0, react.useMemo)(() => data?.pages.flatMap((p) => p.media) ?? [], [data?.pages]);
1789
- const filteredItems = ownerFilter === "all" ? allItems : ownerFilter === "my" ? allItems.filter((item) => item.owner_type === "user") : allItems.filter((item) => item.owner_type === "company");
1780
+ const filteredItems = (0, react.useMemo)(() => data?.pages.flatMap((p) => p.media) ?? [], [data?.pages]);
1790
1781
  const sentinelRef = useInfiniteListSentinel({
1791
1782
  hasNextPage,
1792
1783
  isFetchingNextPage,
@@ -9091,7 +9082,7 @@ function PlaylistsListingScreen(_props) {
9091
9082
  const allPlaylists = (0, react.useMemo)(() => data?.pages.flatMap((page) => page.playlists) ?? [], [data?.pages]);
9092
9083
  const filteredPlaylists = (0, react.useMemo)(() => {
9093
9084
  if (ownerFilter === "all" || !user?.id) return allPlaylists;
9094
- if (ownerFilter === "my") return allPlaylists.filter((p) => p.user_id === user.id);
9085
+ if (ownerFilter === "mine") return allPlaylists.filter((p) => p.user_id === user.id);
9095
9086
  return allPlaylists.filter((p) => p.user_id !== user.id);
9096
9087
  }, [
9097
9088
  allPlaylists,
@@ -10846,7 +10837,6 @@ function FilesListingScreen({ onNavigate }) {
10846
10837
  }) }), []));
10847
10838
  const [searchTerm, setSearchTerm] = (0, react.useState)("");
10848
10839
  const [debouncedSearch, setDebouncedSearch] = (0, react.useState)("");
10849
- const observerTarget = (0, react.useRef)(null);
10850
10840
  (0, react.useEffect)(() => {
10851
10841
  const timer = setTimeout(() => {
10852
10842
  setDebouncedSearch(searchTerm);
@@ -10869,28 +10859,22 @@ function FilesListingScreen({ onNavigate }) {
10869
10859
  initialPageParam: 1
10870
10860
  });
10871
10861
  const files = (0, react.useMemo)(() => data?.pages.flatMap((page) => page.file_resources) ?? [], [data?.pages]);
10872
- const handleIntersect = (0, react.useCallback)((entries) => {
10873
- if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage) fetchNextPage();
10874
- }, [
10862
+ const sentinelRef = useInfiniteListSentinel({
10875
10863
  hasNextPage,
10876
10864
  isFetchingNextPage,
10877
- fetchNextPage
10878
- ]);
10879
- (0, react.useEffect)(() => {
10880
- const target = observerTarget.current;
10881
- if (!target) return;
10882
- const observer = new IntersectionObserver(handleIntersect, {
10883
- threshold: .1,
10884
- rootMargin: "200px"
10885
- });
10886
- observer.observe(target);
10887
- return () => observer.disconnect();
10888
- }, [handleIntersect]);
10865
+ fetchNextPage,
10866
+ error
10867
+ });
10868
+ const footer = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10869
+ className: "flex justify-center py-4",
10870
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
10871
+ }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10872
+ className: "bg-destructive/10 text-destructive rounded-lg px-3 py-2",
10873
+ children: "Failed to load files. Please try again."
10874
+ })] });
10889
10875
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListLayout, {
10890
10876
  isLoading,
10891
- error,
10892
- errorMessage: "Failed to load files. Please try again.",
10893
- isEmpty: files.length === 0,
10877
+ isEmpty: !error && files.length === 0 && !isFetchingNextPage && !hasNextPage,
10894
10878
  emptyMessage: debouncedSearch ? `No files match "${debouncedSearch}". Try a different search term.` : "No files available.",
10895
10879
  filters: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10896
10880
  className: "flex justify-end",
@@ -10903,11 +10887,8 @@ function FilesListingScreen({ onNavigate }) {
10903
10887
  })
10904
10888
  })
10905
10889
  }),
10906
- footer: isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10907
- className: "flex justify-center py-4",
10908
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
10909
- }),
10910
- sentinelRef: observerTarget,
10890
+ footer,
10891
+ sentinelRef,
10911
10892
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10912
10893
  className: SHAREABLE_GRID_CLASS,
10913
10894
  children: files.map((file) => {
@@ -11403,6 +11384,7 @@ function createRawMediaAdapter(client) {
11403
11384
  "page[cursor]": params?.cursor,
11404
11385
  "page[limit]": params?.limit,
11405
11386
  "filter[title]": params?.["filter[title]"],
11387
+ "filter[ownership]": params?.["filter[ownership]"],
11406
11388
  "filter[content_format]": params?.["filter[content_format]"],
11407
11389
  sort: params?.sort
11408
11390
  });
@@ -11575,6 +11557,7 @@ function createMediaAdapter(client) {
11575
11557
  cursor: options?.["page[cursor]"],
11576
11558
  limit: options?.["page[limit]"],
11577
11559
  "filter[title]": options?.["filter[title]"],
11560
+ "filter[ownership]": options?.["filter[ownership]"],
11578
11561
  "filter[content_format]": options?.["filter[content_format]"],
11579
11562
  sort: options?.sort
11580
11563
  });
@@ -11585,7 +11568,7 @@ function createMediaAdapter(client) {
11585
11568
  },
11586
11569
  getMedia: async (options) => {
11587
11570
  const pageNumber = options?.page ?? 1;
11588
- const filterKey = `${options?.search_query ?? ""}|${options?.sorted_by ?? ""}`;
11571
+ const filterKey = `${options?.search_query ?? ""}|${options?.sorted_by ?? ""}|${options?.ownership ?? ""}`;
11589
11572
  if (filterKey !== lastFilterKey) {
11590
11573
  cursorByPage.clear();
11591
11574
  lastFilterKey = filterKey;
@@ -11595,10 +11578,13 @@ function createMediaAdapter(client) {
11595
11578
  let bffSort;
11596
11579
  if (rawSort === "title_asc") bffSort = "title_asc";
11597
11580
  else if (rawSort === "title_desc") bffSort = "title_desc";
11581
+ const ownership = options?.ownership;
11582
+ const bffOwnership = ownership === "all" || ownership === "mine" || ownership === "company" ? ownership : void 0;
11598
11583
  const response = await portAdapter.listMedia({
11599
11584
  cursor,
11600
11585
  limit: options?.per_page,
11601
11586
  "filter[title]": options?.search_query,
11587
+ "filter[ownership]": bffOwnership,
11602
11588
  sort: bffSort
11603
11589
  });
11604
11590
  const nextCursor = response.meta.pagination?.next_cursor;
@@ -12531,4 +12517,4 @@ Object.defineProperty(exports, "shareablesScreenPropertySchema", {
12531
12517
  }
12532
12518
  });
12533
12519
 
12534
- //# sourceMappingURL=ShareablesScreen-D6zYE_qj.cjs.map
12520
+ //# sourceMappingURL=ShareablesScreen-o6frhZUM.cjs.map