diff --git a/package-lock.json b/package-lock.json index 2648c858..05622870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,6 +75,7 @@ "jest-diff": "^30.2.0", "json5": "^2.2.3", "jsondiffpatch": "^0.7.3", + "jsondiffpatch-react": "^1.0.8", "keyv": "^5.5.0", "kodi-api": "^0.2.1", "lastfm-ts-api": "^2.6.0", @@ -96,6 +97,7 @@ "prom-client": "^15.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^6.1.1", "react-icons": "^5.6.0", "react-redux": "^8.1.2", "react-router-dom": "^6.15.0", @@ -10463,6 +10465,12 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -13739,6 +13747,107 @@ "node": "^18.0.0 || >=20.0.0" } }, + "node_modules/jsondiffpatch-react": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/jsondiffpatch-react/-/jsondiffpatch-react-1.0.8.tgz", + "integrity": "sha512-dbipCQLrCsIN3w1+F+AtIaU9CmcTzfeHQ74QqUW0qZHITpi6F2P5580TB2u1A943Wyod8+09NSxxDjy5gFnXzw==", + "license": "MIT", + "dependencies": { + "jsondiffpatch": "^0.4.1", + "yarn.lock": "^0.0.1-security" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "node_modules/jsondiffpatch-react/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsondiffpatch-react/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsondiffpatch-react/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/jsondiffpatch-react/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/jsondiffpatch-react/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/jsondiffpatch-react/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jsondiffpatch-react/node_modules/jsondiffpatch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz", + "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==", + "license": "MIT", + "dependencies": { + "chalk": "^2.3.0", + "diff-match-patch": "^1.0.0" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/jsondiffpatch-react/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -16747,6 +16856,15 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.1.tgz", + "integrity": "sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, "node_modules/react-icons": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", @@ -21283,6 +21401,11 @@ "node": ">=8" } }, + "node_modules/yarn.lock": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/yarn.lock/-/yarn.lock-0.0.1-security.tgz", + "integrity": "sha512-ZRX6v5zGCJMI1T2aO+BQxJggy1vvorXEwonQhWXIC+brO7lkDB3zWelVNAti183ddH6FmJP8z4UDCJnJlioK4Q==" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2980cfa7..1d7a3eb2 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "jest-diff": "^30.2.0", "json5": "^2.2.3", "jsondiffpatch": "^0.7.3", + "jsondiffpatch-react": "^1.0.8", "keyv": "^5.5.0", "kodi-api": "^0.2.1", "lastfm-ts-api": "^2.6.0", @@ -134,6 +135,7 @@ "prom-client": "^15.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^6.1.1", "react-icons": "^5.6.0", "react-redux": "^8.1.2", "react-router-dom": "^6.15.0", diff --git a/src/backend/common/AbstractComponent.ts b/src/backend/common/AbstractComponent.ts index 3e98499c..f7df5ae1 100644 --- a/src/backend/common/AbstractComponent.ts +++ b/src/backend/common/AbstractComponent.ts @@ -27,7 +27,7 @@ import { findCauseByReference } from "../utils/ErrorUtils.js"; import { hashObject } from "../utils/StringUtils.js"; import { metaInvariantTransform, playContentInvariantTransform } from "../utils/PlayComparisonUtils.js"; import { MSCache } from "./Cache.js"; -import { jdiff } from "../utils/DataUtils.js"; +import { jdiff } from "../../core/DataUtils.js"; import ConsoleFormatter from "jsondiffpatch/formatters/console"; const console = new ConsoleFormatter(); diff --git a/src/backend/utils/DataUtils.ts b/src/backend/utils/DataUtils.ts index fbc0faef..0e8cc66c 100644 --- a/src/backend/utils/DataUtils.ts +++ b/src/backend/utils/DataUtils.ts @@ -1,14 +1,6 @@ import JSON5 from "json5"; import { constants, promises } from "fs"; import { MaybeLogger } from "../common/logging.js"; -import { create as diffCreate } from 'jsondiffpatch'; - -export const jdiff = diffCreate({ - propertyFilter(name, context) { - return name !== 'lifecycle'; - }, - //omitRemovedValues: true -}); export const asArray = (data: T | T[]): T[] => { if (Array.isArray(data)) { diff --git a/src/client/components/ActivityTimeline.tsx b/src/client/components/ActivityTimeline.tsx new file mode 100644 index 00000000..608daef3 --- /dev/null +++ b/src/client/components/ActivityTimeline.tsx @@ -0,0 +1,211 @@ +import { ComponentProps } from "react" +import { Accordion, Timeline, Icon, Span, Stack, Heading, Card, Box } from '@chakra-ui/react'; +import { ErrorLike, JsonPlayObject, LifecycleStep, PlayActivity } from "../../core/Atomic"; +import { PlayData } from "./PlayData"; +import { ErrorAlert } from "./ErrorAlert"; +import { IoMdCodeDownload } from "react-icons/io"; +import { BiWrench } from "react-icons/bi"; +import { IoMusicalNoteOutline } from "react-icons/io5"; +import { MdFiberNew } from "react-icons/md"; +import { capitalize } from "../../core/StringUtils"; +import { shortTodayAwareFormat, todayAwareFormat } from "../../core/TimeUtils"; +import dayjs from "dayjs"; +import { ChakraCodeBlockShort, ChakraPlainBlockShort } from "./CodeBlock"; +import { JsonDiffPatch } from "./JsonDiff"; +import { jdiff } from "../../core/DataUtils"; + + +export interface ActivityDetailProps { + play: JsonPlayObject +} + +export const ActivityTimeline = (props: ActivityDetailProps) => { + const { + play + } = props; + const { + data: { + playDate + }, + meta: { + source, + lifecycle: { + input, + original, + steps = [] + }, + } = {} + } = play; + return ( + + + + + + + + + + + + + Discovered new activity from + {capitalize(source)} + at {shortTodayAwareFormat(dayjs(playDate))} + + + + + + + + + + + + + + + + + Created new Play + + + + + {steps.length > 0 ? ( + + + + + + + + + + + + Transforms + + + {/* + + + + */} + + + ) : null} + {/* + + + + + + + + + + + Created new Play + + test + + */} + + + {/* + + + + + + + + + + + + + */} + + ) +} + +export interface LifeycleStepsTimelineProps { + steps: LifecycleStep[] + original: JsonPlayObject +} + +const TransformSteps = (props: LifeycleStepsTimelineProps) => { + const { + steps, + original + } = props; + + let currentPlay: JsonPlayObject | false = JSON.parse(JSON.stringify(original)); + + return ( + + {steps.map((x, index) => { + let err: Error; + const left = JSON.parse(JSON.stringify(currentPlay)); + if (currentPlay !== false) { + try { + currentPlay = jdiff.patch(currentPlay, x.patch) as JsonPlayObject; + } catch (e) { + err = new Error('Could not patch Play object', { cause: e }); + currentPlay = false; + } + } + return + + + + + + + + + + + {x.name} with {x.source} + + Diff + {err !== undefined ? : null} + + {left !== false && currentPlay !== false ? ( + + + + ) : } + Inputs + + {x.inputs.map((y) => { + return + })} + + + + })} + {currentPlay !== false ? ( + + + + + + + + + + + + Final Playafter all Transforms + + + + + ) : null} + + ) +} \ No newline at end of file diff --git a/src/client/components/CodeBlock.tsx b/src/client/components/CodeBlock.tsx index 9aa00acd..37c20b8a 100644 --- a/src/client/components/CodeBlock.tsx +++ b/src/client/components/CodeBlock.tsx @@ -1,7 +1,8 @@ import type { HighlighterGeneric } from "shiki" import { createShikiAdapter, CodeBlock, IconButton, ClientOnly, ScrollArea } from "@chakra-ui/react" import { useColorMode } from "./Color-Mode"; -import { ComponentProps } from "react"; +import { ComponentProps, PropsWithChildren, useMemo } from "react"; +import { safeStringify } from "../../core/StringUtils"; const shikiAdapter = createShikiAdapter>({ async load() { @@ -17,8 +18,7 @@ const shikiAdapter = createShikiAdapter>({ }, }); -export type ChakraCodeBlockProps = Omit, 'children'> & { - code: string +export interface ChakraCodeBaseProps { language?: string title?: string maxHeight?: string @@ -26,27 +26,45 @@ export type ChakraCodeBlockProps = Omit, ' collapsedMaxHeight?: string } +export type ChakraCodelessBlock = Omit, 'code'> + +export type ChakraCodeBlockProps = Omit & ChakraCodeBaseProps & { + code: string | object +} + +const DEFAULT_MAX_HEIGHT = '70vh', + DEFAULT_COLLAPSED_MAX_HEIGHT = '320px', + DEFAULT_LANGUAGE = 'json'; + export const ChakraCodeBlock = (props: ChakraCodeBlockProps) => { const { - maxHeight = '70vh', + maxHeight = DEFAULT_MAX_HEIGHT, maxLines, - collapsedMaxHeight = '320px', - language = 'json', + collapsedMaxHeight = DEFAULT_COLLAPSED_MAX_HEIGHT, + language = DEFAULT_LANGUAGE, + code, ...rest } = props; - const contentProps: ComponentProps = maxLines === undefined ? { maxHeight, overflowY: 'auto' } : { css: {"--code-block-max-height": collapsedMaxHeight}}; + const contentProps: ComponentProps = maxLines === undefined ? { maxHeight, overflowY: 'auto' } : { css: { "--code-block-max-height": collapsedMaxHeight } }; + + const codeVal = useMemo(() => { + if (typeof code === 'string') { + return code; + } + return safeStringify(code); + }, [code]); return ( Loading...}> {() => ( - {props.title ?? ' '} @@ -67,16 +85,74 @@ export const ChakraCodeBlock = (props: ChakraCodeBlockProps) => { - + - - - - + + + + )} ) -} \ No newline at end of file +} + +export const ChakraCodeBlockShort = (props: ChakraCodeBlockProps) => ; + +export type ChakraPlainBlockProps = ChakraCodelessBlock & ChakraCodeBaseProps & {code?: string | object}; + +export const ChakraPlainBlock = (props: ChakraPlainBlockProps) => { + const { + maxHeight = DEFAULT_MAX_HEIGHT, + maxLines, + collapsedMaxHeight = DEFAULT_COLLAPSED_MAX_HEIGHT, + code, + ...rest + } = props; + + const contentProps: ComponentProps = maxLines === undefined ? { maxHeight, overflowY: 'auto' } : { css: { "--code-block-max-height": collapsedMaxHeight } }; + + const codeVal = useMemo(() => { + if(code === undefined) { + return ' '; + } + if (typeof code === 'string') { + return code; + } + return safeStringify(code); + }, [code]); + + return ( + + + {props.title ?? ' '} + + + + + + + + + + + {props.children} + + + + + + + + + ); +} + +export const ChakraPlainBlockShort = (props: ChakraPlainBlockProps) => ; \ No newline at end of file diff --git a/src/client/components/ErrorBoundary.tsx b/src/client/components/ErrorBoundary.tsx new file mode 100644 index 00000000..4f59aacd --- /dev/null +++ b/src/client/components/ErrorBoundary.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { Alert } from '@chakra-ui/react'; +import { ErrorBoundary, getErrorMessage } from "react-error-boundary"; +import { ChakraCodeBlock } from './CodeBlock'; + +export const MSErrorBoundary = (props: React.PropsWithChildren) => { + return ( + ( + + + + Error while render + + + + + + )} + > + {props.children} + + + ) +} + diff --git a/src/client/components/JsonDiff.css b/src/client/components/JsonDiff.css new file mode 100644 index 00000000..8f8ac6e4 --- /dev/null +++ b/src/client/components/JsonDiff.css @@ -0,0 +1,17 @@ +.jsondiffpatch-added .jsondiffpatch-property-name, +.jsondiffpatch-added .jsondiffpatch-value pre, +.jsondiffpatch-modified .jsondiffpatch-right-value pre, +.jsondiffpatch-textdiff-added { + background: #0e2e1b; +} + +.jsondiffpatch-deleted .jsondiffpatch-property-name, +.jsondiffpatch-deleted pre, +.jsondiffpatch-modified .jsondiffpatch-left-value pre, +.jsondiffpatch-textdiff-deleted { + background: #371516 ; +} + +/* .jsondiffpatch-delta, .jsondiffpatch-delta ul { + padding: 0 0 0 0; +} */ \ No newline at end of file diff --git a/src/client/components/JsonDiff.tsx b/src/client/components/JsonDiff.tsx new file mode 100644 index 00000000..e893d332 --- /dev/null +++ b/src/client/components/JsonDiff.tsx @@ -0,0 +1,41 @@ +import React, { ComponentProps } from 'react'; +import JsonDiffReact from 'jsondiffpatch-react'; +import { MSErrorBoundary } from './ErrorBoundary'; +import { Delta } from 'jsondiffpatch'; +import { MarkOptional } from 'ts-essentials'; +import { jdiff } from '../../core/DataUtils'; +import './JsonDiff.css'; + +type DiffableVal = [] | object | string; + +export interface JsonDiffReactProps { + right: DiffableVal, + left: DiffableVal + show?: boolean, + annotated?: boolean, + tips?: any, + objectHash?: CallableFunction, +} + +export type JsonDiffPatchProps = MarkOptional & { + diff?: Delta | object +} + +export const JsonDiffPatch = (props: JsonDiffPatchProps) => { + const { + right, + diff, + ...rest + } = props; + let realRight: DiffableVal; + if (right !== undefined) { + realRight = right; + } else if (diff !== undefined) { + realRight = jdiff.patch(props.left, diff as Delta) as DiffableVal; + } else { + throw new Error(`must provide either 'right' or 'diff'`); + } + return + + +}; \ No newline at end of file diff --git a/src/client/components/PlayData.tsx b/src/client/components/PlayData.tsx index c7aede08..e926d560 100644 --- a/src/client/components/PlayData.tsx +++ b/src/client/components/PlayData.tsx @@ -26,7 +26,9 @@ export interface PlayInfoProps { final?: JsonPlayObject showCodeToggle?: boolean showCompare?: boolean - showDates?: false | 'all' | 'played' | 'seen' + compareDefault?: 'Initial' | 'Final' + datesFooter?: false | 'all' | 'played' | 'seen' + dates?: false | 'all' | 'played' | 'seen' } export const PlayData = (props?: PlayInfoProps) => { @@ -35,26 +37,28 @@ export const PlayData = (props?: PlayInfoProps) => { final, showCodeToggle = true, showCompare = true, - showDates = false + compareDefault = 'Initial', + datesFooter = false, + dates = 'all' } = props ?? {}; if (play === undefined) { return } - const [compareVal, setCompareVal] = useState('Original') + const [compareVal, setCompareVal] = useState(compareDefault) const [codeMode, setCodeMode] = useState(false); - const displayedPlay = compareVal === 'Original' ? play : final; + const displayedPlay = compareVal === 'Initial' ? play : final; let comparer: JSX.Element | null = null, copy: JSX.Element | null = null; if (showCompare && final !== undefined) { comparer = ( - setCompareVal(e.value)}> + setCompareVal(e.value as 'Initial' | 'Final')}> - + ); } @@ -66,16 +70,16 @@ export const PlayData = (props?: PlayInfoProps) => { ); } - const datesFooter = useMemo(() => { + const datesFooterContent = useMemo(() => { let dateElm: JSX.Element; - if (showDates !== false) { + if (datesFooter !== false) { let playDate: JSX.Element, seenDate: JSX.Element; - if (play.data.playDate !== undefined && ['all', 'played'].includes(showDates)) { + if (play.data.playDate !== undefined && ['all', 'played'].includes(datesFooter)) { playDate = {`Played ${shortTodayAwareFormat(dayjs(play.data.playDate))}`} } // TODO implement seenAt for play data - if (play.data.playDateCompleted !== undefined && ['all', 'seen'].includes(showDates)) { + if (play.data.playDateCompleted !== undefined && ['all', 'seen'].includes(datesFooter)) { seenDate = {`Seen ${shortTodayAwareFormat(dayjs(play.data.playDateCompleted))}`} } if (playDate !== undefined && seenDate !== undefined) { @@ -87,7 +91,7 @@ export const PlayData = (props?: PlayInfoProps) => { } } return dateElm; - }, [play, showDates]); + }, [play, datesFooter]); let albumArtistElm: JSX.Element; @@ -114,21 +118,31 @@ export const PlayData = (props?: PlayInfoProps) => { } = {} } = displayedPlay; - const datesItem = ( - - Dates - - - - {`Played ${shortTodayAwareFormat(dayjs(play.data.playDate))}`} - - {play.data.playDate !== undefined ? {`Played Until ${shortTodayAwareFormat(dayjs(play.data.playDate))}`} : null } - {/* TODO seenAt */} - {play.data.playDate !== undefined ? {`Seen ${shortTodayAwareFormat(dayjs(play.data.playDate))}`} : null } - - - - ) + let datesItem: JSX.Element | null; + if(dates === false) { + datesItem = null; + } else { + const dateElements = []; + if(dates.includes('played') || dates.includes('all')) { + dateElements.push(({`Played ${shortTodayAwareFormat(dayjs(play.data.playDate))}`})); + if(play.data.playDateCompleted !== undefined) { + dateElements.push(({`Played Until ${shortTodayAwareFormat(dayjs(play.data.playDateCompleted))}`})); + } + } + if(dates.includes('seen') || dates.includes('all')) { + dateElements.push(({`Seen ${shortTodayAwareFormat(dayjs(play.data.playDate))}`})); + } + datesItem = ( + + Dates + + + {dateElements} + + + + ) + } return ( @@ -170,7 +184,7 @@ export const PlayData = (props?: PlayInfoProps) => { {datesItem} - {datesFooter} + {datesFooterContent} ) } diff --git a/src/core/DataUtils.ts b/src/core/DataUtils.ts new file mode 100644 index 00000000..1c4b0bbe --- /dev/null +++ b/src/core/DataUtils.ts @@ -0,0 +1,10 @@ +import { create as diffCreate } from "jsondiffpatch"; + + +export const jdiff = diffCreate({ + propertyFilter(name, context) { + return name !== 'lifecycle'; + }, + cloneDiffValues: true + //omitRemovedValues: true +}); diff --git a/src/stories/ActivityTimeline.stories.tsx b/src/stories/ActivityTimeline.stories.tsx new file mode 100644 index 00000000..b890f50a --- /dev/null +++ b/src/stories/ActivityTimeline.stories.tsx @@ -0,0 +1,1131 @@ +import preview from "../../.storybook/preview.js"; +import React from 'react'; + +import { fn } from 'storybook/test'; +import { Container } from '@chakra-ui/react'; +import { ActivityTimeline } from "../client/components/ActivityTimeline"; +import {Provider} from "../client/components/Provider"; +import { generateJsonPlays } from "../backend/tests/utils/PlayTestUtils.js"; +import { ErrorLike, JsonPlayObject, PlayLifecycle } from "../core/Atomic.js"; + +const exampleLifecycle = (): PlayLifecycle => ({ + "input": { + "device": { + "id": "3cc6dc47a8551498a70e519ee09cd8423ff9d48c", + "is_active": true, + "is_private_session": false, + "is_restricted": false, + "name": "Pixel 9a", + "supports_volume": false, + "type": "Smartphone", + "volume_percent": 100 + }, + "shuffle_state": true, + "smart_shuffle": false, + "repeat_state": "context", + "is_playing": true, + "timestamp": 1773588429309, + "context": { + "external_urls": { + "spotify": "https://open.spotify.com/collection/tracks" + }, + "href": "https://api.spotify.com/v1/me/tracks", + "type": "collection", + "uri": "spotify:user:socrates13:collection" + }, + "progress_ms": 5455, + "item": { + "album": { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4V8LLVI7PbaPR0K2TGSxFF" + }, + "href": "https://api.spotify.com/v1/artists/4V8LLVI7PbaPR0K2TGSxFF", + "id": "4V8LLVI7PbaPR0K2TGSxFF", + "name": "Tyler, The Creator", + "type": "artist", + "uri": "spotify:artist:4V8LLVI7PbaPR0K2TGSxFF" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/2nkto6YNI4rUYTLqEwWJ3o" + }, + "href": "https://api.spotify.com/v1/albums/2nkto6YNI4rUYTLqEwWJ3o", + "id": "2nkto6YNI4rUYTLqEwWJ3o", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2738940ac99f49e44f59e6f7fb3", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e028940ac99f49e44f59e6f7fb3", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d000048518940ac99f49e44f59e6f7fb3", + "width": 64 + } + ], + "name": "Flower Boy", + "release_date": "2017-07-21", + "release_date_precision": "day", + "total_tracks": 14, + "type": "album", + "uri": "spotify:album:2nkto6YNI4rUYTLqEwWJ3o" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4V8LLVI7PbaPR0K2TGSxFF" + }, + "href": "https://api.spotify.com/v1/artists/4V8LLVI7PbaPR0K2TGSxFF", + "id": "4V8LLVI7PbaPR0K2TGSxFF", + "name": "Tyler, The Creator", + "type": "artist", + "uri": "spotify:artist:4V8LLVI7PbaPR0K2TGSxFF" + }, + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/7pbDxGE6nQSZVfiFdq9lOL" + }, + "href": "https://api.spotify.com/v1/artists/7pbDxGE6nQSZVfiFdq9lOL", + "id": "7pbDxGE6nQSZVfiFdq9lOL", + "name": "Rex Orange County", + "type": "artist", + "uri": "spotify:artist:7pbDxGE6nQSZVfiFdq9lOL" + }, + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/1mSJCvDX0W7Dn7S9C6vmvI" + }, + "href": "https://api.spotify.com/v1/artists/1mSJCvDX0W7Dn7S9C6vmvI", + "id": "1mSJCvDX0W7Dn7S9C6vmvI", + "name": "Anna of the North", + "type": "artist", + "uri": "spotify:artist:1mSJCvDX0W7Dn7S9C6vmvI" + } + ], + "disc_number": 1, + "duration_ms": 320720, + "explicit": true, + "external_ids": { + "isrc": "USQX91701279" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/5WNYg3usc6H8N3MBEp4zVk" + }, + "href": "https://api.spotify.com/v1/tracks/5WNYg3usc6H8N3MBEp4zVk", + "id": "5WNYg3usc6H8N3MBEp4zVk", + "is_local": false, + "name": "Boredom (feat. Rex Orange County & Anna of the North)", + "popularity": 66, + "preview_url": "https://p.scdn.co/mp3-preview/64c36bb59fe8c48d0ed5b38dc5a7b0189c1a642b?cid=f85f5aa4211f4ea78aa364547798b340", + "track_number": 8, + "type": "track", + "uri": "spotify:track:5WNYg3usc6H8N3MBEp4zVk" + }, + "currently_playing_type": "track", + "actions": { + "disallows": { + "resuming": true + } + } + }, + "original": { + "data": { + "artists": [ + "Tyler, The Creator", + "Rex Orange County", + "Anna of the North" + ], + "albumArtists": [ + "Tyler, The Creator" + ], + "album": "Flower Boy", + "track": "Boredom (feat. Rex Orange County & Anna of the North)", + "duration": 320.72, + // @ts-ignore + "playDate": "2026-03-15T15:27:09.309Z", + "isrc": "USQX91701279", + "meta": { + "spotify": { + "track": "5WNYg3usc6H8N3MBEp4zVk", + "artist": [ + "4V8LLVI7PbaPR0K2TGSxFF", + "7pbDxGE6nQSZVfiFdq9lOL", + "1mSJCvDX0W7Dn7S9C6vmvI" + ], + "albumArtist": [ + "4V8LLVI7PbaPR0K2TGSxFF" + ], + "album": "2nkto6YNI4rUYTLqEwWJ3o" + }, + "brainz": { + "trackNumber": 8 + } + } + }, + "meta": { + "deviceId": "3cc6dc47a8-Pixel 9a", + "source": "Spotify", + "musicService": "Spotify", + "trackId": "5WNYg3usc6H8N3MBEp4zVk", + "trackProgressPosition": 5.455, + "scrobbleTsSOC": 1, + "newFromSource": true, + "url": { + "web": "https://open.spotify.com/track/5WNYg3usc6H8N3MBEp4zVk" + }, + "art": { + "album": "https://i.scdn.co/image/ab67616d00001e028940ac99f49e44f59e6f7fb3" + } + } + }, + "steps": [ + { + "name": "preCompare", + "source": "Spotify - default", + "patch": { + "data": { + "albumArtists": [ + [ + "Tyler, The Creator" + ], + 0, + 0 + ], + "album": [ + "Flower Boy", + "Scum Fuck Flower Boy" + ], + "track": [ + "Boredom (feat. Rex Orange County & Anna of the North)", + "Boredom" + ], + "duration": [ + 320.72, + 320 + ], + "meta": { + "brainz": { + "recording": [ + "379db622-cc58-4bbf-9a3e-a7fa50b25fd1" + ], + "artist": [ + [ + "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "883cbf1f-dcaa-4d17-b9ed-394e1fdabc87", + "4d7db1c4-3c00-4140-8c83-c6d99cfdecf4" + ] + ], + "album": [ + "eb340386-6815-4933-9a28-940f7140f009" + ], + "releaseGroup": [ + "e248e931-c26c-4c94-aba2-2f89ce583901" + ] + } + } + } + }, + "inputs": [ + { + "type": "mbQuery", + "input": "isrc:USQX91701279" + }, + { + "type": "mbRecording", + "input": { + "id": "379db622-cc58-4bbf-9a3e-a7fa50b25fd1", + "score": 100, + "artist-credit-id": "c5abb112-bd0f-4155-9c78-9e3baf1b8ea5", + "title": "Boredom", + "length": 320000, + "video": null, + "artist-credit": [ + { + "joinphrase": " ft. ", + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator", + "aliases": [ + { + "sort-name": "Okonma, Tyler", + "name": "Tyler Okonma", + "locale": null, + "type": null, + "primary": null, + "begin-date": null, + "end-date": null + }, + { + "sort-name": "Okonma, T.", + "name": "T. Okonma", + "locale": null, + "type": null, + "primary": null, + "begin-date": null, + "end-date": null + }, + { + "sort-name": "Okonma, Tyler Gregory", + "type-id": "d4dcd0c0-b341-3612-a332-c0ce797b25cf", + "name": "Tyler Gregory Okonma", + "locale": null, + "type": "Legal name", + "primary": null, + "begin-date": null, + "end-date": null + }, + { + "sort-name": "Haley, Wolf", + "name": "Wolf Haley", + "locale": null, + "type": null, + "primary": null, + "begin-date": null, + "end-date": null + } + ] + } + }, + { + "joinphrase": " & ", + "name": "Rex Orange County", + "artist": { + "id": "883cbf1f-dcaa-4d17-b9ed-394e1fdabc87", + "name": "Rex Orange County", + "sort-name": "Rex Orange County", + "aliases": [ + { + "sort-name": "O'Connor, Alexander James", + "name": "Alexander James O'Connor", + "locale": null, + "type": null, + "primary": null, + "begin-date": null, + "end-date": null + }, + { + "sort-name": "O'Connor, Alexander", + "type-id": "d4dcd0c0-b341-3612-a332-c0ce797b25cf", + "name": "Alexander O'Connor", + "locale": null, + "type": "Legal name", + "primary": null, + "begin-date": null, + "end-date": null + } + ] + } + }, + { + "name": "Anna of the North", + "artist": { + "id": "4d7db1c4-3c00-4140-8c83-c6d99cfdecf4", + "name": "Anna of the North", + "sort-name": "Anna of the North", + "aliases": [ + { + "sort-name": "Lotterud, Anna", + "type-id": "d4dcd0c0-b341-3612-a332-c0ce797b25cf", + "name": "Anna Lotterud", + "locale": null, + "type": "Legal name", + "primary": null, + "begin-date": null, + "end-date": null + } + ] + } + } + ], + "first-release-date": "2017-07-21", + "releases": [ + { + "id": "eb340386-6815-4933-9a28-940f7140f009", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "artist-credit-id": "3289218e-1edd-4ace-91c6-e7af96ce7144", + "count": 1, + "title": "Scum Fuck Flower Boy", + "status": "Official", + "disambiguation": "Explicit Cover", + "artist-credit": [ + { + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator" + } + } + ], + "release-group": { + "id": "e248e931-c26c-4c94-aba2-2f89ce583901", + "type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Flower Boy", + "primary-type": "Album" + }, + "date": "2017-07-21", + "country": "US", + "release-events": [ + { + "date": "2017-07-21", + "area": { + "id": "489ce91b-6658-3307-9877-795b68554c98", + "name": "United States", + "sort-name": "United States", + "iso-3166-1-codes": [ + "US" + ] + } + } + ], + "track-count": 14, + "media": [ + { + "id": "f7525bfa-5263-3c90-926b-4f53cfc28021", + "position": 1, + "format": "CD", + "track": [ + { + "id": "931cb165-a25b-4e54-b606-1e3c31c06348", + "number": "8", + "title": "Boredom", + "length": 321000 + } + ], + "track-count": 14, + "track-offset": 7 + } + ], + "albumScore": 3, + "albumCompareScore": 0 + }, + { + "id": "88aba80a-e240-4464-8dad-a32654ba1348", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "artist-credit-id": "3289218e-1edd-4ace-91c6-e7af96ce7144", + "count": 1, + "title": "Scum Fuck Flower Boy", + "status": "Official", + "artist-credit": [ + { + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator" + } + } + ], + "release-group": { + "id": "e248e931-c26c-4c94-aba2-2f89ce583901", + "type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Flower Boy", + "primary-type": "Album" + }, + "date": "2017-07-21", + "country": "US", + "release-events": [ + { + "date": "2017-07-21", + "area": { + "id": "489ce91b-6658-3307-9877-795b68554c98", + "name": "United States", + "sort-name": "United States", + "iso-3166-1-codes": [ + "US" + ] + } + } + ], + "track-count": 14, + "media": [ + { + "id": "0be9089c-cfa8-36b8-9036-ab3347e48da6", + "position": 1, + "format": "CD", + "track": [ + { + "id": "aeff9385-9073-4c2e-915d-2136dc23be82", + "number": "8", + "title": "Boredom", + "length": 320000 + } + ], + "track-count": 14, + "track-offset": 7 + } + ], + "albumScore": 3, + "albumCompareScore": 0 + }, + { + "id": "dd09e440-879d-447b-9dfa-8547b369548e", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "artist-credit-id": "3289218e-1edd-4ace-91c6-e7af96ce7144", + "count": 1, + "title": "Flower Boy", + "status": "Official", + "disambiguation": "Apple Digital Master", + "artist-credit": [ + { + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator" + } + } + ], + "release-group": { + "id": "e248e931-c26c-4c94-aba2-2f89ce583901", + "type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Flower Boy", + "primary-type": "Album" + }, + "date": "2017-07-21", + "country": "XW", + "release-events": [ + { + "date": "2017-07-21", + "area": { + "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", + "name": "[Worldwide]", + "sort-name": "[Worldwide]", + "iso-3166-1-codes": [ + "XW" + ] + } + } + ], + "track-count": 14, + "media": [ + { + "id": "76a1bbb7-74c4-3953-a224-682a51c011c9", + "position": 1, + "format": "Digital Media", + "track": [ + { + "id": "77f18908-29e2-4bc4-a170-67789188a506", + "number": "8", + "title": "Boredom", + "length": 320000 + } + ], + "track-count": 14, + "track-offset": 7 + } + ], + "albumScore": 2, + "albumCompareScore": 0 + }, + { + "id": "c4d321ac-f21d-45e8-aebc-31986fc8233b", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "artist-credit-id": "3289218e-1edd-4ace-91c6-e7af96ce7144", + "count": 1, + "title": "Flower Boy", + "status": "Official", + "artist-credit": [ + { + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator" + } + } + ], + "release-group": { + "id": "e248e931-c26c-4c94-aba2-2f89ce583901", + "type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Flower Boy", + "primary-type": "Album" + }, + "date": "2017-07-21", + "country": "XW", + "release-events": [ + { + "date": "2017-07-21", + "area": { + "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", + "name": "[Worldwide]", + "sort-name": "[Worldwide]", + "iso-3166-1-codes": [ + "XW" + ] + } + } + ], + "track-count": 14, + "media": [ + { + "id": "359440fa-d653-3ba0-bbb4-57c41c2bdf78", + "position": 1, + "format": "Digital Media", + "track": [ + { + "id": "3496999b-31a1-489d-a395-7f977e0eb00f", + "number": "8", + "title": "Boredom", + "length": 320000 + } + ], + "track-count": 14, + "track-offset": 7 + } + ], + "albumScore": 2, + "albumCompareScore": 0 + }, + { + "id": "523f5e88-9988-436d-ab60-6d514c1f0e15", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "artist-credit-id": "3289218e-1edd-4ace-91c6-e7af96ce7144", + "count": 1, + "title": "Flower Boy", + "status": "Official", + "artist-credit": [ + { + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator" + } + } + ], + "release-group": { + "id": "e248e931-c26c-4c94-aba2-2f89ce583901", + "type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Flower Boy", + "primary-type": "Album" + }, + "date": "2017-07-21", + "country": "XW", + "release-events": [ + { + "date": "2017-07-21", + "area": { + "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", + "name": "[Worldwide]", + "sort-name": "[Worldwide]", + "iso-3166-1-codes": [ + "XW" + ] + } + } + ], + "track-count": 14, + "media": [ + { + "id": "401a7220-e021-3794-b52d-b7a75fdd8fc0", + "position": 1, + "format": "CD", + "track": [ + { + "id": "106952b2-4331-4132-a69f-e926b11b1536", + "number": "8", + "title": "Boredom", + "length": 320720 + } + ], + "track-count": 14, + "track-offset": 7 + } + ], + "albumScore": 2, + "albumCompareScore": 0 + }, + { + "id": "dacce9af-884c-4b60-a814-12e7d65f01be", + "status-id": "1156806e-d06a-38bd-83f0-cf2284a808b9", + "artist-credit-id": "949a7fd5-fe73-3e8f-922e-01ff4ca958f7", + "count": 1, + "title": "Insecure Music (Complete Season 02)", + "status": "Bootleg", + "artist-credit": [ + { + "name": "Various Artists", + "artist": { + "id": "89ad4ac3-39f7-470e-963a-56509c546377", + "name": "Various Artists", + "sort-name": "Various Artists", + "disambiguation": "add compilations to this artist" + } + } + ], + "release-group": { + "id": "8daa4b76-fb5d-4ece-98c4-beb0aaf09bcb", + "type-id": "dd2a21e1-0c00-3729-a7a0-de60b84eb5d1", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Insecure Music (Complete Season 02)", + "primary-type": "Album", + "secondary-types": [ + "Compilation", + "Soundtrack" + ], + "secondary-type-ids": [ + "dd2a21e1-0c00-3729-a7a0-de60b84eb5d1", + "22a628ad-c082-3c4f-b1b6-d41665107b88" + ] + }, + "track-count": 74, + "media": [ + { + "id": "d78e3f46-874b-4acb-968f-826896a81fd7", + "position": 1, + "format": "Digital Media", + "track": [ + { + "id": "db7f1d1f-71a9-4e59-9add-137e265af169", + "number": "63", + "title": "Boredom" + } + ], + "track-count": 74, + "track-offset": 62 + } + ], + "albumScore": 1, + "albumCompareScore": 0 + }, + { + "id": "7b19feb7-2c94-4f49-a887-868cde3c5941", + "status-id": "4e304316-386d-3409-af2e-78857eec5cfe", + "artist-credit-id": "3289218e-1edd-4ace-91c6-e7af96ce7144", + "count": 2, + "title": "Flower Boy", + "status": "Official", + "disambiguation": "limited edition translucent yellow", + "artist-credit": [ + { + "name": "Tyler, The Creator", + "artist": { + "id": "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "name": "Tyler, The Creator", + "sort-name": "Tyler, The Creator" + } + } + ], + "release-group": { + "id": "e248e931-c26c-4c94-aba2-2f89ce583901", + "type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", + "title": "Flower Boy", + "primary-type": "Album" + }, + "date": "2017-11-27", + "country": "GB", + "release-events": [ + { + "date": "2017-11-27", + "area": { + "id": "8a754a16-0027-3a29-b6d7-2b40ea0481ed", + "name": "United Kingdom", + "sort-name": "United Kingdom", + "iso-3166-1-codes": [ + "GB" + ] + } + }, + { + "date": "2017-11-27", + "area": { + "id": "489ce91b-6658-3307-9877-795b68554c98", + "name": "United States", + "sort-name": "United States", + "iso-3166-1-codes": [ + "US" + ] + } + }, + { + "date": "2017-11-27", + "area": { + "id": "89a675c2-3e37-3518-b83c-418bad59a85a", + "name": "Europe", + "sort-name": "Europe", + "iso-3166-1-codes": [ + "XE" + ] + } + } + ], + "track-count": 14, + "media": [ + { + "id": "8051dbab-8499-33b7-a6ad-ebae0991fe35", + "position": 2, + "format": "Vinyl", + "track": [ + { + "id": "3de6c108-2ee1-4a5f-8fc5-446d91168a95", + "number": "C1", + "title": "Boredom", + "length": 324000 + } + ], + "track-count": 7, + "track-offset": 0 + } + ], + "albumScore": 1, + "albumCompareScore": 0 + } + ], + "isrcs": [ + "USQX91701279" + ], + "titleScore": 0, + "artistScore": 0, + "albumScore": 0, + "rankScore": 0 + } + } + ] + } + ], + "scrobble": { + "match": { + "match": false, + "score": 0.09299683367479979, + "breakdowns": [ + "Artist: 0.02 * 0.3 = 0.01", + "Title: 0.22 * 0.4 = 0.09", + "Time: (No correlation) 0 * 0.5 = 0.00", + "Time Detail => Existing: 2026-03-14T20:35:17.000Z - Candidate: 2026-03-15T15:27:14.682Z | Temporal Sameness: No correlation | Play Diff: 68,221s (Needed <10s) | Fuzzy Duration Diff: 67,901s (Needed <= 10s) | Range Comparison N/A", + "Score 0.09 => No Match" + ], + "reason": "Score 0.09 => No Match", + "closestMatchedPlay": { + "data": { + "artists": [ + "Gorillaz" + ], + "track": "Tormenta", + "album": "Cracker Island", + // @ts-ignore + "playDate": "2026-03-14T20:35:17.000Z", + "meta": { + "brainz": { + "album": "52518f4a-9543-4f9c-9a86-89052f4bf458" + } + } + }, + "meta": { + "nowPlaying": false, + "mbid": "", + "source": "Lastfm", + "url": { + "web": "https://www.last.fm/music/Gorillaz/_/Tormenta" + }, + "lifecycle": { + "input": { + "artist": { + "url": "https://www.last.fm/music/Gorillaz", + "name": "Gorillaz", + "image": [ + { + "size": "small", + "#text": "https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png" + }, + { + "size": "medium", + "#text": "https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png" + }, + { + "size": "large", + "#text": "https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png" + }, + { + "size": "extralarge", + "#text": "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png" + } + ], + "mbid": "" + }, + "date": { + "uts": "1773520517", + "#text": "14 Mar 2026, 20:35" + }, + "mbid": "", + "name": "Tormenta", + "image": [ + { + "size": "small", + "#text": "https://lastfm.freetls.fastly.net/i/u/34s/507c3f4c1863a05626a445057871b56e.png" + }, + { + "size": "medium", + "#text": "https://lastfm.freetls.fastly.net/i/u/64s/507c3f4c1863a05626a445057871b56e.png" + }, + { + "size": "large", + "#text": "https://lastfm.freetls.fastly.net/i/u/174s/507c3f4c1863a05626a445057871b56e.png" + }, + { + "size": "extralarge", + "#text": "https://lastfm.freetls.fastly.net/i/u/300x300/507c3f4c1863a05626a445057871b56e.png" + } + ], + "url": "https://www.last.fm/music/Gorillaz/_/Tormenta", + "streamable": "0", + "album": { + "mbid": "52518f4a-9543-4f9c-9a86-89052f4bf458", + "#text": "Cracker Island" + }, + "loved": "0" + }, + "original": { + "data": { + "artists": [ + "Gorillaz" + ], + "track": "Tormenta", + "album": "Cracker Island", + // @ts-ignore + "playDate": "2026-03-14T20:35:17.000Z", + "meta": { + "brainz": { + "album": "52518f4a-9543-4f9c-9a86-89052f4bf458" + } + } + }, + "meta": { + "nowPlaying": false, + "mbid": "", + "source": "Lastfm", + "url": { + "web": "https://www.last.fm/music/Gorillaz/_/Tormenta" + } + } + }, + "steps": [] + } + } + } + }, + "payload": { + "artist": "Tyler, The Creator", + "track": "Boredom", + "album": "Scum Fuck Flower Boy", + "timestamp": 1773588738, + "mbid": "379db622-cc58-4bbf-9a3e-a7fa50b25fd1", + "duration": 320 + }, + "response": { + "scrobbles": { + "scrobble": { + "artist": { + "corrected": "0", + "#text": "Tyler, The Creator" + }, + "album": { + "corrected": "0", + "#text": "Scum Fuck Flower Boy" + }, + "track": { + "corrected": "0", + "#text": "Boredom" + }, + "ignoredMessage": { + "code": "0", + "#text": "" + }, + "albumArtist": { + "corrected": "0", + "#text": "" + }, + "timestamp": "1773588738" + }, + "@attr": { + "ignored": 0, + "accepted": 1 + } + } + }, + "mergedScrobble": { + "data": { + "artists": [ + "Tyler, The Creator", + "Rex Orange County", + "Anna of the North" + ], + "album": "Scum Fuck Flower Boy", + "track": "Boredom", + "duration": 320, + // @ts-ignore + "playDate": "2026-03-15T15:32:18.000Z", + "isrc": "USQX91701279", + "meta": { + "spotify": { + "track": "5WNYg3usc6H8N3MBEp4zVk", + "artist": [ + "4V8LLVI7PbaPR0K2TGSxFF", + "7pbDxGE6nQSZVfiFdq9lOL", + "1mSJCvDX0W7Dn7S9C6vmvI" + ], + "albumArtist": [ + "4V8LLVI7PbaPR0K2TGSxFF" + ], + "album": "2nkto6YNI4rUYTLqEwWJ3o" + }, + "brainz": { + "trackNumber": 8, + "recording": "379db622-cc58-4bbf-9a3e-a7fa50b25fd1", + "artist": [ + "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "883cbf1f-dcaa-4d17-b9ed-394e1fdabc87", + "4d7db1c4-3c00-4140-8c83-c6d99cfdecf4" + ], + "album": "eb340386-6815-4933-9a28-940f7140f009", + "releaseGroup": "e248e931-c26c-4c94-aba2-2f89ce583901" + } + }, + "listenedFor": 293.56100000000004, + + "listenRanges": [ + // @ts-ignore + [ + { + "timestamp": "2026-03-15T15:27:14.682Z", + "position": 5.455 + }, + { + "timestamp": "2026-03-15T15:32:08.223Z", + "position": 299.016 + } + ] + ], + "repeat": false + }, + "meta": { + "deviceId": "3cc6dc47a8-Pixel 9a", + "source": "Spotify", + "musicService": "Spotify", + "trackId": "5WNYg3usc6H8N3MBEp4zVk", + "trackProgressPosition": 5.455, + "scrobbleTsSOC": 2, + "newFromSource": true, + "url": { + "web": "https://open.spotify.com/track/5WNYg3usc6H8N3MBEp4zVk" + }, + "art": { + "album": "https://i.scdn.co/image/ab67616d00001e028940ac99f49e44f59e6f7fb3" + } + } + } + } +}); + +const examplePlay = (): JsonPlayObject => ({ + "data": { + "artists": [ + "Tyler, The Creator", + "Rex Orange County", + "Anna of the North" + ], + "album": "Scum Fuck Flower Boy", + "track": "Boredom", + "duration": 320, + // @ts-ignore + "playDate": "2026-03-15T15:32:18.000Z", + "isrc": "USQX91701279", + "meta": { + "spotify": { + "track": "5WNYg3usc6H8N3MBEp4zVk", + "artist": [ + "4V8LLVI7PbaPR0K2TGSxFF", + "7pbDxGE6nQSZVfiFdq9lOL", + "1mSJCvDX0W7Dn7S9C6vmvI" + ], + "albumArtist": [ + "4V8LLVI7PbaPR0K2TGSxFF" + ], + "album": "2nkto6YNI4rUYTLqEwWJ3o" + }, + "brainz": { + "trackNumber": 8, + "recording": "379db622-cc58-4bbf-9a3e-a7fa50b25fd1", + "artist": [ + "f6beac20-5dfe-4d1f-ae02-0b0a740aafd6", + "883cbf1f-dcaa-4d17-b9ed-394e1fdabc87", + "4d7db1c4-3c00-4140-8c83-c6d99cfdecf4" + ], + "album": "eb340386-6815-4933-9a28-940f7140f009", + "releaseGroup": "e248e931-c26c-4c94-aba2-2f89ce583901" + } + }, + "listenedFor": 293.56100000000004, + "listenRanges": [ + // @ts-ignore + [ + { + "timestamp": "2026-03-15T15:27:14.682Z", + "position": 5.455 + }, + { + "timestamp": "2026-03-15T15:32:08.223Z", + "position": 299.016 + } + ] + ], + "repeat": false + }, + "meta": { + "deviceId": "3cc6dc47a8-Pixel 9a", + "source": "Spotify", + "musicService": "Spotify", + "trackId": "5WNYg3usc6H8N3MBEp4zVk", + "trackProgressPosition": 5.455, + "scrobbleTsSOC": 2, + "newFromSource": true, + "url": { + "web": "https://open.spotify.com/track/5WNYg3usc6H8N3MBEp4zVk" + }, + "art": { + "album": "https://i.scdn.co/image/ab67616d00001e028940ac99f49e44f59e6f7fb3" + }, + lifecycle: exampleLifecycle() + } + }); + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = preview.meta({ + title: 'Examples/ActivityTimline', + component: ActivityTimeline, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'padded', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + args: { + play: examplePlay() + }, +decorators: [ + (Story) => (), + ] + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#story-args +}); + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const ActivityTimelineStory = meta.story({ + //render: function Render(args) { return () } +}); \ No newline at end of file