mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-09-03 05:10:00 +03:00
virtualized activity log
This commit is contained in:
Generated
+11
@@ -101,6 +101,7 @@
|
||||
"react-icons": "^5.6.0",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"react-virtuoso": "^4.17.0",
|
||||
"round-robin-js": "^3.0.10",
|
||||
"sass": "^1.93.3",
|
||||
"serialize-error": "^13.0.1",
|
||||
@@ -16973,6 +16974,16 @@
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16 || >=17 || >= 18 || >= 19",
|
||||
"react-dom": ">=16 || >=17 || >= 18 || >=19"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
"react-icons": "^5.6.0",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"react-virtuoso": "^4.17.0",
|
||||
"round-robin-js": "^3.0.10",
|
||||
"sass": "^1.93.3",
|
||||
"serialize-error": "^13.0.1",
|
||||
|
||||
@@ -4,20 +4,21 @@ import { useColorMode } from "./Color-Mode";
|
||||
import { ComponentProps, PropsWithChildren, useMemo } from "react";
|
||||
import { safeStringify } from "../../core/StringUtils";
|
||||
import { MarkOptional } from "ts-essentials";
|
||||
import { shikiAdapter } from "./shikiAdapter";
|
||||
|
||||
const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
|
||||
async load() {
|
||||
const { createHighlighter } = await import("shiki")
|
||||
return createHighlighter({
|
||||
langs: ["json", "plaintext"],
|
||||
themes: ["github-dark", "github-light"],
|
||||
})
|
||||
},
|
||||
theme: {
|
||||
light: "github-light",
|
||||
dark: "github-dark",
|
||||
},
|
||||
});
|
||||
// const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
|
||||
// async load() {
|
||||
// const { createHighlighter } = await import("shiki")
|
||||
// return createHighlighter({
|
||||
// langs: ["json", "plaintext"],
|
||||
// themes: ["github-dark", "github-light"],
|
||||
// })
|
||||
// },
|
||||
// theme: {
|
||||
// light: "github-light",
|
||||
// dark: "github-dark",
|
||||
// },
|
||||
// });
|
||||
|
||||
export interface ChakraCodeBaseProps {
|
||||
language?: string
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { Accordion, For, Span, Stack, Text, Box, AbsoluteCenter, Button, Separator, HStack, Flex, Badge, IconButton, Container } from '@chakra-ui/react';
|
||||
import { JsonPlayObject, PlayActivity } from '../../core/Atomic';
|
||||
import { ShortDateDisplay } from './DateDisplay';
|
||||
import { TextMuted } from './TextMuted';
|
||||
import { capitalize } from '../../core/StringUtils';
|
||||
import { ComponentProps } from "react"
|
||||
import { VscDebugRestart } from "react-icons/vsc";
|
||||
import { PlayData, PlayInfoContainer } from './PlayData';
|
||||
import { ActivityDetails } from './ActivityDetail';
|
||||
export interface ActivityLogProps {
|
||||
data: PlayActivity[]
|
||||
}
|
||||
|
||||
export const CList = (props: ActivityLogProps) => {
|
||||
return (
|
||||
<Stack gap="2">
|
||||
<Box>
|
||||
<Flex direction="row" justify="space-between">
|
||||
|
||||
<Text fontWeight="semibold">Today</Text>
|
||||
|
||||
<IconButton variant="ghost" size="xs" maxWidth="fit-content">
|
||||
<VscDebugRestart />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
<Separator orientation="horizontal" height="4" />
|
||||
</Box>
|
||||
<Accordion.Root variant="enclosed" collapsible multiple>
|
||||
{props.data.map((activity, index) => {
|
||||
const { play } = activity;
|
||||
return (
|
||||
<Accordion.Item key={index} value={index.toString()}>
|
||||
<Flex justify="space-between">
|
||||
<Accordion.ItemTrigger truncate cursor="pointer">
|
||||
<Accordion.ItemIndicator />
|
||||
<Stack gap="1" truncate>
|
||||
<Span>{play.data.track}</Span>
|
||||
<TextMuted truncate>{play.data.artists.join(' / ')}</TextMuted>
|
||||
<HStack gap="1">
|
||||
<ShortDateDisplay date={play.data.playDate} prefix="Played" /><Separator orientation="vertical" height="4" />
|
||||
<TextMuted>{play.meta?.source}</TextMuted>
|
||||
</HStack>
|
||||
</Stack>
|
||||
</Accordion.ItemTrigger>
|
||||
<Stack style={{
|
||||
paddingBlock: "var(--accordion-padding-y)",
|
||||
paddingInline: "var(--accordion-padding-x)"
|
||||
}} justify="flex-start" alignItems="flex-end">
|
||||
<StatusBadge maxWidth="fit-content" data={activity} />
|
||||
{activity.status === 'error' ? <IconButton variant="ghost" size="xs" maxWidth="fit-content">
|
||||
<VscDebugRestart />
|
||||
</IconButton> : null}
|
||||
</Stack>
|
||||
|
||||
</Flex>
|
||||
<Accordion.ItemContent>
|
||||
<Accordion.ItemBody borderTopColor="gray.border" >
|
||||
<ActivityDetails activity={activity}/>
|
||||
</Accordion.ItemBody>
|
||||
</Accordion.ItemContent>
|
||||
</Accordion.Item>
|
||||
)
|
||||
})}
|
||||
</Accordion.Root>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const StatusBadge = (props: ComponentProps<typeof Badge> & { data: PlayActivity }) => {
|
||||
|
||||
const { data, ...rest } = props;
|
||||
|
||||
let badgeColor = undefined,
|
||||
badgeText = capitalize(data.status);
|
||||
|
||||
switch (data.status) {
|
||||
case 'queued':
|
||||
badgeColor = 'gray';
|
||||
break;
|
||||
case 'scrobbled':
|
||||
badgeColor = 'green';
|
||||
break;
|
||||
case 'error':
|
||||
badgeColor = 'red';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Badge variant="surface" colorPalette={badgeColor} {...rest}>{badgeText}</Badge>
|
||||
}
|
||||
|
||||
export const ListContainer = (props?: ComponentProps<typeof CList>) => {
|
||||
return <Container maxWidth="3xl"><CList {...props}/></Container>
|
||||
}
|
||||
@@ -0,0 +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]:last-child div.chakra-collapsible__root {
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import { Accordion, For, Span, Stack, Text, Box, AbsoluteCenter, Button, Separator, HStack, Flex, Badge, IconButton, Container, Collapsible } from '@chakra-ui/react';
|
||||
import { JsonPlayObject, PlayActivity } from '../../../core/Atomic.js';
|
||||
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 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';
|
||||
import "./PlayList.scss";
|
||||
|
||||
dayjs.extend(doy);
|
||||
|
||||
export interface ActivityLogProps {
|
||||
data: PlayActivity[]
|
||||
sortBy?: 'played' | 'seen'
|
||||
virtual?: boolean
|
||||
}
|
||||
|
||||
interface GroupInfo {
|
||||
count: number
|
||||
date: Dayjs
|
||||
}
|
||||
|
||||
export const PlayList = (props: ActivityLogProps) => {
|
||||
|
||||
const {
|
||||
data = [],
|
||||
sortBy = 'played',
|
||||
virtual = false,
|
||||
} = 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);
|
||||
|
||||
return (
|
||||
<GroupedVirtuoso
|
||||
style={{ height: '700px' }}
|
||||
fixedItemHeight={80}
|
||||
fixedGroupHeight={50}
|
||||
groupCounts={allGroups.map(x => 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 (
|
||||
<Box paddingY="2">
|
||||
<Flex direction="row" justify="space-between">
|
||||
|
||||
<Text fontWeight="semibold">{headerText}</Text>
|
||||
|
||||
<IconButton variant="ghost" size="xs" maxWidth="fit-content">
|
||||
<VscDebugRestart />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
<Separator orientation="horizontal" height="4" />
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
itemContent={(index, groupIndex) => {
|
||||
const activity = sorted[index];
|
||||
const { play } = activity;
|
||||
return (
|
||||
<Collapsible.Root key={index}
|
||||
|
||||
lazyMount
|
||||
_open={{
|
||||
background: "var(--chakra-colors-bg-subtle)"
|
||||
}}
|
||||
style={{
|
||||
borderColor: "var(--chakra-colors-border)",
|
||||
borderWidth: '1px',
|
||||
}}
|
||||
>
|
||||
<Flex justify="space-between">
|
||||
<Collapsible.Trigger
|
||||
userSelect="text"
|
||||
|
||||
paddingY="3"
|
||||
display="flex"
|
||||
gap="2"
|
||||
alignItems="center"
|
||||
truncate cursor="pointer"
|
||||
style={{
|
||||
paddingBlock: "var(--chakra-spacing-2)",
|
||||
paddingInline: "var(--chakra-spacing-4)"
|
||||
}}
|
||||
>
|
||||
<Collapsible.Indicator
|
||||
transition="transform 0.2s"
|
||||
_open={{ transform: "rotate(90deg)" }}
|
||||
>
|
||||
<LuChevronRight />
|
||||
</Collapsible.Indicator>
|
||||
<Stack gap="1" truncate alignItems="flex-start">
|
||||
<Span>{play.data.track}</Span>
|
||||
<TextMuted truncate>{play.data.artists.join(' / ')}</TextMuted>
|
||||
<HStack gap="1">
|
||||
<ShortDateDisplay date={play.data.playDate} prefix="Played" /><Separator orientation="vertical" height="4" />
|
||||
<TextMuted>{play.meta?.source}</TextMuted>
|
||||
</HStack>
|
||||
</Stack>
|
||||
</Collapsible.Trigger>
|
||||
<Stack style={{
|
||||
paddingBlock: "var(--chakra-spacing-2)",
|
||||
paddingInline: "var(--chakra-spacing-4)"
|
||||
}} justify="flex-start" alignItems="flex-end">
|
||||
<StatusBadge maxWidth="fit-content" data={activity} />
|
||||
{activity.status === 'error' ? <IconButton variant="ghost" size="xs" maxWidth="fit-content">
|
||||
<VscDebugRestart />
|
||||
</IconButton> : null}
|
||||
</Stack>
|
||||
|
||||
</Flex>
|
||||
<Collapsible.Content borderTopColor="gray.border"
|
||||
style={{
|
||||
paddingBlock: "var(--chakra-spacing-4)",
|
||||
paddingInline: "var(--chakra-spacing-4)"
|
||||
}}>
|
||||
<ActivityDetails activity={activity} />
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap="2">
|
||||
<Box>
|
||||
<Flex direction="row" justify="space-between">
|
||||
|
||||
<Text fontWeight="semibold">Today</Text>
|
||||
|
||||
<IconButton variant="ghost" size="xs" maxWidth="fit-content">
|
||||
<VscDebugRestart />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
<Separator orientation="horizontal" height="4" />
|
||||
</Box>
|
||||
<Accordion.Root variant="enclosed" collapsible multiple>
|
||||
{props.data.map((activity, index) => {
|
||||
const { play } = activity;
|
||||
return (
|
||||
<Accordion.Item key={index} value={index.toString()}>
|
||||
<Flex justify="space-between">
|
||||
<Accordion.ItemTrigger truncate cursor="pointer">
|
||||
<Accordion.ItemIndicator />
|
||||
<Stack gap="1" truncate>
|
||||
<Span>{play.data.track}</Span>
|
||||
<TextMuted truncate>{play.data.artists.join(' / ')}</TextMuted>
|
||||
<HStack gap="1">
|
||||
<ShortDateDisplay date={play.data.playDate} prefix="Played" /><Separator orientation="vertical" height="4" />
|
||||
<TextMuted>{play.meta?.source}</TextMuted>
|
||||
</HStack>
|
||||
</Stack>
|
||||
</Accordion.ItemTrigger>
|
||||
<Stack style={{
|
||||
paddingBlock: "var(--accordion-padding-y)",
|
||||
paddingInline: "var(--accordion-padding-x)"
|
||||
}} justify="flex-start" alignItems="flex-end">
|
||||
<StatusBadge maxWidth="fit-content" data={activity} />
|
||||
{activity.status === 'error' ? <IconButton variant="ghost" size="xs" maxWidth="fit-content">
|
||||
<VscDebugRestart />
|
||||
</IconButton> : null}
|
||||
</Stack>
|
||||
|
||||
</Flex>
|
||||
<Accordion.ItemContent>
|
||||
<Accordion.ItemBody borderTopColor="gray.border" >
|
||||
<ActivityDetails activity={activity}/>
|
||||
</Accordion.ItemBody>
|
||||
</Accordion.ItemContent>
|
||||
</Accordion.Item>
|
||||
)
|
||||
})}
|
||||
</Accordion.Root>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const StatusBadge = (props: ComponentProps<typeof Badge> & { data: PlayActivity }) => {
|
||||
|
||||
const { data, ...rest } = props;
|
||||
|
||||
let badgeColor = undefined,
|
||||
badgeText = capitalize(data.status);
|
||||
|
||||
switch (data.status) {
|
||||
case 'queued':
|
||||
badgeColor = 'gray';
|
||||
break;
|
||||
case 'scrobbled':
|
||||
badgeColor = 'green';
|
||||
break;
|
||||
case 'error':
|
||||
badgeColor = 'red';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Badge variant="surface" colorPalette={badgeColor} {...rest}>{badgeText}</Badge>
|
||||
}
|
||||
|
||||
export const ListContainer = (props?: ComponentProps<typeof PlayList>) => {
|
||||
return <Container maxWidth="3xl"><PlayList {...props} /></Container>
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createShikiAdapter } from "@chakra-ui/react"
|
||||
import type { HighlighterGeneric } from "shiki"
|
||||
|
||||
export const shikiAdapter = createShikiAdapter<HighlighterGeneric<any, any>>({
|
||||
async load() {
|
||||
const { createHighlighter } = await import("shiki")
|
||||
return createHighlighter({
|
||||
langs: ["json", "plaintext"],
|
||||
themes: ["github-dark", "github-light"],
|
||||
})
|
||||
},
|
||||
theme: {
|
||||
light: "github-light",
|
||||
dark: "github-dark",
|
||||
},
|
||||
});
|
||||
@@ -75,6 +75,13 @@ export const normalizePlays = (plays: PlayObject[],
|
||||
...defaultMeta
|
||||
}
|
||||
|
||||
if(cleanPlay.meta.lifecycle?.original?.data?.playDate !== undefined) {
|
||||
cleanPlay.meta.lifecycle.original.data.playDate = lastDate;
|
||||
}
|
||||
if(cleanPlay.meta.lifecycle?.original?.data?.playDateCompleted !== undefined) {
|
||||
cleanPlay.meta.lifecycle.original.data.playDate = lastDate.add(cleanPlay.data.listenedFor ?? cleanPlay.data.duration, 's');
|
||||
}
|
||||
|
||||
if(index + 1 <= plays.length - 1) {
|
||||
const listenTime = (plays[index+1].data.duration ?? defaultDuration) + faker.number.int({min: 0, max: 2});
|
||||
lastDate = cleanPlay.data.playDate.subtract(listenTime, 'seconds');
|
||||
@@ -111,6 +118,13 @@ export const normalizePlays = (plays: PlayObject[],
|
||||
...defaultMeta
|
||||
}
|
||||
|
||||
if(cleanPlay.meta.lifecycle?.original?.data?.playDate !== undefined) {
|
||||
cleanPlay.meta.lifecycle.original.data.playDate = lastDate;
|
||||
}
|
||||
if(cleanPlay.meta.lifecycle?.original?.data?.playDateCompleted !== undefined) {
|
||||
cleanPlay.meta.lifecycle.original.data.playDate = lastDate.add(cleanPlay.data.listenedFor ?? cleanPlay.data.duration, 's');
|
||||
}
|
||||
|
||||
if(progressDirection === 'newer') {
|
||||
const listenTime = (cleanPlay.data.duration ?? defaultDuration) + faker.number.int({min: 0, max: 2});
|
||||
lastDate = cleanPlay.data.playDate.add(listenTime, 'seconds');
|
||||
|
||||
+11
-4
@@ -1,9 +1,10 @@
|
||||
import { PlayPlatformId } from "../backend/common/infrastructure/Atomic.js";
|
||||
import { PlayObject, PlayObjectLifecycleless } from "./Atomic.js";
|
||||
import { AmbPlayObject, DateLike, PlayObject, PlayObjectLifecycleless } from "./Atomic.js";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
|
||||
/** sorts playObj formatted objects by playDate in descending (newest first) order */
|
||||
export const sortByNewestPlayDate = (a: PlayObject, b: PlayObject) => {
|
||||
export const sortByNewestPlayDate = (a: AmbPlayObject<DateLike>, b: AmbPlayObject<DateLike>) => {
|
||||
const {
|
||||
data: {
|
||||
playDate: aPlayDate
|
||||
@@ -23,10 +24,16 @@ export const sortByNewestPlayDate = (a: PlayObject, b: PlayObject) => {
|
||||
if (bPlayDate === undefined) {
|
||||
return -1;
|
||||
}
|
||||
return aPlayDate.isBefore(bPlayDate) ? 1 : -1;
|
||||
};export const genGroupIdStr = (id: PlayPlatformId) => {
|
||||
|
||||
const realA = typeof aPlayDate === 'string' ? dayjs(aPlayDate) : aPlayDate;
|
||||
const realB = typeof bPlayDate === 'string' ? dayjs(bPlayDate) : bPlayDate;
|
||||
return realA.isBefore(realB) ? 1 : -1;
|
||||
};
|
||||
|
||||
export const genGroupIdStr = (id: PlayPlatformId) => {
|
||||
return `${id[0]}-${id[1]}`;
|
||||
};
|
||||
|
||||
export const lifecyclelessInvariantTransform = (play: PlayObject): PlayObjectLifecycleless => {
|
||||
const {
|
||||
meta: {
|
||||
|
||||
@@ -3,12 +3,14 @@ import React from 'react';
|
||||
|
||||
import { fn } from 'storybook/test';
|
||||
import { Container } from '@chakra-ui/react';
|
||||
import { CList } from "../client/components/List";
|
||||
import { PlayList } from "../client/components/playActivity/PlayList.js";
|
||||
import {Provider} from "../client/components/Provider";
|
||||
import { generateJsonPlays } from "../core/PlayTestUtils.js";
|
||||
import { generateJsonPlays, normalizePlays } from "../core/PlayTestUtils.js";
|
||||
import { ErrorLike, JsonPlayObject } from "../core/Atomic.js";
|
||||
import {examplePlay, lastfmErrorExample} from './storyUtils.js';
|
||||
import {playWithLifecycleScrobble, generatePlayWithLifecycle} from '../core/tests/utils/fixtures'
|
||||
import { generateArray } from "../core/DataUtils.js";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const stack = "Scrobble Submit Error: Failed to submit to Listenbrainz (listen_type single)\n at ListenbrainzApiClient.submitListen (/app/src/backend/common/vendor/ListenbrainzApiClient.ts:246:19)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async ListenbrainzScrobbler.doScrobble (/app/src/backend/scrobblers/ListenbrainzScrobbler.ts:87:28)\n at async ListenbrainzScrobbler.scrobble (/app/src/backend/scrobblers/AbstractScrobbleClient.ts:679:28)\n at async ListenbrainzScrobbler.processDeadLetterScrobble (/app/src/backend/scrobblers/AbstractScrobbleClient.ts:920:39)\n at async ListenbrainzScrobbler.processDeadLetterQueue (/app/src/backend/scrobblers/AbstractScrobbleClient.ts:894:43)\n at async PromisePoolExecutor.handler (/app/src/backend/tasks/heartbeatClients.ts:35:21)\n at async PromisePoolExecutor.waitForActiveTaskToFinish (/app/node_modules/@supercharge/promise-pool/dist/promise-pool-executor.js:375:9)\n at async PromisePoolExecutor.waitForProcessingSlot (/app/node_modules/@supercharge/promise-pool/dist/promise-pool-executor.js:368:13)\n at async PromisePoolExecutor.process (/app/node_modules/@supercharge/promise-pool/dist/promise-pool-executor.js:354:13)";
|
||||
|
||||
@@ -30,7 +32,7 @@ const errorExample: ErrorLike = {
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta = preview.meta({
|
||||
title: 'Examples/ActivityLog',
|
||||
component: CList,
|
||||
component: PlayList,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'padded',
|
||||
@@ -40,12 +42,9 @@ const meta = preview.meta({
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
args: {
|
||||
data:[
|
||||
// ...generateJsonPlays(2).map((x) => ({play: x, status: 'queued'})),
|
||||
// {play: examplePlay(), status: 'scrobbled'},
|
||||
// {play: lastfmErrorExample(), status: 'error'}
|
||||
] ,
|
||||
},
|
||||
render: function Render(args, { loaded: { data } }) { return (<CList {...args} data={data ?? []}/>) },
|
||||
render: function Render(args, { loaded: { data } }) { return (<PlayList {...args} data={data ?? []}/>) },
|
||||
decorators: [
|
||||
(Story) => (<Provider><Container maxWidth="4xl"><Story/></Container></Provider>),
|
||||
]
|
||||
@@ -56,13 +55,19 @@ decorators: [
|
||||
export const List = meta.story({
|
||||
loaders: [
|
||||
async () => {
|
||||
const queued = await generatePlayWithLifecycle();
|
||||
const queued = normalizePlays(generateArray(7,() => generatePlayWithLifecycle()), {endDate: dayjs()}).map(x => ({play: x, status: 'queued'}));
|
||||
|
||||
const scrobbled = await playWithLifecycleScrobble(generatePlayWithLifecycle({lifecycleSteps: {preCompare: [true, 'skipped', true]}}));
|
||||
const scrobbleError = await playWithLifecycleScrobble(generatePlayWithLifecycle(), {error: true});
|
||||
|
||||
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) => ({play: x, status: 'scrobbled'}));
|
||||
return {data: [
|
||||
{play: queued, status: 'queued'},
|
||||
...queued,
|
||||
{play: scrobbled, status: 'scrobbled'},
|
||||
{play: scrobbleError, status: 'error'}
|
||||
{play: scrobbleError, status: 'error'},
|
||||
...yesterdayScrobbled
|
||||
]};
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user