diff --git a/package-lock.json b/package-lock.json index d4699f5f..dcc7d5f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,7 +174,7 @@ "react-icons": "^5.6.0", "react-redux": "^9.2.0", "react-router-dom": "^7.15.0", - "react-virtuoso": "^4.18.4", + "react-virtuoso": "^4.18.7", "sass": "^1.99.0", "shiki": "^4.0.2", "sinon": "^21.0.2", diff --git a/package.json b/package.json index b4ab3343..984a9754 100644 --- a/package.json +++ b/package.json @@ -213,7 +213,7 @@ "react-icons": "^5.6.0", "react-redux": "^9.2.0", "react-router-dom": "^7.15.0", - "react-virtuoso": "^4.18.4", + "react-virtuoso": "^4.18.7", "sass": "^1.99.0", "shiki": "^4.0.2", "sinon": "^21.0.2", @@ -251,4 +251,4 @@ "public" ] } -} \ No newline at end of file +} diff --git a/src/client/components/playActivity/PlayList.tsx b/src/client/components/playActivity/PlayList.tsx index 99a460f6..ffaf82c5 100644 --- a/src/client/components/playActivity/PlayList.tsx +++ b/src/client/components/playActivity/PlayList.tsx @@ -4,11 +4,11 @@ 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, forwardRef, Fragment } from "react" +import React, { 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 { GroupedVirtuoso } from 'react-virtuoso' +import { GroupedVirtuoso, Components, LogLevel } from 'react-virtuoso' import { ActivityDetailFetchable, ActivityDetails } from '../ActivityDetail.js'; import { sortByNewestPlayDate, sortByNewestSeenDate } from '../../../core/PlayUtils.js'; import "./PlayList.scss"; @@ -192,7 +192,7 @@ const VirtualizedCollapse = (props: { data: PlayApiCommon[] }) => { paddingBlock: "var(--chakra-spacing-4)", paddingInline: "var(--chakra-spacing-4)" }}> - + ) @@ -201,18 +201,7 @@ const VirtualizedCollapse = (props: { data: PlayApiCommon[] }) => { ); } -const VirtualizedAccordian = (props: { data: PlayApiCommon[] }) => { - const { - data, - } = props; - const groups = useMemo(() => generateGroupInfo(data), [data]); - return ( - { +const CustomList: Components['List'] = React.forwardRef((args, ref) => { // @ts-ignore if (args.children.length === 1 && args.children[0].type.name === 'Group') { // @ts-ignore @@ -220,14 +209,59 @@ const VirtualizedAccordian = (props: { data: PlayApiCommon[] }) => { } // @ts-ignore return {args.children} - }), - Group: (args) => { - return
{args.children}
- } - }} - groupCounts={groups.map(x => x.count)} - groupContent={(index) => { - const gData = groups[index]; +}); + +const CustomGroup: Components['Group'] = React.forwardRef((args) => { + return
{args.children}
; +}); + +const ItemComponent = React.memo((props: {index: number, activity}) => { + const {index, activity} = props; + const { play } = activity; + console.log(`render ${play.data.track}`); + return ( + + + + + + {play.data.track} + {play.data.artists.map(x => x.name).join(' / ')} + + + {play.meta?.source} + + + + + + {activity.state === 'failed' ? + + : null} + + + + + + + + + + ) +}); + +const VirtualizedAccordian = (props: { data: PlayApiCommon[] }) => { + const { + data, + } = props; + + const groups = useMemo(() => generateGroupInfo(data), [data]); + + const GroupComponent = (props: {index: number}) => { + const gData = groups[props.index]; let headerText: string; if (gData.date.isToday()) { headerText = 'Today'; @@ -238,7 +272,7 @@ const VirtualizedAccordian = (props: { data: PlayApiCommon[] }) => { } } return ( - + {headerText} @@ -247,46 +281,24 @@ const VirtualizedAccordian = (props: { data: PlayApiCommon[] }) => { - + - ) - }} - itemContent={(index, groupIndex) => { - const activity = data[index]; - const { play } = activity; - return ( - - - - - - {play.data.track} - {play.data.artists.map(x => x.name).join(' / ')} - - - {play.meta?.source} - - - - - - {activity.state === 'failed' ? - - : null} - + ); + } - - - - - - - - ) + return ( + x.count)} + groupContent={(index) => } + itemContent={(index) => } /> ); } diff --git a/src/stories/List.stories.tsx b/src/stories/List.stories.tsx index 7a0a15ba..a83cfe0c 100644 --- a/src/stories/List.stories.tsx +++ b/src/stories/List.stories.tsx @@ -58,56 +58,61 @@ let playData: PlayApiCommonDetailed[] = []; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args export const List = meta.story({ - parameters: { - msw: { - handlers: [ - http.get<{uid: string}>('/api/plays/:uid', async ({ params }) => { - const existing = playData.find(x => x.uid === params.uid); - if(existing !== undefined) { - return HttpResponse.json(existing); - } - return HttpResponse.json(generatePlayApiCommonDetailed()); - }), - ], - }, + args: { + render: "accordian" }, - loaders: [ - async () => { - const queued = normalizePlays(generateArray(7,() => generatePlayWithLifecycle()), {endDate: dayjs()}).map(x => { - const jsonPlay = asJsonPlayObject(x); - return generatePlayApiCommonDetailed({playOpts: [{state: 'queued', play: jsonPlay}], inputOpts: [{play: jsonPlay}]}) - }); - const scrobbledPlay = asJsonPlayObject(await playWithLifecycleScrobble(generatePlayWithLifecycle({lifecycleSteps: {preCompare: [true, 'skipped', true]}}))); - const scrobbledApi = generatePlayApiCommonDetailed({ - playOpts: [{ play: scrobbledPlay, state: 'scrobbled'}], - inputOpts: [{play: scrobbledPlay}] - }); + parameters: { + msw: { + handlers: [ + http.get<{uid: string}>('/api/plays/:uid', async ({ params }) => { + const existing = playData.find(x => x.uid === params.uid); + if(existing !== undefined) { + return HttpResponse.json(existing); + } + return HttpResponse.json(generatePlayApiCommonDetailed()); + }), + ], + }, +}, - const scrobbleErrorPlay = asJsonPlayObject(await playWithLifecycleScrobble(generatePlayWithLifecycle(), {error: true})); + //render: function Render(args) { return () } + loaders: [ + async () => { + const queued = normalizePlays(generateArray(7,() => generatePlayWithLifecycle()), {endDate: dayjs()}).map(x => { + const jsonPlay = asJsonPlayObject(x); + return generatePlayApiCommonDetailed({playOpts: [{state: 'queued', play: jsonPlay}], inputOpts: [{play: jsonPlay}]}) + }); - const scrobbleError = generatePlayApiCommonDetailed({ - playOpts: [{ play: scrobbleErrorPlay, state: 'failed'}], - inputOpts: [{play: scrobbleErrorPlay}] - }); + const scrobbledPlay = asJsonPlayObject(await playWithLifecycleScrobble(generatePlayWithLifecycle({lifecycleSteps: {preCompare: [true, 'skipped', true]}}))); + const scrobbledApi = generatePlayApiCommonDetailed({ + playOpts: [{ play: scrobbledPlay, state: 'scrobbled'}], + inputOpts: [{play: scrobbledPlay}] + }); - const promisedScrobbled = generateArray(10,() => playWithLifecycleScrobble(generatePlayWithLifecycle({lifecycleSteps: {preCompare: [true, 'skipped', true]}}))); - const promised = await Promise.all(promisedScrobbled); - const yesterdayScrobbled = normalizePlays(promised, {endDate: dayjs().subtract(1, 'd').subtract(100, 'm')}).map((x) => { - const jPlay = asJsonPlayObject(x); - return generatePlayApiCommonDetailed({ - playOpts: [{ play: jPlay, state: 'scrobbled'}], - inputOpts: [{play: jPlay}] - }); + const scrobbleErrorPlay = asJsonPlayObject(await playWithLifecycleScrobble(generatePlayWithLifecycle(), {error: true})); + + const scrobbleError = generatePlayApiCommonDetailed({ + playOpts: [{ play: scrobbleErrorPlay, state: 'failed'}], + inputOpts: [{play: scrobbleErrorPlay}] }); - playData = [ - ...queued, - scrobbledApi, - scrobbleError, - ...yesterdayScrobbled - ]; - return {data: playData}; - } - ], - //render: function Render(args) { return () } + + const promisedScrobbled = generateArray(10,() => playWithLifecycleScrobble(generatePlayWithLifecycle({lifecycleSteps: {preCompare: [true, 'skipped', true]}}))); + const promised = await Promise.all(promisedScrobbled); + const yesterdayScrobbled = normalizePlays(promised, {endDate: dayjs().subtract(1, 'd').subtract(100, 'm')}).map((x) => { + const jPlay = asJsonPlayObject(x); + return generatePlayApiCommonDetailed({ + playOpts: [{ play: jPlay, state: 'scrobbled'}], + inputOpts: [{play: jPlay}] + }); + }); + playData = [ + ...queued, + scrobbledApi, + scrobbleError, + ...yesterdayScrobbled + ]; + return {data: playData}; + } +] }); \ No newline at end of file