diff --git a/package-lock.json b/package-lock.json
index 7d1efd30..8e0fc7c2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -101,7 +101,7 @@
"react-icons": "^5.6.0",
"react-redux": "^8.1.2",
"react-router-dom": "^6.15.0",
- "react-virtuoso": "^4.17.0",
+ "react-virtuoso": "^4.18.3",
"round-robin-js": "^3.0.10",
"sass": "^1.93.3",
"serialize-error": "^13.0.1",
@@ -16975,9 +16975,9 @@
}
},
"node_modules/react-virtuoso": {
- "version": "4.17.0",
- "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.17.0.tgz",
- "integrity": "sha512-od3pi2v13v31uzn5zPXC2u3ouISFCVhjFVFch2VvS2Cx7pWA2F1aJa3XhNTN2F07M3lhfnMnsmGeH+7wZICr7w==",
+ "version": "4.18.3",
+ "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.3.tgz",
+ "integrity": "sha512-fLz/peHAx4Eu0DLHurFEEI7Y6n5CqEoxBh04rgJM9yMuOJah2a9zWg/MUOmZLcp7zuWYorXq5+5bf3IRgkNvWg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16 || >=17 || >= 18 || >= 19",
diff --git a/package.json b/package.json
index e32bfff7..676b9622 100644
--- a/package.json
+++ b/package.json
@@ -139,7 +139,7 @@
"react-icons": "^5.6.0",
"react-redux": "^8.1.2",
"react-router-dom": "^6.15.0",
- "react-virtuoso": "^4.17.0",
+ "react-virtuoso": "^4.18.3",
"round-robin-js": "^3.0.10",
"sass": "^1.93.3",
"serialize-error": "^13.0.1",
diff --git a/src/client/components/playActivity/PlayList.scss b/src/client/components/playActivity/PlayList.scss
index 2621f18c..79bf745b 100644
--- a/src/client/components/playActivity/PlayList.scss
+++ b/src/client/components/playActivity/PlayList.scss
@@ -1,9 +1,9 @@
-[data-testid="virtuoso-item-list"] div[data-index]:first-child div.chakra-collapsible__root {
- border-top-left-radius: 0.25rem;
- border-top-right-radius: 0.25rem;
-}
+// [data-testid="virtuoso-item-list"] div[data-index]:first-child div.chakra-collapsible__root {
+// border-top-left-radius: 0.25rem;
+// border-top-right-radius: 0.25rem;
+// }
-[data-testid="virtuoso-item-list"] div[data-index]:last-child div.chakra-collapsible__root {
- border-bottom-left-radius: 0.25rem;
- border-bottom-right-radius: 0.25rem;
-}
\ No newline at end of file
+// [data-testid="virtuoso-item-list"] div[data-index]:last-child div.chakra-collapsible__root {
+// border-bottom-left-radius: 0.25rem;
+// border-bottom-right-radius: 0.25rem;
+// }
\ No newline at end of file
diff --git a/src/client/components/playActivity/PlayList.tsx b/src/client/components/playActivity/PlayList.tsx
index 174a4d83..9b38b435 100644
--- a/src/client/components/playActivity/PlayList.tsx
+++ b/src/client/components/playActivity/PlayList.tsx
@@ -4,11 +4,10 @@ import { ShortDateDisplay } from '../DateDisplay.js';
import { TextMuted } from '../TextMuted.js';
import { LuChevronRight } from "react-icons/lu"
import { capitalize } from '../../../core/StringUtils.js';
-import { ComponentProps, useMemo } from "react"
+import { ComponentProps, useMemo, forwardRef, Fragment } from "react"
import dayjs, { Dayjs } from 'dayjs';
import doy from 'dayjs/plugin/dayOfYear.js';
import { VscDebugRestart } from "react-icons/vsc";
-import { PlayData, PlayInfoContainer } from '../PlayData.js';
import { GroupedVirtuoso } from 'react-virtuoso'
import { ActivityDetails } from '../ActivityDetail.js';
import { sortByNewestPlayDate } from '../../../core/PlayUtils.js';
@@ -19,7 +18,7 @@ dayjs.extend(doy);
export interface ActivityLogProps {
data: PlayActivity[]
sortBy?: 'played' | 'seen'
- virtual?: boolean
+ render?: 'virtCollapse' | 'virtAccordian' | 'accordian'
}
interface GroupInfo {
@@ -27,186 +26,333 @@ interface GroupInfo {
date: Dayjs
}
+interface GroupData {
+ plays: PlayActivity[]
+ date: Dayjs
+}
+
+const generateGroupInfo = (data: PlayActivity[]): GroupInfo[] => {
+
+ const groupsReduced = data.reduce((acc: { groups: GroupInfo[], active?: GroupInfo }, curr, index) => {
+ const date = dayjs(curr.play.data.playDate);
+ if (acc.active === undefined) {
+ return { ...acc, active: { count: 1, date } };
+ }
+ if (acc.active.date.dayOfYear() !== date.dayOfYear()) {
+ return { groups: [...acc.groups, acc.active], active: { count: 1, date } }
+ }
+
+ return { groups: acc.groups, active: { ...acc.active, count: acc.active.count + 1 } };
+ }, { groups: [] });
+
+ return groupsReduced.groups.concat(groupsReduced.active);
+}
+
+const generateGroupPlays = (data: PlayActivity[]): GroupData[] => {
+
+ const groupsReduced = data.reduce((acc: { groups: GroupData[], active?: GroupData }, curr, index) => {
+ const date = dayjs(curr.play.data.playDate);
+ if (acc.active === undefined) {
+ return { ...acc, active: { plays: [curr], date } };
+ }
+ if (acc.active.date.dayOfYear() !== date.dayOfYear()) {
+ return { groups: [...acc.groups, acc.active], active: { plays: [curr], date } }
+ }
+
+ return { groups: acc.groups, active: { ...acc.active, plays: acc.active.plays.concat(curr) } };
+ }, { groups: [] });
+
+ return groupsReduced.groups.concat(groupsReduced.active);
+}
+
export const PlayList = (props: ActivityLogProps) => {
const {
data = [],
sortBy = 'played',
- virtual = false,
+ render = 'accordian'
} = props;
- if(virtual) {
-
-
-
const sorted = useMemo(() => props.data.toSorted((a, b) => sortByNewestPlayDate(a.play, b.play)), [data, sortBy]);
- const groupsReduced = useMemo(() => {
- return sorted.reduce((acc: { groups: GroupInfo[], active?: GroupInfo }, curr, index) => {
- const date = dayjs(curr.play.data.playDate);
- if (acc.active === undefined) {
- return { ...acc, active: { count: 1, date } };
- }
- if (acc.active.date.dayOfYear() !== date.dayOfYear()) {
- return { groups: [...acc.groups, acc.active], active: { count: 1, date } }
- }
-
- return { groups: acc.groups, active: { ...acc.active, count: acc.active.count + 1 } };
- }, { groups: [] });
- }, [sorted, sortBy]);
-
- const allGroups = groupsReduced.groups.concat(groupsReduced.active);
+ if (render === 'accordian') {
+ return
+ }
+ if (render === 'virtCollapse') {
+ return
+ }
+ if (render === 'virtAccordian') {
+ return ;
+ }
+}
+
+const VirtualizedCollapse = (props: { data: PlayActivity[] }) => {
+ const {
+ data,
+ } = props;
+ const groups = useMemo(() => generateGroupInfo(data), [data]);
return (
- x.count)}
- groupContent={(index) => {
- const gData = allGroups[index];
- let headerText: string;
- if (gData.date.isToday()) {
- headerText = 'Today';
- } else {
- headerText = gData.date.format('MMM DD');
- if (gData.date.year() !== dayjs().year()) {
- headerText += `, ${gData.date.year()}`;
- }
+ {
+ // return {args.children}
}
+ // }}
+ groupCounts={groups.map(x => x.count)}
+ groupContent={(index) => {
+ const gData = groups[index];
+ let headerText: string;
+ if (gData.date.isToday()) {
+ headerText = 'Today';
+ } else {
+ headerText = gData.date.format('MMM DD');
+ if (gData.date.year() !== dayjs().year()) {
+ headerText += `, ${gData.date.year()}`;
}
- return (
-
-
+ }
+ return (
+
+
- {headerText}
+ {headerText}
+
+
+
+
+
+
+
+ )
+ }}
+ itemContent={(index, groupIndex) => {
+ const activity = data[index];
+ const { play } = activity;
+ return (
+
-
-
-
-
-
- )
- }}
- itemContent={(index, groupIndex) => {
- const activity = sorted[index];
- const { play } = activity;
- return (
-
-
-
+
+
+
-
-
-
-
- {play.data.track}
- {play.data.artists.join(' / ')}
-
-
- {play.meta?.source}
-
-
-
-
-
- {activity.status === 'error' ?
-
- : null}
+
+
+
+ {play.data.track}
+ {play.data.artists.join(' / ')}
+
+
+ {play.meta?.source}
+
+
+
+
+ {activity.status === 'error' ?
+
+ : null}
+
-
-
-
-
-
- )
- }}
- />
+
+
+
+
+
+ )
+ }}
+ />
);
+}
+
+const VirtualizedAccordian = (props: { data: PlayActivity[] }) => {
+ const {
+ data,
+ } = props;
+ const groups = useMemo(() => generateGroupInfo(data), [data]);
+ return (
+ {
+ // @ts-ignore
+ if (args.children.length === 1 && args.children[0].type.name === 'Group') {
+ // @ts-ignore
+ return {args.children}
+ }
+ // @ts-ignore
+ return {args.children}
+ }),
+ Group: (args) => {
+ return {args.children}
+ }
+ }}
+ groupCounts={groups.map(x => x.count)}
+ groupContent={(index) => {
+ const gData = groups[index];
+ let headerText: string;
+ if (gData.date.isToday()) {
+ headerText = 'Today';
+ } else {
+ headerText = gData.date.format('MMM DD');
+ if (gData.date.year() !== dayjs().year()) {
+ headerText += `, ${gData.date.year()}`;
+ }
+ }
+ return (
+
+
+
+ {headerText}
+
+
+
+
+
+
+ )
+ }}
+ itemContent={(index, groupIndex) => {
+ const activity = data[index];
+ const { play } = activity;
+ return (
+
+
+
+
+
+ {play.data.track}
+ {play.data.artists.join(' / ')}
+
+
+ {play.meta?.source}
+
+
+
+
+
+ {activity.status === 'error' ?
+
+ : null}
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+ );
}
+const PlainAccordian = (props: { data: PlayActivity[] }) => {
+ const { data = [] } = props;
+ const groups = generateGroupPlays(data);
return (
-
-
-
- Today
-
-
-
-
-
-
-
-
- {props.data.map((activity, index) => {
- const { play } = activity;
- return (
-
-
-
-
-
- {play.data.track}
- {play.data.artists.join(' / ')}
-
-
- {play.meta?.source}
-
-
-
-
-
- {activity.status === 'error' ?
-
- : null}
-
+ {groups.map((g) => {
+ let headerText: string;
+ if (g.date.isToday()) {
+ headerText = 'Today';
+ } else {
+ headerText = g.date.format('MMM DD');
+ if (g.date.year() !== dayjs().year()) {
+ headerText += `, ${g.date.year()}`;
+ }
+ }
+ return (
+
+
+
+ {headerText}
+
+
+
+
-
-
-
-
-
-
- )
- })}
-
+
+
+
+ {g.plays.map((activity, index) => {
+ const { play } = activity;
+ return (
+
+
+
+
+
+ {play.data.track}
+ {play.data.artists.join(' / ')}
+
+
+ {play.meta?.source}
+
+
+
+
+
+ {activity.status === 'error' ?
+
+ : null}
+
+
+
+
+
+
+
+
+
+ )
+ })}
+
+
+ )
+ })}
);
}
diff --git a/src/core/tests/utils/fixtures.ts b/src/core/tests/utils/fixtures.ts
index 679fe8a9..ca1e405c 100644
--- a/src/core/tests/utils/fixtures.ts
+++ b/src/core/tests/utils/fixtures.ts
@@ -296,7 +296,7 @@ export const generateLifecycleStep = (play: PlayObject, opts: GenerateLifecycleO
return;
}
somethingModified = true;
- if(ctx.key === 'brainz' && Object.keys(x).length === 0) {
+ if(ctx.key === 'brainz' && Object.keys(x ?? {}).length === 0) {
ctx.update(generateBrainz(play, {include: ['album', 'artist', 'track']}), true);
} else if (ctx.parent !== undefined && ctx.parent.key === 'brainz') {
if (Array.isArray(x)) {