diff --git a/package-lock.json b/package-lock.json
index a412726..f1e6d5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,7 +18,6 @@
"axios": "^1.7.2",
"globals": "^15.6.0",
"material-react-table": "^2.13.0",
- "prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rss-parser": "^3.13.0"
@@ -36,6 +35,7 @@
"nodemon": "^3.1.4",
"parcel": "^2.12.0",
"process": "^0.11.10",
+ "prop-types": "^15.8.1",
"punycode": "^1.4.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
diff --git a/package.json b/package.json
index 6d9471a..70ff83b 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,6 @@
"axios": "^1.7.2",
"globals": "^15.6.0",
"material-react-table": "^2.13.0",
- "prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rss-parser": "^3.13.0"
@@ -52,6 +51,7 @@
"nodemon": "^3.1.4",
"parcel": "^2.12.0",
"process": "^0.11.10",
+ "prop-types": "^15.8.1",
"punycode": "^1.4.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
diff --git a/src/components/Watchlist/index.js b/src/components/Watchlist/index.js
index f807624..9ba827b 100644
--- a/src/components/Watchlist/index.js
+++ b/src/components/Watchlist/index.js
@@ -1,6 +1,6 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { fetchPlexWatchlistFeed } from '../../api/plexApi';
-import { useEffect, useMemo } from 'react';
+import { useCallback, useEffect, useMemo, useRef } from 'react';
import React from 'react';
import WatchlistTable from '../WatchlistTable';
@@ -8,6 +8,7 @@ export default function Watchlist() {
const {
data,
isPending,
+ isLoading,
isLoadingError,
fetchNextPage,
hasNextPage,
@@ -18,15 +19,34 @@ export default function Watchlist() {
queryFn: fetchPlexWatchlistFeed,
initialPageParam: process.env.BASE_RSS_FEED,
getNextPageParam: (lastPage) => lastPage?.paginationLinks?.next,
+ refetchOnWindowFocus: false,
});
- useEffect(() => {
- if (!isFetching && !isFetchingNextPage && hasNextPage) {
- fetchNextPage();
- }
- }, [isFetching, isFetchingNextPage, hasNextPage, fetchNextPage]);
+ let loadMoreItems = useCallback(
+ (containerRefElement) => {
+ // if (containerRefElement) {
+ // const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
+ //once the user has scrolled within 400px of the bottom of the table, fetch more data if we can
- const justItems = useMemo(
+ if (
+ // scrollHeight - scrollTop - clientHeight < 400 &&
+ !isFetching &&
+ hasNextPage
+ ) {
+ fetchNextPage();
+ }
+ // }
+ },
+ [fetchNextPage, isFetching, hasNextPage]
+ );
+
+ useEffect(() => {
+ if (hasNextPage && !isFetchingNextPage) {
+ loadMoreItems();
+ }
+ }, [loadMoreItems, hasNextPage, isFetchingNextPage, data]);
+
+ const flatItems = useMemo(
() => data?.pages?.flatMap((row) => row.items) ?? [],
[data]
);
@@ -35,10 +55,11 @@ export default function Watchlist() {
<>
{!isPending && (
)}
>
diff --git a/src/components/WatchlistTable/index.js b/src/components/WatchlistTable/index.js
index 7887730..5877c3f 100644
--- a/src/components/WatchlistTable/index.js
+++ b/src/components/WatchlistTable/index.js
@@ -1,10 +1,11 @@
-import { useMemo, useState } from 'react';
+import { useMemo, useRef, useEffect, useState, useCallback } from 'react';
import {
MaterialReactTable,
useMaterialReactTable,
} from 'material-react-table';
import { Chip, Link, Stack } from '@mui/material';
import LaunchOutlined from '@mui/icons-material/Launch';
+import PropTypes from 'prop-types';
export default function WatchlistTable({
items,
@@ -12,16 +13,42 @@ export default function WatchlistTable({
isErrorLoading,
isPageLoading,
}) {
+ const rowVirtualizerInstanceRef = useRef(null);
+
+ const [columnFilters, setColumnFilters] = useState([]);
+ const [globalFilter, setGlobalFilter] = useState();
+ const [sorting, setSorting] = useState([]);
+ const mediaKeywords = 'media:keywords';
+
+ //scroll to top of table when sorting or filters change
+ useEffect(() => {
+ try {
+ if (rowVirtualizerInstanceRef.current?.getTotalSize() > 0) {
+ rowVirtualizerInstanceRef.current?.scrollToIndex(0);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }, [sorting, columnFilters, globalFilter]);
+
const keywordOptions = useMemo(
() =>
- [
- ...new Set(items.flatMap((item) => item['media:keywords'].split(', '))),
- ].map((keyword) => {
- return { label: keyword, value: keyword };
- }),
+ [...new Set(items.flatMap((item) => item[mediaKeywords].split(', ')))]
+ .sort()
+ .map((keyword) => {
+ return { label: keyword, value: keyword };
+ }),
[items]
);
+ const getKeywordsForRow = useCallback((renderedCellValue, row) => {
+ if (typeof renderedCellValue === 'string') {
+ return renderedCellValue.split(', ');
+ }
+
+ return row.original[mediaKeywords].split(', ');
+ }, []);
+
const columns = useMemo(
() => [
{
@@ -53,11 +80,11 @@ export default function WatchlistTable({
header: 'Keywords',
id: 'keywords',
accessorFn: (row) => {
- return row['media:keywords'].split(', ');
+ return row[mediaKeywords].split(',');
},
Cell: ({ renderedCellValue, row }) => (
- {renderedCellValue.map((keyword) => (
+ {getKeywordsForRow(renderedCellValue, row).map((keyword) => (
))}
@@ -66,6 +93,14 @@ export default function WatchlistTable({
size: 400,
maxSize: 700,
filterSelectOptions: keywordOptions,
+ filterVariant: 'multi-select',
+ filterFn: (row, columnId, filterValue) => {
+ return (
+ row.original[mediaKeywords]
+ .split(',')
+ .filter((keyword) => keyword.includes(filterValue)).length > 0
+ );
+ },
},
{
header: 'Rating',
@@ -74,24 +109,30 @@ export default function WatchlistTable({
filterVariant: 'multi-select',
},
],
- [keywordOptions]
+ [keywordOptions, getKeywordsForRow]
);
const table = useMaterialReactTable({
columns,
data: items,
layoutMode: 'semantic',
- filterFromLeafRows: true,
enableFacetedValues: true,
enablePagination: false,
enableRowVirtualization: true,
+ rowVirtualizerInstanceRef,
+ rowVirtualizerOptions: { overscan: 4 },
+ onColumnFiltersChange: setColumnFilters,
+ onGlobalFilterChange: setGlobalFilter,
+ onSortingChange: setSorting,
state: {
isLoading: isLoadingItems,
- showAlertBanner: isErrorLoading,
showProgressBars: isPageLoading,
+ showAlertBanner: isErrorLoading,
+ columnFilters,
+ globalFilter,
+ sorting,
},
initialState: {
- showColumnFilters: true,
showGlobalFilter: true,
},
});
@@ -100,8 +141,9 @@ export default function WatchlistTable({
}
WatchlistTable.propTypes = {
- items: PropTypes.object.isRequired,
+ items: PropTypes.array.isRequired,
isLoadingItems: PropTypes.bool.isRequired,
isErrorLoading: PropTypes.bool.isRequired,
isPageLoading: PropTypes.bool.isRequired,
+ loadMoreItems: PropTypes.func.isRequired,
};