import React, { useCallback, useState, type ComponentProps } from 'react'; import { EmptyState, DataList, HStack, Tabs, Box, Flex, Stack, Text, Separator, IconButton, Container, Float, Icon, Link, Span, Show, Menu, Group, Portal, type MenuItemProps, type MenuSelectionDetails } from "@chakra-ui/react" import { LuCode, LuText, LuCheck, LuX } from "react-icons/lu" import type { JsonPlayObject, PlayObjectMinimal } from '../../core/Atomic.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'; import { ArtistCreditTags } from './ArtistCreditDisplay.js'; import { MSErrorBoundary } from './ErrorBoundary.js'; import { EllipsisButton, EyeClosedIcon, EyeIcon, getMusicServiceIconElement } from './icons/ChakraIcons.js'; import { MusicbrainzInfoIcon } from './musicServices/Musicbrainz.js'; import type { IconType } from 'react-icons/lib'; import { capitalize } from '../../core/StringUtils.js'; const EmptyPlayData = () => { return ( No Play object was provided. ); } export type DisplayDates = false | 'all' | 'played' | 'seen'; export interface PlayInfoProps { play?: JsonPlayObject | PlayObjectMinimal final?: JsonPlayObject showCodeToggle?: boolean showCompare?: boolean compareDefault?: 'Initial' | 'Final' dates?: false | 'all' | 'played' | 'seen' } const primaryActionProps: ComponentProps = { //margin: "1px", variant: "outline", size: 'xs' } const menuItem = (Icon: IconType, value: string, name?: string) => (props: Pick = {}) => ({name ?? capitalize(value)}) const MenuMbidShow = menuItem(EyeIcon, 'mbidShow', 'Show MBIDs'); const MenuMbidHide = menuItem(EyeClosedIcon, 'mbidHide', 'Hide MBIDs'); export const PlayData = (props?: PlayInfoProps) => { const { play, final, showCodeToggle = true, showCompare = true, compareDefault = 'Initial', dates = 'all' } = props ?? {}; const [codeMode, setCodeMode] = useState(false); const [showMbid, setShowMBid] = useState(false); const menuCb = useCallback((select: MenuSelectionDetails) => { if (select.value === 'mbidShow') { setShowMBid(true); } else if (select.value === 'mbidHide') { setShowMBid(false); } }, [setShowMBid]) if (play === undefined) { return } let code: React.JSX.Element | null = null; const comparable = showCompare && final !== undefined; const menuItems: React.JSX.Element[] = [ showMbid ? : ]; if (showCodeToggle) { code = ( setCodeMode(!codeMode)}> {codeMode ? : } ); } let content: React.JSX.Element; if (!comparable) { content = codeMode ? : ; } else { content = ( Initial Final {codeMode ? : } {codeMode ? : } ) } return ( {code} {menuItems} {content} ); } export const PlayDataDataList = (props: { play: JsonPlayObject, dates: DisplayDates, showMbid?: boolean }) => { const { play, dates, showMbid = false } = props; let albumArtistElm: React.JSX.Element; if (play.data.albumArtists !== undefined && play.data.albumArtists.length > 0) { albumArtistElm = ( Album Artists ); } const { data: { track, artists = [], listenedFor, duration, repeat, meta: { brainz = {} } = {} } = {}, meta: { url: { web: webUrl, origin: originUrl } = {}, source, musicService } = {} } = play; const titleLinks: React.JSX.Element[] = []; if (webUrl !== undefined || originUrl !== undefined) { titleLinks.push({getMusicServiceIconElement(musicService ?? source)}); } if (brainz.track !== undefined) { titleLinks.push() } if (brainz.recording !== undefined) { titleLinks.push() } const titleElm = {track}{titleLinks} return ( Title {titleElm} Artists {artists.length === 0 ? (No Artists) : } {albumArtistElm} Album {play.data.album} Duration Track Length: {duration === undefined ? 'N/A' : timeToHumanTimestamp(dayjs.duration(duration, 's'))} {listenedFor !== undefined ? Listened For: {timeToHumanTimestamp(dayjs.duration(listenedFor, 's'))} ({formatNumber((listenedFor / duration) * 100)}%) : null} Repeat? {repeat ? : } ) } export const PlayInfoContainer = (props?: PlayInfoProps) => { return } export const PlayDatesStack = (props: { play: JsonPlayObject, dates: DisplayDates }) => { const { play, dates } = props; let datesItem: React.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.meta.seenAt))}`})); } datesItem = ( Dates {dateElements} ) } return datesItem; } export const PlayDatesFooter = (props: { play: JsonPlayObject, dates: DisplayDates }) => { const { play, dates } = props; let dateElm: React.JSX.Element; if (dates !== false) { let playDate: React.JSX.Element, seenDate: React.JSX.Element; if (play.data.playDate !== undefined && ['all', 'played'].includes(dates)) { playDate = {`Played ${shortTodayAwareFormat(dayjs(play.data.playDate))}`} } // TODO implement seenAt for play data if (play.meta.seenAt !== undefined && ['all', 'seen'].includes(dates)) { seenDate = {`Seen ${shortTodayAwareFormat(dayjs(play.meta.seenAt))}`} } if (playDate !== undefined && seenDate !== undefined) { dateElm = {playDate}{seenDate} } else if (playDate !== undefined) { dateElm = playDate; } else if (seenDate !== undefined) { dateElm = seenDate; } } return dateElm; }