mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-07-09 17:28:28 +00:00
feat(ui): Implement component summary list
Some checks failed
Publish Docker image to Dockerhub / test (push) Has been cancelled
Publish Docker image to Dockerhub / Build OCI Images (push) Has been cancelled
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Has been cancelled
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Has been cancelled
Some checks failed
Publish Docker image to Dockerhub / test (push) Has been cancelled
Publish Docker image to Dockerhub / Build OCI Images (push) Has been cancelled
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Has been cancelled
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Has been cancelled
This commit is contained in:
parent
d4019e6dea
commit
892d035d7a
6 changed files with 194 additions and 11 deletions
82
src/client/components/ToggleTip.tsx
Normal file
82
src/client/components/ToggleTip.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import {
|
||||
Popover as ChakraPopover,
|
||||
IconButton,
|
||||
type IconButtonProps,
|
||||
Portal,
|
||||
} from "@chakra-ui/react"
|
||||
import * as React from "react"
|
||||
import { HiOutlineInformationCircle } from "react-icons/hi"
|
||||
|
||||
export interface ToggleTipProps extends ChakraPopover.RootProps {
|
||||
showArrow?: boolean
|
||||
portalled?: boolean
|
||||
portalRef?: React.RefObject<HTMLElement | null>
|
||||
content?: React.ReactNode
|
||||
contentProps?: ChakraPopover.ContentProps
|
||||
}
|
||||
|
||||
export const ToggleTip = React.forwardRef<HTMLDivElement, ToggleTipProps>(
|
||||
function ToggleTip(props, ref) {
|
||||
const {
|
||||
showArrow,
|
||||
children,
|
||||
portalled = true,
|
||||
content,
|
||||
contentProps,
|
||||
portalRef,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
return (
|
||||
<ChakraPopover.Root
|
||||
{...rest}
|
||||
positioning={{ ...rest.positioning, gutter: 4 }}
|
||||
>
|
||||
<ChakraPopover.Trigger asChild>{children}</ChakraPopover.Trigger>
|
||||
<Portal disabled={!portalled} container={portalRef}>
|
||||
<ChakraPopover.Positioner>
|
||||
<ChakraPopover.Content
|
||||
width="auto"
|
||||
px="2"
|
||||
py="1"
|
||||
textStyle="xs"
|
||||
rounded="sm"
|
||||
ref={ref}
|
||||
{...contentProps}
|
||||
>
|
||||
{showArrow && (
|
||||
<ChakraPopover.Arrow>
|
||||
<ChakraPopover.ArrowTip />
|
||||
</ChakraPopover.Arrow>
|
||||
)}
|
||||
{content}
|
||||
</ChakraPopover.Content>
|
||||
</ChakraPopover.Positioner>
|
||||
</Portal>
|
||||
</ChakraPopover.Root>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export interface InfoTipProps extends Partial<ToggleTipProps> {
|
||||
buttonProps?: IconButtonProps | undefined
|
||||
}
|
||||
|
||||
export const InfoTip = React.forwardRef<HTMLDivElement, InfoTipProps>(
|
||||
function InfoTip(props, ref) {
|
||||
const { children, buttonProps, ...rest } = props
|
||||
return (
|
||||
<ToggleTip content={children} {...rest} ref={ref}>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
aria-label="info"
|
||||
size="2xs"
|
||||
colorPalette="gray"
|
||||
{...buttonProps}
|
||||
>
|
||||
<HiOutlineInformationCircle />
|
||||
</IconButton>
|
||||
</ToggleTip>
|
||||
)
|
||||
},
|
||||
)
|
||||
34
src/client/components/msComponent/MSComponentList.tsx
Normal file
34
src/client/components/msComponent/MSComponentList.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React, { ComponentProps, useMemo, useState, forwardRef, Fragment } from "react"
|
||||
import { CheckboxCard, Span, Stack, SegmentGroup, Text, Box, Center, Heading, Button, Separator, HStack, Flex, Badge } from '@chakra-ui/react';
|
||||
import { ComponentsApiJson } from "../../../core/Api";
|
||||
import { components } from "storybook/internal/components";
|
||||
import { MSComponentSummary } from "./MSComponentSummary";
|
||||
|
||||
export interface ComponentListProps {
|
||||
components: ComponentsApiJson[]
|
||||
}
|
||||
|
||||
export const MSComponentList = (props: ComponentListProps) => {
|
||||
const [shownType, setShownType] = useState("All");
|
||||
return (
|
||||
<Stack gap="3">
|
||||
<Center>
|
||||
<SegmentGroup.Root value={shownType} onValueChange={(val) => setShownType(val.value)}>
|
||||
<SegmentGroup.Indicator />
|
||||
<SegmentGroup.Items items={["All", "Sources", "Clients"]} />
|
||||
</SegmentGroup.Root>
|
||||
</Center>
|
||||
<Stack>
|
||||
{props.components.filter(x => {
|
||||
if (shownType === 'All') {
|
||||
return true;
|
||||
}
|
||||
if (shownType === 'Sources') {
|
||||
return x.mode === 'source';
|
||||
}
|
||||
return x.mode === 'client';
|
||||
}).map(x => <MSComponentSummary data={x} key={x.uid} />)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import React, { ComponentProps, useMemo, forwardRef, Fragment } from "react"
|
||||
import { Accordion, For, Span, Stack, Text, Box, Heading, AbsoluteCenter, Button, Separator, HStack, Flex, Badge, IconButton, Container, Collapsible, Card, LinkOverlay, LinkBox } from '@chakra-ui/react';
|
||||
import { ComponentCommonApi, ComponentCommonApiJson, isComponentClientApiJson, isComponentSourceApiJson } from "../../../core/Api";
|
||||
import { TextMuted } from "../TextMuted";
|
||||
import { isClientType } from "../../../backend/common/infrastructure/Atomic";
|
||||
import { capitalize } from "../../../core/StringUtils";
|
||||
import { ShortDateDisplay } from "../DateDisplay";
|
||||
import { ChevronRightButton } from "../icons/ChakraIcons";
|
||||
import { ChakraPlayer } from "../chakraPlayer/Player";
|
||||
import { ComponentCommonApi, ComponentCommonApiJson, isComponentClientApiJson, isComponentSourceApiJson } from "../../../core/Api.js";
|
||||
import { TextMuted } from "../TextMuted.js";
|
||||
import { isClientType } from "../../../backend/common/infrastructure/Atomic.js";
|
||||
import { capitalize } from "../../../core/StringUtils.js";
|
||||
import { ShortDateDisplay } from "../DateDisplay.js";
|
||||
import { ChevronRightButton } from "../icons/ChakraIcons.js";
|
||||
import { ChakraPlayer } from "../chakraPlayer/Player.js";
|
||||
import { InfoTip } from "../ToggleTip.js";
|
||||
|
||||
export const MSComponentSummary = (props: { data: ComponentCommonApiJson }) => {
|
||||
const {
|
||||
|
|
@ -26,7 +27,7 @@ export const MSComponentSummary = (props: { data: ComponentCommonApiJson }) => {
|
|||
body = (<Card.Body px="3" py="2" paddingTop="3">
|
||||
<Stack gap="2">
|
||||
{
|
||||
Object.values(players).map((x) => <Container maxW="lg" bg="bg.emphasized" borderWidth="1px" p="2" py="3" rounded="md"><ChakraPlayer data={x}/></Container>)
|
||||
Object.values(players).map((x) => <Container bg="bg.emphasized" borderWidth="1px" p="2" py="3" rounded="md"><ChakraPlayer data={x}/></Container>)
|
||||
}
|
||||
</Stack>
|
||||
</Card.Body>);
|
||||
|
|
@ -50,7 +51,7 @@ export const MSComponentSummary = (props: { data: ComponentCommonApiJson }) => {
|
|||
</Stack>
|
||||
</Flex>
|
||||
</LinkBox>
|
||||
<TextMuted textStyle="md">{isClient ? `(${data.mode}) ` : ''}{capitalize(data.type)}</TextMuted>
|
||||
<TextMuted textStyle="md"><Badge colorPalette={data.mode === 'client' ? 'purple' : 'pink'} size="sm" variant="subtle">{capitalize(data.mode)}</Badge> {capitalize(data.type)}</TextMuted>
|
||||
<QuickStatsSource data={data} />
|
||||
</Card.Header>
|
||||
{body}
|
||||
|
|
@ -83,10 +84,12 @@ const QuickStatsSource = (props: { data: ComponentCommonApiJson }) => {
|
|||
<HStack gap="2">
|
||||
<TextMuted textStyle="sm">{queued} Queued</TextMuted>
|
||||
<Separator orientation="vertical" height="4" />
|
||||
<TextMuted textStyle="sm">{deadLetterScrobbles} ({deadLetterScrobblesTotal}) Dead (Total) </TextMuted>
|
||||
<TextMuted textStyle="sm">{deadLetterScrobbles} ({deadLetterScrobblesTotal}) Dead<InfoTip content="Dead scrobbles that can be automatically retried and (all) dead scrobbles, including those that have hit the retry limit."/></TextMuted>
|
||||
<HStack gap="2" hideBelow="sm">
|
||||
<Separator orientation="vertical" height="4" />
|
||||
<TextMuted textStyle="sm">{countLive} Scrobbled</TextMuted>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
|
@ -81,6 +81,8 @@ export type ComponentSourceApiBase = {
|
|||
export type ComponentSourceApi = ComponentCommonApi & ComponentSourceApiBase;
|
||||
export type ComponentSourceApiJson = Replace<ComponentSourceApi, PickKeys<ComponentSourceApi, Dayjs>, string>;
|
||||
|
||||
export type ComponentsApiJson = ComponentSourceApiJson | ComponentClientApiJson;
|
||||
|
||||
export const isComponentSourceApiJson = (data: ComponentCommonApiJson): data is ComponentSourceApiJson => {
|
||||
return data.mode === 'source';
|
||||
}
|
||||
|
|
|
|||
62
src/stories/ComponentList.stories.tsx
Normal file
62
src/stories/ComponentList.stories.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import preview from "../../.storybook/preview.js";
|
||||
import React from 'react';
|
||||
|
||||
import { Container } from '@chakra-ui/react';
|
||||
import { MSComponentList } from "../client/components/msComponent/MSComponentList.js";
|
||||
import {Provider} from "../client/components/Provider";
|
||||
import { generateClientApiJson, generateSourceApiJson, generateSourcePlayerJson } from "../core/tests/utils/apiFixtures.js";
|
||||
import { generateArray } from "../core/DataUtils.js";
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta = preview.meta({
|
||||
title: 'Examples/ComponentList',
|
||||
component: MSComponentList,
|
||||
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: {
|
||||
// data: generatePlayApiCommonDetailed(),
|
||||
// },
|
||||
// argTypes: {
|
||||
// componentType: {
|
||||
// control: { type: 'select' },
|
||||
// options: ['source', 'client'],
|
||||
// }
|
||||
// },
|
||||
render: function Render(args) {
|
||||
return (<MSComponentList {...args} />)
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (<Provider><Container maxWidth="4xl"><Story/></Container></Provider>),
|
||||
]
|
||||
// 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 Simple = meta.story({
|
||||
args: {
|
||||
components: [generateSourceApiJson(), generateClientApiJson()]
|
||||
}
|
||||
});
|
||||
|
||||
export const Many = meta.story({
|
||||
args: {
|
||||
components: [...generateArray(7, () => generateSourceApiJson()),...generateArray(4, () => generateClientApiJson())]
|
||||
}
|
||||
});
|
||||
|
||||
export const WithSourcePlayers = meta.story({
|
||||
args: {
|
||||
components: [...generateArray(3, () => {
|
||||
if(faker.datatype.boolean()) {
|
||||
return generateSourceApiJson({players: {test: generateSourcePlayerJson(undefined, {art: true})}});
|
||||
}
|
||||
return generateSourceApiJson();
|
||||
}),...generateArray(3, () => generateClientApiJson())]
|
||||
}
|
||||
});
|
||||
|
|
@ -2,7 +2,7 @@ import preview from "../../.storybook/preview.js";
|
|||
import React from 'react';
|
||||
|
||||
import { Container } from '@chakra-ui/react';
|
||||
import { MSComponentSummary } from "../client/components/msComponent/MSComponentQuickDisplay";
|
||||
import { MSComponentSummary } from "../client/components/msComponent/MSComponentSummary.js";
|
||||
import {Provider} from "../client/components/Provider";
|
||||
import { generateClientApiJson, generateSourceApiJson, generateSourcePlayerJson } from "../core/tests/utils/apiFixtures.js";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue