mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-07-09 17:28:28 +00:00
feat(ui): Add placeholder primary actions from component state
Some checks are pending
Publish Docker image to Dockerhub / test (push) Waiting to run
Publish Docker image to Dockerhub / Build OCI Images (push) Blocked by required conditions
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Blocked by required conditions
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Blocked by required conditions
Some checks are pending
Publish Docker image to Dockerhub / test (push) Waiting to run
Publish Docker image to Dockerhub / Build OCI Images (push) Blocked by required conditions
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Blocked by required conditions
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Blocked by required conditions
This commit is contained in:
parent
66fc7a2b09
commit
91ba24e165
3 changed files with 123 additions and 24 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Badge } from "@chakra-ui/react";
|
||||
import { ComponentProps, useState, useCallback, useEffect } from "react";
|
||||
import { Badge, Separator, HStack } from "@chakra-ui/react";
|
||||
import React, { ComponentProps, useState, useCallback, useEffect } from "react";
|
||||
import { COMPONENT_STATE, ComponentCommonApiJson, componentStateToFriendly, MsSseEvent, MsSseEventPayload, PlayApiCommon } from "../../core/Api";
|
||||
import { capitalize } from "../../core/StringUtils";
|
||||
import { PlayerState } from "../../backend/common/infrastructure/config/source/mpd";
|
||||
|
|
@ -60,12 +60,22 @@ export const NewBadge = (props: ComponentProps<typeof Badge> & { expires?: Secon
|
|||
return null;
|
||||
}
|
||||
|
||||
export const ComponentStateBadge = (props: ComponentProps<typeof Badge> & { data: Pick<ComponentCommonApiJson, 'state'>, componentId?: number, live?: boolean }) => {
|
||||
export const ComponentStateBadge = (props: ComponentProps<typeof Badge> & {
|
||||
data: Pick<ComponentCommonApiJson, 'state'>,
|
||||
componentId?: number,
|
||||
live?: boolean,
|
||||
separator?: boolean | React.JSX.Element,
|
||||
suffix?: React.JSX.Element
|
||||
}) => {
|
||||
|
||||
const { data, ...rest } = props;
|
||||
const { data, suffix, separator, ...rest } = props;
|
||||
|
||||
const [componentState, setComponentState] = useState(data.state);
|
||||
|
||||
useEffect(() =>{
|
||||
setComponentState(data.state);
|
||||
},[data.state, setComponentState]);
|
||||
|
||||
if(props.componentId !== undefined && props.live) {
|
||||
const client = useSSEContext<MsSseEvent>();
|
||||
const connection = useSSEEvent(client, 'componentUpdate', (payload) => {
|
||||
|
|
@ -99,5 +109,14 @@ export const ComponentStateBadge = (props: ComponentProps<typeof Badge> & { data
|
|||
break;
|
||||
}
|
||||
|
||||
return <Badge variant="surface" colorPalette={badgeColor} {...rest}>{componentStateToFriendly(componentState)}</Badge>
|
||||
let sep: React.JSX.Element | undefined;
|
||||
if(suffix !== undefined) {
|
||||
if(separator === true) {
|
||||
sep = <Separator orientation="vertical" borderColor="var(--chakra-colors-color-palette-muted)" height="5"/>;
|
||||
} else if(separator !== false) {
|
||||
sep = separator;
|
||||
}
|
||||
}
|
||||
|
||||
return <Badge variant="surface" colorPalette={badgeColor} {...rest}><HStack>{componentStateToFriendly(componentState)}{sep}{suffix}</HStack></Badge>
|
||||
}
|
||||
|
|
@ -1,4 +1,23 @@
|
|||
import { LuChevronRight, LuChevronLeft, LuActivity, LuGithub, LuTerminal, LuAlignJustify, LuX, LuCheck, LuExternalLink, LuArrowUp, LuArrowDown, LuEllipsis, LuArrowBigRight, LuBug } from "react-icons/lu"
|
||||
import {
|
||||
LuChevronRight,
|
||||
LuChevronLeft,
|
||||
LuActivity,
|
||||
LuGithub,
|
||||
LuTerminal,
|
||||
LuAlignJustify,
|
||||
LuX,
|
||||
LuCheck,
|
||||
LuExternalLink,
|
||||
LuArrowUp,
|
||||
LuArrowDown,
|
||||
LuEllipsis,
|
||||
LuArrowBigRight,
|
||||
LuBug,
|
||||
LuPower,
|
||||
LuPowerOff,
|
||||
LuEye,
|
||||
LuEyeClosed
|
||||
} from "react-icons/lu"
|
||||
import { VscDebugRestart } from 'react-icons/vsc';
|
||||
import { RiZzzFill } from "react-icons/ri";
|
||||
import { SiGoogledocs } from "react-icons/si";
|
||||
|
|
@ -59,6 +78,9 @@ export const MenuButton = makeIconButton(MenuIcon);
|
|||
export const XIcon = LuX;
|
||||
export const XButton = makeIconButton(XIcon);
|
||||
|
||||
export const CheckIcon = LuCheck;
|
||||
export const CheckButton = makeIconButton(CheckIcon);
|
||||
|
||||
export const ExternalLinkIcon = LuExternalLink;
|
||||
export const ExternalLinkButton = makeIconButton(ExternalLinkIcon);
|
||||
|
||||
|
|
@ -101,4 +123,16 @@ export const DebugCopy = (props: {value: Clipboard.RootProps['value']} & Compone
|
|||
}
|
||||
|
||||
export const RetryIcon = VscDebugRestart;
|
||||
export const RetryButton = makeIconButton(RetryIcon);
|
||||
export const RetryButton = makeIconButton(RetryIcon);
|
||||
|
||||
export const PowerIcon = LuPower;
|
||||
export const PowerButton = makeIconButton(PowerIcon);
|
||||
|
||||
export const PowerOffIcon = LuPowerOff;
|
||||
export const PowerOffButton = makeIconButton(PowerOffIcon);
|
||||
|
||||
export const EyeIcon = LuEye;
|
||||
export const EyeButton = makeIconButton(EyeIcon);
|
||||
|
||||
export const EyeClosedIcon = LuEyeClosed;
|
||||
export const EyeClosedButton = makeIconButton(LuEyeClosed);
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { ComponentProps, useMemo, forwardRef, Fragment, useEffect, useState, useCallback } from "react"
|
||||
import { DataList, Badge, Grid, Spacer, Span, ButtonGroup, Button, GridItem, Text, Box, Heading, Skeleton, Wrap, Stat, Separator, HStack, Stack, Flex, Collapsible, Card, LinkOverlay, LinkBox, SkeletonText, IconButtonProps } from '@chakra-ui/react';
|
||||
import { COMPONENT_STATE, ComponentClientApiJson, ComponentCommonApiJson, ComponentsApiJson, ComponentSourceApiJson, componentStateToFriendly, isComponentClientApiJson, isComponentSourceApiJson, MsSseEvent, MsSseEventPayload } from "../../../core/Api.js";
|
||||
import React, { ComponentProps, useState } from "react"
|
||||
import { SegmentGroup, Switch, Span, ButtonGroup, Button, Box, Heading, Skeleton, Wrap, HStack, Stack, Flex, Card, LinkOverlay, SkeletonText, BadgeProps, Badge } from '@chakra-ui/react';
|
||||
import { COMPONENT_STATE, ComponentClientApiJson, ComponentCommonApiJson, ComponentsApiJson, ComponentSourceApiJson, ComponentState, componentStateToFriendly, isComponentClientApiJson, isComponentSourceApiJson, MsSseEvent, MsSseEventPayload } 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 { ChevronLeftButton, ChevronRightButton, IdleIcon } from "../icons/ChakraIcons.js";
|
||||
import { CheckIcon, ChevronLeftButton, ChevronRightButton, EyeButton, IdleIcon, PowerButton, PowerOffButton, PowerOffIcon, XIcon } from "../icons/ChakraIcons.js";
|
||||
import { ChakraPlayer, ChakraPlayerFetchable, PlayersContainer, PlayersContainerFetchable } from "../chakraPlayer/Player.js";
|
||||
import { InfoTip, ToggleTip, Tooltip } from "../ToggleTip.js";
|
||||
import { QueryFunctionContext, queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
|
@ -71,18 +71,69 @@ export const MSComponentStats = (props: { data?: ComponentCommonApiJson, live?:
|
|||
)
|
||||
}
|
||||
|
||||
const ComponentSettings = () => {
|
||||
const stateIsStarted = (state: ComponentState): boolean => state <= COMPONENT_STATE.MUTED;
|
||||
|
||||
const ComponentSettings = (props: { data: ComponentCommonApiJson }) => {
|
||||
const [startChecked, setStartChecked] = useState<boolean>(stateIsStarted(props.data.state));
|
||||
return (
|
||||
<Stack>
|
||||
<ButtonGroup size="sm" variant="surface" attached>
|
||||
<Button disabled colorPalette="green">Start</Button>
|
||||
<Button disabled colorPalette="yellow">Mute</Button>
|
||||
<Button disabled colorPalette="red">Stop</Button>
|
||||
</ButtonGroup>
|
||||
<Switch.Root
|
||||
checked={startChecked}
|
||||
onCheckedChange={(e) => setStartChecked(e.checked)}
|
||||
>
|
||||
<Switch.HiddenInput />
|
||||
<Switch.Control>
|
||||
<Switch.Thumb>
|
||||
<Switch.ThumbIndicator fallback={<XIcon color="black" />}>
|
||||
<CheckIcon />
|
||||
</Switch.ThumbIndicator>
|
||||
</Switch.Thumb>
|
||||
</Switch.Control>
|
||||
<Switch.Label>Start</Switch.Label>
|
||||
</Switch.Root>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const primaryActionProps: ComponentProps<typeof PowerOffButton> = {
|
||||
margin: "1px",
|
||||
variant: "subtle",
|
||||
size: 'xs'
|
||||
}
|
||||
|
||||
export const ComponentStateBadgeActionable = (props: Omit<ComponentProps<typeof ComponentStateBadge>, 'suffix'>) => {
|
||||
const {
|
||||
componentId,
|
||||
live,
|
||||
...rest
|
||||
} = props;
|
||||
let suffix: React.JSX.Element | null;
|
||||
let primaryAction: React.JSX.Element | null;
|
||||
let badgeProps: BadgeProps = {};
|
||||
switch(props.data.state) {
|
||||
case COMPONENT_STATE.RUNNING:
|
||||
primaryAction = <PowerOffButton {...primaryActionProps}/>
|
||||
break;
|
||||
case COMPONENT_STATE.MUTED:
|
||||
primaryAction = <EyeButton {...primaryActionProps}/>
|
||||
break;
|
||||
case COMPONENT_STATE.INITIALIZING:
|
||||
// no actions while init is occurring
|
||||
break;
|
||||
default:
|
||||
// otherwise generic start action for all non-running states
|
||||
primaryAction = <PowerButton {...primaryActionProps}/>
|
||||
}
|
||||
if(primaryAction !== undefined) {
|
||||
suffix = primaryAction;
|
||||
}
|
||||
if(suffix !== undefined || primaryAction !== undefined) {
|
||||
//badgeProps.paddingRight = 0;
|
||||
}
|
||||
|
||||
return <ComponentStateBadge size="lg" maxWidth="fit-content" {...badgeProps} separator suffix={suffix} {...rest}/>;
|
||||
}
|
||||
|
||||
export const ComponentDetailedDesktop = (props: {data?: ComponentCommonApiJson, live?: boolean}) => {
|
||||
let sleepingRender: React.JSX.Element = null;
|
||||
const {data} = props;
|
||||
|
|
@ -110,7 +161,7 @@ export const ComponentDetailedDesktop = (props: {data?: ComponentCommonApiJson,
|
|||
<Flex direction="column" style={{whiteSpace: 'break-spaces'}} truncate rowGap="1">
|
||||
<Flex width="100%" truncate>
|
||||
<Box marginEnd="auto" truncate><MSComponentName data={props.data}/></Box>
|
||||
<ComponentStateBadge size="lg" maxWidth="fit-content" data={props.data} />
|
||||
<ComponentStateBadgeActionable size="lg" maxWidth="fit-content" data={props.data} />
|
||||
</Flex>
|
||||
<Wrap>
|
||||
<Box marginEnd="auto">
|
||||
|
|
@ -119,12 +170,7 @@ export const ComponentDetailedDesktop = (props: {data?: ComponentCommonApiJson,
|
|||
<HStack truncate>{sleepingRender}{props.data.status}</HStack>
|
||||
</Wrap>
|
||||
<Flex justifyContent="flex-end" rowGap="6" flexDirection="row-reverse" wrap="wrap">
|
||||
<Card.Root bgColor="bg.subtle" size="sm">
|
||||
<Card.Header>Actions</Card.Header>
|
||||
<Card.Body>
|
||||
<ComponentSettings/>
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
<ComponentSettings data={props.data}/>
|
||||
<Box marginEnd="auto"><MSComponentStats {...props}/></Box>
|
||||
</Flex>
|
||||
{props.live ? <PlayersContainerFetchable data={props.data}/> : <PlayersContainer data={props.data} live={props.live}/>}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue