diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 15522711..e7ff9783 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -33,7 +33,7 @@ import { } from "../utils.js"; import { sortByNewestPlayDate } from '../../core/PlayUtils.js'; import { formatNumber } from '../../core/DataUtils.js'; -import { timeToHumanTimestamp } from "../utils/TimeUtils.js"; +import { timeToHumanTimestamp } from "../../core/TimeUtils.js"; import { todayAwareFormat } from "../../core/TimeUtils.js"; import { getRoot } from '../ioc.js'; import { componentFileLogger } from '../common/logging.js'; diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index ed6d5ad0..386ba9c8 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -21,7 +21,8 @@ import { } from "../utils.js"; import { genGroupIdStr } from '../../core/PlayUtils.js'; import { formatNumber } from '../../core/DataUtils.js'; -import { timePassesScrobbleThreshold, timeToHumanTimestamp } from "../utils/TimeUtils.js"; +import { timePassesScrobbleThreshold } from "../utils/TimeUtils.js"; +import { timeToHumanTimestamp } from "../../core/TimeUtils.js"; import { PromisePool } from "@supercharge/promise-pool"; import AbstractSource from "./AbstractSource.js"; import { AbstractPlayerState, createPlayerOptions, PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 7ab4c7d2..db1faee4 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -18,7 +18,8 @@ import { genGroupIdStr } from '../../../core/PlayUtils.js'; import { formatNumber } from '../../../core/DataUtils.js'; import { ListenProgress } from "./ListenProgress.js"; import { ListenRange, ListenRangePositional } from "./ListenRange.js"; -import { closeToPlayEnd, closeToPlayStart, repeatDurationPlayed, timeToHumanTimestamp } from "../../utils/TimeUtils.js"; +import { closeToPlayEnd, closeToPlayStart, repeatDurationPlayed } from "../../utils/TimeUtils.js"; +import { timeToHumanTimestamp } from "../../../core/TimeUtils.js"; import { todayAwareFormat } from "../../../core/TimeUtils.js"; export interface PlayerStateIntervals { diff --git a/src/backend/utils/TimeUtils.ts b/src/backend/utils/TimeUtils.ts index b602a839..15a69200 100644 --- a/src/backend/utils/TimeUtils.ts +++ b/src/backend/utils/TimeUtils.ts @@ -318,18 +318,6 @@ export const parseDurationFromTimestamp = (timestamp: any) => { export type Milliseconds = number; -export const timeToHumanTimestamp = (val: ReturnType | Milliseconds): string => { - const ms = dayjs.isDuration(val) ? Math.abs(val.asMilliseconds()) : val; - - // less than one hour - if(ms < 3600000) { - // EX 14:07 - return new Date(ms).toISOString().substring(14, 19) - } - // EX 01:15:45 - return new Date(ms).toISOString().substring(11, 19); -} - /** Is Position earlier than X seconds or Y% percent of the start of a Play? */ export const closeToPlayStart = (play: PlayObject, position: number, thresholds: {absolute?: number, percent?: number, hintPrefix?: boolean} = {}): [boolean, string] => { const { diff --git a/src/client/components/PlayData.tsx b/src/client/components/PlayData.tsx index 2d6e989c..118a13b1 100644 --- a/src/client/components/PlayData.tsx +++ b/src/client/components/PlayData.tsx @@ -1,11 +1,13 @@ import React, { Fragment, useMemo, useState } from 'react'; -import { EmptyState, DataList, HStack, Tag, Tabs, Wrap, Box, Flex, SegmentGroup, Stack, Text, Separator, IconButton, Container, SimpleGrid, Float } from "@chakra-ui/react" -import { LuCode, LuText } from "react-icons/lu" +import { EmptyState, DataList, HStack, Tag, Tabs, Wrap, Box, Flex, SegmentGroup, Stack, Text, Separator, IconButton, Container, SimpleGrid, Float, Spacer, Icon, Link, Span, Show } from "@chakra-ui/react" +import { LuCode, LuText, LuCheck, LuX } from "react-icons/lu" import { JsonPlayObject, PlayObjectLifecycleless } from '../../core/Atomic.js'; -import { shortTodayAwareFormat } from '../../core/TimeUtils.js'; +import { shortTodayAwareFormat, timeToHumanTimestamp } from '../../core/TimeUtils.js'; import dayjs from 'dayjs'; import { ChakraCodeBlock } from './CodeBlock.js'; import { TextMuted } from './TextMuted.js'; +import { formatNumber } from '../../core/DataUtils.js'; +import { Muted } from './Typography.js'; const EmptyPlayData = () => { return ( @@ -114,36 +116,87 @@ export const PlayDataDataList = (props: { play: JsonPlayObject, dates: DisplayDa const { data: { - artists = [] + track, + artists = [], + listenedFor, + duration, + repeat, + meta: { + brainz = {} + } = {} + } = {}, + meta: { + url: { + web: webUrl, + origin: originUrl + } = {} } = {} } = play; + let titleElm: JSX.Element; + if(webUrl !== undefined || originUrl !== undefined) { + titleElm = {track} + } else { + titleElm = {track}; + } + return ( - - - Title - {play.data.track} - - - Artists - - {artists.length === 0 ? (No Artists) : - {play.data.artists.map((x, index) => { - return ( - - {x} - - ); - })}} - - - {albumArtistElm} - - Album - {play.data.album} - - - + + + + Title + {titleElm} + + + Artists + + {artists.length === 0 ? (No Artists) : + {play.data.artists.map((x, index) => { + return ( + + {x} + + ); + })}} + + + {albumArtistElm} + + Album + {play.data.album} + + + + + + Duration + + + Track Length: {timeToHumanTimestamp(dayjs.duration(duration, 's'))} + {listenedFor !== undefined ? Listened For: {timeToHumanTimestamp(dayjs.duration(listenedFor, 's'))} ({formatNumber((listenedFor / duration) * 100)}%) : null} + + + + + Repeat? + {repeat ? : } + + + + 0}> + + MBIDs + + + Track: {brainz.track} + Recording: {brainz.recording} + Album: {brainz.album} + + + + + + ) } @@ -163,7 +216,7 @@ export const PlayDatesStack = (props: { play: JsonPlayObject, dates: DisplayDate } else { const dateElements = []; if (dates.includes('played') || dates.includes('all')) { - dateElements.push(({`Played ${shortTodayAwareFormat(dayjs(play.data.playDate))}`})); + dateElements.push(({`Played ${shortTodayAwareFormat(dayjs(play.data.playDate))}`})); if (play.data.playDateCompleted !== undefined) { dateElements.push(({`Played Until ${shortTodayAwareFormat(dayjs(play.data.playDateCompleted))}`})); } diff --git a/src/core/PlayTestUtils.ts b/src/core/PlayTestUtils.ts index b418a82d..5724b60a 100644 --- a/src/core/PlayTestUtils.ts +++ b/src/core/PlayTestUtils.ts @@ -154,13 +154,15 @@ export const generatePlay = (data: ObjectPlayData = {}, meta: MarkOptional { return todayAwareFormat(date, {fullFormat: SHORT_CALENDAR_NOTZ_FORMAT, todayFormat: SHORT_TODAY_NOTZ_FORMAT}); -} \ No newline at end of file +}; +export const timeToHumanTimestamp = (val: ReturnType | Milliseconds): string => { + const ms = dayjs.isDuration(val) ? Math.abs(val.asMilliseconds()) : val; + + // less than one hour + if (ms < 3600000) { + // EX 14:07 + return new Date(ms).toISOString().substring(14, 19); + } + // EX 01:15:45 + return new Date(ms).toISOString().substring(11, 19); +}; diff --git a/src/stories/PlayInfo.stories.tsx b/src/stories/PlayInfo.stories.tsx index 9651d5ce..fde02b31 100644 --- a/src/stories/PlayInfo.stories.tsx +++ b/src/stories/PlayInfo.stories.tsx @@ -5,12 +5,14 @@ import { fn } from 'storybook/test'; import { PlayData } from "../client/components/PlayData.js"; import {Provider} from "../client/components/Provider"; import { Container } from '@chakra-ui/react'; -import { generateArtists, generateJsonPlay, generatePlay } from "../core/PlayTestUtils.js" +import { generateArtists, generateJsonPlay, generatePlay, withBrainz } from "../core/PlayTestUtils.js" import clone from "clone"; +import { asJsonPlayObject } from "../core/tests/utils/fixtures.js"; type PropsAndCustomArgs = React.ComponentProps & { includeAlbumArtists?: boolean; defaultFinal?: boolean + brainz?: boolean }; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = preview.type<{args: PropsAndCustomArgs}>().meta({ @@ -29,7 +31,8 @@ args: { play: generateJsonPlay(), includeAlbumArtists: false, showCodeToggle: true, - defaultFinal: true + defaultFinal: true, + brainz: false }, // 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 }); @@ -51,6 +54,11 @@ export const PlayInfoStory = meta.story({ args.final.data.albumArtists = aa; } } + + if(args.brainz) { + // @ts-ignore + args.play = asJsonPlayObject(withBrainz(args.play, {include: ['album','track','artist']})); + } return () } }); \ No newline at end of file