mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
disable single attribute per line
This commit is contained in:
parent
92ed8e20c9
commit
8b141d652c
154 changed files with 390 additions and 1800 deletions
|
|
@ -8,7 +8,7 @@ arrowParens: always
|
|||
proseWrap: never
|
||||
htmlWhitespaceSensitivity: strict
|
||||
endOfLine: lf
|
||||
singleAttributePerLine: true
|
||||
singleAttributePerLine: false
|
||||
bracketSpacing: true
|
||||
plugins:
|
||||
- prettier-plugin-packagejson
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ export const App = () => {
|
|||
const { mode, theme } = useAppTheme(isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT);
|
||||
|
||||
return (
|
||||
<MantineProvider
|
||||
defaultColorScheme={mode}
|
||||
theme={theme}
|
||||
>
|
||||
<MantineProvider defaultColorScheme={mode} theme={theme}>
|
||||
<Shell />
|
||||
</MantineProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,17 +18,7 @@ export const ThemeButton = () => {
|
|||
}}
|
||||
variant="default"
|
||||
>
|
||||
{isDark ? (
|
||||
<Icon
|
||||
icon="themeLight"
|
||||
size={30}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
icon="themeDark"
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
{isDark ? <Icon icon="themeLight" size={30} /> : <Icon icon="themeDark" size={30} />}
|
||||
</ActionIcon>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,17 +32,9 @@ export const RemoteContainer = () => {
|
|||
const debouncedSetRating = debounce(setRating, 400);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap="md"
|
||||
h="100dvh"
|
||||
w="100%"
|
||||
>
|
||||
<Stack gap="md" h="100dvh" w="100%">
|
||||
{showImage && (
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
w="100%"
|
||||
>
|
||||
<Flex align="center" justify="center" w="100%">
|
||||
<PlayerImage src={song?.imageUrl} />
|
||||
</Flex>
|
||||
)}
|
||||
|
|
@ -87,10 +79,7 @@ export const RemoteContainer = () => {
|
|||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
<Group
|
||||
gap={0}
|
||||
grow
|
||||
>
|
||||
<Group gap={0} grow>
|
||||
<ActionIcon
|
||||
disabled={!id}
|
||||
icon="favorite"
|
||||
|
|
@ -109,10 +98,7 @@ export const RemoteContainer = () => {
|
|||
/>
|
||||
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
|
||||
<div style={{ margin: 'auto' }}>
|
||||
<Tooltip
|
||||
label="Double click to clear"
|
||||
openDelay={1000}
|
||||
>
|
||||
<Tooltip label="Double click to clear" openDelay={1000}>
|
||||
<Rating
|
||||
onChange={debouncedSetRating}
|
||||
onDoubleClick={() => debouncedSetRating(0)}
|
||||
|
|
@ -123,10 +109,7 @@ export const RemoteContainer = () => {
|
|||
</div>
|
||||
)}
|
||||
</Group>
|
||||
<Group
|
||||
gap="xs"
|
||||
grow
|
||||
>
|
||||
<Group gap="xs" grow>
|
||||
<ActionIcon
|
||||
disabled={!id}
|
||||
icon="mediaPrevious"
|
||||
|
|
@ -174,10 +157,7 @@ export const RemoteContainer = () => {
|
|||
variant="default"
|
||||
/>
|
||||
</Group>
|
||||
<Group
|
||||
gap="xs"
|
||||
grow
|
||||
>
|
||||
<Group gap="xs" grow>
|
||||
<ActionIcon
|
||||
icon="mediaShuffle"
|
||||
iconProps={{
|
||||
|
|
@ -232,10 +212,7 @@ export const RemoteContainer = () => {
|
|||
max={100}
|
||||
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
|
||||
rightLabel={
|
||||
<Text
|
||||
fw={600}
|
||||
size="xs"
|
||||
>
|
||||
<Text fw={600} size="xs">
|
||||
{volume ?? 0}
|
||||
</Text>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,16 +13,9 @@ export const Shell = () => {
|
|||
const connected = useConnected();
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
h="100vh"
|
||||
padding="md"
|
||||
w="100vw"
|
||||
>
|
||||
<AppShell h="100vh" padding="md" w="100vw">
|
||||
<AppShell.Header style={{ background: 'var(--theme-colors-surface)' }}>
|
||||
<Grid
|
||||
px="md"
|
||||
py="sm"
|
||||
>
|
||||
<Grid px="md" py="sm">
|
||||
<Grid.Col span={4}>
|
||||
<Flex
|
||||
align="center"
|
||||
|
|
@ -33,20 +26,11 @@ export const Shell = () => {
|
|||
justifySelf: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
fit="contain"
|
||||
height={32}
|
||||
src="/favicon.ico"
|
||||
width={32}
|
||||
/>
|
||||
<Image fit="contain" height={32} src="/favicon.ico" width={32} />
|
||||
</Flex>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Group
|
||||
gap="sm"
|
||||
justify="flex-end"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap="sm" justify="flex-end" wrap="nowrap">
|
||||
<ReconnectButton />
|
||||
<ImageButton />
|
||||
<ThemeButton />
|
||||
|
|
@ -58,10 +42,7 @@ export const Shell = () => {
|
|||
{connected ? (
|
||||
<RemoteContainer />
|
||||
) : (
|
||||
<Center
|
||||
h="100vh"
|
||||
w="100vw"
|
||||
>
|
||||
<Center h="100vh" w="100vw">
|
||||
<Spinner />
|
||||
</Center>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -61,10 +61,7 @@ export const WrappedSlider = ({ leftLabel, rightLabel, value, ...props }: Wrappe
|
|||
const [seek, setSeek] = useState(0);
|
||||
|
||||
return (
|
||||
<Group
|
||||
align="center"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group align="center" wrap="nowrap">
|
||||
{leftLabel && <Text size="sm">{leftLabel}</Text>}
|
||||
<PlayerbarSlider
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -190,15 +190,8 @@ export const App = () => {
|
|||
}, [language]);
|
||||
|
||||
return (
|
||||
<MantineProvider
|
||||
defaultColorScheme={mode as 'dark' | 'light'}
|
||||
theme={theme}
|
||||
>
|
||||
<Notifications
|
||||
containerWidth="300px"
|
||||
position="bottom-center"
|
||||
zIndex={50000}
|
||||
/>
|
||||
<MantineProvider defaultColorScheme={mode as 'dark' | 'light'} theme={theme}>
|
||||
<Notifications containerWidth="300px" position="bottom-center" zIndex={50000} />
|
||||
<PlayQueueHandlerContext.Provider value={providerValue}>
|
||||
<ContextMenuProvider>
|
||||
<WebAudioContext.Provider value={webAudioProvider}>
|
||||
|
|
|
|||
|
|
@ -47,10 +47,7 @@ export const CardControls = ({
|
|||
return (
|
||||
<div className={styles.gridCardControlsContainer}>
|
||||
<div className={styles.bottomControls}>
|
||||
<button
|
||||
className={styles.playButton}
|
||||
onClick={handlePlay}
|
||||
>
|
||||
<button className={styles.playButton} onClick={handlePlay}>
|
||||
<Icon icon="mediaPlay" />
|
||||
</button>
|
||||
<Group gap="xs">
|
||||
|
|
|
|||
|
|
@ -55,14 +55,8 @@ export const PosterCard = ({
|
|||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Link
|
||||
className={styles.imageContainer}
|
||||
to={path}
|
||||
>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={data?.imageUrl}
|
||||
/>
|
||||
<Link className={styles.imageContainer} to={path}>
|
||||
<Image className={styles.image} src={data?.imageUrl} />
|
||||
<GridCardControls
|
||||
handleFavorite={controls.handleFavorite}
|
||||
handlePlayQueueAdd={controls.handlePlayQueueAdd}
|
||||
|
|
@ -72,30 +66,21 @@ export const PosterCard = ({
|
|||
/>
|
||||
</Link>
|
||||
<div className={styles.detailContainer}>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={controls.cardRows}
|
||||
/>
|
||||
<CardRows data={data} rows={controls.cardRows} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
key={`placeholder-${uniqueId}-${data.id}`}
|
||||
>
|
||||
<div className={styles.container} key={`placeholder-${uniqueId}-${data.id}`}>
|
||||
<div className={styles.imageContainer}>
|
||||
<Skeleton className={styles.image} />
|
||||
</div>
|
||||
<div className={styles.detailContainer}>
|
||||
<Stack gap="xs">
|
||||
{(controls?.cardRows || []).map((row, index) => (
|
||||
<Skeleton
|
||||
height={14}
|
||||
key={`${index}-${row.arrayProperty}`}
|
||||
/>
|
||||
<Skeleton height={14} key={`${index}-${row.arrayProperty}`} />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,14 +35,8 @@ export const ContextMenuButton = forwardRef(
|
|||
onClick={props.onClick}
|
||||
ref={ref}
|
||||
>
|
||||
<Group
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Group
|
||||
className={styles.left}
|
||||
gap="md"
|
||||
>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Group className={styles.left} gap="md">
|
||||
{leftIcon}
|
||||
{children}
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -77,11 +77,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
|||
className={styles.wrapper}
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: currentItem?.id || '' })}
|
||||
>
|
||||
<AnimatePresence
|
||||
custom={direction}
|
||||
initial={false}
|
||||
mode="popLayout"
|
||||
>
|
||||
<AnimatePresence custom={direction} initial={false} mode="popLayout">
|
||||
{data && (
|
||||
<motion.div
|
||||
animate="animate"
|
||||
|
|
@ -101,10 +97,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
|||
/>
|
||||
</div>
|
||||
<div className={styles.infoColumn}>
|
||||
<Stack
|
||||
gap="md"
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Stack gap="md" style={{ width: '100%' }}>
|
||||
<div className={styles.titleWrapper}>
|
||||
<TextTitle
|
||||
fw={900}
|
||||
|
|
@ -117,10 +110,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
|||
</div>
|
||||
<div className={styles.titleWrapper}>
|
||||
{currentItem?.albumArtists.slice(0, 1).map((artist) => (
|
||||
<Text
|
||||
fw={600}
|
||||
key={`carousel-artist-${artist.id}`}
|
||||
>
|
||||
<Text fw={600} key={`carousel-artist-${artist.id}`}>
|
||||
{artist.name}
|
||||
</Text>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -60,10 +60,7 @@ const Title = ({ handleNext, handlePrev, label, pagination }: TitleProps) => {
|
|||
{isValidElement(label) ? (
|
||||
label
|
||||
) : (
|
||||
<TextTitle
|
||||
order={3}
|
||||
weight={700}
|
||||
>
|
||||
<TextTitle order={3} weight={700}>
|
||||
{label}
|
||||
</TextTitle>
|
||||
)}
|
||||
|
|
@ -280,11 +277,7 @@ export const SwiperGridCarousel = ({
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="grid-carousel"
|
||||
gap="md"
|
||||
ref={containerRef as any}
|
||||
>
|
||||
<Stack className="grid-carousel" gap="md" ref={containerRef as any}>
|
||||
{title ? (
|
||||
<Title
|
||||
{...title}
|
||||
|
|
|
|||
|
|
@ -91,11 +91,7 @@ export const NativeScrollArea = forwardRef(
|
|||
{...pageHeaderProps}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={styles.scrollArea}
|
||||
ref={mergedRef}
|
||||
{...props}
|
||||
>
|
||||
<div className={styles.scrollArea} ref={mergedRef} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -99,10 +99,7 @@ export const QueryBuilder = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap="sm"
|
||||
ml={`${level * 10}px`}
|
||||
>
|
||||
<Stack gap="sm" ml={`${level * 10}px`}>
|
||||
<Group gap="sm">
|
||||
<Select
|
||||
data={FILTER_GROUP_OPTIONS_DATA}
|
||||
|
|
@ -112,12 +109,7 @@ export const QueryBuilder = ({
|
|||
value={data.type}
|
||||
width="20%"
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="add"
|
||||
onClick={handleAddRule}
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon icon="add" onClick={handleAddRule} size="sm" variant="subtle" />
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<ActionIcon
|
||||
|
|
@ -150,24 +142,14 @@ export const QueryBuilder = ({
|
|||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item
|
||||
isDanger
|
||||
leftSection={
|
||||
<Icon
|
||||
color="error"
|
||||
icon="refresh"
|
||||
/>
|
||||
}
|
||||
leftSection={<Icon color="error" icon="refresh" />}
|
||||
onClick={onResetFilters}
|
||||
>
|
||||
Reset to default
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
isDanger
|
||||
leftSection={
|
||||
<Icon
|
||||
color="error"
|
||||
icon="delete"
|
||||
/>
|
||||
}
|
||||
leftSection={<Icon color="error" icon="delete" />}
|
||||
onClick={onClearFilters}
|
||||
>
|
||||
Clear filters
|
||||
|
|
|
|||
|
|
@ -48,13 +48,7 @@ const QueryValueInput = ({ data, onChange, type, ...props }: any) => {
|
|||
/>
|
||||
);
|
||||
case 'date':
|
||||
return (
|
||||
<TextInput
|
||||
onChange={onChange}
|
||||
size="sm"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return <TextInput onChange={onChange} size="sm" {...props} />;
|
||||
case 'dateRange':
|
||||
return (
|
||||
<>
|
||||
|
|
@ -92,21 +86,9 @@ const QueryValueInput = ({ data, onChange, type, ...props }: any) => {
|
|||
/>
|
||||
);
|
||||
case 'playlist':
|
||||
return (
|
||||
<Select
|
||||
data={data}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return <Select data={data} onChange={onChange} {...props} />;
|
||||
case 'string':
|
||||
return (
|
||||
<TextInput
|
||||
onChange={onChange}
|
||||
size="sm"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return <TextInput onChange={onChange} size="sm" {...props} />;
|
||||
|
||||
default:
|
||||
return <></>;
|
||||
|
|
@ -188,10 +170,7 @@ export const QueryBuilderOption = ({
|
|||
const ml = (level + 1) * 10;
|
||||
|
||||
return (
|
||||
<Group
|
||||
gap="sm"
|
||||
ml={ml}
|
||||
>
|
||||
<Group gap="sm" ml={ml}>
|
||||
<Select
|
||||
data={filters}
|
||||
maxWidth={170}
|
||||
|
|
|
|||
|
|
@ -81,10 +81,7 @@ export const DefaultCard = ({
|
|||
data?.userFavorite && styles.isFavorite,
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={data?.imageUrl}
|
||||
/>
|
||||
<Image className={styles.image} src={data?.imageUrl} />
|
||||
<GridCardControls
|
||||
handleFavorite={controls.handleFavorite}
|
||||
handlePlayQueueAdd={controls.handlePlayQueueAdd}
|
||||
|
|
@ -95,10 +92,7 @@ export const DefaultCard = ({
|
|||
/>
|
||||
</div>
|
||||
<div className={styles.detailContainer}>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={controls.cardRows}
|
||||
/>
|
||||
<CardRows data={data} rows={controls.cardRows} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -86,10 +86,7 @@ export const GridCardControls = ({
|
|||
onClick={handlePlay}
|
||||
variant="filled"
|
||||
>
|
||||
<Icon
|
||||
icon="mediaPlay"
|
||||
size="xl"
|
||||
/>
|
||||
<Icon icon="mediaPlay" size="xl" />
|
||||
</Button>
|
||||
<div className={styles.bottomControls}>
|
||||
{itemType !== LibraryItem.PLAYLIST && (
|
||||
|
|
|
|||
|
|
@ -73,17 +73,11 @@ export const PosterCard = ({
|
|||
margin: controls.itemGap,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.linkContainer}
|
||||
onClick={() => navigate(path)}
|
||||
>
|
||||
<div className={styles.linkContainer} onClick={() => navigate(path)}>
|
||||
<div
|
||||
className={`${styles.imageContainer} ${data?.userFavorite ? styles.isFavorite : ''}`}
|
||||
>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={data?.imageUrl}
|
||||
/>
|
||||
<Image className={styles.image} src={data?.imageUrl} />
|
||||
<GridCardControls
|
||||
handleFavorite={controls.handleFavorite}
|
||||
handlePlayQueueAdd={controls.handlePlayQueueAdd}
|
||||
|
|
@ -95,10 +89,7 @@ export const PosterCard = ({
|
|||
</div>
|
||||
</div>
|
||||
<div className={styles.detailContainer}>
|
||||
<CardRows
|
||||
data={data}
|
||||
rows={controls.cardRows}
|
||||
/>
|
||||
<CardRows data={data} rows={controls.cardRows} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,21 +15,14 @@ export const AlbumArtistCell = ({ data, value }: ICellRendererParams) => {
|
|||
if (value === undefined) {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Skeleton
|
||||
height="1rem"
|
||||
width="80%"
|
||||
/>
|
||||
<Skeleton height="1rem" width="80%" />
|
||||
</CellContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted overflow="hidden" size="md">
|
||||
{value?.map((item: AlbumArtist | Artist, index: number) => (
|
||||
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
|
||||
{index > 0 && <Separator />}
|
||||
|
|
@ -47,11 +40,7 @@ export const AlbumArtistCell = ({ data, value }: ICellRendererParams) => {
|
|||
{item.name || '—'}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted overflow="hidden" size="md">
|
||||
{item.name || '—'}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -15,21 +15,14 @@ export const ArtistCell = ({ data, value }: ICellRendererParams) => {
|
|||
if (value === undefined) {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Skeleton
|
||||
height="1rem"
|
||||
width="80%"
|
||||
/>
|
||||
<Skeleton height="1rem" width="80%" />
|
||||
</CellContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted overflow="hidden" size="md">
|
||||
{value?.map((item: AlbumArtist | Artist, index: number) => (
|
||||
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
|
||||
{index > 0 && <Separator />}
|
||||
|
|
@ -47,11 +40,7 @@ export const ArtistCell = ({ data, value }: ICellRendererParams) => {
|
|||
{item.name || '—'}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted overflow="hidden" size="md">
|
||||
{item.name || '—'}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -41,11 +41,7 @@ export const CombinedTitleCell = ({
|
|||
>
|
||||
<Skeleton className={styles.image} />
|
||||
</div>
|
||||
<Skeleton
|
||||
className={styles.skeletonMetadata}
|
||||
height="1rem"
|
||||
width="80%"
|
||||
/>
|
||||
<Skeleton className={styles.skeletonMetadata} height="1rem" width="80%" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -62,11 +58,7 @@ export const CombinedTitleCell = ({
|
|||
width: `${(node.rowHeight || 40) - 10}px`,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
alt="cover"
|
||||
className={styles.image}
|
||||
src={value.imageUrl}
|
||||
/>
|
||||
<Image alt="cover" className={styles.image} src={value.imageUrl} />
|
||||
|
||||
<ListCoverControls
|
||||
className={styles.playButton}
|
||||
|
|
@ -77,18 +69,10 @@ export const CombinedTitleCell = ({
|
|||
/>
|
||||
</div>
|
||||
<div className={styles.metadataWrapper}>
|
||||
<Text
|
||||
className="current-song-child"
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text className="current-song-child" overflow="hidden" size="md">
|
||||
{value.name}
|
||||
</Text>
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted overflow="hidden" size="md">
|
||||
{artists?.length ? (
|
||||
artists.map((artist: AlbumArtist | Artist, index: number) => (
|
||||
<React.Fragment key={`queue-${rowIndex}-artist-${artist.id}`}>
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ export const FullWidthDiscCell = ({ api, data, node }: ICellRendererParams) => {
|
|||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Group
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Button
|
||||
leftSection={isSelected ? <Icon icon="squareCheck" /> : <Icon icon="square" />}
|
||||
onClick={handleToggleDiscNodes}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ export const GenericCell = ({ value, valueFormatted }: ICellRendererParams, opti
|
|||
if (value === undefined) {
|
||||
return (
|
||||
<CellContainer position={position || 'left'}>
|
||||
<Skeleton
|
||||
height="1rem"
|
||||
width="80%"
|
||||
/>
|
||||
<Skeleton height="1rem" width="80%" />
|
||||
</CellContainer>
|
||||
);
|
||||
}
|
||||
|
|
@ -45,12 +42,7 @@ export const GenericCell = ({ value, valueFormatted }: ICellRendererParams, opti
|
|||
{isLink ? displayedValue.value : displayedValue}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
isMuted={!primary}
|
||||
isNoSelect={false}
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted={!primary} isNoSelect={false} overflow="hidden" size="md">
|
||||
{displayedValue}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ export const GenreCell = ({ data, value }: ICellRendererParams) => {
|
|||
const genrePath = useGenreRoute();
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted overflow="hidden" size="md">
|
||||
{value?.map((item: AlbumArtist | Artist, index: number) => (
|
||||
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
|
||||
{index > 0 && <Separator />}
|
||||
|
|
|
|||
|
|
@ -19,20 +19,14 @@ export const NoteCell = ({ value }: ICellRendererParams) => {
|
|||
if (value === undefined) {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Skeleton
|
||||
height="1rem"
|
||||
width="80%"
|
||||
/>
|
||||
<Skeleton height="1rem" width="80%" />
|
||||
</CellContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
>
|
||||
<Text isMuted overflow="hidden">
|
||||
{formattedValue}
|
||||
</Text>
|
||||
</CellContainer>
|
||||
|
|
|
|||
|
|
@ -26,11 +26,7 @@ export const RatingCell = ({ node, value }: ICellRendererParams) => {
|
|||
|
||||
return (
|
||||
<CellContainer position="center">
|
||||
<Rating
|
||||
onChange={handleUpdateRating}
|
||||
size="xs"
|
||||
value={value?.userRating}
|
||||
/>
|
||||
<Rating onChange={handleUpdateRating} size="xs" value={value?.userRating} />
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -144,15 +144,9 @@ export const RowIndexCell = ({ eGridCell, value }: ICellRendererParams) => {
|
|||
return (
|
||||
<CellContainer position="right">
|
||||
{isPlaying && isCurrentSong ? (
|
||||
<Icon
|
||||
fill="primary"
|
||||
icon="mediaPlay"
|
||||
/>
|
||||
<Icon fill="primary" icon="mediaPlay" />
|
||||
) : isCurrentSong ? (
|
||||
<Icon
|
||||
fill="primary"
|
||||
icon="mediaPause"
|
||||
/>
|
||||
<Icon fill="primary" icon="mediaPause" />
|
||||
) : (
|
||||
<Text
|
||||
className="current-song-child current-song-index"
|
||||
|
|
|
|||
|
|
@ -8,21 +8,14 @@ export const TitleCell = ({ value }: ICellRendererParams) => {
|
|||
if (value === undefined) {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Skeleton
|
||||
height="1rem"
|
||||
width="80%"
|
||||
/>
|
||||
<Skeleton height="1rem" width="80%" />
|
||||
</CellContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
className="current-song-child"
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
<Text className="current-song-child" overflow="hidden" size="md">
|
||||
{value}
|
||||
</Text>
|
||||
</CellContainer>
|
||||
|
|
|
|||
|
|
@ -7,10 +7,5 @@ export interface ICustomHeaderParams extends IHeaderParams {
|
|||
}
|
||||
|
||||
export const DurationHeader = () => {
|
||||
return (
|
||||
<Icon
|
||||
icon="duration"
|
||||
size="sm"
|
||||
/>
|
||||
);
|
||||
return <Icon icon="duration" size="sm" />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,36 +16,11 @@ type Options = {
|
|||
type Presets = 'actions' | 'duration' | 'rowIndex' | 'userFavorite' | 'userRating';
|
||||
|
||||
const headerPresets = {
|
||||
actions: (
|
||||
<Icon
|
||||
icon="ellipsisHorizontal"
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
duration: (
|
||||
<Icon
|
||||
icon="duration"
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
rowIndex: (
|
||||
<Icon
|
||||
icon="hash"
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
userFavorite: (
|
||||
<Icon
|
||||
icon="favorite"
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
userRating: (
|
||||
<Icon
|
||||
icon="star"
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
actions: <Icon icon="ellipsisHorizontal" size="sm" />,
|
||||
duration: <Icon icon="duration" size="sm" />,
|
||||
rowIndex: <Icon icon="hash" size="sm" />,
|
||||
userFavorite: <Icon icon="favorite" size="sm" />,
|
||||
userRating: <Icon icon="star" size="sm" />,
|
||||
};
|
||||
|
||||
export const GenericTableHeader = (
|
||||
|
|
|
|||
|
|
@ -635,15 +635,8 @@ export const VirtualTable = forwardRef(
|
|||
onNewColumnsLoaded={handleNewColumnsLoaded}
|
||||
/>
|
||||
{paginationProps && (
|
||||
<AnimatePresence
|
||||
initial={false}
|
||||
mode="wait"
|
||||
presenceAffectsLayout
|
||||
>
|
||||
<TablePagination
|
||||
{...paginationProps}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<AnimatePresence initial={false} mode="wait" presenceAffectsLayout>
|
||||
<TablePagination {...paginationProps} tableRef={tableRef} />
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -76,10 +76,7 @@ export const TablePagination = ({
|
|||
ref={containerQuery.ref}
|
||||
style={{ borderTop: '1px solid var(--theme-generic-border-color)' }}
|
||||
>
|
||||
<Text
|
||||
isMuted
|
||||
size="md"
|
||||
>
|
||||
<Text isMuted size="md">
|
||||
{containerQuery.isMd ? (
|
||||
<>
|
||||
Showing <b>{currentPageStartIndex}</b> - <b>{currentPageStopIndex}</b> of{' '}
|
||||
|
|
@ -97,11 +94,7 @@ export const TablePagination = ({
|
|||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={containerQuery.ref}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap="sm" ref={containerQuery.ref} wrap="nowrap">
|
||||
<Popover
|
||||
onClose={() => handlers.close()}
|
||||
opened={isGoToPageOpen}
|
||||
|
|
@ -127,10 +120,7 @@ export const TablePagination = ({
|
|||
min={1}
|
||||
width={70}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="filled"
|
||||
>
|
||||
<Button type="submit" variant="filled">
|
||||
Go
|
||||
</Button>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -13,15 +13,8 @@ interface ActionRequiredContainerProps {
|
|||
export const ActionRequiredContainer = ({ children, title }: ActionRequiredContainerProps) => (
|
||||
<Stack style={{ cursor: 'default', maxWidth: '700px' }}>
|
||||
<Group>
|
||||
<Icon
|
||||
fill="warn"
|
||||
icon="warn"
|
||||
size="lg"
|
||||
/>
|
||||
<Text
|
||||
size="xl"
|
||||
style={{ textTransform: 'uppercase' }}
|
||||
>
|
||||
<Icon fill="warn" icon="warn" size="lg" />
|
||||
<Text size="xl" style={{ textTransform: 'uppercase' }}>
|
||||
{title}
|
||||
</Text>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -21,18 +21,11 @@ export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => {
|
|||
<Center style={{ height: '100vh' }}>
|
||||
<Stack style={{ maxWidth: '50%' }}>
|
||||
<Group gap="xs">
|
||||
<Icon
|
||||
fill="error"
|
||||
icon="error"
|
||||
size="lg"
|
||||
/>
|
||||
<Icon fill="error" icon="error" size="lg" />
|
||||
<Text size="lg">{t('error.genericError')}</Text>
|
||||
</Group>
|
||||
<Text>{error?.message}</Text>
|
||||
<Button
|
||||
onClick={resetErrorBoundary}
|
||||
variant="filled"
|
||||
>
|
||||
<Button onClick={resetErrorBoundary} variant="filled">
|
||||
{t('common.reload')}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -43,18 +43,11 @@ export const MpvRequired = () => {
|
|||
<Text>Set your MPV executable location below and restart the application.</Text>
|
||||
<Text>
|
||||
MPV is available at the following:{' '}
|
||||
<a
|
||||
href="https://mpv.io/installation/"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<a href="https://mpv.io/installation/" rel="noreferrer" target="_blank">
|
||||
https://mpv.io/
|
||||
</a>
|
||||
</Text>
|
||||
<FileInput
|
||||
disabled={disabled}
|
||||
onChange={handleSetMpvPath}
|
||||
/>
|
||||
<FileInput disabled={disabled} onChange={handleSetMpvPath} />
|
||||
<Text>{t('setting.disable_mpv', { context: 'description' })}</Text>
|
||||
<Checkbox
|
||||
label={t('setting.disableMpv')}
|
||||
|
|
|
|||
|
|
@ -42,19 +42,12 @@ const RouteErrorBoundary = () => {
|
|||
px={10}
|
||||
variant="subtle"
|
||||
/>
|
||||
<Icon
|
||||
fill="error"
|
||||
icon="error"
|
||||
size="lg"
|
||||
/>
|
||||
<Icon fill="error" icon="error" size="lg" />
|
||||
<Text size="lg">{t('error.genericError')}</Text>
|
||||
</Group>
|
||||
<Divider my={5} />
|
||||
<Text size="sm">{error?.message}</Text>
|
||||
<Group
|
||||
gap="sm"
|
||||
grow
|
||||
>
|
||||
<Group gap="sm" grow>
|
||||
<Button
|
||||
leftSection={<Icon icon="home" />}
|
||||
onClick={handleHome}
|
||||
|
|
@ -81,11 +74,7 @@ const RouteErrorBoundary = () => {
|
|||
</DropdownMenu>
|
||||
</Group>
|
||||
<Group grow>
|
||||
<Button
|
||||
onClick={handleReload}
|
||||
size="md"
|
||||
variant="filled"
|
||||
>
|
||||
<Button onClick={handleReload} size="md" variant="filled">
|
||||
{t('common.reload')}
|
||||
</Button>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -132,10 +132,7 @@ function ServerSelector() {
|
|||
}}
|
||||
variant={server.id === currentServer?.id ? 'filled' : 'default'}
|
||||
>
|
||||
<Group
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Group justify="space-between" w="100%">
|
||||
<Group>
|
||||
<img
|
||||
src={logo}
|
||||
|
|
@ -144,10 +141,7 @@ function ServerSelector() {
|
|||
width: 'var(--theme-font-size-2xl)',
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
fw={600}
|
||||
size="lg"
|
||||
>
|
||||
<Text fw={600} size="lg">
|
||||
{server.name}
|
||||
</Text>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -49,10 +49,7 @@ const ActionRequiredRoute = () => {
|
|||
<AnimatedPage>
|
||||
<PageHeader />
|
||||
<Center style={{ height: '100%', width: '100vw' }}>
|
||||
<Stack
|
||||
gap="xl"
|
||||
style={{ maxWidth: '50%' }}
|
||||
>
|
||||
<Stack gap="xl" style={{ maxWidth: '50%' }}>
|
||||
<Group wrap="nowrap">
|
||||
{displayedCheck && (
|
||||
<ActionRequiredContainer title={displayedCheck.title}>
|
||||
|
|
@ -64,10 +61,7 @@ const ActionRequiredRoute = () => {
|
|||
{canReturnHome && <Navigate to={AppRoute.HOME} />}
|
||||
{/* This should be displayed if a credential is required */}
|
||||
{isCredentialRequired && (
|
||||
<Group
|
||||
justify="center"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group justify="center" wrap="nowrap">
|
||||
<Button
|
||||
fullWidth
|
||||
leftSection={<Icon icon="edit" />}
|
||||
|
|
|
|||
|
|
@ -18,24 +18,14 @@ const InvalidRoute = () => {
|
|||
<AnimatedPage>
|
||||
<Center style={{ height: '100%', width: '100%' }}>
|
||||
<Stack>
|
||||
<Group
|
||||
justify="center"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Icon
|
||||
color="warn"
|
||||
icon="error"
|
||||
/>
|
||||
<Group justify="center" wrap="nowrap">
|
||||
<Icon color="warn" icon="error" />
|
||||
<Text size="xl">
|
||||
{t('error.apiRouteError', { postProcess: 'sentenceCase' })}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text>{location.pathname}</Text>
|
||||
<ActionIcon
|
||||
icon="arrowLeftS"
|
||||
onClick={() => navigate(-1)}
|
||||
variant="filled"
|
||||
/>
|
||||
<ActionIcon icon="arrowLeftS" onClick={() => navigate(-1)} variant="filled" />
|
||||
</Stack>
|
||||
</Center>
|
||||
</AnimatedPage>
|
||||
|
|
|
|||
|
|
@ -319,17 +319,11 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
|||
const mbzId = detailQuery?.data?.mbzId;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.contentContainer}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<div className={styles.contentContainer} ref={cq.ref}>
|
||||
<LibraryBackgroundOverlay backgroundColor={background} />
|
||||
<div className={styles.detailContainer}>
|
||||
<section>
|
||||
<Group
|
||||
gap="sm"
|
||||
justify="space-between"
|
||||
>
|
||||
<Group gap="sm" justify="space-between">
|
||||
<Group>
|
||||
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||
<Group gap="xs">
|
||||
|
|
@ -485,11 +479,7 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
|||
suppressRowDrag
|
||||
/>
|
||||
</div>
|
||||
<Stack
|
||||
gap="lg"
|
||||
mt="3rem"
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap="lg" mt="3rem" ref={cq.ref}>
|
||||
{cq.height || cq.width ? (
|
||||
<>
|
||||
{carousels
|
||||
|
|
|
|||
|
|
@ -33,15 +33,9 @@ export const AlbumListContent = ({ gridRef, itemCount, tableRef }: AlbumListCont
|
|||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
{display === ListDisplayType.CARD || display === ListDisplayType.GRID ? (
|
||||
<AlbumListGridView
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
/>
|
||||
<AlbumListGridView gridRef={gridRef} itemCount={itemCount} />
|
||||
) : (
|
||||
<AlbumListTableView
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<AlbumListTableView itemCount={itemCount} tableRef={tableRef} />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -448,11 +448,7 @@ export const AlbumListHeaderFilters = ({
|
|||
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={cq.ref}
|
||||
w="100%"
|
||||
>
|
||||
<Group gap="sm" ref={cq.ref} w="100%">
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button variant="subtle">{sortByLabel}</Button>
|
||||
|
|
@ -471,10 +467,7 @@ export const AlbumListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
<OrderToggleButton onToggle={handleToggleSortOrder} sortOrder={filter.sortOrder} />
|
||||
{server?.type === ServerType.JELLYFIN && (
|
||||
<>
|
||||
<Divider orientation="vertical" />
|
||||
|
|
@ -497,10 +490,7 @@ export const AlbumListHeaderFilters = ({
|
|||
</DropdownMenu>
|
||||
</>
|
||||
)}
|
||||
<FilterButton
|
||||
isActive={!!isFilterApplied}
|
||||
onClick={handleOpenFiltersModal}
|
||||
/>
|
||||
<FilterButton isActive={!!isFilterApplied} onClick={handleOpenFiltersModal} />
|
||||
<RefreshButton onClick={handleRefresh} />
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
|
|
@ -535,10 +525,7 @@ export const AlbumListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ListConfigMenu
|
||||
autoFitColumns={table.autoFit}
|
||||
disabledViewTypes={[ListDisplayType.LIST]}
|
||||
|
|
|
|||
|
|
@ -61,15 +61,9 @@ export const AlbumListHeader = ({
|
|||
}, [filter, genreId, refresh, tableRef]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={0}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap={0} ref={cq.ref}>
|
||||
<PageHeader backgroundColor="var(--theme-colors-background)">
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Flex justify="space-between" w="100%">
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.PlayButton
|
||||
onClick={() => handlePlay?.({ playType: playButtonBehavior })}
|
||||
|
|
@ -85,10 +79,7 @@ export const AlbumListHeader = ({
|
|||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
defaultValue={filter.searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<SearchInput defaultValue={filter.searchTerm} onChange={handleSearch} />
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
|
|
|
|||
|
|
@ -227,16 +227,9 @@ export const JellyfinAlbumFilters = ({
|
|||
return (
|
||||
<Stack p="0.8rem">
|
||||
{yesNoFilter.map((filter) => (
|
||||
<Group
|
||||
justify="space-between"
|
||||
key={`nd-filter-${filter.label}`}
|
||||
>
|
||||
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
|
||||
<Text>{filter.label}</Text>
|
||||
<YesNoSelect
|
||||
onChange={filter.onChange}
|
||||
size="xs"
|
||||
value={filter.value}
|
||||
/>
|
||||
<YesNoSelect onChange={filter.onChange} size="xs" value={filter.value} />
|
||||
</Group>
|
||||
))}
|
||||
<Divider my="0.5rem" />
|
||||
|
|
|
|||
|
|
@ -248,28 +248,15 @@ export const NavidromeAlbumFilters = ({
|
|||
return (
|
||||
<Stack p="0.8rem">
|
||||
{yesNoUndefinedFilters.map((filter) => (
|
||||
<Group
|
||||
justify="space-between"
|
||||
key={`nd-filter-${filter.label}`}
|
||||
>
|
||||
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
|
||||
<Text>{filter.label}</Text>
|
||||
<YesNoSelect
|
||||
onChange={filter.onChange}
|
||||
size="xs"
|
||||
value={filter.value}
|
||||
/>
|
||||
<YesNoSelect onChange={filter.onChange} size="xs" value={filter.value} />
|
||||
</Group>
|
||||
))}
|
||||
{toggleFilters.map((filter) => (
|
||||
<Group
|
||||
justify="space-between"
|
||||
key={`nd-filter-${filter.label}`}
|
||||
>
|
||||
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
|
||||
<Text>{filter.label}</Text>
|
||||
<Switch
|
||||
checked={filter?.value || false}
|
||||
onChange={filter.onChange}
|
||||
/>
|
||||
<Switch checked={filter?.value || false} onChange={filter.onChange} />
|
||||
</Group>
|
||||
))}
|
||||
<Divider my="0.5rem" />
|
||||
|
|
@ -307,10 +294,7 @@ export const NavidromeAlbumFilters = ({
|
|||
{tagsQuery.data?.enumTags?.length &&
|
||||
tagsQuery.data.enumTags.length > 0 &&
|
||||
tagsQuery.data.enumTags.map((tag) => (
|
||||
<Group
|
||||
grow
|
||||
key={tag.name}
|
||||
>
|
||||
<Group grow key={tag.name}>
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
data={tag.options}
|
||||
|
|
|
|||
|
|
@ -148,15 +148,9 @@ export const SubsonicAlbumFilters = ({
|
|||
return (
|
||||
<Stack p="0.8rem">
|
||||
{toggleFilters.map((filter) => (
|
||||
<Group
|
||||
justify="space-between"
|
||||
key={`nd-filter-${filter.label}`}
|
||||
>
|
||||
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
|
||||
<Text>{filter.label}</Text>
|
||||
<Switch
|
||||
checked={filter?.value || false}
|
||||
onChange={filter.onChange}
|
||||
/>
|
||||
<Switch checked={filter?.value || false} onChange={filter.onChange} />
|
||||
</Group>
|
||||
))}
|
||||
<Divider my="0.5rem" />
|
||||
|
|
|
|||
|
|
@ -70,10 +70,7 @@ const AlbumDetailRoute = () => {
|
|||
}}
|
||||
ref={headerRef}
|
||||
/>
|
||||
<AlbumDetailContent
|
||||
background={background}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<AlbumDetailContent background={background} tableRef={tableRef} />
|
||||
</NativeScrollArea>
|
||||
</AnimatedPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -144,11 +144,7 @@ const AlbumListRoute = () => {
|
|||
tableRef={tableRef}
|
||||
title={title}
|
||||
/>
|
||||
<AlbumListContent
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<AlbumListContent gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
</ListContext.Provider>
|
||||
</AnimatedPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -174,10 +174,7 @@ const DummyAlbumDetailRoute = () => {
|
|||
</Stack>
|
||||
<div className={styles.detailContainer}>
|
||||
<section>
|
||||
<Group
|
||||
gap="sm"
|
||||
justify="space-between"
|
||||
>
|
||||
<Group gap="sm" justify="space-between">
|
||||
<Group>
|
||||
<PlayButton onClick={() => handlePlay()} />
|
||||
<ActionIcon
|
||||
|
|
@ -231,11 +228,7 @@ const DummyAlbumDetailRoute = () => {
|
|||
<section>
|
||||
<Center>
|
||||
<Group mr={5}>
|
||||
<Icon
|
||||
fill="error"
|
||||
icon="error"
|
||||
size={30}
|
||||
/>
|
||||
<Icon fill="error" icon="error" size={30} />
|
||||
</Group>
|
||||
<h2>{t('error.badAlbum', { postProcess: 'sentenceCase' })}</h2>
|
||||
</Center>
|
||||
|
|
|
|||
|
|
@ -202,10 +202,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||
order: itemOrder.recentAlbums,
|
||||
title: (
|
||||
<Group align="flex-end">
|
||||
<TextTitle
|
||||
fw={700}
|
||||
order={2}
|
||||
>
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.recentReleases', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
|
|
@ -232,10 +229,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||
loading: compilationAlbumsQuery?.isLoading || compilationAlbumsQuery.isFetching,
|
||||
order: itemOrder.compilations,
|
||||
title: (
|
||||
<TextTitle
|
||||
fw={700}
|
||||
order={2}
|
||||
>
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.appearsOn', { postProcess: 'sentenceCase' })}
|
||||
</TextTitle>
|
||||
),
|
||||
|
|
@ -247,10 +241,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||
itemType: LibraryItem.ALBUM_ARTIST,
|
||||
order: itemOrder.similarArtists,
|
||||
title: (
|
||||
<TextTitle
|
||||
fw={700}
|
||||
order={2}
|
||||
>
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.relatedArtists', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
|
|
@ -355,19 +346,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||
detailQuery?.isLoading ||
|
||||
(server?.type === ServerType.NAVIDROME && enabledItem.topSongs && topSongsQuery?.isLoading);
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div
|
||||
className={styles.contentContainer}
|
||||
ref={cq.ref}
|
||||
/>
|
||||
);
|
||||
if (isLoading) return <div className={styles.contentContainer} ref={cq.ref} />;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.contentContainer}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<div className={styles.contentContainer} ref={cq.ref}>
|
||||
<LibraryBackgroundOverlay backgroundColor={background} />
|
||||
<div className={styles.detailContainer}>
|
||||
<Group gap="md">
|
||||
|
|
@ -481,15 +463,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||
) : null}
|
||||
<Grid gutter="xl">
|
||||
{biography ? (
|
||||
<Grid.Col
|
||||
order={itemOrder.biography}
|
||||
span={12}
|
||||
>
|
||||
<Grid.Col order={itemOrder.biography} span={12}>
|
||||
<section style={{ maxWidth: '1280px' }}>
|
||||
<TextTitle
|
||||
fw={700}
|
||||
order={2}
|
||||
>
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.about', {
|
||||
artist: detailQuery?.data?.name,
|
||||
})}
|
||||
|
|
@ -499,23 +475,11 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||
</Grid.Col>
|
||||
) : null}
|
||||
{showTopSongs ? (
|
||||
<Grid.Col
|
||||
order={itemOrder.topSongs}
|
||||
span={12}
|
||||
>
|
||||
<Grid.Col order={itemOrder.topSongs} span={12}>
|
||||
<section>
|
||||
<Group
|
||||
justify="space-between"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group
|
||||
align="flex-end"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<TextTitle
|
||||
fw={700}
|
||||
order={2}
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group align="flex-end" wrap="nowrap">
|
||||
<TextTitle fw={700} order={2}>
|
||||
{t('page.albumArtistDetail.topSongs', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -42,15 +42,9 @@ export const AlbumArtistListContent = ({
|
|||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
{isGrid ? (
|
||||
<AlbumArtistListGridView
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
/>
|
||||
<AlbumArtistListGridView gridRef={gridRef} itemCount={itemCount} />
|
||||
) : (
|
||||
<AlbumArtistListTableView
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<AlbumArtistListTableView itemCount={itemCount} tableRef={tableRef} />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -372,11 +372,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={cq.ref}
|
||||
w="100%"
|
||||
>
|
||||
<Group gap="sm" ref={cq.ref} w="100%">
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button variant="subtle">{sortByLabel}</Button>
|
||||
|
|
@ -395,10 +391,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
<OrderToggleButton onToggle={handleToggleSortOrder} sortOrder={filter.sortOrder} />
|
||||
{server?.type === ServerType.JELLYFIN && (
|
||||
<>
|
||||
<DropdownMenu position="bottom-start">
|
||||
|
|
@ -437,10 +430,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ListConfigMenu
|
||||
autoFitColumns={table.autoFit}
|
||||
disabledViewTypes={[ListDisplayType.LIST]}
|
||||
|
|
|
|||
|
|
@ -46,15 +46,9 @@ export const AlbumArtistListHeader = ({
|
|||
}, 500);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={0}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap={0} ref={cq.ref}>
|
||||
<PageHeader>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Flex justify="space-between" w="100%">
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>
|
||||
{t('page.albumArtistList.title', { postProcess: 'titleCase' })}
|
||||
|
|
@ -66,18 +60,12 @@ export const AlbumArtistListHeader = ({
|
|||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
defaultValue={filter.searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<SearchInput defaultValue={filter.searchTerm} onChange={handleSearch} />
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
<FilterBar>
|
||||
<AlbumArtistListHeaderFilters
|
||||
gridRef={gridRef}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<AlbumArtistListHeaderFilters gridRef={gridRef} tableRef={tableRef} />
|
||||
</FilterBar>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -34,15 +34,9 @@ export const ArtistListContent = ({ gridRef, itemCount, tableRef }: ArtistListCo
|
|||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
{isGrid ? (
|
||||
<ArtistListGridView
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
/>
|
||||
<ArtistListGridView gridRef={gridRef} itemCount={itemCount} />
|
||||
) : (
|
||||
<ArtistListTableView
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<ArtistListTableView itemCount={itemCount} tableRef={tableRef} />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -388,11 +388,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
|||
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={cq.ref}
|
||||
w="100%"
|
||||
>
|
||||
<Group gap="sm" ref={cq.ref} w="100%">
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button variant="subtle">{sortByLabel}</Button>
|
||||
|
|
@ -411,19 +407,13 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
<OrderToggleButton onToggle={handleToggleSortOrder} sortOrder={filter.sortOrder} />
|
||||
{server?.type === ServerType.JELLYFIN && (
|
||||
<>
|
||||
<Divider orientation="vertical" />
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<ActionIcon
|
||||
icon="folder"
|
||||
variant="subtle"
|
||||
/>
|
||||
<ActionIcon icon="folder" variant="subtle" />
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
{musicFoldersQuery.data?.items.map((folder) => (
|
||||
|
|
@ -442,11 +432,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
|||
)}
|
||||
{roles.data?.length && (
|
||||
<>
|
||||
<Select
|
||||
data={roles.data}
|
||||
onChange={handleSetRole}
|
||||
value={filter.role}
|
||||
/>
|
||||
<Select data={roles.data} onChange={handleSetRole} value={filter.role} />
|
||||
</>
|
||||
)}
|
||||
<RefreshButton onClick={handleRefresh} />
|
||||
|
|
@ -466,10 +452,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
<Group
|
||||
gap="xs"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<ListConfigMenu
|
||||
autoFitColumns={table.autoFit}
|
||||
displayType={display}
|
||||
|
|
|
|||
|
|
@ -42,15 +42,9 @@ export const ArtistListHeader = ({ gridRef, itemCount, tableRef }: ArtistListHea
|
|||
}, 500);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={0}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap={0} ref={cq.ref}>
|
||||
<PageHeader>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Flex justify="space-between" w="100%">
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>
|
||||
{t('entity.artist_other', { postProcess: 'titleCase' })}
|
||||
|
|
@ -62,18 +56,12 @@ export const ArtistListHeader = ({ gridRef, itemCount, tableRef }: ArtistListHea
|
|||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
defaultValue={filter.searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<SearchInput defaultValue={filter.searchTerm} onChange={handleSearch} />
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
<FilterBar>
|
||||
<ArtistListHeaderFilters
|
||||
gridRef={gridRef}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<ArtistListHeaderFilters gridRef={gridRef} tableRef={tableRef} />
|
||||
</FilterBar>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,16 +41,8 @@ const ArtistListRoute = () => {
|
|||
return (
|
||||
<AnimatedPage>
|
||||
<ListContext.Provider value={providerValue}>
|
||||
<ArtistListHeader
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<ArtistListContent
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<ArtistListHeader gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
<ArtistListContent gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
</ListContext.Provider>
|
||||
</AnimatedPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -533,10 +533,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||
|
||||
openModal({
|
||||
children: (
|
||||
<ConfirmModal
|
||||
loading={removeFromPlaylistMutation.isLoading}
|
||||
onConfirm={confirm}
|
||||
>
|
||||
<ConfirmModal loading={removeFromPlaylistMutation.isLoading} onConfirm={confirm}>
|
||||
{t('common.areYouSure', { postProcess: 'sentenceCase' })}
|
||||
</ConfirmModal>
|
||||
),
|
||||
|
|
@ -922,26 +919,15 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||
<Portal>
|
||||
<AnimatePresence>
|
||||
{opened && (
|
||||
<ContextMenu
|
||||
minWidth={125}
|
||||
ref={mergedRef}
|
||||
xPos={ctx.xPos}
|
||||
yPos={ctx.yPos}
|
||||
>
|
||||
<ContextMenu minWidth={125} ref={mergedRef} xPos={ctx.xPos} yPos={ctx.yPos}>
|
||||
<Stack gap={0}>
|
||||
<Stack
|
||||
gap={0}
|
||||
onClick={closeContextMenu}
|
||||
>
|
||||
<Stack gap={0} onClick={closeContextMenu}>
|
||||
{ctx.menuItems?.map((item) => {
|
||||
return (
|
||||
!contextMenuItems[item.id].disabled && (
|
||||
<Fragment key={`context-menu-${item.id}`}>
|
||||
{item.children ? (
|
||||
<HoverCard
|
||||
offset={0}
|
||||
position="right"
|
||||
>
|
||||
<HoverCard offset={0} position="right">
|
||||
<HoverCard.Target>
|
||||
<ContextMenuButton
|
||||
leftIcon={
|
||||
|
|
|
|||
|
|
@ -33,15 +33,9 @@ export const GenreListContent = ({ gridRef, itemCount, tableRef }: AlbumListCont
|
|||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
{display === ListDisplayType.CARD || display === ListDisplayType.GRID ? (
|
||||
<GenreListGridView
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
/>
|
||||
<GenreListGridView gridRef={gridRef} itemCount={itemCount} />
|
||||
) : (
|
||||
<GenreListTableView
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<GenreListTableView itemCount={itemCount} tableRef={tableRef} />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -254,11 +254,7 @@ export const GenreListHeaderFilters = ({
|
|||
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={cq.ref}
|
||||
w="100%"
|
||||
>
|
||||
<Group gap="sm" ref={cq.ref} w="100%">
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button variant="subtle">{sortByLabel}</Button>
|
||||
|
|
@ -277,10 +273,7 @@ export const GenreListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
<OrderToggleButton onToggle={handleToggleSortOrder} sortOrder={filter.sortOrder} />
|
||||
{server?.type === ServerType.JELLYFIN && (
|
||||
<>
|
||||
<Divider orientation="vertical" />
|
||||
|
|
@ -340,10 +333,7 @@ export const GenreListHeaderFilters = ({
|
|||
</Button>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ListConfigMenu
|
||||
autoFitColumns={table.autoFit}
|
||||
disabledViewTypes={[ListDisplayType.LIST]}
|
||||
|
|
|
|||
|
|
@ -40,15 +40,9 @@ export const GenreListHeader = ({ gridRef, itemCount, tableRef }: GenreListHeade
|
|||
}, 500);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={0}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap={0} ref={cq.ref}>
|
||||
<PageHeader>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Flex justify="space-between" w="100%">
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>
|
||||
{t('page.genreList.title', { postProcess: 'titleCase' })}
|
||||
|
|
@ -60,10 +54,7 @@ export const GenreListHeader = ({ gridRef, itemCount, tableRef }: GenreListHeade
|
|||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
defaultValue={filter.searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<SearchInput defaultValue={filter.searchTerm} onChange={handleSearch} />
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
|
|
|
|||
|
|
@ -42,16 +42,8 @@ const GenreListRoute = () => {
|
|||
return (
|
||||
<AnimatedPage>
|
||||
<ListContext.Provider value={providerValue}>
|
||||
<GenreListHeader
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<GenreListContent
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<GenreListHeader gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
<GenreListContent gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
</ListContext.Provider>
|
||||
</AnimatedPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -81,10 +81,7 @@ const formatArtists = (artists: null | RelatedArtist[] | undefined) =>
|
|||
{artist.name || '—'}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
overflow="visible"
|
||||
size="md"
|
||||
>
|
||||
<Text overflow="visible" size="md">
|
||||
{artist.name || '-'}
|
||||
</Text>
|
||||
)}
|
||||
|
|
@ -119,17 +116,7 @@ const FormatGenre = (item: Album | AlbumArtist | Playlist | Song) => {
|
|||
};
|
||||
|
||||
const BoolField = (key: boolean) =>
|
||||
key ? (
|
||||
<Icon
|
||||
color="success"
|
||||
icon="check"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
color="error"
|
||||
icon="x"
|
||||
/>
|
||||
);
|
||||
key ? <Icon color="success" icon="check" /> : <Icon color="error" icon="x" />;
|
||||
|
||||
const AlbumPropertyMapping: ItemDetailRow<Album>[] = [
|
||||
{ key: 'name', label: 'common.title' },
|
||||
|
|
@ -409,12 +396,7 @@ export const ItemDetailsModal = ({ item }: ItemDetailsModalProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
highlightOnHover
|
||||
variant="vertical"
|
||||
withRowBorders={false}
|
||||
withTableBorder
|
||||
>
|
||||
<Table highlightOnHover variant="vertical" withRowBorders={false} withTableBorder>
|
||||
<Table.Tbody>{body}</Table.Tbody>
|
||||
</Table>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ export const SongPath = ({ path }: SongPathProps) => {
|
|||
|
||||
return (
|
||||
<Group>
|
||||
<CopyButton
|
||||
timeout={2000}
|
||||
value={path}
|
||||
>
|
||||
<CopyButton timeout={2000} value={path}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip
|
||||
label={t(
|
||||
|
|
@ -36,10 +33,7 @@ export const SongPath = ({ path }: SongPathProps) => {
|
|||
)}
|
||||
withinPortal
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={copy}
|
||||
variant="transparent"
|
||||
>
|
||||
<ActionIcon onClick={copy} variant="transparent">
|
||||
{copied ? <Icon icon="check" /> : <Icon icon="clipboardCopy" />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -38,33 +38,15 @@ const SearchResult = ({ data, onClick }: SearchResultProps) => {
|
|||
source === LyricSource.GENIUS ? id.replace(/^((http[s]?|ftp):\/)?\/?([^:/\s]+)/g, '') : id;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={styles.searchItem}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Group
|
||||
justify="space-between"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Stack
|
||||
gap={0}
|
||||
maw="65%"
|
||||
>
|
||||
<Text
|
||||
fw={600}
|
||||
size="md"
|
||||
>
|
||||
<button className={styles.searchItem} onClick={onClick}>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Stack gap={0} maw="65%">
|
||||
<Text fw={600} size="md">
|
||||
{name}
|
||||
</Text>
|
||||
<Text isMuted>{artist}</Text>
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Text
|
||||
isMuted
|
||||
size="sm"
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Text isMuted size="sm">
|
||||
{[source, cleanId].join(' — ')}
|
||||
</Text>
|
||||
</Group>
|
||||
|
|
@ -167,11 +149,7 @@ export const LyricsSearchForm = ({ artist, name, onSearchOverride }: LyricSearch
|
|||
export const openLyricSearchModal = ({ artist, name, onSearchOverride }: LyricSearchFormProps) => {
|
||||
openModal({
|
||||
children: (
|
||||
<LyricsSearchForm
|
||||
artist={artist}
|
||||
name={name}
|
||||
onSearchOverride={onSearchOverride}
|
||||
/>
|
||||
<LyricsSearchForm artist={artist} name={name} onSearchOverride={onSearchOverride} />
|
||||
),
|
||||
size: 'lg',
|
||||
title: i18n.t('form.lyricSearch.title', { postProcess: 'titleCase' }) as string,
|
||||
|
|
|
|||
|
|
@ -151,10 +151,7 @@ export const Lyrics = () => {
|
|||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<div className={styles.lyricsContainer}>
|
||||
{isLoadingLyrics ? (
|
||||
<Spinner
|
||||
container
|
||||
size={25}
|
||||
/>
|
||||
<Spinner container size={25} />
|
||||
) : (
|
||||
<AnimatePresence mode="sync">
|
||||
{hasNoLyrics ? (
|
||||
|
|
|
|||
|
|
@ -29,10 +29,7 @@ export const UnsynchronizedLyrics = ({
|
|||
}, [translatedLyrics]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
style={{ gap: `${settings.gapUnsync}px` }}
|
||||
>
|
||||
<div className={styles.container} style={{ gap: `${settings.gapUnsync}px` }}>
|
||||
{settings.showProvider && source && (
|
||||
<LyricLine
|
||||
alignment={settings.alignment}
|
||||
|
|
|
|||
|
|
@ -11,30 +11,17 @@ export const DrawerPlayQueue = () => {
|
|||
const queueRef = useRef<null | { grid: AgGridReactType<Song> }>(null);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
h="100%"
|
||||
>
|
||||
<Flex direction="column" h="100%">
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'var(--theme-colors-background)',
|
||||
borderRadius: '10px',
|
||||
}}
|
||||
>
|
||||
<PlayQueueListControls
|
||||
tableRef={queueRef}
|
||||
type="sideQueue"
|
||||
/>
|
||||
<PlayQueueListControls tableRef={queueRef} type="sideQueue" />
|
||||
</div>
|
||||
<Flex
|
||||
bg="var(--theme-colors-background)"
|
||||
h="100%"
|
||||
mb="0.6rem"
|
||||
>
|
||||
<PlayQueue
|
||||
ref={queueRef}
|
||||
type="sideQueue"
|
||||
/>
|
||||
<Flex bg="var(--theme-colors-background)" h="100%" mb="0.6rem">
|
||||
<PlayQueue ref={queueRef} type="sideQueue" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -174,10 +174,7 @@ export const PlayQueueListControls = ({ tableRef, type }: PlayQueueListOptionsPr
|
|||
/>
|
||||
</Group>
|
||||
<Group>
|
||||
<Popover
|
||||
position="top-end"
|
||||
transitionProps={{ transition: 'fade' }}
|
||||
>
|
||||
<Popover position="top-end" transitionProps={{ transition: 'fade' }}>
|
||||
<Popover.Target>
|
||||
<ActionIcon
|
||||
icon="settings"
|
||||
|
|
|
|||
|
|
@ -18,19 +18,10 @@ export const SidebarPlayQueue = () => {
|
|||
const isWeb = windowBarStyle === Platform.WEB;
|
||||
return (
|
||||
<VirtualGridContainer>
|
||||
<Box
|
||||
display={!isWeb ? 'flex' : undefined}
|
||||
h="65px"
|
||||
>
|
||||
<PlayQueueListControls
|
||||
tableRef={queueRef}
|
||||
type="sideQueue"
|
||||
/>
|
||||
<Box display={!isWeb ? 'flex' : undefined} h="65px">
|
||||
<PlayQueueListControls tableRef={queueRef} type="sideQueue" />
|
||||
</Box>
|
||||
<PlayQueue
|
||||
ref={queueRef}
|
||||
type="sideQueue"
|
||||
/>
|
||||
<PlayQueue ref={queueRef} type="sideQueue" />
|
||||
</VirtualGridContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,14 +16,8 @@ const NowPlayingRoute = () => {
|
|||
<AnimatedPage>
|
||||
<VirtualGridContainer>
|
||||
<NowPlayingHeader />
|
||||
<PlayQueueListControls
|
||||
tableRef={queueRef}
|
||||
type="nowPlaying"
|
||||
/>
|
||||
<PlayQueue
|
||||
ref={queueRef}
|
||||
type="nowPlaying"
|
||||
/>
|
||||
<PlayQueueListControls tableRef={queueRef} type="nowPlaying" />
|
||||
<PlayQueue ref={queueRef} type="nowPlaying" />
|
||||
</VirtualGridContainer>
|
||||
</AnimatedPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -115,13 +115,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
<div className={styles.controlsContainer}>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<PlayerButton
|
||||
icon={
|
||||
<Icon
|
||||
fill="default"
|
||||
icon="mediaStop"
|
||||
size={buttonSize - 2}
|
||||
/>
|
||||
}
|
||||
icon={<Icon fill="default" icon="mediaStop" size={buttonSize - 2} />}
|
||||
onClick={handleStop}
|
||||
tooltip={{
|
||||
label: t('player.stop', { postProcess: 'sentenceCase' }),
|
||||
|
|
@ -152,13 +146,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
variant="tertiary"
|
||||
/>
|
||||
<PlayerButton
|
||||
icon={
|
||||
<Icon
|
||||
fill="default"
|
||||
icon="mediaPrevious"
|
||||
size={buttonSize}
|
||||
/>
|
||||
}
|
||||
icon={<Icon fill="default" icon="mediaPrevious" size={buttonSize} />}
|
||||
onClick={handlePrevTrack}
|
||||
tooltip={{
|
||||
label: t('player.previous', { postProcess: 'sentenceCase' }),
|
||||
|
|
@ -169,11 +157,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
{skip?.enabled && (
|
||||
<PlayerButton
|
||||
icon={
|
||||
<Icon
|
||||
fill="default"
|
||||
icon="mediaStepBackward"
|
||||
size={buttonSize}
|
||||
/>
|
||||
<Icon fill="default" icon="mediaStepBackward" size={buttonSize} />
|
||||
}
|
||||
onClick={() => handleSkipBackward(skip?.skipBackwardSeconds)}
|
||||
tooltip={{
|
||||
|
|
@ -194,13 +178,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
/>
|
||||
{skip?.enabled && (
|
||||
<PlayerButton
|
||||
icon={
|
||||
<Icon
|
||||
fill="default"
|
||||
icon="mediaStepForward"
|
||||
size={buttonSize}
|
||||
/>
|
||||
}
|
||||
icon={<Icon fill="default" icon="mediaStepForward" size={buttonSize} />}
|
||||
onClick={() => handleSkipForward(skip?.skipForwardSeconds)}
|
||||
tooltip={{
|
||||
label: t('player.skip', {
|
||||
|
|
@ -214,13 +192,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
/>
|
||||
)}
|
||||
<PlayerButton
|
||||
icon={
|
||||
<Icon
|
||||
fill="default"
|
||||
icon="mediaNext"
|
||||
size={buttonSize}
|
||||
/>
|
||||
}
|
||||
icon={<Icon fill="default" icon="mediaNext" size={buttonSize} />}
|
||||
onClick={handleNextTrack}
|
||||
tooltip={{
|
||||
label: t('player.next', { postProcess: 'sentenceCase' }),
|
||||
|
|
@ -231,11 +203,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
<PlayerButton
|
||||
icon={
|
||||
repeat === PlayerRepeat.ONE ? (
|
||||
<Icon
|
||||
fill="primary"
|
||||
icon="mediaRepeatOne"
|
||||
size={buttonSize}
|
||||
/>
|
||||
<Icon fill="primary" icon="mediaRepeatOne" size={buttonSize} />
|
||||
) : (
|
||||
<Icon
|
||||
fill={repeat === PlayerRepeat.NONE ? 'default' : 'primary'}
|
||||
|
|
@ -268,13 +236,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
variant="tertiary"
|
||||
/>
|
||||
<PlayerButton
|
||||
icon={
|
||||
<Icon
|
||||
fill="default"
|
||||
icon="mediaRandom"
|
||||
size={buttonSize}
|
||||
/>
|
||||
}
|
||||
icon={<Icon fill="default" icon="mediaRandom" size={buttonSize} />}
|
||||
onClick={() =>
|
||||
openShuffleAllModal({
|
||||
handlePlayQueueAdd,
|
||||
|
|
@ -291,12 +253,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
</div>
|
||||
<div className={styles.sliderContainer}>
|
||||
<div className={styles.sliderValueWrapper}>
|
||||
<Text
|
||||
fw={600}
|
||||
isMuted
|
||||
isNoSelect
|
||||
size="xs"
|
||||
>
|
||||
<Text fw={600} isMuted isNoSelect size="xs">
|
||||
{formattedTime}
|
||||
</Text>
|
||||
</div>
|
||||
|
|
@ -324,12 +281,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
|||
/>
|
||||
</div>
|
||||
<div className={styles.sliderValueWrapper}>
|
||||
<Text
|
||||
fw={600}
|
||||
isMuted
|
||||
isNoSelect
|
||||
size="xs"
|
||||
>
|
||||
<Text fw={600} isMuted isNoSelect size="xs">
|
||||
{duration}
|
||||
</Text>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,11 +68,7 @@ const ImageWithPlaceholder = ({
|
|||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
color="muted"
|
||||
icon="itemAlbum"
|
||||
size="25%"
|
||||
/>
|
||||
<Icon color="muted" icon="itemAlbum" size="25%" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
|
@ -167,14 +163,8 @@ export const FullScreenPlayerImage = () => {
|
|||
justify="flex-start"
|
||||
p="1rem"
|
||||
>
|
||||
<div
|
||||
className={styles.imageContainer}
|
||||
ref={mainImageRef}
|
||||
>
|
||||
<AnimatePresence
|
||||
initial={false}
|
||||
mode="sync"
|
||||
>
|
||||
<div className={styles.imageContainer} ref={mainImageRef}>
|
||||
<AnimatePresence initial={false} mode="sync">
|
||||
{imageState.current === 0 && (
|
||||
<ImageWithPlaceholder
|
||||
animate="open"
|
||||
|
|
@ -206,18 +196,8 @@ export const FullScreenPlayerImage = () => {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<Stack
|
||||
className={styles.metadataContainer}
|
||||
gap="md"
|
||||
maw="100%"
|
||||
>
|
||||
<Text
|
||||
fw={900}
|
||||
lh="1.2"
|
||||
overflow="hidden"
|
||||
size="4xl"
|
||||
w="100%"
|
||||
>
|
||||
<Stack className={styles.metadataContainer} gap="md" maw="100%">
|
||||
<Text fw={900} lh="1.2" overflow="hidden" size="4xl" w="100%">
|
||||
{currentSong?.name}
|
||||
</Text>
|
||||
<Text
|
||||
|
|
@ -257,10 +237,7 @@ export const FullScreenPlayerImage = () => {
|
|||
</Fragment>
|
||||
))}
|
||||
</Text>
|
||||
<Group
|
||||
justify="center"
|
||||
mt="sm"
|
||||
>
|
||||
<Group justify="center" mt="sm">
|
||||
{currentSong?.container && (
|
||||
<Badge variant="transparent">{currentSong?.container}</Badge>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -76,10 +76,7 @@ export const FullScreenPlayerQueue = () => {
|
|||
justify="center"
|
||||
>
|
||||
{headerItems.map((item) => (
|
||||
<div
|
||||
className={styles.headerItemWrapper}
|
||||
key={`tab-${item.label}`}
|
||||
>
|
||||
<div className={styles.headerItemWrapper} key={`tab-${item.label}`}>
|
||||
<Button
|
||||
flex={1}
|
||||
fw="600"
|
||||
|
|
|
|||
|
|
@ -238,10 +238,7 @@ const Controls = ({ isPageHovered }: ControlsProps) => {
|
|||
})}
|
||||
</Option.Label>
|
||||
<Option.Control>
|
||||
<Group
|
||||
w="100%"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group w="100%" wrap="nowrap">
|
||||
<Slider
|
||||
defaultValue={lyricConfig.fontSize}
|
||||
label={(e) =>
|
||||
|
|
@ -278,10 +275,7 @@ const Controls = ({ isPageHovered }: ControlsProps) => {
|
|||
})}
|
||||
</Option.Label>
|
||||
<Option.Control>
|
||||
<Group
|
||||
w="100%"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group w="100%" wrap="nowrap">
|
||||
<Slider
|
||||
defaultValue={lyricConfig.gap}
|
||||
label={(e) => `Synchronized: ${e}px`}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,5 @@ import { useCurrentSong } from '/@/renderer/store';
|
|||
export const FullScreenSimilarSongs = () => {
|
||||
const currentSong = useCurrentSong();
|
||||
|
||||
return currentSong?.id ? (
|
||||
<SimilarSongsList
|
||||
fullScreen
|
||||
song={currentSong}
|
||||
/>
|
||||
) : null;
|
||||
return currentSong?.id ? <SimilarSongsList fullScreen song={currentSong} /> : null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,10 +69,7 @@ export const LeftControls = () => {
|
|||
return (
|
||||
<div className={styles.leftControlsContainer}>
|
||||
<LayoutGroup>
|
||||
<AnimatePresence
|
||||
initial={false}
|
||||
mode="popLayout"
|
||||
>
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{!hideImage && (
|
||||
<div className={styles.imageWrapper}>
|
||||
<motion.div
|
||||
|
|
@ -123,19 +120,9 @@ export const LeftControls = () => {
|
|||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<motion.div
|
||||
className={styles.metadataStack}
|
||||
layout="position"
|
||||
>
|
||||
<div
|
||||
className={styles.lineItem}
|
||||
onClick={stopPropagation}
|
||||
>
|
||||
<Group
|
||||
align="center"
|
||||
gap="xs"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<motion.div className={styles.metadataStack} layout="position">
|
||||
<div className={styles.lineItem} onClick={stopPropagation}>
|
||||
<Group align="center" gap="xs" wrap="nowrap">
|
||||
<Text
|
||||
component={Link}
|
||||
fw={500}
|
||||
|
|
|
|||
|
|
@ -193,13 +193,7 @@ export const RightControls = () => {
|
|||
}, [addToFavoritesMutation, removeFromFavoritesMutation, updateRatingMutation]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align="flex-end"
|
||||
direction="column"
|
||||
h="100%"
|
||||
px="1rem"
|
||||
py="0.5rem"
|
||||
>
|
||||
<Flex align="flex-end" direction="column" h="100%" px="1rem" py="0.5rem">
|
||||
<Group h="calc(100% / 3)">
|
||||
{showRating && (
|
||||
<Rating
|
||||
|
|
@ -209,18 +203,8 @@ export const RightControls = () => {
|
|||
/>
|
||||
)}
|
||||
</Group>
|
||||
<Group
|
||||
align="center"
|
||||
gap="xs"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<DropdownMenu
|
||||
arrowOffset={12}
|
||||
offset={0}
|
||||
position="top-end"
|
||||
width={425}
|
||||
withArrow
|
||||
>
|
||||
<Group align="center" gap="xs" wrap="nowrap">
|
||||
<DropdownMenu arrowOffset={12} offset={0} position="top-end" width={425} withArrow>
|
||||
<DropdownMenu.Target>
|
||||
<ActionIcon
|
||||
icon="mediaSpeed"
|
||||
|
|
|
|||
|
|
@ -33,10 +33,5 @@ export const Visualizer = () => {
|
|||
return () => {};
|
||||
}, [accent, canvasRef, motion, webAudio]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
ref={canvasRef}
|
||||
/>
|
||||
);
|
||||
return <div className={styles.container} ref={canvasRef} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -155,10 +155,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
|||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -331,11 +331,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
|
|||
/>
|
||||
</VirtualGridAutoSizerContainer>
|
||||
{isPaginationEnabled && (
|
||||
<AnimatePresence
|
||||
initial={false}
|
||||
mode="wait"
|
||||
presenceAffectsLayout
|
||||
>
|
||||
<AnimatePresence initial={false} mode="wait" presenceAffectsLayout>
|
||||
{page.display === ListDisplayType.TABLE_PAGINATED && (
|
||||
<TablePagination
|
||||
pageKey={playlistId}
|
||||
|
|
|
|||
|
|
@ -469,11 +469,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={cq.ref}
|
||||
w="100%"
|
||||
>
|
||||
<Group gap="sm" ref={cq.ref} w="100%">
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button
|
||||
|
|
@ -555,10 +551,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
{server?.type === ServerType.NAVIDROME && !isSmartPlaylist && (
|
||||
<>
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item
|
||||
isDanger
|
||||
onClick={handleToggleShowQueryBuilder}
|
||||
>
|
||||
<DropdownMenu.Item isDanger onClick={handleToggleShowQueryBuilder}>
|
||||
{t('action.toggleSmartPlaylistEditor', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -33,15 +33,9 @@ export const PlaylistListContent = ({ gridRef, itemCount, tableRef }: PlaylistLi
|
|||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
{display === ListDisplayType.CARD || display === ListDisplayType.GRID ? (
|
||||
<PlaylistListGridView
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
/>
|
||||
<PlaylistListGridView gridRef={gridRef} itemCount={itemCount} />
|
||||
) : (
|
||||
<PlaylistListTableView
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<PlaylistListTableView itemCount={itemCount} tableRef={tableRef} />
|
||||
)}
|
||||
<div />
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -355,11 +355,7 @@ export const PlaylistListHeaderFilters = ({
|
|||
|
||||
return (
|
||||
<Flex justify="space-between">
|
||||
<Group
|
||||
gap="sm"
|
||||
ref={cq.ref}
|
||||
w="100%"
|
||||
>
|
||||
<Group gap="sm" ref={cq.ref} w="100%">
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button variant="subtle">{sortByLabel}</Button>
|
||||
|
|
@ -378,10 +374,7 @@ export const PlaylistListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
<OrderToggleButton onToggle={handleToggleSortOrder} sortOrder={filter.sortOrder} />
|
||||
<RefreshButton onClick={handleRefresh} />
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
|
|
@ -397,14 +390,8 @@ export const PlaylistListHeaderFilters = ({
|
|||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</Group>
|
||||
<Group
|
||||
gap="xs"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Button
|
||||
onClick={handleCreatePlaylistModal}
|
||||
variant="subtle"
|
||||
>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Button onClick={handleCreatePlaylistModal} variant="subtle">
|
||||
{t('action.createPlaylist', { postProcess: 'sentenceCase' })}
|
||||
</Button>
|
||||
<ListConfigMenu
|
||||
|
|
|
|||
|
|
@ -44,16 +44,9 @@ export const PlaylistListHeader = ({ gridRef, itemCount, tableRef }: PlaylistLis
|
|||
}, 500);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={0}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap={0} ref={cq.ref}>
|
||||
<PageHeader>
|
||||
<Flex
|
||||
align="center"
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Flex align="center" justify="space-between" w="100%">
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>
|
||||
{t('page.playlistList.title', { postProcess: 'titleCase' })}
|
||||
|
|
@ -67,18 +60,12 @@ export const PlaylistListHeader = ({ gridRef, itemCount, tableRef }: PlaylistLis
|
|||
</Badge>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
defaultValue={filter.searchTerm}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<SearchInput defaultValue={filter.searchTerm} onChange={handleSearch} />
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
<FilterBar>
|
||||
<PlaylistListHeaderFilters
|
||||
gridRef={gridRef}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<PlaylistListHeaderFilters gridRef={gridRef} tableRef={tableRef} />
|
||||
</FilterBar>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -410,11 +410,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
];
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
h="calc(100% - 2rem)"
|
||||
justify="space-between"
|
||||
>
|
||||
<Flex direction="column" h="calc(100% - 2rem)" justify="space-between">
|
||||
<ScrollArea>
|
||||
<QueryBuilder
|
||||
data={filters}
|
||||
|
|
@ -442,17 +438,8 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
uniqueId={filters.uniqueId}
|
||||
/>
|
||||
</ScrollArea>
|
||||
<Group
|
||||
align="flex-end"
|
||||
justify="space-between"
|
||||
m="1rem"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group
|
||||
gap="sm"
|
||||
w="100%"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group align="flex-end" justify="space-between" m="1rem" wrap="nowrap">
|
||||
<Group gap="sm" w="100%" wrap="nowrap">
|
||||
<Select
|
||||
data={sortOptions}
|
||||
label="Sort"
|
||||
|
|
@ -485,20 +472,11 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
/>
|
||||
</Group>
|
||||
{onSave && onSaveAs && (
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Button
|
||||
loading={isSaving}
|
||||
onClick={handleSaveAs}
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Button loading={isSaving} onClick={handleSaveAs}>
|
||||
{t('common.saveAs', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={openPreviewModal}
|
||||
variant="subtle"
|
||||
>
|
||||
<Button onClick={openPreviewModal} variant="subtle">
|
||||
{t('common.preview', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<DropdownMenu position="bottom-end">
|
||||
|
|
@ -512,12 +490,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Item
|
||||
isDanger
|
||||
leftSection={
|
||||
<Icon
|
||||
color="error"
|
||||
icon="save"
|
||||
/>
|
||||
}
|
||||
leftSection={<Icon color="error" icon="save" />}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t('common.saveAndReplace', { postProcess: 'titleCase' })}
|
||||
|
|
|
|||
|
|
@ -103,10 +103,7 @@ export const SaveAsPlaylistForm = ({
|
|||
/>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -140,10 +140,7 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl
|
|||
</>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -175,12 +175,7 @@ const PlaylistDetailSongListRoute = () => {
|
|||
|
||||
{(isSmartPlaylist || showQueryBuilder) && (
|
||||
<motion.div>
|
||||
<Box
|
||||
h="100%"
|
||||
mah="35vh"
|
||||
p="md"
|
||||
w="100%"
|
||||
>
|
||||
<Box h="100%" mah="35vh" p="md" w="100%">
|
||||
<Group pb="md">
|
||||
<ActionIcon
|
||||
icon={isQueryBuilderExpanded ? 'arrowUpS' : 'arrowDownS'}
|
||||
|
|
|
|||
|
|
@ -50,16 +50,8 @@ const PlaylistListRoute = () => {
|
|||
return (
|
||||
<AnimatedPage>
|
||||
<ListContext.Provider value={providerValue}>
|
||||
<PlaylistListHeader
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<PlaylistListContent
|
||||
gridRef={gridRef}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<PlaylistListHeader gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
<PlaylistListContent gridRef={gridRef} itemCount={itemCount} tableRef={tableRef} />
|
||||
</ListContext.Provider>
|
||||
</AnimatedPage>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -95,18 +95,11 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
|||
header: { display: 'none' },
|
||||
}}
|
||||
>
|
||||
<Group
|
||||
gap="sm"
|
||||
mb="1rem"
|
||||
>
|
||||
<Group gap="sm" mb="1rem">
|
||||
{pages.map((page, index) => (
|
||||
<Fragment key={page}>
|
||||
{index > 0 && ' > '}
|
||||
<Button
|
||||
disabled
|
||||
size="compact-md"
|
||||
variant="default"
|
||||
>
|
||||
<Button disabled size="compact-md" variant="default">
|
||||
{page?.toLocaleUpperCase()}
|
||||
</Button>
|
||||
</Fragment>
|
||||
|
|
@ -267,10 +260,7 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
|||
)}
|
||||
</Command.List>
|
||||
</Command>
|
||||
<Box
|
||||
mt="0.5rem"
|
||||
p="0.5rem"
|
||||
>
|
||||
<Box mt="0.5rem" p="0.5rem">
|
||||
<Group justify="space-between">
|
||||
<Command.Loading>
|
||||
{isHome && isLoading && query !== '' && <Spinner />}
|
||||
|
|
|
|||
|
|
@ -56,10 +56,7 @@ export const LibraryCommandItem = ({
|
|||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{ height: '40px', width: '100%' }}
|
||||
>
|
||||
<div
|
||||
className={styles.itemGrid}
|
||||
style={{ '--item-height': '40px' } as CSSProperties}
|
||||
>
|
||||
<div className={styles.itemGrid} style={{ '--item-height': '40px' } as CSSProperties}>
|
||||
<div className={styles.imageWrapper}>
|
||||
<Image
|
||||
alt="cover"
|
||||
|
|
@ -71,21 +68,13 @@ export const LibraryCommandItem = ({
|
|||
</div>
|
||||
<div className={styles.metadataWrapper}>
|
||||
<Text overflow="hidden">{title}</Text>
|
||||
<Text
|
||||
isMuted
|
||||
overflow="hidden"
|
||||
>
|
||||
<Text isMuted overflow="hidden">
|
||||
{subtitle}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
{isHovered && (
|
||||
<Group
|
||||
align="center"
|
||||
gap="sm"
|
||||
justify="flex-end"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group align="center" gap="sm" justify="flex-end" wrap="nowrap">
|
||||
<ActionIcon
|
||||
disabled={disabled}
|
||||
icon="mediaPlay"
|
||||
|
|
|
|||
|
|
@ -49,15 +49,9 @@ export const SearchHeader = ({ navigationId, tableRef }: SearchHeaderProps) => {
|
|||
}, 200);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={0}
|
||||
ref={cq.ref}
|
||||
>
|
||||
<Stack gap={0} ref={cq.ref}>
|
||||
<PageHeader>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<Flex justify="space-between" w="100%">
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>Search</LibraryHeaderBar.Title>
|
||||
</LibraryHeaderBar>
|
||||
|
|
|
|||
|
|
@ -16,14 +16,8 @@ const SearchRoute = () => {
|
|||
|
||||
return (
|
||||
<AnimatedPage key={`search-${navigationId}`}>
|
||||
<SearchHeader
|
||||
navigationId={navigationId}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<SearchContent
|
||||
key={`page-${itemType}`}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<SearchHeader navigationId={navigationId} tableRef={tableRef} />
|
||||
<SearchContent key={`page-${itemType}`} tableRef={tableRef} />
|
||||
</AnimatedPage>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,15 +31,8 @@ interface AddServerFormProps {
|
|||
|
||||
function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) {
|
||||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<img
|
||||
height="50"
|
||||
src={icon}
|
||||
width="50"
|
||||
/>
|
||||
<Stack align="center" justify="center">
|
||||
<img height="50" src={icon} width="50" />
|
||||
<Text>{label}</Text>
|
||||
</Stack>
|
||||
);
|
||||
|
|
@ -47,30 +40,15 @@ function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) {
|
|||
|
||||
const SERVER_TYPES = [
|
||||
{
|
||||
label: (
|
||||
<ServerIconWithLabel
|
||||
icon={JellyfinIcon}
|
||||
label="Jellyfin"
|
||||
/>
|
||||
),
|
||||
label: <ServerIconWithLabel icon={JellyfinIcon} label="Jellyfin" />,
|
||||
value: ServerType.JELLYFIN,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<ServerIconWithLabel
|
||||
icon={NavidromeIcon}
|
||||
label="Navidrome"
|
||||
/>
|
||||
),
|
||||
label: <ServerIconWithLabel icon={NavidromeIcon} label="Navidrome" />,
|
||||
value: ServerType.NAVIDROME,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<ServerIconWithLabel
|
||||
icon={SubsonicIcon}
|
||||
label="OpenSubsonic"
|
||||
/>
|
||||
),
|
||||
label: <ServerIconWithLabel icon={SubsonicIcon} label="OpenSubsonic" />,
|
||||
value: ServerType.SUBSONIC,
|
||||
},
|
||||
];
|
||||
|
|
@ -174,10 +152,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack
|
||||
m={5}
|
||||
ref={focusTrapRef}
|
||||
>
|
||||
<Stack m={5} ref={focusTrapRef}>
|
||||
<SegmentedControl
|
||||
data={SERVER_TYPES}
|
||||
disabled={Boolean(serverLock)}
|
||||
|
|
@ -238,15 +213,9 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
|
||||
/>
|
||||
)}
|
||||
<Group
|
||||
grow
|
||||
justify="flex-end"
|
||||
>
|
||||
<Group grow justify="flex-end">
|
||||
{onCancel && (
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,7 @@ interface EditServerFormProps {
|
|||
const ModifiedFieldIndicator = () => {
|
||||
return (
|
||||
<Tooltip label={i18n.t('common.modified', { postProcess: 'titleCase' }) as string}>
|
||||
<Icon
|
||||
color="warn"
|
||||
icon="info"
|
||||
/>
|
||||
<Icon color="warn" icon="info" />
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
|
@ -193,17 +190,10 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
|
|||
/>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
type="submit"
|
||||
variant="filled"
|
||||
>
|
||||
<Button loading={isLoading} type="submit" variant="filled">
|
||||
{t('common.save', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -66,11 +66,7 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
|||
/>
|
||||
) : (
|
||||
<Stack>
|
||||
<Table
|
||||
layout="fixed"
|
||||
variant="vertical"
|
||||
withTableBorder
|
||||
>
|
||||
<Table layout="fixed" variant="vertical" withTableBorder>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Table.Th>
|
||||
|
|
|
|||
|
|
@ -73,10 +73,7 @@ export const ServerList = () => {
|
|||
{Object.keys(serverListQuery)?.map((serverId) => {
|
||||
const server = serverListQuery[serverId];
|
||||
return (
|
||||
<Accordion.Item
|
||||
key={server.id}
|
||||
value={server.name}
|
||||
>
|
||||
<Accordion.Item key={server.id} value={server.name}>
|
||||
<Accordion.Control>
|
||||
<Group>
|
||||
<img
|
||||
|
|
@ -103,10 +100,7 @@ export const ServerList = () => {
|
|||
</Accordion.Item>
|
||||
);
|
||||
})}
|
||||
<Group
|
||||
grow
|
||||
pt="md"
|
||||
>
|
||||
<Group grow pt="md">
|
||||
<Button
|
||||
autoFocus
|
||||
leftSection={<Icon icon="add" />}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ export const ContextMenuSettings = () => {
|
|||
<>
|
||||
<SettingsOptions
|
||||
control={
|
||||
<Button
|
||||
onClick={() => setOpen(!open)}
|
||||
size="compact-md"
|
||||
variant="filled"
|
||||
>
|
||||
<Button onClick={() => setOpen(!open)} size="compact-md" variant="filled">
|
||||
{t(open ? 'common.close' : 'common.edit', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,17 +35,8 @@ export const DraggableItem = ({ handleChangeDisabled, item, value }: DraggableIt
|
|||
const dragControls = useDragControls();
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
as="div"
|
||||
dragControls={dragControls}
|
||||
dragListener={false}
|
||||
value={item}
|
||||
>
|
||||
<Group
|
||||
py="md"
|
||||
style={{ boxShadow: '0 1px 3px rgba(0,0,0,.1)' }}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Reorder.Item as="div" dragControls={dragControls} dragListener={false} value={item}>
|
||||
<Group py="md" style={{ boxShadow: '0 1px 3px rgba(0,0,0,.1)' }} wrap="nowrap">
|
||||
<Checkbox
|
||||
checked={!item.disabled}
|
||||
onChange={(e) => handleChangeDisabled(item.id, e.target.checked)}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue