import React, { useCallback, useState, type ComponentProps, type PropsWithChildren } from 'react'; import { EmptyState, DataList, HStack, Tabs, Box, Badge, Stack, Text, Separator, IconButton, Container, Float, Link, Span, Menu, Group, Portal, type MenuItemProps, type MenuSelectionDetails } from "@chakra-ui/react" import { LuCode, LuText } 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 { EditIconRaw, EllipsisButton, EyeClosedIcon, EyeIcon, hasMusicServiceIcon } from '../icons/ChakraIcons.js'; import type { IconType } from 'react-icons/lib'; import { capitalize, capitalizeWords } from '../../../core/StringUtils.js'; import { getMusicServiceIconTooltip, MusicServiceIndicators } from '../musicServices/MusicServiceIndicators.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' editable?: boolean onEditClick?: () => void codeContent?: React.JSX.Element } const primaryActionProps: ComponentProps = { //margin: "1px", variant: "outline", size: 'xs' } const menuItem = (Icon: IconType, value: string, name?: string) => (props: Pick = {}) => ({name ?? capitalize(value)}) const MenuTextMode = menuItem(LuText, 'code', 'Show Text'); const MenuCodeMode = menuItem(LuCode, 'code', 'Show Code'); export const PlayData = (props?: PlayInfoProps) => { const { play, final, codeContent, showCodeToggle = true, showCompare = true, compareDefault = 'Initial', dates = 'all', editable = false, onEditClick } = props ?? {}; const [codeMode, setCodeMode] = useState(false); const menuCb = useCallback((select: MenuSelectionDetails) => { if(select.value === 'code') { setCodeMode((old) => !old); } }, [setCodeMode]) if (play === undefined) { return } let primary: React.JSX.Element | null = null; const comparable = showCompare && final !== undefined; const menuItems: React.JSX.Element[] = [ ]; if(editable && onEditClick !== undefined) { primary = ( onEditClick()}> ); if(showCodeToggle) { menuItems.push(codeMode ? : ); } } else if (showCodeToggle) { primary = ( setCodeMode(!codeMode)}> {codeMode ? : } ); } let content: React.JSX.Element; if (!comparable) { content = codeMode ? (codeContent ?? ) : ; } else { content = ( Initial Final {codeMode ? (codeContent ?? ) : } {codeMode ? (codeContent ?? ) : } ) } return ( {primary} {menuItems} {content} ); } const SourceMaybeLink = (props: PropsWithChildren<{link?: string}>) => { if(props.link === undefined) { return props.children; } return {props.children} } export const PlayDataDataList = (props: { play: JsonPlayObject, dates: DisplayDates, showMbid?: boolean }) => { const { play, dates, showMbid = false } = props; let albumArtistElm: React.JSX.Element | undefined; if (play.data.albumArtists !== undefined && play.data.albumArtists.length > 0) { albumArtistElm = ( Album Artists ); } const { data: { track, artists = [], listenedFor, duration, repeat, } = {}, meta: { url: { web: webUrl, origin: originUrl } = {}, source, musicService } = {} } = play; let sourceElm: React.JSX.Element | undefined = undefined; if(musicService !== undefined && musicService !== source && source !== undefined) { sourceElm = {hasMusicServiceIcon(musicService) ? getMusicServiceIconTooltip(musicService)({size: 'md'}) : capitalizeWords(musicService)} via {capitalizeWords(source)} } else if(source !== undefined) { sourceElm = {hasMusicServiceIcon(source) ? getMusicServiceIconTooltip(source)({size: 'md'}) : capitalizeWords(source)} } const titleElm = {track?.name} return ( Title {titleElm} Artists {artists.length === 0 ? (No Artists) : } {albumArtistElm} Album {play.data.album?.name} Duration Track Length: {duration === undefined ? 'N/A' : timeToHumanTimestamp(dayjs.duration(duration, 's'))} {listenedFor !== undefined ? Listened For: {timeToHumanTimestamp(dayjs.duration(listenedFor, 's'))}{duration !== undefined && duration > 0 ? ` (${formatNumber((listenedFor / duration) * 100)}%)` : null} : null} {sourceElm !== undefined ? ( Source {sourceElm} ) : undefined} ) } export const PlayInfoContainer = (props?: PlayInfoProps) => { return } export const PlayDatesStack = (props: { play: JsonPlayObject, dates: DisplayDates }) => { const { play, play: { data: { repeat = false, } = {} } = {}, 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))}`}{repeat ? Repeat : null})); 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 | null = null; if (dates !== false) { let playDate: React.JSX.Element | undefined, seenDate: React.JSX.Element | undefined; 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; }