mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 06:27:09 +01:00
Begin migration (round 1)
This commit is contained in:
parent
875d302a2f
commit
c4378e3f11
2830
package-lock.json
generated
2830
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -217,7 +217,7 @@
|
|||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"@types/react-window-infinite-loader": "^1.0.6",
|
"@types/react-window-infinite-loader": "^1.0.6",
|
||||||
"@types/sanitize-html": "^2.11.0",
|
"@types/sanitize-html": "^2.11.0",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.34",
|
||||||
"@types/terser-webpack-plugin": "^5.0.4",
|
"@types/terser-webpack-plugin": "^5.0.4",
|
||||||
"@types/webpack-bundle-analyzer": "^4.4.1",
|
"@types/webpack-bundle-analyzer": "^4.4.1",
|
||||||
"@types/webpack-env": "^1.16.3",
|
"@types/webpack-env": "^1.16.3",
|
||||||
@ -342,15 +342,15 @@
|
|||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^11.18.6",
|
||||||
"react-icons": "^4.10.1",
|
"react-icons": "^4.10.1",
|
||||||
"react-player": "^2.11.0",
|
"react-player": "^2.11.0",
|
||||||
"react-router": "^6.16.0",
|
"react-router": "^6.23.0",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.23.0",
|
||||||
"react-simple-img": "^3.0.0",
|
"react-simple-img": "^3.0.0",
|
||||||
"react-virtualized-auto-sizer": "^1.0.17",
|
"react-virtualized-auto-sizer": "^1.0.17",
|
||||||
"react-window": "^1.8.9",
|
"react-window": "^1.8.9",
|
||||||
"react-window-infinite-loader": "^1.0.9",
|
"react-window-infinite-loader": "^1.0.9",
|
||||||
"sanitize-html": "^2.13.0",
|
"sanitize-html": "^2.13.0",
|
||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"styled-components": "^6.0.8",
|
"styled-components": "^6.1.0",
|
||||||
"swiper": "^9.3.1",
|
"swiper": "^9.3.1",
|
||||||
"zod": "^3.22.3",
|
"zod": "^3.22.3",
|
||||||
"zustand": "^4.3.9"
|
"zustand": "^4.3.9"
|
||||||
|
@ -14,10 +14,8 @@ export const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MantineProvider
|
<MantineProvider
|
||||||
withGlobalStyles
|
forceColorScheme={isDark ? 'dark' : 'light'}
|
||||||
withNormalizeCSS
|
|
||||||
theme={{
|
theme={{
|
||||||
colorScheme: isDark ? 'dark' : 'light',
|
|
||||||
components: {
|
components: {
|
||||||
AppShell: {
|
AppShell: {
|
||||||
styles: {
|
styles: {
|
||||||
@ -44,18 +42,17 @@ export const App = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultRadius: 'xs',
|
defaultRadius: 'xs',
|
||||||
dir: 'ltr',
|
|
||||||
focusRing: 'auto',
|
focusRing: 'auto',
|
||||||
focusRingStyles: {
|
// focusRingStyles: {
|
||||||
inputStyles: () => ({
|
// inputStyles: () => ({
|
||||||
border: '1px solid var(--primary-color)',
|
// border: '1px solid var(--primary-color)',
|
||||||
}),
|
// }),
|
||||||
resetStyles: () => ({ outline: 'none' }),
|
// resetStyles: () => ({ outline: 'none' }),
|
||||||
styles: () => ({
|
// styles: () => ({
|
||||||
outline: '1px solid var(--primary-color)',
|
// outline: '1px solid var(--primary-color)',
|
||||||
outlineOffset: '-1px',
|
// outlineOffset: '-1px',
|
||||||
}),
|
// }),
|
||||||
},
|
// },
|
||||||
fontFamily: 'var(--content-font-family)',
|
fontFamily: 'var(--content-font-family)',
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
lg: '1.1rem',
|
lg: '1.1rem',
|
||||||
@ -66,7 +63,7 @@ export const App = () => {
|
|||||||
},
|
},
|
||||||
headings: {
|
headings: {
|
||||||
fontFamily: 'var(--content-font-family)',
|
fontFamily: 'var(--content-font-family)',
|
||||||
fontWeight: 700,
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
other: {},
|
other: {},
|
||||||
spacing: {
|
spacing: {
|
||||||
|
@ -45,7 +45,7 @@ export const RemoteContainer = () => {
|
|||||||
<Title order={2}>Album: {song.album}</Title>
|
<Title order={2}>Album: {song.album}</Title>
|
||||||
<Title order={2}>Artist: {song.artistName}</Title>
|
<Title order={2}>Artist: {song.artistName}</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
|
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
|
||||||
{song.releaseDate && (
|
{song.releaseDate && (
|
||||||
<Title order={3}>
|
<Title order={3}>
|
||||||
@ -58,7 +58,7 @@ export const RemoteContainer = () => {
|
|||||||
)}
|
)}
|
||||||
<Group
|
<Group
|
||||||
grow
|
grow
|
||||||
spacing={0}
|
gap={0}
|
||||||
>
|
>
|
||||||
<RemoteButton
|
<RemoteButton
|
||||||
disabled={!song}
|
disabled={!song}
|
||||||
@ -97,7 +97,7 @@ export const RemoteContainer = () => {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
grow
|
grow
|
||||||
spacing={0}
|
gap={0}
|
||||||
>
|
>
|
||||||
<RemoteButton
|
<RemoteButton
|
||||||
$active={shuffle || false}
|
$active={shuffle || false}
|
||||||
@ -145,7 +145,7 @@ export const RemoteContainer = () => {
|
|||||||
openDelay={1000}
|
openDelay={1000}
|
||||||
>
|
>
|
||||||
<Rating
|
<Rating
|
||||||
sx={{ margin: 'auto' }}
|
style={{ margin: 'auto' }}
|
||||||
value={song.userRating ?? 0}
|
value={song.userRating ?? 0}
|
||||||
onChange={debouncedSetRating}
|
onChange={debouncedSetRating}
|
||||||
onDoubleClick={() => debouncedSetRating(0)}
|
onDoubleClick={() => debouncedSetRating(0)}
|
||||||
@ -159,8 +159,8 @@ export const RemoteContainer = () => {
|
|||||||
max={100}
|
max={100}
|
||||||
rightLabel={
|
rightLabel={
|
||||||
<Text
|
<Text
|
||||||
|
fw={600}
|
||||||
size="xs"
|
size="xs"
|
||||||
weight={600}
|
|
||||||
>
|
>
|
||||||
{volume ?? 0}
|
{volume ?? 0}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -1,65 +1,57 @@
|
|||||||
import {
|
import { AppShell, Container, Flex, Grid, Image, Skeleton } from '@mantine/core';
|
||||||
AppShell,
|
|
||||||
Container,
|
|
||||||
Flex,
|
|
||||||
Grid,
|
|
||||||
Header,
|
|
||||||
Image,
|
|
||||||
MediaQuery,
|
|
||||||
Skeleton,
|
|
||||||
Title,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { ThemeButton } from '/@/remote/components/buttons/theme-button';
|
import { ThemeButton } from '/@/remote/components/buttons/theme-button';
|
||||||
import { ImageButton } from '/@/remote/components/buttons/image-button';
|
import { ImageButton } from '/@/remote/components/buttons/image-button';
|
||||||
import { RemoteContainer } from '/@/remote/components/remote-container';
|
import { RemoteContainer } from '/@/remote/components/remote-container';
|
||||||
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
|
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
|
||||||
import { useConnected } from '/@/remote/store';
|
import { useConnected } from '/@/remote/store';
|
||||||
|
|
||||||
|
// TODO: Fix media query
|
||||||
|
|
||||||
export const Shell = () => {
|
export const Shell = () => {
|
||||||
const connected = useConnected();
|
const connected = useConnected();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
header={
|
header={{ height: 60 }}
|
||||||
<Header height={60}>
|
|
||||||
<Grid>
|
|
||||||
<Grid.Col span="auto">
|
|
||||||
<div>
|
|
||||||
<Image
|
|
||||||
fit="contain"
|
|
||||||
height={60}
|
|
||||||
src="/favicon.ico"
|
|
||||||
width={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid.Col>
|
|
||||||
<MediaQuery
|
|
||||||
smallerThan="sm"
|
|
||||||
styles={{ display: 'none' }}
|
|
||||||
>
|
|
||||||
<Grid.Col
|
|
||||||
sm={6}
|
|
||||||
xs={0}
|
|
||||||
>
|
|
||||||
<Title ta="center">Feishin Remote</Title>
|
|
||||||
</Grid.Col>
|
|
||||||
</MediaQuery>
|
|
||||||
|
|
||||||
<Grid.Col span="auto">
|
|
||||||
<Flex
|
|
||||||
direction="row"
|
|
||||||
justify="right"
|
|
||||||
>
|
|
||||||
<ReconnectButton />
|
|
||||||
<ImageButton />
|
|
||||||
<ThemeButton />
|
|
||||||
</Flex>
|
|
||||||
</Grid.Col>
|
|
||||||
</Grid>
|
|
||||||
</Header>
|
|
||||||
}
|
|
||||||
padding="md"
|
padding="md"
|
||||||
>
|
>
|
||||||
|
<AppShell.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span="auto">
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
fit="contain"
|
||||||
|
height={60}
|
||||||
|
src="/favicon.ico"
|
||||||
|
width={60}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid.Col>
|
||||||
|
{/* <MediaQuery
|
||||||
|
smallerThan="sm"
|
||||||
|
styles={{ display: 'none' }}
|
||||||
|
>
|
||||||
|
<Grid.Col
|
||||||
|
span={{
|
||||||
|
sm: 6,
|
||||||
|
xs: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Title ta="center">Feishin Remote</Title>
|
||||||
|
</Grid.Col>
|
||||||
|
</MediaQuery> */}
|
||||||
|
<Grid.Col span="auto">
|
||||||
|
<Flex
|
||||||
|
direction="row"
|
||||||
|
justify="right"
|
||||||
|
>
|
||||||
|
<ReconnectButton />
|
||||||
|
<ImageButton />
|
||||||
|
<ThemeButton />
|
||||||
|
</Flex>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</AppShell.Header>
|
||||||
<Container>
|
<Container>
|
||||||
{connected ? (
|
{connected ? (
|
||||||
<RemoteContainer />
|
<RemoteContainer />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||||
import type { NotificationProps as MantineNotificationProps } from '@mantine/notifications';
|
import type { NotificationData as MantineNotificationProps } from '@mantine/notifications';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
|
@ -1,30 +1,32 @@
|
|||||||
import { useEffect, useMemo, useRef } from 'react';
|
|
||||||
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
|
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
|
||||||
import { ModuleRegistry } from '@ag-grid-community/core';
|
import { ModuleRegistry } from '@ag-grid-community/core';
|
||||||
import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model';
|
import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model';
|
||||||
|
import '@ag-grid-community/styles/ag-grid.css';
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { initSimpleImg } from 'react-simple-img';
|
import { initSimpleImg } from 'react-simple-img';
|
||||||
import { toast } from './components';
|
import { useTheme } from '/@/renderer/hooks';
|
||||||
import { useTheme } from './hooks';
|
import { AppRouter } from '/@/renderer/router/app-router';
|
||||||
import { IsUpdatedDialog } from './is-updated-dialog';
|
import { useRef, useEffect, useMemo } from 'react';
|
||||||
import { AppRouter } from './router/app-router';
|
|
||||||
import {
|
|
||||||
useHotkeySettings,
|
|
||||||
usePlaybackSettings,
|
|
||||||
useRemoteSettings,
|
|
||||||
useSettingsStore,
|
|
||||||
} from './store/settings.store';
|
|
||||||
import './styles/global.scss';
|
|
||||||
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
|
|
||||||
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
|
||||||
import { PlayQueueHandlerContext } from '/@/renderer/features/player';
|
|
||||||
import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings';
|
|
||||||
import { PlayerState, usePlayerStore, useQueueControls } from '/@/renderer/store';
|
|
||||||
import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types';
|
|
||||||
import '@ag-grid-community/styles/ag-grid.css';
|
|
||||||
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import { toast } from '/@/renderer/components';
|
||||||
|
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
|
||||||
|
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
||||||
|
import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings';
|
||||||
import { useServerVersion } from '/@/renderer/hooks/use-server-version';
|
import { useServerVersion } from '/@/renderer/hooks/use-server-version';
|
||||||
|
import {
|
||||||
|
useSettingsStore,
|
||||||
|
usePlaybackSettings,
|
||||||
|
useHotkeySettings,
|
||||||
|
useQueueControls,
|
||||||
|
useRemoteSettings,
|
||||||
|
usePlayerStore,
|
||||||
|
PlayerState,
|
||||||
|
} from '/@/renderer/store';
|
||||||
|
import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types';
|
||||||
|
import { PlayQueueHandlerContext } from '/@/renderer/features/player';
|
||||||
|
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
|
||||||
|
import { IsUpdatedDialog } from '/@/renderer/is-updated-dialog';
|
||||||
|
|
||||||
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
||||||
|
|
||||||
@ -189,13 +191,13 @@ export const App = () => {
|
|||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<MantineProvider forceColorScheme={theme as 'light' | 'dark'}>
|
||||||
<PlayQueueHandlerContext.Provider value={providerValue}>
|
<PlayQueueHandlerContext.Provider value={providerValue}>
|
||||||
<ContextMenuProvider>
|
<ContextMenuProvider>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
</ContextMenuProvider>
|
</ContextMenuProvider>
|
||||||
</PlayQueueHandlerContext.Provider>
|
</PlayQueueHandlerContext.Provider>
|
||||||
<IsUpdatedDialog />
|
<IsUpdatedDialog />
|
||||||
</>
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
64
src/renderer/components/action-icon/index.tsx
Normal file
64
src/renderer/components/action-icon/index.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import type { ActionIconProps as MantineActionIconProps } from '@mantine/core';
|
||||||
|
import { createPolymorphicComponent, ActionIcon as MantineActionIcon } from '@mantine/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { BadgeProps } from '/@/renderer/components/badge';
|
||||||
|
|
||||||
|
export type ActionIconProps = MantineActionIconProps;
|
||||||
|
|
||||||
|
const StyledActionIcon = styled(MantineActionIcon)<ActionIconProps>`
|
||||||
|
color: ${(props) => `var(--btn-${props.variant}-fg)`};
|
||||||
|
background: ${(props) => `var(--btn-${props.variant}-bg)`};
|
||||||
|
border: ${(props) => `var(--btn-${props.variant}-border)`};
|
||||||
|
border-radius: ${(props) => `var(--btn-${props.variant}-radius)`};
|
||||||
|
transition: background 0.2s ease-in-out, color 0.2s ease-in-out, border 0.2s ease-in-out;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: ${(props) => `var(--btn-${props.variant}-fg)`};
|
||||||
|
transition: fill 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: ${(props) => `var(--btn-${props.variant}-fg)`};
|
||||||
|
background: ${(props) => `var(--btn-${props.variant}-bg)`};
|
||||||
|
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([data-disabled])&:hover {
|
||||||
|
color: ${(props) => `var(--btn-${props.variant}-fg) !important`};
|
||||||
|
background: ${(props) => `var(--btn-${props.variant}-bg)`};
|
||||||
|
filter: brightness(85%);
|
||||||
|
border: ${(props) => `var(--btn-${props.variant}-border-hover)`};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([data-disabled])&:focus-visible {
|
||||||
|
color: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
|
||||||
|
background: ${(props) => `var(--btn-${props.variant}-bg)`};
|
||||||
|
filter: brightness(85%);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .mantine-ActionIcon-centerLoader {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const _ActionIcon = ({ children, ...props }: ActionIconProps) => {
|
||||||
|
return (
|
||||||
|
<StyledActionIcon
|
||||||
|
radius="md"
|
||||||
|
size="sm"
|
||||||
|
styles={{
|
||||||
|
root: { background: 'var(--badge-bg)' },
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledActionIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActionIcon = createPolymorphicComponent<'button', BadgeProps>(_ActionIcon);
|
136
src/renderer/components/box/index.tsx
Normal file
136
src/renderer/components/box/index.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import styled, { CSSProperties } from 'styled-components';
|
||||||
|
import { StyledComponentsStyleProps, StyleProps } from '/@/renderer/components/shared';
|
||||||
|
|
||||||
|
interface BoxProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
|
||||||
|
align?: CSSProperties['alignItems'];
|
||||||
|
children?: ReactNode;
|
||||||
|
columnGap?: CSSProperties['columnGap'];
|
||||||
|
direction?: CSSProperties['flexDirection'];
|
||||||
|
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
|
||||||
|
justify?: CSSProperties['justifyContent'];
|
||||||
|
rowGap?: CSSProperties['rowGap'];
|
||||||
|
wrap?: CSSProperties['flexWrap'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Box = forwardRef<HTMLDivElement, BoxProps>(({ children, ...props }: BoxProps, ref) => {
|
||||||
|
const {
|
||||||
|
bg,
|
||||||
|
bottom,
|
||||||
|
display,
|
||||||
|
fw,
|
||||||
|
h,
|
||||||
|
left,
|
||||||
|
lts,
|
||||||
|
m,
|
||||||
|
mah,
|
||||||
|
maw,
|
||||||
|
mb,
|
||||||
|
mih,
|
||||||
|
miw,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mt,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
p,
|
||||||
|
pb,
|
||||||
|
pl,
|
||||||
|
pos,
|
||||||
|
pr,
|
||||||
|
pt,
|
||||||
|
px,
|
||||||
|
py,
|
||||||
|
right,
|
||||||
|
ta,
|
||||||
|
td,
|
||||||
|
top,
|
||||||
|
tt,
|
||||||
|
w,
|
||||||
|
...htmlProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const styleProps = {
|
||||||
|
$bg: bg,
|
||||||
|
$bottom: bottom,
|
||||||
|
$display: display,
|
||||||
|
$fw: fw,
|
||||||
|
$h: h,
|
||||||
|
$left: left,
|
||||||
|
$lts: lts,
|
||||||
|
$m: m,
|
||||||
|
$mah: mah,
|
||||||
|
$maw: maw,
|
||||||
|
$mb: mb,
|
||||||
|
$mih: mih,
|
||||||
|
$miw: miw,
|
||||||
|
$ml: ml,
|
||||||
|
$mr: mr,
|
||||||
|
$mt: mt,
|
||||||
|
$mx: mx,
|
||||||
|
$my: my,
|
||||||
|
$p: p,
|
||||||
|
$pb: pb,
|
||||||
|
$pl: pl,
|
||||||
|
$pos: pos,
|
||||||
|
$pr: pr,
|
||||||
|
$pt: pt,
|
||||||
|
$px: px,
|
||||||
|
$py: py,
|
||||||
|
$right: right,
|
||||||
|
$ta: ta,
|
||||||
|
$td: td,
|
||||||
|
$top: top,
|
||||||
|
$tt: tt,
|
||||||
|
$w: w,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledBox
|
||||||
|
ref={ref}
|
||||||
|
{...styleProps}
|
||||||
|
{...htmlProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledBox>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledBox = styled.div<StyledComponentsStyleProps>`
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
|
`;
|
@ -92,7 +92,6 @@ export const _Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
>
|
>
|
||||||
<StyledButton
|
<StyledButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
loaderPosition="center"
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
|
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
|
||||||
@ -109,7 +108,6 @@ export const _Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
return (
|
return (
|
||||||
<StyledButton
|
<StyledButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
loaderPosition="center"
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
|
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
|
||||||
@ -171,7 +169,7 @@ export const TimeoutButton = ({ timeoutProps, ...props }: HoldButtonProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
sx={{ color: 'var(--danger-color)' }}
|
style={{ color: 'var(--danger-color)' }}
|
||||||
onClick={startTimeout}
|
onClick={startTimeout}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
@ -156,7 +156,7 @@ export const AlbumCard = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
borderRadius: 'var(--card-default-radius)',
|
borderRadius: 'var(--card-default-radius)',
|
||||||
height: `${size}px`,
|
height: `${size}px`,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { UnstyledButtonProps } from '@mantine/core';
|
import type { UnstyledButtonProps } from '@mantine/core';
|
||||||
import { Group } from '@mantine/core';
|
|
||||||
import { RiPlayFill, RiMore2Fill, RiHeartFill, RiHeartLine } from 'react-icons/ri';
|
import { RiPlayFill, RiMore2Fill, RiHeartFill, RiHeartLine } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { _Button } from '/@/renderer/components/button';
|
import { _Button } from '/@/renderer/components/button';
|
||||||
@ -14,6 +13,7 @@ import {
|
|||||||
ALBUM_CONTEXT_MENU_ITEMS,
|
ALBUM_CONTEXT_MENU_ITEMS,
|
||||||
ARTIST_CONTEXT_MENU_ITEMS,
|
ARTIST_CONTEXT_MENU_ITEMS,
|
||||||
} from '/@/renderer/features/context-menu/context-menu-items';
|
} from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
|
||||||
type PlayButtonType = UnstyledButtonProps & React.ComponentPropsWithoutRef<'button'>;
|
type PlayButtonType = UnstyledButtonProps & React.ComponentPropsWithoutRef<'button'>;
|
||||||
|
|
||||||
@ -137,11 +137,11 @@ export const CardControls = ({
|
|||||||
<PlayButton onClick={handlePlay}>
|
<PlayButton onClick={handlePlay}>
|
||||||
<RiPlayFill size={25} />
|
<RiPlayFill size={25} />
|
||||||
</PlayButton>
|
</PlayButton>
|
||||||
<Group spacing="xs">
|
<Group gap="xs">
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
disabled
|
disabled
|
||||||
p={5}
|
p={5}
|
||||||
sx={{ svg: { fill: 'white !important' } }}
|
style={{ svg: { fill: 'white !important' } }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<FavoriteWrapper isFavorite={itemData?.isFavorite}>
|
<FavoriteWrapper isFavorite={itemData?.isFavorite}>
|
||||||
@ -157,7 +157,7 @@ export const CardControls = ({
|
|||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
p={5}
|
p={5}
|
||||||
sx={{ svg: { fill: 'white !important' } }}
|
style={{ svg: { fill: 'white !important' } }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -40,7 +40,7 @@ export const CardRows = ({ data, rows }: CardRowsProps) => {
|
|||||||
<Text
|
<Text
|
||||||
$noSelect
|
$noSelect
|
||||||
$secondary
|
$secondary
|
||||||
sx={{
|
style={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
padding: '0 2px 0 1px',
|
padding: '0 2px 0 1px',
|
||||||
}}
|
}}
|
||||||
|
@ -151,7 +151,7 @@ export const PosterCard = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
borderRadius: 'var(--card-default-radius)',
|
borderRadius: 'var(--card-default-radius)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -190,7 +190,7 @@ export const PosterCard = ({
|
|||||||
<ImageContainerSkeleton />
|
<ImageContainerSkeleton />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Stack spacing="sm">
|
<Stack gap="sm">
|
||||||
{controls.cardRows.map((row, index) => (
|
{controls.cardRows.map((row, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${index}-${row.arrayProperty}`}
|
key={`${index}-${row.arrayProperty}`}
|
||||||
|
134
src/renderer/components/center/index.tsx
Normal file
134
src/renderer/components/center/index.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { forwardRef, ReactNode, HTMLAttributes } from 'react';
|
||||||
|
import { Center as MantineCenter } from '@mantine/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { StyleProps, StyledComponentsStyleProps } from '/@/renderer/components/shared';
|
||||||
|
|
||||||
|
interface CenterProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
|
||||||
|
children?: ReactNode;
|
||||||
|
inline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Center = forwardRef<HTMLDivElement, CenterProps>(
|
||||||
|
({ inline, children, ...props }: CenterProps, ref) => {
|
||||||
|
const {
|
||||||
|
bg,
|
||||||
|
bottom,
|
||||||
|
display,
|
||||||
|
fw,
|
||||||
|
h,
|
||||||
|
left,
|
||||||
|
lts,
|
||||||
|
m,
|
||||||
|
mah,
|
||||||
|
maw,
|
||||||
|
mb,
|
||||||
|
mih,
|
||||||
|
miw,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mt,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
p,
|
||||||
|
pb,
|
||||||
|
pl,
|
||||||
|
pos,
|
||||||
|
pr,
|
||||||
|
pt,
|
||||||
|
px,
|
||||||
|
py,
|
||||||
|
right,
|
||||||
|
ta,
|
||||||
|
td,
|
||||||
|
top,
|
||||||
|
tt,
|
||||||
|
w,
|
||||||
|
...htmlProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const styleProps = {
|
||||||
|
$bg: bg,
|
||||||
|
$bottom: bottom,
|
||||||
|
$display: display,
|
||||||
|
$fw: fw,
|
||||||
|
$h: h,
|
||||||
|
$left: left,
|
||||||
|
$lts: lts,
|
||||||
|
$m: m,
|
||||||
|
$mah: mah,
|
||||||
|
$maw: maw,
|
||||||
|
$mb: mb,
|
||||||
|
$mih: mih,
|
||||||
|
$miw: miw,
|
||||||
|
$ml: ml,
|
||||||
|
$mr: mr,
|
||||||
|
$mt: mt,
|
||||||
|
$mx: mx,
|
||||||
|
$my: my,
|
||||||
|
$p: p,
|
||||||
|
$pb: pb,
|
||||||
|
$pl: pl,
|
||||||
|
$pos: pos,
|
||||||
|
$pr: pr,
|
||||||
|
$pt: pt,
|
||||||
|
$px: px,
|
||||||
|
$py: py,
|
||||||
|
$right: right,
|
||||||
|
$ta: ta,
|
||||||
|
$td: td,
|
||||||
|
$top: top,
|
||||||
|
$tt: tt,
|
||||||
|
$w: w,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledCenter
|
||||||
|
ref={ref}
|
||||||
|
inline={inline}
|
||||||
|
{...styleProps}
|
||||||
|
{...htmlProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledCenter>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledCenter = styled(MantineCenter)<StyledComponentsStyleProps>`
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
|
`;
|
@ -1,7 +1,8 @@
|
|||||||
import { ComponentPropsWithoutRef, forwardRef, ReactNode, Ref } from 'react';
|
import { ComponentPropsWithoutRef, forwardRef, ReactNode, Ref } from 'react';
|
||||||
import { Box, Group, UnstyledButton, UnstyledButtonProps } from '@mantine/core';
|
import { UnstyledButton, UnstyledButtonProps } from '@mantine/core';
|
||||||
import { motion, Variants } from 'framer-motion';
|
import { motion, Variants } from 'framer-motion';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -80,12 +81,12 @@ export const ContextMenuButton = forwardRef(
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Group spacing="md">
|
<Group gap="md">
|
||||||
<Box>{leftIcon}</Box>
|
<div>{leftIcon}</div>
|
||||||
<Box mr="2rem">{children}</Box>
|
<div style={{ marginRight: '2rem' }}>{children}</div>
|
||||||
</Group>
|
</Group>
|
||||||
<Box>{rightIcon}</Box>
|
<div>{rightIcon}</div>
|
||||||
</Group>
|
</Group>
|
||||||
</StyledContextMenuButton>
|
</StyledContextMenuButton>
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ export const DatePicker = ({ width, maxWidth, ...props }: DatePickerProps) => {
|
|||||||
return (
|
return (
|
||||||
<StyledDatePicker
|
<StyledDatePicker
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ maxWidth, width }}
|
style={{ maxWidth, width }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
44
src/renderer/components/divider/index.tsx
Normal file
44
src/renderer/components/divider/index.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { forwardRef, HTMLAttributes, CSSProperties } from 'react';
|
||||||
|
import { Divider as MantineDivider } from '@mantine/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
interface DividerProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
color?: string;
|
||||||
|
label?: string;
|
||||||
|
labelPosition?: 'left' | 'center' | 'right';
|
||||||
|
marginX?: CSSProperties['margin'];
|
||||||
|
marginY?: CSSProperties['margin'];
|
||||||
|
orientation?: 'horizontal' | 'vertical';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Divider = forwardRef<HTMLDivElement, DividerProps>(
|
||||||
|
(
|
||||||
|
{ color, label, labelPosition, marginX, marginY, orientation, ...props }: DividerProps,
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<StyledDivider
|
||||||
|
ref={ref}
|
||||||
|
$color={color}
|
||||||
|
$marginX={marginX}
|
||||||
|
$marginY={marginY}
|
||||||
|
label={label}
|
||||||
|
labelPosition={labelPosition}
|
||||||
|
orientation={orientation}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledDivider = styled(MantineDivider)<{
|
||||||
|
$color?: DividerProps['color'];
|
||||||
|
$marginX?: DividerProps['marginX'];
|
||||||
|
$marginY?: DividerProps['marginY'];
|
||||||
|
}>`
|
||||||
|
margin-top: ${(props) => props.$marginY};
|
||||||
|
margin-bottom: ${(props) => props.$marginY};
|
||||||
|
margin-left: ${(props) => props.$marginX};
|
||||||
|
margin-right: ${(props) => props.$marginX};
|
||||||
|
border-color: ${(props) => props.$color || 'var(--generic-border-color)'} !important;
|
||||||
|
`;
|
@ -1,6 +1,6 @@
|
|||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Group, Image, Stack } from '@mantine/core';
|
import { Image } from '@mantine/core';
|
||||||
import type { Variants } from 'framer-motion';
|
import type { Variants } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -15,6 +15,8 @@ import { AppRoute } from '/@/renderer/router/routes';
|
|||||||
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
|
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
|
||||||
import { Play } from '/@/renderer/types';
|
import { Play } from '/@/renderer/types';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
import { Stack } from '/@/renderer/components/stack';
|
||||||
|
|
||||||
const Carousel = styled(motion.div)`
|
const Carousel = styled(motion.div)`
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -166,21 +168,21 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
|||||||
placeholder="var(--card-default-bg)"
|
placeholder="var(--card-default-bg)"
|
||||||
radius="md"
|
radius="md"
|
||||||
src={data[itemIndex]?.imageUrl}
|
src={data[itemIndex]?.imageUrl}
|
||||||
sx={{ objectFit: 'cover' }}
|
style={{ objectFit: 'cover' }}
|
||||||
width={225}
|
width={225}
|
||||||
/>
|
/>
|
||||||
</ImageColumn>
|
</ImageColumn>
|
||||||
<InfoColumn>
|
<InfoColumn>
|
||||||
<Stack
|
<Stack
|
||||||
spacing="md"
|
gap="md"
|
||||||
sx={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<TextTitle
|
<TextTitle
|
||||||
lh="3.5rem"
|
lh="3.5rem"
|
||||||
order={1}
|
order={1}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
sx={{ fontSize: '3.5rem' }}
|
style={{ fontSize: '3.5rem' }}
|
||||||
weight={900}
|
weight={900}
|
||||||
>
|
>
|
||||||
{currentItem?.name}
|
{currentItem?.name}
|
||||||
@ -209,7 +211,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
|||||||
<Badge size="lg">{currentItem?.releaseYear}</Badge>
|
<Badge size="lg">{currentItem?.releaseYear}</Badge>
|
||||||
<Badge size="lg">{currentItem?.songCount} tracks</Badge>
|
<Badge size="lg">{currentItem?.songCount} tracks</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
style={{ borderRadius: '5rem' }}
|
style={{ borderRadius: '5rem' }}
|
||||||
@ -237,7 +239,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
|||||||
{ postProcess: 'titleCase' },
|
{ postProcess: 'titleCase' },
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Button
|
<Button
|
||||||
radius="lg"
|
radius="lg"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
149
src/renderer/components/flex/index.tsx
Normal file
149
src/renderer/components/flex/index.tsx
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { Flex as MantineFlex } from '@mantine/core';
|
||||||
|
import styled, { CSSProperties } from 'styled-components';
|
||||||
|
import { StyleProps, StyledComponentsStyleProps } from '/@/renderer/components/shared';
|
||||||
|
|
||||||
|
interface FlexProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
|
||||||
|
align?: CSSProperties['alignItems'];
|
||||||
|
children?: ReactNode;
|
||||||
|
columnGap?: CSSProperties['columnGap'];
|
||||||
|
direction?: CSSProperties['flexDirection'];
|
||||||
|
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
|
||||||
|
justify?: CSSProperties['justifyContent'];
|
||||||
|
rowGap?: CSSProperties['rowGap'];
|
||||||
|
wrap?: CSSProperties['flexWrap'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Flex = forwardRef<HTMLDivElement, FlexProps>(
|
||||||
|
(
|
||||||
|
{ align, columnGap, direction, gap, justify, rowGap, wrap, children, ...props }: FlexProps,
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
bg,
|
||||||
|
bottom,
|
||||||
|
display,
|
||||||
|
fw,
|
||||||
|
h,
|
||||||
|
left,
|
||||||
|
lts,
|
||||||
|
m,
|
||||||
|
mah,
|
||||||
|
maw,
|
||||||
|
mb,
|
||||||
|
mih,
|
||||||
|
miw,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mt,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
p,
|
||||||
|
pb,
|
||||||
|
pl,
|
||||||
|
pos,
|
||||||
|
pr,
|
||||||
|
pt,
|
||||||
|
px,
|
||||||
|
py,
|
||||||
|
right,
|
||||||
|
ta,
|
||||||
|
td,
|
||||||
|
top,
|
||||||
|
tt,
|
||||||
|
w,
|
||||||
|
...htmlProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const styleProps = {
|
||||||
|
$bg: bg,
|
||||||
|
$bottom: bottom,
|
||||||
|
$display: display,
|
||||||
|
$fw: fw,
|
||||||
|
$h: h,
|
||||||
|
$left: left,
|
||||||
|
$lts: lts,
|
||||||
|
$m: m,
|
||||||
|
$mah: mah,
|
||||||
|
$maw: maw,
|
||||||
|
$mb: mb,
|
||||||
|
$mih: mih,
|
||||||
|
$miw: miw,
|
||||||
|
$ml: ml,
|
||||||
|
$mr: mr,
|
||||||
|
$mt: mt,
|
||||||
|
$mx: mx,
|
||||||
|
$my: my,
|
||||||
|
$p: p,
|
||||||
|
$pb: pb,
|
||||||
|
$pl: pl,
|
||||||
|
$pos: pos,
|
||||||
|
$pr: pr,
|
||||||
|
$pt: pt,
|
||||||
|
$px: px,
|
||||||
|
$py: py,
|
||||||
|
$right: right,
|
||||||
|
$ta: ta,
|
||||||
|
$td: td,
|
||||||
|
$top: top,
|
||||||
|
$tt: tt,
|
||||||
|
$w: w,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledFlex
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
columnGap={columnGap}
|
||||||
|
direction={direction}
|
||||||
|
gap={gap}
|
||||||
|
justify={justify}
|
||||||
|
rowGap={rowGap}
|
||||||
|
wrap={wrap}
|
||||||
|
{...styleProps}
|
||||||
|
{...htmlProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledFlex>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledFlex = styled(MantineFlex)<StyledComponentsStyleProps>`
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
|
`;
|
@ -9,7 +9,6 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Group, Stack } from '@mantine/core';
|
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@ -25,6 +24,8 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
|
|||||||
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||||
import { CardRoute, CardRow } from '/@/renderer/types';
|
import { CardRoute, CardRow } from '/@/renderer/types';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
import { Stack } from '/@/renderer/components/stack';
|
||||||
|
|
||||||
const getSlidesPerView = (windowWidth: number) => {
|
const getSlidesPerView = (windowWidth: number) => {
|
||||||
if (windowWidth < 400) return 2;
|
if (windowWidth < 400) return 2;
|
||||||
@ -53,7 +54,7 @@ interface TitleProps {
|
|||||||
|
|
||||||
const Title = ({ label, handleNext, handlePrev, pagination }: TitleProps) => {
|
const Title = ({ label, handleNext, handlePrev, pagination }: TitleProps) => {
|
||||||
return (
|
return (
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
{isValidElement(label) ? (
|
{isValidElement(label) ? (
|
||||||
label
|
label
|
||||||
) : (
|
) : (
|
||||||
@ -65,20 +66,18 @@ const Title = ({ label, handleNext, handlePrev, pagination }: TitleProps) => {
|
|||||||
</TextTitle>
|
</TextTitle>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
disabled={!pagination.hasPreviousPage}
|
disabled={!pagination.hasPreviousPage}
|
||||||
size="lg"
|
size="compact-lg"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handlePrev}
|
onClick={handlePrev}
|
||||||
>
|
>
|
||||||
<RiArrowLeftSLine />
|
<RiArrowLeftSLine />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
disabled={!pagination.hasNextPage}
|
disabled={!pagination.hasNextPage}
|
||||||
size="lg"
|
size="compact-lg"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
>
|
>
|
||||||
@ -280,8 +279,7 @@ export const SwiperGridCarousel = ({
|
|||||||
return (
|
return (
|
||||||
<CarouselContainer
|
<CarouselContainer
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="grid-carousel"
|
gap="md"
|
||||||
spacing="md"
|
|
||||||
>
|
>
|
||||||
{title ? (
|
{title ? (
|
||||||
<Title
|
<Title
|
||||||
|
147
src/renderer/components/group/index.tsx
Normal file
147
src/renderer/components/group/index.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { forwardRef, HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { Group as MantineGroup } from '@mantine/core';
|
||||||
|
import styled, { CSSProperties } from 'styled-components';
|
||||||
|
import { StyledComponentsStyleProps, StyleProps } from '/@/renderer/components/shared';
|
||||||
|
|
||||||
|
interface GroupProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
|
||||||
|
align?: CSSProperties['alignItems'];
|
||||||
|
children?: ReactNode;
|
||||||
|
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
|
||||||
|
grow?: boolean;
|
||||||
|
justify?: CSSProperties['justifyContent'];
|
||||||
|
preventGrowOverflow?: boolean;
|
||||||
|
wrap?: CSSProperties['flexWrap'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Group = forwardRef<HTMLDivElement, GroupProps>(
|
||||||
|
(
|
||||||
|
{ align, gap, grow, justify, preventGrowOverflow, wrap, children, ...props }: GroupProps,
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
bg,
|
||||||
|
bottom,
|
||||||
|
display,
|
||||||
|
fw,
|
||||||
|
h,
|
||||||
|
left,
|
||||||
|
lts,
|
||||||
|
m,
|
||||||
|
mah,
|
||||||
|
maw,
|
||||||
|
mb,
|
||||||
|
mih,
|
||||||
|
miw,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mt,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
p,
|
||||||
|
pb,
|
||||||
|
pl,
|
||||||
|
pos,
|
||||||
|
pr,
|
||||||
|
pt,
|
||||||
|
px,
|
||||||
|
py,
|
||||||
|
right,
|
||||||
|
ta,
|
||||||
|
td,
|
||||||
|
top,
|
||||||
|
tt,
|
||||||
|
w,
|
||||||
|
...htmlProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const styleProps = {
|
||||||
|
$bg: bg,
|
||||||
|
$bottom: bottom,
|
||||||
|
$display: display,
|
||||||
|
$fw: fw,
|
||||||
|
$h: h,
|
||||||
|
$left: left,
|
||||||
|
$lts: lts,
|
||||||
|
$m: m,
|
||||||
|
$mah: mah,
|
||||||
|
$maw: maw,
|
||||||
|
$mb: mb,
|
||||||
|
$mih: mih,
|
||||||
|
$miw: miw,
|
||||||
|
$ml: ml,
|
||||||
|
$mr: mr,
|
||||||
|
$mt: mt,
|
||||||
|
$mx: mx,
|
||||||
|
$my: my,
|
||||||
|
$p: p,
|
||||||
|
$pb: pb,
|
||||||
|
$pl: pl,
|
||||||
|
$pos: pos,
|
||||||
|
$pr: pr,
|
||||||
|
$pt: pt,
|
||||||
|
$px: px,
|
||||||
|
$py: py,
|
||||||
|
$right: right,
|
||||||
|
$ta: ta,
|
||||||
|
$td: td,
|
||||||
|
$top: top,
|
||||||
|
$tt: tt,
|
||||||
|
$w: w,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledGroup
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
gap={gap}
|
||||||
|
grow={grow}
|
||||||
|
justify={justify}
|
||||||
|
preventGrowOverflow={preventGrowOverflow}
|
||||||
|
wrap={wrap}
|
||||||
|
{...styleProps}
|
||||||
|
{...htmlProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledGroup = styled(MantineGroup)<StyledComponentsStyleProps>`
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
|
`;
|
@ -34,3 +34,11 @@ export * from './text';
|
|||||||
export * from './text-title';
|
export * from './text-title';
|
||||||
export * from './toast';
|
export * from './toast';
|
||||||
export * from './tooltip';
|
export * from './tooltip';
|
||||||
|
export * from './group';
|
||||||
|
export * from './stack';
|
||||||
|
export * from './flex';
|
||||||
|
export * from './center';
|
||||||
|
export * from './divider';
|
||||||
|
export * from './portal';
|
||||||
|
export * from './box';
|
||||||
|
export * from './action-icon';
|
||||||
|
@ -282,7 +282,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ maxWidth, width }}
|
style={{ maxWidth, width }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledTextInput>
|
</StyledTextInput>
|
||||||
@ -298,7 +298,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|||||||
hideControls
|
hideControls
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ maxWidth, width }}
|
style={{ maxWidth, width }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledNumberInput>
|
</StyledNumberInput>
|
||||||
@ -312,7 +312,7 @@ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
|
|||||||
<StyledPasswordInput
|
<StyledPasswordInput
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ maxWidth, width }}
|
style={{ maxWidth, width }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledPasswordInput>
|
</StyledPasswordInput>
|
||||||
@ -326,12 +326,7 @@ export const FileInput = forwardRef<HTMLButtonElement, FileInputProps>(
|
|||||||
<StyledFileInput
|
<StyledFileInput
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
styles={{
|
style={{ maxWidth, width }}
|
||||||
placeholder: {
|
|
||||||
color: 'var(--input-placeholder-fg)',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
sx={{ maxWidth, width }}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledFileInput>
|
</StyledFileInput>
|
||||||
@ -345,7 +340,7 @@ export const JsonInput = forwardRef<HTMLTextAreaElement, JsonInputProps>(
|
|||||||
<StyledJsonInput
|
<StyledJsonInput
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ maxWidth, width }}
|
style={{ maxWidth, width }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledJsonInput>
|
</StyledJsonInput>
|
||||||
@ -359,7 +354,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||||||
<StyledTextarea
|
<StyledTextarea
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ maxWidth, width }}
|
style={{ maxWidth, width }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledTextarea>
|
</StyledTextarea>
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import {
|
import { ModalProps as MantineModalProps, Modal as MantineModal } from '@mantine/core';
|
||||||
ModalProps as MantineModalProps,
|
|
||||||
Stack,
|
|
||||||
Modal as MantineModal,
|
|
||||||
Flex,
|
|
||||||
Group,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { closeAllModals, ContextModalProps } from '@mantine/modals';
|
import { closeAllModals, ContextModalProps } from '@mantine/modals';
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
|
import { Flex } from '/@/renderer/components/flex';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
import { Stack } from '/@/renderer/components/stack';
|
||||||
|
|
||||||
export interface ModalProps extends Omit<MantineModalProps, 'onClose'> {
|
export interface ModalProps extends Omit<MantineModalProps, 'onClose'> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
@ -77,7 +74,7 @@ export const ConfirmModal = ({
|
|||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Flex>{children}</Flex>
|
<Flex>{children}</Flex>
|
||||||
<Group position="right">
|
<Group align="flex-end">
|
||||||
<Button
|
<Button
|
||||||
data-focus
|
data-focus
|
||||||
variant="default"
|
variant="default"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Flex, Group, Stack } from '@mantine/core';
|
import { Flex, Group, Stack } from '@mantine/core';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
export const MotionFlex = motion(Flex);
|
export const MotionFlex = motion(Flex as any);
|
||||||
|
|
||||||
export const MotionGroup = motion(Group);
|
export const MotionGroup = motion(Group);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Flex, FlexProps } from '@mantine/core';
|
import { FlexProps } from '@mantine/core';
|
||||||
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
||||||
import { ReactNode, useRef } from 'react';
|
import { ReactNode, useRef } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@ -6,10 +6,11 @@ import { useShouldPadTitlebar, useTheme } from '/@/renderer/hooks';
|
|||||||
import { useWindowSettings } from '/@/renderer/store/settings.store';
|
import { useWindowSettings } from '/@/renderer/store/settings.store';
|
||||||
import { Platform } from '/@/renderer/types';
|
import { Platform } from '/@/renderer/types';
|
||||||
|
|
||||||
const Container = styled(motion(Flex))<{
|
const Container = styled(motion.div)<{
|
||||||
$height?: string;
|
$height?: string;
|
||||||
$position?: string;
|
$position?: string;
|
||||||
}>`
|
}>`
|
||||||
|
display: flex;
|
||||||
position: ${(props) => props.$position || 'relative'};
|
position: ${(props) => props.$position || 'relative'};
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -98,7 +99,6 @@ export const PageHeader = ({
|
|||||||
backgroundColor,
|
backgroundColor,
|
||||||
isHidden,
|
isHidden,
|
||||||
children,
|
children,
|
||||||
...props
|
|
||||||
}: PageHeaderProps) => {
|
}: PageHeaderProps) => {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const padRight = useShouldPadTitlebar();
|
const padRight = useShouldPadTitlebar();
|
||||||
@ -111,7 +111,6 @@ export const PageHeader = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
$height={height}
|
$height={height}
|
||||||
$position={position}
|
$position={position}
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
<Header
|
<Header
|
||||||
$isDraggable={windowBarStyle === Platform.WEB}
|
$isDraggable={windowBarStyle === Platform.WEB}
|
||||||
|
10
src/renderer/components/portal/index.tsx
Normal file
10
src/renderer/components/portal/index.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { Portal as MantinePortal } from '@mantine/core';
|
||||||
|
|
||||||
|
interface PortalProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Portal = ({ children, ...props }: PortalProps) => {
|
||||||
|
return <MantinePortal {...props}>{children}</MantinePortal>;
|
||||||
|
};
|
@ -1,4 +1,3 @@
|
|||||||
import { Group, Stack } from '@mantine/core';
|
|
||||||
import { Select } from '/@/renderer/components/select';
|
import { Select } from '/@/renderer/components/select';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { RiAddFill, RiAddLine, RiDeleteBinFill, RiMore2Line, RiRestartLine } from 'react-icons/ri';
|
import { RiAddFill, RiAddLine, RiDeleteBinFill, RiMore2Line, RiRestartLine } from 'react-icons/ri';
|
||||||
@ -7,6 +6,8 @@ import { DropdownMenu } from '/@/renderer/components/dropdown-menu';
|
|||||||
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
|
import { QueryBuilderOption } from '/@/renderer/components/query-builder/query-builder-option';
|
||||||
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
import { Stack } from '/@/renderer/components/stack';
|
||||||
|
|
||||||
const FILTER_GROUP_OPTIONS_DATA = [
|
const FILTER_GROUP_OPTIONS_DATA = [
|
||||||
{
|
{
|
||||||
@ -98,10 +99,10 @@ export const QueryBuilder = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
ml={`${level * 10}px`}
|
gap="sm"
|
||||||
spacing="sm"
|
style={{ marginLeft: `${level * 10}px` }}
|
||||||
>
|
>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Select
|
<Select
|
||||||
data={FILTER_GROUP_OPTIONS_DATA}
|
data={FILTER_GROUP_OPTIONS_DATA}
|
||||||
maxWidth={175}
|
maxWidth={175}
|
||||||
@ -131,7 +132,7 @@ export const QueryBuilder = ({
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddFill />}
|
leftSection={<RiAddFill />}
|
||||||
onClick={handleAddRuleGroup}
|
onClick={handleAddRuleGroup}
|
||||||
>
|
>
|
||||||
Add rule group
|
Add rule group
|
||||||
@ -139,7 +140,7 @@ export const QueryBuilder = ({
|
|||||||
|
|
||||||
{level > 0 && (
|
{level > 0 && (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiDeleteBinFill />}
|
leftSection={<RiDeleteBinFill />}
|
||||||
onClick={handleDeleteRuleGroup}
|
onClick={handleDeleteRuleGroup}
|
||||||
>
|
>
|
||||||
Remove rule group
|
Remove rule group
|
||||||
@ -150,14 +151,14 @@ export const QueryBuilder = ({
|
|||||||
<DropdownMenu.Divider />
|
<DropdownMenu.Divider />
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
$danger
|
$danger
|
||||||
icon={<RiRestartLine color="var(--danger-color)" />}
|
leftSection={<RiRestartLine color="var(--danger-color)" />}
|
||||||
onClick={onResetFilters}
|
onClick={onResetFilters}
|
||||||
>
|
>
|
||||||
Reset to default
|
Reset to default
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
$danger
|
$danger
|
||||||
icon={<RiDeleteBinFill color="var(--danger-color)" />}
|
leftSection={<RiDeleteBinFill color="var(--danger-color)" />}
|
||||||
onClick={onClearFilters}
|
onClick={onClearFilters}
|
||||||
>
|
>
|
||||||
Clear filters
|
Clear filters
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Group } from '@mantine/core';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { RiSubtractLine } from 'react-icons/ri';
|
import { RiSubtractLine } from 'react-icons/ri';
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
import { NumberInput, TextInput } from '/@/renderer/components/input';
|
import { NumberInput, TextInput } from '/@/renderer/components/input';
|
||||||
import { Select } from '/@/renderer/components/select';
|
import { Select } from '/@/renderer/components/select';
|
||||||
import { QueryBuilderRule } from '/@/renderer/types';
|
import { QueryBuilderRule } from '/@/renderer/types';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
|
||||||
type DeleteArgs = {
|
type DeleteArgs = {
|
||||||
groupIndex: number[];
|
groupIndex: number[];
|
||||||
@ -69,7 +69,7 @@ const QueryValueInput = ({ onChange, type, data, ...props }: any) => {
|
|||||||
maxWidth={81}
|
maxWidth={81}
|
||||||
width="10%"
|
width="10%"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newRange = [e || 0, numberRange[1]];
|
const newRange = [Number(e) || 0, numberRange[1]];
|
||||||
setNumberRange(newRange);
|
setNumberRange(newRange);
|
||||||
onChange(newRange);
|
onChange(newRange);
|
||||||
}}
|
}}
|
||||||
@ -80,7 +80,7 @@ const QueryValueInput = ({ onChange, type, data, ...props }: any) => {
|
|||||||
maxWidth={81}
|
maxWidth={81}
|
||||||
width="10%"
|
width="10%"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newRange = [numberRange[0], e || 0];
|
const newRange = [numberRange[0], Number(e) || 0];
|
||||||
setNumberRange(newRange);
|
setNumberRange(newRange);
|
||||||
onChange(newRange);
|
onChange(newRange);
|
||||||
}}
|
}}
|
||||||
@ -188,8 +188,8 @@ export const QueryBuilderOption = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
ml={ml}
|
gap="sm"
|
||||||
spacing="sm"
|
style={{ marginLeft: `${ml}px` }}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
searchable
|
searchable
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { ChangeEvent, KeyboardEvent } from 'react';
|
import { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
import { ActionIcon, TextInputProps } from '@mantine/core';
|
import { TextInputProps } from '@mantine/core';
|
||||||
import { useFocusWithin, useHotkeys, useMergedRef } from '@mantine/hooks';
|
import { useFocusWithin, useHotkeys, useMergedRef } from '@mantine/hooks';
|
||||||
import { RiCloseFill, RiSearchLine } from 'react-icons/ri';
|
import { RiCloseFill, RiSearchLine } from 'react-icons/ri';
|
||||||
import { TextInput } from '/@/renderer/components/input';
|
import { TextInput } from '/@/renderer/components/input';
|
||||||
import { useSettingsStore } from '/@/renderer/store';
|
import { useSettingsStore } from '/@/renderer/store';
|
||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
|
import { ActionIcon } from '/@/renderer/components/action-icon';
|
||||||
|
|
||||||
interface SearchInputProps extends TextInputProps {
|
interface SearchInputProps extends TextInputProps {
|
||||||
initialWidth?: number;
|
initialWidth?: number;
|
||||||
@ -39,7 +40,7 @@ export const SearchInput = ({
|
|||||||
<TextInput
|
<TextInput
|
||||||
ref={mergedRef}
|
ref={mergedRef}
|
||||||
{...props}
|
{...props}
|
||||||
icon={showIcon && <RiSearchLine />}
|
leftSection={showIcon && <RiSearchLine />}
|
||||||
rightSection={
|
rightSection={
|
||||||
isOpened ? (
|
isOpened ? (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
@ -55,13 +56,13 @@ export const SearchInput = ({
|
|||||||
}
|
}
|
||||||
size="md"
|
size="md"
|
||||||
styles={{
|
styles={{
|
||||||
icon: { svg: { fill: 'var(--titlebar-fg)' } },
|
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: isOpened ? 'inherit' : 'transparent !important',
|
backgroundColor: isOpened ? 'inherit' : 'transparent !important',
|
||||||
border: 'none !important',
|
border: 'none !important',
|
||||||
cursor: isOpened ? 'text' : 'pointer',
|
cursor: isOpened ? 'text' : 'pointer',
|
||||||
padding: isOpened ? '10px' : 0,
|
padding: isOpened ? '10px' : 0,
|
||||||
},
|
},
|
||||||
|
section: { svg: { fill: 'var(--titlebar-fg)' }, userSelect: 'none' },
|
||||||
}}
|
}}
|
||||||
width={isOpened ? openedWidth || 150 : initialWidth || 35}
|
width={isOpened ? openedWidth || 150 : initialWidth || 35}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
@ -40,7 +40,11 @@ const StyledSelect = styled(MantineSelect)`
|
|||||||
export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
|
export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
withinPortal
|
comboboxProps={{
|
||||||
|
transitionProps: { duration: 100, transition: 'fade' },
|
||||||
|
withinPortal: true,
|
||||||
|
}}
|
||||||
|
style={{ maxWidth, width }}
|
||||||
styles={{
|
styles={{
|
||||||
dropdown: {
|
dropdown: {
|
||||||
background: 'var(--dropdown-menu-bg)',
|
background: 'var(--dropdown-menu-bg)',
|
||||||
@ -50,14 +54,14 @@ export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
|
|||||||
background: 'var(--input-bg)',
|
background: 'var(--input-bg)',
|
||||||
color: 'var(--input-fg)',
|
color: 'var(--input-fg)',
|
||||||
},
|
},
|
||||||
item: {
|
option: {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: 'var(--dropdown-menu-bg-hover)',
|
background: 'var(--dropdown-menu-bg-hover)',
|
||||||
},
|
},
|
||||||
'&[data-hovered]': {
|
'&[dataHovered]': {
|
||||||
background: 'var(--dropdown-menu-bg-hover)',
|
background: 'var(--dropdown-menu-bg-hover)',
|
||||||
},
|
},
|
||||||
'&[data-selected="true"]': {
|
'&[dataSelected="true"]': {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: 'var(--dropdown-menu-bg-hover)',
|
background: 'var(--dropdown-menu-bg-hover)',
|
||||||
},
|
},
|
||||||
@ -68,8 +72,6 @@ export const Select = ({ width, maxWidth, ...props }: SelectProps) => {
|
|||||||
padding: '.3rem',
|
padding: '.3rem',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
sx={{ maxWidth, width }}
|
|
||||||
transitionProps={{ duration: 100, transition: 'fade' }}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -95,7 +97,11 @@ const StyledMultiSelect = styled(MantineMultiSelect)`
|
|||||||
export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) => {
|
export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledMultiSelect
|
<StyledMultiSelect
|
||||||
withinPortal
|
comboboxProps={{
|
||||||
|
transitionProps: { duration: 100, transition: 'fade' },
|
||||||
|
withinPortal: true,
|
||||||
|
}}
|
||||||
|
style={{ maxWidth, width }}
|
||||||
styles={{
|
styles={{
|
||||||
dropdown: {
|
dropdown: {
|
||||||
background: 'var(--dropdown-menu-bg)',
|
background: 'var(--dropdown-menu-bg)',
|
||||||
@ -105,14 +111,14 @@ export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) =>
|
|||||||
background: 'var(--input-bg)',
|
background: 'var(--input-bg)',
|
||||||
color: 'var(--input-fg)',
|
color: 'var(--input-fg)',
|
||||||
},
|
},
|
||||||
item: {
|
option: {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: 'var(--dropdown-menu-bg-hover)',
|
background: 'var(--dropdown-menu-bg-hover)',
|
||||||
},
|
},
|
||||||
'&[data-hovered]': {
|
'&[dataHovered]': {
|
||||||
background: 'var(--dropdown-menu-bg-hover)',
|
background: 'var(--dropdown-menu-bg-hover)',
|
||||||
},
|
},
|
||||||
'&[data-selected="true"]': {
|
'&[dataSelected="true"]': {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: 'var(--dropdown-menu-bg-hover)',
|
background: 'var(--dropdown-menu-bg-hover)',
|
||||||
},
|
},
|
||||||
@ -122,15 +128,13 @@ export const MultiSelect = ({ width, maxWidth, ...props }: MultiSelectProps) =>
|
|||||||
color: 'var(--dropdown-menu-fg)',
|
color: 'var(--dropdown-menu-fg)',
|
||||||
padding: '.5rem .1rem',
|
padding: '.5rem .1rem',
|
||||||
},
|
},
|
||||||
value: {
|
pill: {
|
||||||
margin: '.2rem',
|
margin: '.2rem',
|
||||||
paddingBottom: '1rem',
|
paddingBottom: '1rem',
|
||||||
paddingLeft: '1rem',
|
paddingLeft: '1rem',
|
||||||
paddingTop: '1rem',
|
paddingTop: '1rem',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
sx={{ maxWidth, width }}
|
|
||||||
transitionProps={{ duration: 100, transition: 'fade' }}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
114
src/renderer/components/shared.ts
Normal file
114
src/renderer/components/shared.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { CSSProperties } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export type StyleProps = {
|
||||||
|
bg?: CSSProperties['backgroundColor'];
|
||||||
|
bottom?: CSSProperties['bottom'];
|
||||||
|
display?: CSSProperties['display'];
|
||||||
|
fw?: CSSProperties['fontWeight'];
|
||||||
|
h?: CSSProperties['height'];
|
||||||
|
left?: CSSProperties['left'];
|
||||||
|
lh?: CSSProperties['lineHeight'];
|
||||||
|
lts?: CSSProperties['letterSpacing'];
|
||||||
|
m?: CSSProperties['margin'];
|
||||||
|
mah?: CSSProperties['maxHeight'];
|
||||||
|
maw?: CSSProperties['maxWidth'];
|
||||||
|
mb?: CSSProperties['marginBottom'];
|
||||||
|
mih?: CSSProperties['minHeight'];
|
||||||
|
miw?: CSSProperties['minWidth'];
|
||||||
|
ml?: CSSProperties['marginLeft'];
|
||||||
|
mr?: CSSProperties['marginRight'];
|
||||||
|
mt?: CSSProperties['marginTop'];
|
||||||
|
mx?: CSSProperties['marginLeft'] | CSSProperties['marginRight'];
|
||||||
|
my?: CSSProperties['marginTop'] | CSSProperties['marginBottom'];
|
||||||
|
p?: CSSProperties['padding'];
|
||||||
|
pb?: CSSProperties['paddingBottom'];
|
||||||
|
pl?: CSSProperties['paddingLeft'];
|
||||||
|
pos?: CSSProperties['position'];
|
||||||
|
pr?: CSSProperties['paddingRight'];
|
||||||
|
pt?: CSSProperties['paddingTop'];
|
||||||
|
px?: CSSProperties['paddingLeft'] | CSSProperties['paddingRight'];
|
||||||
|
py?: CSSProperties['paddingTop'] | CSSProperties['paddingBottom'];
|
||||||
|
right?: CSSProperties['right'];
|
||||||
|
ta?: CSSProperties['textAlign'];
|
||||||
|
td?: CSSProperties['textDecoration'];
|
||||||
|
top?: CSSProperties['top'];
|
||||||
|
tt?: CSSProperties['textTransform'];
|
||||||
|
w?: CSSProperties['width'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StyledComponentsStyleProps = {
|
||||||
|
$bg?: CSSProperties['backgroundColor'];
|
||||||
|
$bottom?: CSSProperties['bottom'];
|
||||||
|
$display?: CSSProperties['display'];
|
||||||
|
$fw?: CSSProperties['fontWeight'];
|
||||||
|
$h?: CSSProperties['height'];
|
||||||
|
$left?: CSSProperties['left'];
|
||||||
|
$lh?: CSSProperties['lineHeight'];
|
||||||
|
$lts?: CSSProperties['letterSpacing'];
|
||||||
|
$m?: CSSProperties['margin'];
|
||||||
|
$mah?: CSSProperties['maxHeight'];
|
||||||
|
$maw?: CSSProperties['maxWidth'];
|
||||||
|
$mb?: CSSProperties['marginBottom'];
|
||||||
|
$mih?: CSSProperties['minHeight'];
|
||||||
|
$miw?: CSSProperties['minWidth'];
|
||||||
|
$ml?: CSSProperties['marginLeft'];
|
||||||
|
$mr?: CSSProperties['marginRight'];
|
||||||
|
$mt?: CSSProperties['marginTop'];
|
||||||
|
$mx?: CSSProperties['marginLeft'] | CSSProperties['marginRight'];
|
||||||
|
$my?: CSSProperties['marginTop'] | CSSProperties['marginBottom'];
|
||||||
|
$p?: CSSProperties['padding'];
|
||||||
|
$pb?: CSSProperties['paddingBottom'];
|
||||||
|
$pl?: CSSProperties['paddingLeft'];
|
||||||
|
$pos?: CSSProperties['position'];
|
||||||
|
$pr?: CSSProperties['paddingRight'];
|
||||||
|
$pt?: CSSProperties['paddingTop'];
|
||||||
|
$px?: CSSProperties['paddingLeft'] | CSSProperties['paddingRight'];
|
||||||
|
$py?: CSSProperties['paddingTop'] | CSSProperties['paddingBottom'];
|
||||||
|
$right?: CSSProperties['right'];
|
||||||
|
$ta?: CSSProperties['textAlign'];
|
||||||
|
$td?: CSSProperties['textDecoration'];
|
||||||
|
$top?: CSSProperties['top'];
|
||||||
|
$tt?: CSSProperties['textTransform'];
|
||||||
|
$w?: CSSProperties['width'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DivWithStyleProps = styled.div<StyledComponentsStyleProps>`
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lh && `line-height: ${props.$lh};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
|
`;
|
@ -1,8 +1,8 @@
|
|||||||
import { Center } from '@mantine/core';
|
|
||||||
import type { IconType } from 'react-icons';
|
import type { IconType } from 'react-icons';
|
||||||
import { RiLoader5Fill } from 'react-icons/ri';
|
import { RiLoader5Fill } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { rotating } from '/@/renderer/styles';
|
import { rotating } from '/@/renderer/styles';
|
||||||
|
import { Center } from '/@/renderer/components/center';
|
||||||
|
|
||||||
interface SpinnerProps extends IconType {
|
interface SpinnerProps extends IconType {
|
||||||
color?: string;
|
color?: string;
|
||||||
@ -18,21 +18,23 @@ export const SpinnerIcon = styled(RiLoader5Fill)`
|
|||||||
export const Spinner = ({ ...props }: SpinnerProps) => {
|
export const Spinner = ({ ...props }: SpinnerProps) => {
|
||||||
if (props.container) {
|
if (props.container) {
|
||||||
return (
|
return (
|
||||||
<Center
|
<SpinnerContainer>
|
||||||
h="100%"
|
|
||||||
w="100%"
|
|
||||||
>
|
|
||||||
<SpinnerIcon
|
<SpinnerIcon
|
||||||
color={props.color}
|
color={props.color}
|
||||||
size={props.size}
|
size={props.size}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</SpinnerContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SpinnerIcon {...props} />;
|
return <SpinnerIcon {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SpinnerContainer = styled(Center)`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
Spinner.defaultProps = {
|
Spinner.defaultProps = {
|
||||||
color: undefined,
|
color: undefined,
|
||||||
size: 15,
|
size: 15,
|
||||||
|
138
src/renderer/components/stack/index.tsx
Normal file
138
src/renderer/components/stack/index.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { forwardRef, HTMLAttributes, ReactNode, CSSProperties } from 'react';
|
||||||
|
import { Stack as MantineStack } from '@mantine/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { StyleProps, StyledComponentsStyleProps } from '../shared';
|
||||||
|
|
||||||
|
interface StackProps extends HTMLAttributes<HTMLDivElement>, StyleProps {
|
||||||
|
align?: CSSProperties['alignItems'];
|
||||||
|
children?: ReactNode;
|
||||||
|
gap?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | CSSProperties['gap'];
|
||||||
|
justify?: CSSProperties['justifyContent'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
||||||
|
({ align, gap, justify, children, ...props }: StackProps, ref) => {
|
||||||
|
const {
|
||||||
|
bg,
|
||||||
|
bottom,
|
||||||
|
display,
|
||||||
|
fw,
|
||||||
|
h,
|
||||||
|
left,
|
||||||
|
lts,
|
||||||
|
m,
|
||||||
|
mah,
|
||||||
|
maw,
|
||||||
|
mb,
|
||||||
|
mih,
|
||||||
|
miw,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mt,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
p,
|
||||||
|
pb,
|
||||||
|
pl,
|
||||||
|
pos,
|
||||||
|
pr,
|
||||||
|
pt,
|
||||||
|
px,
|
||||||
|
py,
|
||||||
|
right,
|
||||||
|
ta,
|
||||||
|
td,
|
||||||
|
top,
|
||||||
|
tt,
|
||||||
|
w,
|
||||||
|
...htmlProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const styleProps = {
|
||||||
|
$bg: bg,
|
||||||
|
$bottom: bottom,
|
||||||
|
$display: display,
|
||||||
|
$fw: fw,
|
||||||
|
$h: h,
|
||||||
|
$left: left,
|
||||||
|
$lts: lts,
|
||||||
|
$m: m,
|
||||||
|
$mah: mah,
|
||||||
|
$maw: maw,
|
||||||
|
$mb: mb,
|
||||||
|
$mih: mih,
|
||||||
|
$miw: miw,
|
||||||
|
$ml: ml,
|
||||||
|
$mr: mr,
|
||||||
|
$mt: mt,
|
||||||
|
$mx: mx,
|
||||||
|
$my: my,
|
||||||
|
$p: p,
|
||||||
|
$pb: pb,
|
||||||
|
$pl: pl,
|
||||||
|
$pos: pos,
|
||||||
|
$pr: pr,
|
||||||
|
$pt: pt,
|
||||||
|
$px: px,
|
||||||
|
$py: py,
|
||||||
|
$right: right,
|
||||||
|
$ta: ta,
|
||||||
|
$td: td,
|
||||||
|
$top: top,
|
||||||
|
$tt: tt,
|
||||||
|
$w: w,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledStack
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
gap={gap}
|
||||||
|
justify={justify}
|
||||||
|
{...styleProps}
|
||||||
|
{...htmlProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledStack>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const StyledStack = styled(MantineStack)<StyledComponentsStyleProps>`
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
|
`;
|
0
src/renderer/components/stack/stack.module.scss
Normal file
0
src/renderer/components/stack/stack.module.scss
Normal file
@ -1,22 +1,21 @@
|
|||||||
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { TitleProps as MantineTitleProps } from '@mantine/core';
|
|
||||||
import { createPolymorphicComponent, Title as MantineHeader } from '@mantine/core';
|
import { createPolymorphicComponent, Title as MantineHeader } from '@mantine/core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { textEllipsis } from '/@/renderer/styles';
|
import { textEllipsis } from '/@/renderer/styles';
|
||||||
|
import { StyleProps, StyledComponentsStyleProps } from '/@/renderer/components/shared';
|
||||||
|
|
||||||
type MantineTextTitleDivProps = MantineTitleProps & ComponentPropsWithoutRef<'div'>;
|
export interface TextTitleProps extends StyleProps {
|
||||||
|
|
||||||
interface TextTitleProps extends MantineTextTitleDivProps {
|
|
||||||
$link?: boolean;
|
$link?: boolean;
|
||||||
$noSelect?: boolean;
|
$noSelect?: boolean;
|
||||||
$secondary?: boolean;
|
$secondary?: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
order?: number;
|
||||||
overflow?: 'hidden' | 'visible';
|
overflow?: 'hidden' | 'visible';
|
||||||
to?: string;
|
to?: string;
|
||||||
weight?: number;
|
weight?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledTextTitle = styled(MantineHeader)<TextTitleProps>`
|
const StyledTextTitle = styled(MantineHeader)<TextTitleProps & StyledComponentsStyleProps>`
|
||||||
overflow: ${(props) => props.overflow};
|
overflow: ${(props) => props.overflow};
|
||||||
color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||||
cursor: ${(props) => props.$link && 'cursor'};
|
cursor: ${(props) => props.$link && 'cursor'};
|
||||||
@ -28,15 +27,132 @@ const StyledTextTitle = styled(MantineHeader)<TextTitleProps>`
|
|||||||
color: ${(props) => props.$link && 'var(--main-fg)'};
|
color: ${(props) => props.$link && 'var(--main-fg)'};
|
||||||
text-decoration: ${(props) => (props.$link ? 'underline' : 'none')};
|
text-decoration: ${(props) => (props.$link ? 'underline' : 'none')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${(props) => props.$bg && `background-color: ${props.$bg};`}
|
||||||
|
${(props) => props.$bottom && `bottom: ${props.$bottom};`}
|
||||||
|
${(props) => props.$display && `display: ${props.$display};`}
|
||||||
|
${(props) => props.$fw && `font-weight: ${props.$fw};`}
|
||||||
|
${(props) => props.$h && `height: ${props.$h};`}
|
||||||
|
${(props) => props.$left && `left: ${props.$left};`}
|
||||||
|
${(props) => props.$lts && `letter-spacing: ${props.$lts};`}
|
||||||
|
${(props) => props.$m && `margin: ${props.$m};`}
|
||||||
|
${(props) => props.$mah && `max-height: ${props.$mah};`}
|
||||||
|
${(props) => props.$maw && `max-width: ${props.$maw};`}
|
||||||
|
${(props) => props.$mb && `margin-bottom: ${props.$mb};`}
|
||||||
|
${(props) => props.$mih && `min-height: ${props.$mih};`}
|
||||||
|
${(props) => props.$miw && `min-width: ${props.$miw};`}
|
||||||
|
${(props) => props.$ml && `margin-left: ${props.$ml};`}
|
||||||
|
${(props) => props.$mr && `margin-right: ${props.$mr};`}
|
||||||
|
${(props) => props.$mt && `margin-top: ${props.$mt};`}
|
||||||
|
${(props) => props.$mx && `margin-left: ${props.$mx};`}
|
||||||
|
${(props) => props.$mx && `margin-right: ${props.$mx};`}
|
||||||
|
${(props) => props.$my && `margin-top: ${props.$my};`}
|
||||||
|
${(props) => props.$my && `margin-bottom: ${props.$my};`}
|
||||||
|
${(props) => props.$p && `padding: ${props.$p};`}
|
||||||
|
${(props) => props.$pb && `padding-bottom: ${props.$pb};`}
|
||||||
|
${(props) => props.$pl && `padding-left: ${props.$pl};`}
|
||||||
|
${(props) => props.$pos && `position: ${props.$pos};`}
|
||||||
|
${(props) => props.$pr && `padding-right: ${props.$pr};`}
|
||||||
|
${(props) => props.$pt && `padding-top: ${props.$pt};`}
|
||||||
|
${(props) => props.$px && `padding-left: ${props.$px};`}
|
||||||
|
${(props) => props.$px && `padding-right: ${props.$px};`}
|
||||||
|
${(props) => props.$py && `padding-top: ${props.$py};`}
|
||||||
|
${(props) => props.$py && `padding-bottom: ${props.$py};`}
|
||||||
|
${(props) => props.$right && `right: ${props.$right};`}
|
||||||
|
${(props) => props.$ta && `text-align: ${props.$ta};`}
|
||||||
|
${(props) => props.$td && `text-decoration: ${props.$td};`}
|
||||||
|
${(props) => props.$top && `top: ${props.$top};`}
|
||||||
|
${(props) => props.$tt && `text-transform: ${props.$tt};`}
|
||||||
|
${(props) => props.$w && `width: ${props.$w};`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const _TextTitle = ({ children, $secondary, overflow, $noSelect, ...rest }: TextTitleProps) => {
|
const _TextTitle = ({
|
||||||
|
children,
|
||||||
|
order,
|
||||||
|
$secondary,
|
||||||
|
overflow,
|
||||||
|
$noSelect,
|
||||||
|
...props
|
||||||
|
}: TextTitleProps) => {
|
||||||
|
const {
|
||||||
|
bg,
|
||||||
|
bottom,
|
||||||
|
display,
|
||||||
|
fw,
|
||||||
|
h,
|
||||||
|
left,
|
||||||
|
lts,
|
||||||
|
m,
|
||||||
|
mah,
|
||||||
|
maw,
|
||||||
|
mb,
|
||||||
|
mih,
|
||||||
|
miw,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mt,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
|
p,
|
||||||
|
pb,
|
||||||
|
pl,
|
||||||
|
pos,
|
||||||
|
pr,
|
||||||
|
pt,
|
||||||
|
px,
|
||||||
|
py,
|
||||||
|
right,
|
||||||
|
ta,
|
||||||
|
td,
|
||||||
|
top,
|
||||||
|
tt,
|
||||||
|
w,
|
||||||
|
...htmlProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const styleProps = {
|
||||||
|
$bg: bg,
|
||||||
|
$bottom: bottom,
|
||||||
|
$display: display,
|
||||||
|
$fw: fw,
|
||||||
|
$h: h,
|
||||||
|
$left: left,
|
||||||
|
$lts: lts,
|
||||||
|
$m: m,
|
||||||
|
$mah: mah,
|
||||||
|
$maw: maw,
|
||||||
|
$mb: mb,
|
||||||
|
$mih: mih,
|
||||||
|
$miw: miw,
|
||||||
|
$ml: ml,
|
||||||
|
$mr: mr,
|
||||||
|
$mt: mt,
|
||||||
|
$mx: mx,
|
||||||
|
$my: my,
|
||||||
|
$p: p,
|
||||||
|
$pb: pb,
|
||||||
|
$pl: pl,
|
||||||
|
$pos: pos,
|
||||||
|
$pr: pr,
|
||||||
|
$pt: pt,
|
||||||
|
$px: px,
|
||||||
|
$py: py,
|
||||||
|
$right: right,
|
||||||
|
$ta: ta,
|
||||||
|
$td: td,
|
||||||
|
$top: top,
|
||||||
|
$tt: tt,
|
||||||
|
$w: w,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTextTitle
|
<StyledTextTitle
|
||||||
$noSelect={$noSelect}
|
$noSelect={$noSelect}
|
||||||
$secondary={$secondary}
|
$secondary={$secondary}
|
||||||
|
order={order}
|
||||||
overflow={overflow}
|
overflow={overflow}
|
||||||
{...rest}
|
{...styleProps}
|
||||||
|
{...htmlProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledTextTitle>
|
</StyledTextTitle>
|
||||||
|
@ -8,6 +8,7 @@ import { textEllipsis } from '/@/renderer/styles';
|
|||||||
type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>;
|
type MantineTextDivProps = MantineTextProps & ComponentPropsWithoutRef<'div'>;
|
||||||
|
|
||||||
interface TextProps extends MantineTextDivProps {
|
interface TextProps extends MantineTextDivProps {
|
||||||
|
$align?: 'left' | 'center' | 'right' | 'justify';
|
||||||
$link?: boolean;
|
$link?: boolean;
|
||||||
$noSelect?: boolean;
|
$noSelect?: boolean;
|
||||||
$secondary?: boolean;
|
$secondary?: boolean;
|
||||||
@ -24,6 +25,7 @@ const StyledText = styled(MantineText)<TextProps>`
|
|||||||
color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
color: ${(props) => (props.$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||||
cursor: ${(props) => props.$link && 'cursor'};
|
cursor: ${(props) => props.$link && 'cursor'};
|
||||||
user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')};
|
user-select: ${(props) => (props.$noSelect ? 'none' : 'auto')};
|
||||||
|
text-align: ${(props) => props.$align};
|
||||||
${(props) => props.overflow === 'hidden' && !props.lineClamp && textEllipsis}
|
${(props) => props.overflow === 'hidden' && !props.lineClamp && textEllipsis}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -32,9 +34,18 @@ const StyledText = styled(MantineText)<TextProps>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const _Text = ({ children, $secondary, overflow, font, $noSelect, ...rest }: TextProps) => {
|
export const _Text = ({
|
||||||
|
children,
|
||||||
|
$secondary,
|
||||||
|
overflow,
|
||||||
|
font,
|
||||||
|
$noSelect,
|
||||||
|
$align,
|
||||||
|
...rest
|
||||||
|
}: TextProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledText
|
<StyledText
|
||||||
|
$align={$align}
|
||||||
$noSelect={$noSelect}
|
$noSelect={$noSelect}
|
||||||
$secondary={$secondary}
|
$secondary={$secondary}
|
||||||
font={font}
|
font={font}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { NotificationProps as MantineNotificationProps } from '@mantine/notifications';
|
|
||||||
import {
|
import {
|
||||||
showNotification,
|
showNotification,
|
||||||
updateNotification,
|
updateNotification,
|
||||||
@ -7,7 +6,13 @@ import {
|
|||||||
cleanNotificationsQueue,
|
cleanNotificationsQueue,
|
||||||
} from '@mantine/notifications';
|
} from '@mantine/notifications';
|
||||||
|
|
||||||
interface NotificationProps extends MantineNotificationProps {
|
interface NotificationProps {
|
||||||
|
autoClose?: boolean | number;
|
||||||
|
id?: string;
|
||||||
|
message: string;
|
||||||
|
onClick?: (e: any) => void;
|
||||||
|
onClose?: (e: any) => void;
|
||||||
|
title?: string;
|
||||||
type?: 'success' | 'error' | 'warning' | 'info';
|
type?: 'success' | 'error' | 'warning' | 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
src/renderer/components/utility/center.tsx
Normal file
13
src/renderer/components/utility/center.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const Center = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,3 @@
|
|||||||
import { Center, Stack } from '@mantine/core';
|
|
||||||
import { RiAlbumFill, RiUserVoiceFill, RiPlayListFill } from 'react-icons/ri';
|
import { RiAlbumFill, RiUserVoiceFill, RiPlayListFill } from 'react-icons/ri';
|
||||||
import { generatePath, useNavigate } from 'react-router-dom';
|
import { generatePath, useNavigate } from 'react-router-dom';
|
||||||
import { SimpleImg } from 'react-simple-img';
|
import { SimpleImg } from 'react-simple-img';
|
||||||
@ -9,6 +8,8 @@ import { CardRows } from '/@/renderer/components/card';
|
|||||||
import { Skeleton } from '/@/renderer/components/skeleton';
|
import { Skeleton } from '/@/renderer/components/skeleton';
|
||||||
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
|
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
|
||||||
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
|
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
|
||||||
|
import { Center } from '/@/renderer/components/center';
|
||||||
|
import { Stack } from '/@/renderer/components/stack';
|
||||||
|
|
||||||
interface BaseGridCardProps {
|
interface BaseGridCardProps {
|
||||||
columnIndex: number;
|
columnIndex: number;
|
||||||
@ -186,7 +187,7 @@ export const DefaultCard = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
borderRadius: 'var(--card-default-radius)',
|
borderRadius: 'var(--card-default-radius)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -233,7 +234,7 @@ export const DefaultCard = ({
|
|||||||
/>
|
/>
|
||||||
</ImageContainer>
|
</ImageContainer>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Stack spacing="sm">
|
<Stack gap="sm">
|
||||||
{controls.cardRows.map((row, index) => (
|
{controls.cardRows.map((row, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Center, Stack } from '@mantine/core';
|
|
||||||
import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri';
|
import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri';
|
||||||
import { generatePath, useNavigate } from 'react-router-dom';
|
import { generatePath, useNavigate } from 'react-router-dom';
|
||||||
import { SimpleImg } from 'react-simple-img';
|
import { SimpleImg } from 'react-simple-img';
|
||||||
@ -9,6 +8,8 @@ import { CardRows } from '/@/renderer/components/card';
|
|||||||
import { Skeleton } from '/@/renderer/components/skeleton';
|
import { Skeleton } from '/@/renderer/components/skeleton';
|
||||||
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
|
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
|
||||||
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
|
import { CardRow, PlayQueueAddOptions, Play, CardRoute } from '/@/renderer/types';
|
||||||
|
import { Center } from '/@/renderer/components/center';
|
||||||
|
import { Stack } from '/@/renderer/components/stack';
|
||||||
|
|
||||||
interface BaseGridCardProps {
|
interface BaseGridCardProps {
|
||||||
columnIndex: number;
|
columnIndex: number;
|
||||||
@ -173,7 +174,7 @@ export const PosterCard = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
borderRadius: 'var(--card-default-radius)',
|
borderRadius: 'var(--card-default-radius)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -218,7 +219,7 @@ export const PosterCard = ({
|
|||||||
<ImageContainer />
|
<ImageContainer />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Stack spacing="sm">
|
<Stack gap="sm">
|
||||||
{controls.cardRows.map((row, index) => (
|
{controls.cardRows.map((row, index) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
key={`${index}-${columnIndex}-${row.arrayProperty}`}
|
||||||
|
@ -7,7 +7,7 @@ export const ActionsCell = ({ context, api }: ICellRendererParams) => {
|
|||||||
return (
|
return (
|
||||||
<CellContainer $position="center">
|
<CellContainer $position="center">
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||||
import { Center } from '@mantine/core';
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { RiAlbumFill } from 'react-icons/ri';
|
import { RiAlbumFill } from 'react-icons/ri';
|
||||||
import { generatePath } from 'react-router';
|
import { generatePath } from 'react-router';
|
||||||
@ -12,6 +11,7 @@ import { Text } from '/@/renderer/components/text';
|
|||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { Skeleton } from '/@/renderer/components/skeleton';
|
import { Skeleton } from '/@/renderer/components/skeleton';
|
||||||
import { SEPARATOR_STRING } from '/@/renderer/api/utils';
|
import { SEPARATOR_STRING } from '/@/renderer/api/utils';
|
||||||
|
import { Center } from '/@/renderer/components/center';
|
||||||
|
|
||||||
const CellContainer = styled(motion.div)<{ height: number }>`
|
const CellContainer = styled(motion.div)<{ height: number }>`
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -89,7 +89,7 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
borderRadius: 'var(--card-default-radius)',
|
borderRadius: 'var(--card-default-radius)',
|
||||||
height: `${(node.rowHeight || 40) - 10}px`,
|
height: `${(node.rowHeight || 40) - 10}px`,
|
||||||
@ -127,7 +127,7 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
|
|||||||
component={Link}
|
component={Link}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
size="md"
|
size="md"
|
||||||
sx={{ width: 'fit-content' }}
|
style={{ width: 'fit-content' }}
|
||||||
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||||
albumArtistId: artist.id,
|
albumArtistId: artist.id,
|
||||||
})}
|
})}
|
||||||
@ -139,7 +139,7 @@ export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams
|
|||||||
$secondary
|
$secondary
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
size="md"
|
size="md"
|
||||||
sx={{ width: 'fit-content' }}
|
style={{ width: 'fit-content' }}
|
||||||
>
|
>
|
||||||
{artist.name}
|
{artist.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -48,8 +48,8 @@ export const FavoriteCell = ({ value, data, node }: ICellRendererParams) => {
|
|||||||
return (
|
return (
|
||||||
<CellContainer $position="center">
|
<CellContainer $position="center">
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
sx={{
|
style={{
|
||||||
svg: {
|
svg: {
|
||||||
fill: !value
|
fill: !value
|
||||||
? 'var(--main-fg-secondary) !important'
|
? 'var(--main-fg-secondary) !important'
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ICellRendererParams } from '@ag-grid-community/core';
|
import { ICellRendererParams } from '@ag-grid-community/core';
|
||||||
import { Group } from '@mantine/core';
|
|
||||||
import { RiCheckboxBlankLine, RiCheckboxLine } from 'react-icons/ri';
|
import { RiCheckboxBlankLine, RiCheckboxLine } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
import { Paper } from '/@/renderer/components/paper';
|
import { Paper } from '/@/renderer/components/paper';
|
||||||
import { getNodesByDiscNumber, setNodeSelection } from '../utils';
|
import { getNodesByDiscNumber, setNodeSelection } from '../utils';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
const Container = styled(Paper)`
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FullWidthDiscCell = ({ node, data, api }: ICellRendererParams) => {
|
export const FullWidthDiscCell = ({ node, data, api }: ICellRendererParams) => {
|
||||||
const [isSelected, setIsSelected] = useState(false);
|
const [isSelected, setIsSelected] = useState(false);
|
||||||
@ -28,20 +21,27 @@ export const FullWidthDiscCell = ({ node, data, api }: ICellRendererParams) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Group
|
<ButtonContainer>
|
||||||
position="apart"
|
|
||||||
w="100%"
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
compact
|
leftSection={isSelected ? <RiCheckboxLine /> : <RiCheckboxBlankLine />}
|
||||||
leftIcon={isSelected ? <RiCheckboxLine /> : <RiCheckboxBlankLine />}
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleToggleDiscNodes}
|
onClick={handleToggleDiscNodes}
|
||||||
>
|
>
|
||||||
{data.name}
|
{data.name}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</ButtonContainer>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Container = styled(Paper)`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonContainer = styled(Group)`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
@ -149,7 +149,7 @@ export const RowIndexCell = ({ value, eGridCell }: ICellRendererParams) => {
|
|||||||
) : null)}
|
) : null)}
|
||||||
<Text
|
<Text
|
||||||
$secondary
|
$secondary
|
||||||
align="right"
|
$align="right"
|
||||||
className="current-song-child current-song-index"
|
className="current-song-child current-song-index"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
size="md"
|
size="md"
|
||||||
|
@ -288,7 +288,7 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
|
|||||||
const { setSettings } = useSettingsStoreActions();
|
const { setSettings } = useSettingsStoreActions();
|
||||||
const tableConfig = useSettingsStore((state) => state.tables);
|
const tableConfig = useSettingsStore((state) => state.tables);
|
||||||
|
|
||||||
const handleAddOrRemoveColumns = (values: TableColumn[]) => {
|
const handleAddOrRemoveColumns = (values: TableColumn[] | any[]) => {
|
||||||
const existingColumns = tableConfig[type].columns;
|
const existingColumns = tableConfig[type].columns;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -409,9 +409,11 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
|
|||||||
<Option.Control>
|
<Option.Control>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
clearable
|
clearable
|
||||||
|
comboboxProps={{
|
||||||
|
position: 'bottom',
|
||||||
|
}}
|
||||||
data={SONG_TABLE_COLUMNS}
|
data={SONG_TABLE_COLUMNS}
|
||||||
defaultValue={tableConfig[type]?.columns.map((column) => column.column)}
|
defaultValue={tableConfig[type]?.columns.map((column) => column.column)}
|
||||||
dropdownPosition="bottom"
|
|
||||||
width={300}
|
width={300}
|
||||||
onChange={handleAddOrRemoveColumns}
|
onChange={handleAddOrRemoveColumns}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { MutableRefObject } from 'react';
|
import { MutableRefObject } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Group } from '@mantine/core';
|
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -14,6 +13,7 @@ import { Text } from '/@/renderer/components/text';
|
|||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { TablePagination as TablePaginationType } from '/@/renderer/types';
|
import { TablePagination as TablePaginationType } from '/@/renderer/types';
|
||||||
import { ListKey } from '/@/renderer/store';
|
import { ListKey } from '/@/renderer/store';
|
||||||
|
import { Group } from '/@/renderer/components/group';
|
||||||
|
|
||||||
interface TablePaginationProps {
|
interface TablePaginationProps {
|
||||||
pageKey: ListKey;
|
pageKey: ListKey;
|
||||||
@ -72,13 +72,15 @@ export const TablePagination = ({
|
|||||||
<MotionFlex
|
<MotionFlex
|
||||||
ref={containerQuery.ref}
|
ref={containerQuery.ref}
|
||||||
layout
|
layout
|
||||||
align="center"
|
|
||||||
animate={{ y: 0 }}
|
animate={{ y: 0 }}
|
||||||
exit={{ y: 50 }}
|
exit={{ y: 50 }}
|
||||||
initial={{ y: 50 }}
|
initial={{ y: 50 }}
|
||||||
justify="space-between"
|
style={{
|
||||||
p="1rem"
|
alignContent: 'center',
|
||||||
sx={{ borderTop: '1px solid var(--generic-border-color)' }}
|
borderTop: '1px solid var(--generic-border-color)',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '1rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
$secondary
|
$secondary
|
||||||
@ -103,8 +105,8 @@ export const TablePagination = ({
|
|||||||
</Text>
|
</Text>
|
||||||
<Group
|
<Group
|
||||||
ref={containerQuery.ref}
|
ref={containerQuery.ref}
|
||||||
noWrap
|
gap="sm"
|
||||||
spacing="sm"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Popover
|
<Popover
|
||||||
trapFocus
|
trapFocus
|
||||||
@ -116,7 +118,7 @@ export const TablePagination = ({
|
|||||||
<Button
|
<Button
|
||||||
radius="sm"
|
radius="sm"
|
||||||
size="sm"
|
size="sm"
|
||||||
sx={{ height: '26px', padding: '0', width: '26px' }}
|
style={{ height: '26px', padding: '0', width: '26px' }}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('action.goToPage', { postProcess: 'sentenceCase' }),
|
label: t('action.goToPage', { postProcess: 'sentenceCase' }),
|
||||||
}}
|
}}
|
||||||
@ -147,7 +149,6 @@ export const TablePagination = ({
|
|||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Pagination
|
<Pagination
|
||||||
noWrap
|
|
||||||
$hideDividers={!containerQuery.isSm}
|
$hideDividers={!containerQuery.isSm}
|
||||||
boundaries={1}
|
boundaries={1}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Stack, Group } from '@mantine/core';
|
|
||||||
import { RiAlertFill } from 'react-icons/ri';
|
import { RiAlertFill } from 'react-icons/ri';
|
||||||
import { Text } from '/@/renderer/components';
|
import { Group, Stack, Text } from '/@/renderer/components';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
interface ActionRequiredContainerProps {
|
interface ActionRequiredContainerProps {
|
||||||
@ -9,7 +8,7 @@ interface ActionRequiredContainerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ActionRequiredContainer = ({ title, children }: ActionRequiredContainerProps) => (
|
export const ActionRequiredContainer = ({ title, children }: ActionRequiredContainerProps) => (
|
||||||
<Stack sx={{ cursor: 'default', maxWidth: '700px' }}>
|
<Stack style={{ cursor: 'default', maxWidth: '700px' }}>
|
||||||
<Group>
|
<Group>
|
||||||
<RiAlertFill
|
<RiAlertFill
|
||||||
color="var(--warning-color)"
|
color="var(--warning-color)"
|
||||||
@ -17,7 +16,7 @@ export const ActionRequiredContainer = ({ title, children }: ActionRequiredConta
|
|||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
size="xl"
|
size="xl"
|
||||||
sx={{ textTransform: 'uppercase' }}
|
style={{ textTransform: 'uppercase' }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
import { Box, Center, Group, Stack } from '@mantine/core';
|
|
||||||
import type { FallbackProps } from 'react-error-boundary';
|
import type { FallbackProps } from 'react-error-boundary';
|
||||||
import { RiErrorWarningLine } from 'react-icons/ri';
|
import { RiErrorWarningLine } from 'react-icons/ri';
|
||||||
import { useRouteError } from 'react-router';
|
import { useRouteError } from 'react-router';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button, Text } from '/@/renderer/components';
|
import { Button, Center, Group, Stack, Text } from '/@/renderer/components';
|
||||||
|
|
||||||
const Container = styled(Box)`
|
|
||||||
background: var(--main-bg);
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => {
|
export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => {
|
||||||
const error = useRouteError() as any;
|
const error = useRouteError() as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Center sx={{ height: '100vh' }}>
|
<Center style={{ height: '100vh' }}>
|
||||||
<Stack sx={{ maxWidth: '50%' }}>
|
<Stack style={{ maxWidth: '50%' }}>
|
||||||
<Group spacing="xs">
|
<Group gap="xs">
|
||||||
<RiErrorWarningLine
|
<RiErrorWarningLine
|
||||||
color="var(--danger-color)"
|
color="var(--danger-color)"
|
||||||
size={30}
|
size={30}
|
||||||
@ -35,3 +30,7 @@ export const ErrorFallback = ({ resetErrorBoundary }: FallbackProps) => {
|
|||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
background: var(--main-bg);
|
||||||
|
`;
|
||||||
|
@ -14,7 +14,8 @@ export const MpvRequired = () => {
|
|||||||
const [disabled, setDisabled] = useState(false);
|
const [disabled, setDisabled] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleSetMpvPath = (e: File) => {
|
const handleSetMpvPath = (e: File | null) => {
|
||||||
|
if (!e) return;
|
||||||
localSettings?.set('mpv_path', e.path);
|
localSettings?.set('mpv_path', e.path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Center, Stack, Group, Divider, Box } from '@mantine/core';
|
|
||||||
import { RiArrowLeftSLine, RiErrorWarningLine, RiHome4Line, RiMenuFill } from 'react-icons/ri';
|
import { RiArrowLeftSLine, RiErrorWarningLine, RiHome4Line, RiMenuFill } from 'react-icons/ri';
|
||||||
import { useNavigate, useRouteError } from 'react-router';
|
import { useNavigate, useRouteError } from 'react-router';
|
||||||
import { Button, DropdownMenu, Text } from '/@/renderer/components';
|
import { Button, Center, Divider, DropdownMenu, Group, Stack, Text } from '/@/renderer/components';
|
||||||
import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu';
|
import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const RouteErrorBoundary = () => {
|
const RouteErrorBoundary = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -23,9 +23,9 @@ const RouteErrorBoundary = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box bg="var(--main-bg)">
|
<Container>
|
||||||
<Center sx={{ height: '100vh' }}>
|
<Center style={{ height: '100vh' }}>
|
||||||
<Stack sx={{ maxWidth: '50%' }}>
|
<Stack style={{ maxWidth: '50%' }}>
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
px={10}
|
px={10}
|
||||||
@ -40,16 +40,16 @@ const RouteErrorBoundary = () => {
|
|||||||
/>
|
/>
|
||||||
<Text size="lg">Something went wrong</Text>
|
<Text size="lg">Something went wrong</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Divider my={5} />
|
<Divider marginY={5} />
|
||||||
<Text size="sm">{error?.message}</Text>
|
<Text size="sm">{error?.message}</Text>
|
||||||
<Group
|
<Group
|
||||||
grow
|
grow
|
||||||
spacing="sm"
|
gap="sm"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<RiHome4Line />}
|
leftSection={<RiHome4Line />}
|
||||||
size="md"
|
size="md"
|
||||||
sx={{ flex: 0.5 }}
|
style={{ flex: 0.5 }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleHome}
|
onClick={handleHome}
|
||||||
>
|
>
|
||||||
@ -58,9 +58,9 @@ const RouteErrorBoundary = () => {
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<RiMenuFill />}
|
leftSection={<RiMenuFill />}
|
||||||
size="md"
|
size="md"
|
||||||
sx={{ flex: 0.5 }}
|
style={{ flex: 0.5 }}
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
Menu
|
Menu
|
||||||
@ -82,8 +82,12 @@ const RouteErrorBoundary = () => {
|
|||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RouteErrorBoundary;
|
export default RouteErrorBoundary;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
background: var(--main-bg);
|
||||||
|
`;
|
||||||
|
@ -9,7 +9,7 @@ export const ServerRequired = () => {
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<RiMenuFill />}
|
leftSection={<RiMenuFill />}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
Open menu
|
Open menu
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Center, Group, Stack } from '@mantine/core';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RiCheckFill, RiEdit2Line, RiHome4Line } from 'react-icons/ri';
|
import { RiCheckFill, RiEdit2Line, RiHome4Line } from 'react-icons/ri';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Button, PageHeader, Text } from '/@/renderer/components';
|
import { Button, Center, Group, PageHeader, Stack, Text } from '/@/renderer/components';
|
||||||
import { ActionRequiredContainer } from '/@/renderer/features/action-required/components/action-required-container';
|
import { ActionRequiredContainer } from '/@/renderer/features/action-required/components/action-required-container';
|
||||||
import { ServerCredentialRequired } from '/@/renderer/features/action-required/components/server-credential-required';
|
import { ServerCredentialRequired } from '/@/renderer/features/action-required/components/server-credential-required';
|
||||||
import { ServerRequired } from '/@/renderer/features/action-required/components/server-required';
|
import { ServerRequired } from '/@/renderer/features/action-required/components/server-required';
|
||||||
@ -44,24 +43,24 @@ const ActionRequiredRoute = () => {
|
|||||||
return (
|
return (
|
||||||
<AnimatedPage>
|
<AnimatedPage>
|
||||||
<PageHeader />
|
<PageHeader />
|
||||||
<Center sx={{ height: '100%', width: '100vw' }}>
|
<Center style={{ height: '100%', width: '100vw' }}>
|
||||||
<Stack
|
<Stack
|
||||||
spacing="xl"
|
gap="xl"
|
||||||
sx={{ maxWidth: '50%' }}
|
style={{ maxWidth: '50%' }}
|
||||||
>
|
>
|
||||||
<Group noWrap>
|
<Group wrap="nowrap">
|
||||||
{displayedCheck && (
|
{displayedCheck && (
|
||||||
<ActionRequiredContainer title={displayedCheck.title}>
|
<ActionRequiredContainer title={displayedCheck.title}>
|
||||||
{displayedCheck?.component}
|
{displayedCheck?.component}
|
||||||
</ActionRequiredContainer>
|
</ActionRequiredContainer>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Stack mt="2rem">
|
<Stack style={{ marginTop: '2rem' }}>
|
||||||
{canReturnHome && (
|
{canReturnHome && (
|
||||||
<>
|
<>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
justify="center"
|
||||||
position="center"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<RiCheckFill
|
<RiCheckFill
|
||||||
color="var(--success-color)"
|
color="var(--success-color)"
|
||||||
@ -72,7 +71,7 @@ const ActionRequiredRoute = () => {
|
|||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
disabled={!canReturnHome}
|
disabled={!canReturnHome}
|
||||||
leftIcon={<RiHome4Line />}
|
leftSection={<RiHome4Line />}
|
||||||
to={AppRoute.HOME}
|
to={AppRoute.HOME}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
@ -82,12 +81,12 @@ const ActionRequiredRoute = () => {
|
|||||||
)}
|
)}
|
||||||
{!displayedCheck && (
|
{!displayedCheck && (
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
justify="center"
|
||||||
position="center"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
leftIcon={<RiEdit2Line />}
|
leftSection={<RiEdit2Line />}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onClick={handleManageServersModal}
|
onClick={handleManageServersModal}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Center, Group, Stack } from '@mantine/core';
|
|
||||||
import { RiQuestionLine } from 'react-icons/ri';
|
import { RiQuestionLine } from 'react-icons/ri';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { Button, Text } from '/@/renderer/components';
|
import { Button, Center, Group, Stack, Text } from '/@/renderer/components';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
|
|
||||||
const InvalidRoute = () => {
|
const InvalidRoute = () => {
|
||||||
@ -10,11 +9,11 @@ const InvalidRoute = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage>
|
<AnimatedPage>
|
||||||
<Center sx={{ height: '100%', width: '100%' }}>
|
<Center style={{ height: '100%', width: '100%' }}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
justify="center"
|
||||||
position="center"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<RiQuestionLine
|
<RiQuestionLine
|
||||||
color="var(--warning-color)"
|
color="var(--warning-color)"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Box, Group, Stack } from '@mantine/core';
|
|
||||||
import { useSetState } from '@mantine/hooks';
|
import { useSetState } from '@mantine/hooks';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaLastfmSquare } from 'react-icons/fa';
|
import { FaLastfmSquare } from 'react-icons/fa';
|
||||||
@ -12,7 +11,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/api/types';
|
import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/api/types';
|
||||||
import { Button, Popover, Spoiler } from '/@/renderer/components';
|
import { Button, Group, Popover, Spoiler, Stack } from '/@/renderer/components';
|
||||||
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
|
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
|
||||||
import {
|
import {
|
||||||
TableConfigDropdown,
|
TableConfigDropdown,
|
||||||
@ -328,19 +327,19 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<LibraryBackgroundOverlay $backgroundColor={background} />
|
<LibraryBackgroundOverlay $backgroundColor={background} />
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group
|
<Group
|
||||||
position="apart"
|
gap="sm"
|
||||||
spacing="sm"
|
justify="space-between"
|
||||||
>
|
>
|
||||||
<Group>
|
<Group>
|
||||||
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
loading={
|
loading={
|
||||||
createFavoriteMutation.isLoading ||
|
createFavoriteMutation.isLoading ||
|
||||||
deleteFavoriteMutation.isLoading
|
deleteFavoriteMutation.isLoading
|
||||||
}
|
}
|
||||||
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
>
|
>
|
||||||
@ -354,7 +353,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
@ -368,8 +367,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
<Popover position="bottom-end">
|
<Popover position="bottom-end">
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiSettings2Fill size={20} />
|
<RiSettings2Fill size={20} />
|
||||||
@ -380,17 +378,16 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
{showGenres && (
|
{showGenres && (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
{detailQuery?.data?.genres?.map((genre) => (
|
{detailQuery?.data?.genres?.map((genre) => (
|
||||||
<Button
|
<Button
|
||||||
key={`genre-${genre.id}`}
|
key={`genre-${genre.id}`}
|
||||||
compact
|
|
||||||
component={Link}
|
component={Link}
|
||||||
radius={0}
|
radius={0}
|
||||||
size="md"
|
size="compact-md"
|
||||||
to={generatePath(genreRoute, {
|
to={generatePath(genreRoute, {
|
||||||
genreId: genre.id,
|
genreId: genre.id,
|
||||||
})}
|
})}
|
||||||
@ -400,20 +397,19 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
)}
|
)}
|
||||||
{externalLinks ? (
|
{externalLinks ? (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
component="a"
|
component="a"
|
||||||
href={`https://www.last.fm/music/${encodeURIComponent(
|
href={`https://www.last.fm/music/${encodeURIComponent(
|
||||||
detailQuery?.data?.albumArtist || '',
|
detailQuery?.data?.albumArtist || '',
|
||||||
)}/${encodeURIComponent(detailQuery.data?.name || '')}`}
|
)}/${encodeURIComponent(detailQuery.data?.name || '')}`}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
size="md"
|
size="compact-md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('action.openIn.lastfm'),
|
label: t('action.openIn.lastfm'),
|
||||||
@ -424,12 +420,11 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
</Button>
|
</Button>
|
||||||
{mbzId ? (
|
{mbzId ? (
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
component="a"
|
component="a"
|
||||||
href={`https://musicbrainz.org/release/${mbzId}`}
|
href={`https://musicbrainz.org/release/${mbzId}`}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
size="md"
|
size="compact-md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('action.openIn.musicbrainz'),
|
label: t('action.openIn.musicbrainz'),
|
||||||
@ -440,14 +435,14 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
{comment && (
|
{comment && (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
|
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
|
||||||
</Box>
|
</section>
|
||||||
)}
|
)}
|
||||||
<Box style={{ minHeight: '300px' }}>
|
<div style={{ minHeight: '300px' }}>
|
||||||
<VirtualTable
|
<VirtualTable
|
||||||
key={`table-${tableConfig.rowHeight}`}
|
key={`table-${tableConfig.rowHeight}`}
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
@ -482,11 +477,11 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||||||
onColumnMoved={onColumnMoved}
|
onColumnMoved={onColumnMoved}
|
||||||
onRowDoubleClicked={handleRowDoubleClick}
|
onRowDoubleClicked={handleRowDoubleClick}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</div>
|
||||||
<Stack
|
<Stack
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
mt="3rem"
|
gap="lg"
|
||||||
spacing="lg"
|
style={{ marginTop: '3rem' }}
|
||||||
>
|
>
|
||||||
{cq.height || cq.width ? (
|
{cq.height || cq.width ? (
|
||||||
<>
|
<>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { Group, Stack } from '@mantine/core';
|
|
||||||
import { forwardRef, Fragment, Ref } from 'react';
|
import { forwardRef, Fragment, Ref } from 'react';
|
||||||
import { generatePath, useParams } from 'react-router';
|
import { generatePath, useParams } from 'react-router';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
||||||
import { Rating, Text } from '/@/renderer/components';
|
import { Group, Rating, Stack, Text } from '/@/renderer/components';
|
||||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||||
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
@ -66,8 +65,8 @@ export const AlbumDetailHeader = forwardRef(
|
|||||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||||
title={detailQuery?.data?.name || ''}
|
title={detailQuery?.data?.name || ''}
|
||||||
>
|
>
|
||||||
<Stack spacing="sm">
|
<Stack gap="sm">
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
{metadataItems.map((item, index) => (
|
{metadataItems.map((item, index) => (
|
||||||
<Fragment key={`item-${item.id}-${index}`}>
|
<Fragment key={`item-${item.id}-${index}`}>
|
||||||
{index > 0 && <Text $noSelect>•</Text>}
|
{index > 0 && <Text $noSelect>•</Text>}
|
||||||
@ -89,11 +88,11 @@ export const AlbumDetailHeader = forwardRef(
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
mah="4rem"
|
gap="md"
|
||||||
spacing="md"
|
style={{
|
||||||
sx={{
|
|
||||||
WebkitBoxOrient: 'vertical',
|
WebkitBoxOrient: 'vertical',
|
||||||
WebkitLineClamp: 2,
|
WebkitLineClamp: 2,
|
||||||
|
maxHeight: '4rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
|
||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -16,7 +15,18 @@ import {
|
|||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
|
import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
|
||||||
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
DropdownMenu,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
MultiSelect,
|
||||||
|
Slider,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
@ -296,7 +306,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
[pageKey, setDisplayType],
|
[pageKey, setDisplayType],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTableColumns = (values: TableColumn[]) => {
|
const handleTableColumns = (values: TableColumn[] | any[]) => {
|
||||||
const existingColumns = table.columns;
|
const existingColumns = table.columns;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -352,15 +362,14 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
<Group
|
<Group
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing="sm"
|
gap="sm"
|
||||||
w="100%"
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw={600}
|
fw={600}
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{sortByLabel}
|
{sortByLabel}
|
||||||
@ -390,10 +399,9 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw={600}
|
fw={600}
|
||||||
size="md"
|
size="compact-md"
|
||||||
sx={{
|
style={{
|
||||||
svg: {
|
svg: {
|
||||||
fill: isFolderFilterApplied
|
fill: isFolderFilterApplied
|
||||||
? 'var(--primary-color) !important'
|
? 'var(--primary-color) !important'
|
||||||
@ -422,9 +430,8 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
)}
|
)}
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
style={{
|
||||||
sx={{
|
|
||||||
svg: {
|
svg: {
|
||||||
fill: isFilterApplied ? 'var(--primary-color) !important' : undefined,
|
fill: isFilterApplied ? 'var(--primary-color) !important' : undefined,
|
||||||
},
|
},
|
||||||
@ -439,8 +446,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
</Button>
|
</Button>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('common.refresh', { postProcess: 'sentenceCase' }) }}
|
tooltip={{ label: t('common.refresh', { postProcess: 'sentenceCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
@ -451,8 +457,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size={15} />
|
<RiMoreFill size={15} />
|
||||||
@ -460,26 +465,26 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiPlayFill />}
|
leftSection={<RiPlayFill />}
|
||||||
onClick={() => handlePlay?.({ playType: Play.NOW })}
|
onClick={() => handlePlay?.({ playType: Play.NOW })}
|
||||||
>
|
>
|
||||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddBoxFill />}
|
leftSection={<RiAddBoxFill />}
|
||||||
onClick={() => handlePlay?.({ playType: Play.LAST })}
|
onClick={() => handlePlay?.({ playType: Play.LAST })}
|
||||||
>
|
>
|
||||||
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddCircleFill />}
|
leftSection={<RiAddCircleFill />}
|
||||||
onClick={() => handlePlay?.({ playType: Play.NEXT })}
|
onClick={() => handlePlay?.({ playType: Play.NEXT })}
|
||||||
>
|
>
|
||||||
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Divider />
|
<DropdownMenu.Divider />
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiRefreshLine />}
|
leftSection={<RiRefreshLine />}
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
>
|
>
|
||||||
{t('common.refresh', { postProcess: 'sentenceCase' })}
|
{t('common.refresh', { postProcess: 'sentenceCase' })}
|
||||||
@ -488,8 +493,8 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
gap="sm"
|
||||||
spacing="sm"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
position="bottom-end"
|
position="bottom-end"
|
||||||
@ -497,8 +502,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
>
|
>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('common.configure', { postProcess: 'sentenceCase' }),
|
label: t('common.configure', { postProcess: 'sentenceCase' }),
|
||||||
}}
|
}}
|
||||||
@ -573,7 +577,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
closeMenuOnClick={false}
|
closeMenuOnClick={false}
|
||||||
component="div"
|
component="div"
|
||||||
sx={{ cursor: 'default' }}
|
style={{ cursor: 'default' }}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -585,7 +589,7 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil
|
|||||||
width={300}
|
width={300}
|
||||||
onChange={handleTableColumns}
|
onChange={handleTableColumns}
|
||||||
/>
|
/>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text>Auto Fit Columns</Text>
|
<Text>Auto Fit Columns</Text>
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={table.autoFit}
|
defaultChecked={table.autoFit}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { useEffect, useRef, type ChangeEvent, type MutableRefObject } from 'react';
|
import { useEffect, useRef, type ChangeEvent, type MutableRefObject } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Flex, Group, Stack } from '@mantine/core';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
import { Flex, Group, PageHeader, SearchInput, Stack } from '/@/renderer/components';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters';
|
import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters';
|
||||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
@ -57,7 +56,7 @@ export const AlbumListHeader = ({
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing={0}
|
gap={0}
|
||||||
>
|
>
|
||||||
<PageHeader backgroundColor="var(--titlebar-bg)">
|
<PageHeader backgroundColor="var(--titlebar-bg)">
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { ChangeEvent, useMemo, useState } from 'react';
|
import { ChangeEvent, useMemo, useState } from 'react';
|
||||||
import { Divider, Group, Stack } from '@mantine/core';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useListFilterByKey } from '../../../store/list.store';
|
import { useListFilterByKey } from '../../../store/list.store';
|
||||||
import { AlbumArtistListSort, GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
import { AlbumArtistListSort, GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||||
import { MultiSelect, NumberInput, SpinnerIcon, Switch, Text } from '/@/renderer/components';
|
import {
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
MultiSelect,
|
||||||
|
NumberInput,
|
||||||
|
SpinnerIcon,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { useGenreList } from '/@/renderer/features/genres';
|
||||||
import { AlbumListFilter, useListStoreActions } from '/@/renderer/store';
|
import { AlbumListFilter, useListStoreActions } from '/@/renderer/store';
|
||||||
@ -176,11 +184,11 @@ export const JellyfinAlbumFilters = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="0.8rem">
|
<Stack style={{ padding: '0.8rem' }}>
|
||||||
{toggleFilters.map((filter) => (
|
{toggleFilters.map((filter) => (
|
||||||
<Group
|
<Group
|
||||||
key={`nd-filter-${filter.label}`}
|
key={`nd-filter-${filter.label}`}
|
||||||
position="apart"
|
justify="space-between"
|
||||||
>
|
>
|
||||||
<Text>{filter.label}</Text>
|
<Text>{filter.label}</Text>
|
||||||
<Switch
|
<Switch
|
||||||
@ -190,7 +198,7 @@ export const JellyfinAlbumFilters = ({
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
<Divider my="0.5rem" />
|
<Divider marginY="0.5rem" />
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={filter?._custom?.jellyfin?.minYear}
|
defaultValue={filter?._custom?.jellyfin?.minYear}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import { ChangeEvent, useMemo, useState } from 'react';
|
import { ChangeEvent, useMemo, useState } from 'react';
|
||||||
import { Divider, Group, Stack } from '@mantine/core';
|
import {
|
||||||
import { NumberInput, Switch, Text, Select, SpinnerIcon } from '/@/renderer/components';
|
NumberInput,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
Select,
|
||||||
|
SpinnerIcon,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
Stack,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { useGenreList } from '/@/renderer/features/genres';
|
||||||
@ -210,11 +218,11 @@ export const NavidromeAlbumFilters = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="0.8rem">
|
<Stack style={{ padding: '0.8rem' }}>
|
||||||
{toggleFilters.map((filter) => (
|
{toggleFilters.map((filter) => (
|
||||||
<Group
|
<Group
|
||||||
key={`nd-filter-${filter.label}`}
|
key={`nd-filter-${filter.label}`}
|
||||||
position="apart"
|
justify="space-between"
|
||||||
>
|
>
|
||||||
<Text>{filter.label}</Text>
|
<Text>{filter.label}</Text>
|
||||||
<Switch
|
<Switch
|
||||||
@ -223,7 +231,7 @@ export const NavidromeAlbumFilters = ({
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
<Divider my="0.5rem" />
|
<Divider marginY="0.5rem" />
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
defaultValue={filter._custom?.navidrome?.year}
|
defaultValue={filter._custom?.navidrome?.year}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Button, Spinner, Spoiler, Text } from '/@/renderer/components';
|
import { Button, Center, Group, Spinner, Spoiler, Stack, Text } from '/@/renderer/components';
|
||||||
import {
|
import {
|
||||||
AnimatedPage,
|
AnimatedPage,
|
||||||
LibraryHeader,
|
LibraryHeader,
|
||||||
@ -13,7 +13,6 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
|
|||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { LibraryItem, SongDetailResponse } from '/@/renderer/api/types';
|
import { LibraryItem, SongDetailResponse } from '/@/renderer/api/types';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { Stack, Group, Box, Center } from '@mantine/core';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
@ -139,8 +138,8 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
item={{ route: AppRoute.LIBRARY_SONGS, type: LibraryItem.SONG }}
|
item={{ route: AppRoute.LIBRARY_SONGS, type: LibraryItem.SONG }}
|
||||||
title={detailQuery?.data?.name || ''}
|
title={detailQuery?.data?.name || ''}
|
||||||
>
|
>
|
||||||
<Stack spacing="sm">
|
<Stack gap="sm">
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
{metadataItems.map((item, index) => (
|
{metadataItems.map((item, index) => (
|
||||||
<Fragment key={`item-${item.id}-${index}`}>
|
<Fragment key={`item-${item.id}-${index}`}>
|
||||||
{index > 0 && <Text $noSelect>•</Text>}
|
{index > 0 && <Text $noSelect>•</Text>}
|
||||||
@ -149,11 +148,11 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
mah="4rem"
|
gap="md"
|
||||||
spacing="md"
|
style={{
|
||||||
sx={{
|
|
||||||
WebkitBoxOrient: 'vertical',
|
WebkitBoxOrient: 'vertical',
|
||||||
WebkitLineClamp: 2,
|
WebkitLineClamp: 2,
|
||||||
|
maxHeight: '4rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -177,19 +176,19 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
</LibraryHeader>
|
</LibraryHeader>
|
||||||
</Stack>
|
</Stack>
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group
|
<Group
|
||||||
position="apart"
|
gap="sm"
|
||||||
spacing="sm"
|
justify="space-between"
|
||||||
>
|
>
|
||||||
<Group>
|
<Group>
|
||||||
<PlayButton onClick={() => handlePlay()} />
|
<PlayButton onClick={() => handlePlay()} />
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
loading={
|
loading={
|
||||||
createFavoriteMutation.isLoading ||
|
createFavoriteMutation.isLoading ||
|
||||||
deleteFavoriteMutation.isLoading
|
deleteFavoriteMutation.isLoading
|
||||||
}
|
}
|
||||||
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
>
|
>
|
||||||
@ -203,7 +202,7 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
@ -214,17 +213,16 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
{showGenres && (
|
{showGenres && (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
{detailQuery?.data?.genres?.map((genre) => (
|
{detailQuery?.data?.genres?.map((genre) => (
|
||||||
<Button
|
<Button
|
||||||
key={`genre-${genre.id}`}
|
key={`genre-${genre.id}`}
|
||||||
compact
|
|
||||||
component={Link}
|
component={Link}
|
||||||
radius={0}
|
radius={0}
|
||||||
size="md"
|
size="compact-md"
|
||||||
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
|
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
|
||||||
genreId: genre.id,
|
genreId: genre.id,
|
||||||
})}
|
})}
|
||||||
@ -234,16 +232,16 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
)}
|
)}
|
||||||
{comment && (
|
{comment && (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
|
<Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>
|
||||||
</Box>
|
</section>
|
||||||
)}
|
)}
|
||||||
<Box component="section">
|
<section>
|
||||||
<Center>
|
<Center>
|
||||||
<Group mr={5}>
|
<Group style={{ marginRight: '5px' }}>
|
||||||
<RiErrorWarningLine
|
<RiErrorWarningLine
|
||||||
color="var(--danger-color)"
|
color="var(--danger-color)"
|
||||||
size={30}
|
size={30}
|
||||||
@ -251,7 +249,7 @@ const DummyAlbumDetailRoute = () => {
|
|||||||
</Group>
|
</Group>
|
||||||
<h2>{t('error.badAlbum', { postProcess: 'sentenceCase' })}</h2>
|
<h2>{t('error.badAlbum', { postProcess: 'sentenceCase' })}</h2>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</section>
|
||||||
</DetailContainer>
|
</DetailContainer>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||||
import { Box, Group, Stack } from '@mantine/core';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaLastfmSquare } from 'react-icons/fa';
|
import { FaLastfmSquare } from 'react-icons/fa';
|
||||||
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
||||||
@ -17,7 +16,7 @@ import {
|
|||||||
ServerType,
|
ServerType,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { Button, Spoiler, TextTitle } from '/@/renderer/components';
|
import { Button, Group, Spoiler, Stack, TextTitle } from '/@/renderer/components';
|
||||||
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
|
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
|
||||||
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
||||||
@ -221,10 +220,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
})}
|
})}
|
||||||
</TextTitle>
|
</TextTitle>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
uppercase
|
|
||||||
component={Link}
|
component={Link}
|
||||||
|
size="compact-md"
|
||||||
to={artistDiscographyLink}
|
to={artistDiscographyLink}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{t('page.albumArtistDetail.viewDiscography')}
|
{t('page.albumArtistDetail.viewDiscography')}
|
||||||
@ -354,14 +353,14 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
<ContentContainer ref={cq.ref}>
|
<ContentContainer ref={cq.ref}>
|
||||||
<LibraryBackgroundOverlay $backgroundColor={background} />
|
<LibraryBackgroundOverlay $backgroundColor={background} />
|
||||||
<DetailContainer>
|
<DetailContainer>
|
||||||
<Group spacing="md">
|
<Group gap="md">
|
||||||
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||||
<Group spacing="xs">
|
<Group gap="xs">
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
loading={
|
loading={
|
||||||
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
|
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
|
||||||
}
|
}
|
||||||
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
>
|
>
|
||||||
@ -375,7 +374,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (!detailQuery?.data) return;
|
if (!detailQuery?.data) return;
|
||||||
@ -386,36 +385,35 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<Group spacing="md">
|
<Group gap="md">
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
uppercase
|
|
||||||
component={Link}
|
component={Link}
|
||||||
|
size="compact-md"
|
||||||
to={artistDiscographyLink}
|
to={artistDiscographyLink}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{t('page.albumArtistDetail.viewDiscography')}
|
{t('page.albumArtistDetail.viewDiscography')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
uppercase
|
|
||||||
component={Link}
|
component={Link}
|
||||||
|
size="compact-md"
|
||||||
to={artistSongsLink}
|
to={artistSongsLink}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{t('page.albumArtistDetail.viewAllTracks')}
|
{t('page.albumArtistDetail.viewAllTracks')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
{showGenres ? (
|
{showGenres ? (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
{detailQuery?.data?.genres?.map((genre) => (
|
{detailQuery?.data?.genres?.map((genre) => (
|
||||||
<Button
|
<Button
|
||||||
key={`genre-${genre.id}`}
|
key={`genre-${genre.id}`}
|
||||||
compact
|
|
||||||
component={Link}
|
component={Link}
|
||||||
radius="md"
|
radius="md"
|
||||||
size="md"
|
size="compact-md"
|
||||||
to={generatePath(genrePath, {
|
to={generatePath(genrePath, {
|
||||||
genreId: genre.id,
|
genreId: genre.id,
|
||||||
})}
|
})}
|
||||||
@ -425,20 +423,19 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
{externalLinks ? (
|
{externalLinks ? (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
component="a"
|
component="a"
|
||||||
href={`https://www.last.fm/music/${encodeURIComponent(
|
href={`https://www.last.fm/music/${encodeURIComponent(
|
||||||
detailQuery?.data?.name || '',
|
detailQuery?.data?.name || '',
|
||||||
)}`}
|
)}`}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
size="md"
|
size="compact-md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('action.openIn.lastfm'),
|
label: t('action.openIn.lastfm'),
|
||||||
@ -449,12 +446,11 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
</Button>
|
</Button>
|
||||||
{mbzId ? (
|
{mbzId ? (
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
component="a"
|
component="a"
|
||||||
href={`https://musicbrainz.org/artist/${mbzId}`}
|
href={`https://musicbrainz.org/artist/${mbzId}`}
|
||||||
radius="md"
|
radius="md"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
size="md"
|
size="compact-md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('action.openIn.musicbrainz'),
|
label: t('action.openIn.musicbrainz'),
|
||||||
@ -465,13 +461,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
{biography ? (
|
{biography ? (
|
||||||
<Box
|
<section style={{ maxWidth: '1280px' }}>
|
||||||
component="section"
|
|
||||||
maw="1280px"
|
|
||||||
>
|
|
||||||
<TextTitle
|
<TextTitle
|
||||||
order={2}
|
order={2}
|
||||||
weight={700}
|
weight={700}
|
||||||
@ -481,17 +474,17 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
})}
|
})}
|
||||||
</TextTitle>
|
</TextTitle>
|
||||||
<Spoiler dangerouslySetInnerHTML={{ __html: biography }} />
|
<Spoiler dangerouslySetInnerHTML={{ __html: biography }} />
|
||||||
</Box>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
{showTopSongs ? (
|
{showTopSongs ? (
|
||||||
<Box component="section">
|
<section>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
justify="space-between"
|
||||||
position="apart"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
|
||||||
align="flex-end"
|
align="flex-end"
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<TextTitle
|
<TextTitle
|
||||||
order={2}
|
order={2}
|
||||||
@ -502,15 +495,15 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
})}
|
})}
|
||||||
</TextTitle>
|
</TextTitle>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
uppercase
|
|
||||||
component={Link}
|
component={Link}
|
||||||
|
size="compact-md"
|
||||||
to={generatePath(
|
to={generatePath(
|
||||||
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS,
|
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS,
|
||||||
{
|
{
|
||||||
albumArtistId,
|
albumArtistId,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{t('page.albumArtistDetail.viewAll', {
|
{t('page.albumArtistDetail.viewAll', {
|
||||||
@ -537,10 +530,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
onCellContextMenu={handleContextMenu}
|
onCellContextMenu={handleContextMenu}
|
||||||
onRowDoubleClicked={handleRowDoubleClick}
|
onRowDoubleClicked={handleRowDoubleClick}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
<Box component="section">
|
<section>
|
||||||
<Stack spacing="xl">
|
<Stack gap="xl">
|
||||||
{carousels
|
{carousels
|
||||||
.filter((c) => !c.isHidden)
|
.filter((c) => !c.isHidden)
|
||||||
.map((carousel) => (
|
.map((carousel) => (
|
||||||
@ -563,7 +556,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</section>
|
||||||
</DetailContainer>
|
</DetailContainer>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { forwardRef, Fragment, Ref } from 'react';
|
import { forwardRef, Fragment, Ref } from 'react';
|
||||||
import { Group, Rating, Stack } from '@mantine/core';
|
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
import { LibraryItem, ServerType } from '/@/renderer/api/types';
|
||||||
import { Text } from '/@/renderer/components';
|
import { Group, Rating, Stack, Text } from '/@/renderer/components';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
||||||
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
@ -47,8 +47,8 @@ export const AlbumArtistDetailTopSongsListHeader = ({
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size={15} />
|
<RiMoreFill size={15} />
|
||||||
@ -56,19 +56,19 @@ export const AlbumArtistDetailTopSongsListHeader = ({
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiPlayFill />}
|
leftSection={<RiPlayFill />}
|
||||||
onClick={() => handlePlay(Play.NOW)}
|
onClick={() => handlePlay(Play.NOW)}
|
||||||
>
|
>
|
||||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddBoxFill />}
|
leftSection={<RiAddBoxFill />}
|
||||||
onClick={() => handlePlay(Play.LAST)}
|
onClick={() => handlePlay(Play.LAST)}
|
||||||
>
|
>
|
||||||
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddCircleFill />}
|
leftSection={<RiAddCircleFill />}
|
||||||
onClick={() => handlePlay(Play.NEXT)}
|
onClick={() => handlePlay(Play.NEXT)}
|
||||||
>
|
>
|
||||||
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||||
import { IDatasource } from '@ag-grid-community/core';
|
import { IDatasource } from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -10,7 +9,18 @@ import { useListContext } from '../../../context/list-context';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { AlbumArtistListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
|
import { AlbumArtistListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
|
||||||
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
DropdownMenu,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
MultiSelect,
|
||||||
|
Slider,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
||||||
@ -284,7 +294,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
[pageKey, setDisplayType],
|
[pageKey, setDisplayType],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTableColumns = (values: TableColumn[]) => {
|
const handleTableColumns = (values: TableColumn[] | any[]) => {
|
||||||
const existingColumns = table.columns;
|
const existingColumns = table.columns;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -329,15 +339,14 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
<Group
|
<Group
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing="sm"
|
gap="sm"
|
||||||
w="100%"
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{sortByLabel}
|
{sortByLabel}
|
||||||
@ -367,9 +376,8 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{cq.isMd ? 'Folder' : <RiFolder2Line size={15} />}
|
{cq.isMd ? 'Folder' : <RiFolder2Line size={15} />}
|
||||||
@ -392,8 +400,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
)}
|
)}
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
@ -404,8 +411,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size={15} />
|
<RiMoreFill size={15} />
|
||||||
@ -413,7 +419,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiRefreshLine />}
|
leftSection={<RiRefreshLine />}
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
>
|
>
|
||||||
Refresh
|
Refresh
|
||||||
@ -428,8 +434,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
>
|
>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiSettings3Fill size="1.3rem" />
|
<RiSettings3Fill size="1.3rem" />
|
||||||
@ -520,7 +525,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
closeMenuOnClick={false}
|
closeMenuOnClick={false}
|
||||||
component="div"
|
component="div"
|
||||||
sx={{ cursor: 'default' }}
|
style={{ cursor: 'default' }}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -532,7 +537,7 @@ export const AlbumArtistListHeaderFilters = ({
|
|||||||
width={300}
|
width={300}
|
||||||
onChange={handleTableColumns}
|
onChange={handleTableColumns}
|
||||||
/>
|
/>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text>
|
<Text>
|
||||||
{t('table.config.general.autoFitColumns', {
|
{t('table.config.general.autoFitColumns', {
|
||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import type { ChangeEvent, MutableRefObject } from 'react';
|
import type { ChangeEvent, MutableRefObject } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Flex, Group, Stack } from '@mantine/core';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FilterBar } from '../../shared/components/filter-bar';
|
import { FilterBar } from '../../shared/components/filter-bar';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
import { Flex, Group, PageHeader, SearchInput, Stack } from '/@/renderer/components';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
|
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
|
||||||
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
@ -43,12 +42,12 @@ export const AlbumArtistListHeader = ({
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing={0}
|
gap={0}
|
||||||
>
|
>
|
||||||
<PageHeader backgroundColor="var(--titlebar-bg)">
|
<PageHeader backgroundColor="var(--titlebar-bg)">
|
||||||
<Flex
|
<Flex
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
w="100%"
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<LibraryHeaderBar>
|
<LibraryHeaderBar>
|
||||||
<LibraryHeaderBar.Title>
|
<LibraryHeaderBar.Title>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { createContext, Fragment, ReactNode, useState, useMemo, useCallback } from 'react';
|
import { createContext, Fragment, ReactNode, useState, useMemo, useCallback } from 'react';
|
||||||
import { RowNode } from '@ag-grid-community/core';
|
import { RowNode } from '@ag-grid-community/core';
|
||||||
import { Divider, Group, Portal, Stack } from '@mantine/core';
|
|
||||||
import {
|
import {
|
||||||
useClickOutside,
|
useClickOutside,
|
||||||
useMergedRef,
|
useMergedRef,
|
||||||
@ -35,8 +34,12 @@ import {
|
|||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuButton,
|
ContextMenuButton,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
HoverCard,
|
HoverCard,
|
||||||
|
Portal,
|
||||||
Rating,
|
Rating,
|
||||||
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
toast,
|
toast,
|
||||||
} from '/@/renderer/components';
|
} from '/@/renderer/components';
|
||||||
@ -861,9 +864,9 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
xPos={ctx.xPos}
|
xPos={ctx.xPos}
|
||||||
yPos={ctx.yPos}
|
yPos={ctx.yPos}
|
||||||
>
|
>
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
<Stack
|
<Stack
|
||||||
spacing={0}
|
gap={0}
|
||||||
onClick={closeContextMenu}
|
onClick={closeContextMenu}
|
||||||
>
|
>
|
||||||
{ctx.menuItems?.map((item) => {
|
{ctx.menuItems?.map((item) => {
|
||||||
@ -897,7 +900,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
</ContextMenuButton>
|
</ContextMenuButton>
|
||||||
</HoverCard.Target>
|
</HoverCard.Target>
|
||||||
<HoverCard.Dropdown>
|
<HoverCard.Dropdown>
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
{contextMenuItems[
|
{contextMenuItems[
|
||||||
item.id
|
item.id
|
||||||
].children?.map((child) => (
|
].children?.map((child) => (
|
||||||
@ -937,7 +940,6 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
<Divider
|
<Divider
|
||||||
key={`context-menu-divider-${item.id}`}
|
key={`context-menu-divider-${item.id}`}
|
||||||
color="rgb(62, 62, 62)"
|
color="rgb(62, 62, 62)"
|
||||||
size="sm"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@ -945,10 +947,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider
|
<Divider color="rgb(62, 62, 62)" />
|
||||||
color="rgb(62, 62, 62)"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<ContextMenuButton disabled>
|
<ContextMenuButton disabled>
|
||||||
{t('page.contextMenu.numberSelected', {
|
{t('page.contextMenu.numberSelected', {
|
||||||
count: ctx.data?.length || 0,
|
count: ctx.data?.length || 0,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { GridOptions, RowNode } from '@ag-grid-community/core';
|
import { GridOptions, RowNode } from '@ag-grid-community/core';
|
||||||
import { createUseExternalEvents } from '@mantine/utils';
|
import { createUseExternalEvents } from '@mantine/core';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
|
|
||||||
export type OpenContextMenuProps = {
|
export type OpenContextMenuProps = {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -13,7 +12,18 @@ import {
|
|||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { GenreListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
|
import { GenreListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types';
|
||||||
import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
DropdownMenu,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
MultiSelect,
|
||||||
|
Slider,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
@ -182,7 +192,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
[pageKey, setDisplayType],
|
[pageKey, setDisplayType],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTableColumns = (values: TableColumn[]) => {
|
const handleTableColumns = (values: TableColumn[] | any[]) => {
|
||||||
const existingColumns = table.columns;
|
const existingColumns = table.columns;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -229,15 +239,14 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
<Group
|
<Group
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing="sm"
|
gap="sm"
|
||||||
w="100%"
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw={600}
|
fw={600}
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{sortByLabel}
|
{sortByLabel}
|
||||||
@ -267,10 +276,9 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw={600}
|
fw={600}
|
||||||
size="md"
|
size="compact-md"
|
||||||
sx={{
|
style={{
|
||||||
svg: {
|
svg: {
|
||||||
fill: isFolderFilterApplied
|
fill: isFolderFilterApplied
|
||||||
? 'var(--primary-color) !important'
|
? 'var(--primary-color) !important'
|
||||||
@ -299,8 +307,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
)}
|
)}
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
@ -311,8 +318,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size={15} />
|
<RiMoreFill size={15} />
|
||||||
@ -320,7 +326,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiRefreshLine />}
|
leftSection={<RiRefreshLine />}
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
>
|
>
|
||||||
{t('common.refresh', { postProcess: 'titleCase' })}
|
{t('common.refresh', { postProcess: 'titleCase' })}
|
||||||
@ -328,8 +334,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
</DropdownMenu.Dropdown>
|
</DropdownMenu.Dropdown>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t(
|
label: t(
|
||||||
genreTarget === GenreTarget.ALBUM
|
genreTarget === GenreTarget.ALBUM
|
||||||
@ -346,8 +351,8 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
gap="sm"
|
||||||
spacing="sm"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
position="bottom-end"
|
position="bottom-end"
|
||||||
@ -355,8 +360,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
>
|
>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('common.configure', { postProcess: 'titleCase' }),
|
label: t('common.configure', { postProcess: 'titleCase' }),
|
||||||
}}
|
}}
|
||||||
@ -426,7 +430,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
closeMenuOnClick={false}
|
closeMenuOnClick={false}
|
||||||
component="div"
|
component="div"
|
||||||
sx={{ cursor: 'default' }}
|
style={{ cursor: 'default' }}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -438,7 +442,7 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil
|
|||||||
width={300}
|
width={300}
|
||||||
onChange={handleTableColumns}
|
onChange={handleTableColumns}
|
||||||
/>
|
/>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text>
|
<Text>
|
||||||
{t('table.config.general.autoFitColumns', {
|
{t('table.config.general.autoFitColumns', {
|
||||||
postProcess: 'titleCase',
|
postProcess: 'titleCase',
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { ChangeEvent, MutableRefObject } from 'react';
|
import { ChangeEvent, MutableRefObject } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Flex, Group, Stack } from '@mantine/core';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
import { Flex, Group, PageHeader, SearchInput, Stack } from '/@/renderer/components';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { GenreListHeaderFilters } from '/@/renderer/features/genres/components/genre-list-header-filters';
|
import { GenreListHeaderFilters } from '/@/renderer/features/genres/components/genre-list-header-filters';
|
||||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
@ -37,12 +36,12 @@ export const GenreListHeader = ({ itemCount, gridRef, tableRef }: GenreListHeade
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing={0}
|
gap={0}
|
||||||
>
|
>
|
||||||
<PageHeader backgroundColor="var(--titlebar-bg)">
|
<PageHeader backgroundColor="var(--titlebar-bg)">
|
||||||
<Flex
|
<Flex
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
w="100%"
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<LibraryHeaderBar>
|
<LibraryHeaderBar>
|
||||||
<LibraryHeaderBar.Title>
|
<LibraryHeaderBar.Title>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { ActionIcon, Group, Stack } from '@mantine/core';
|
|
||||||
import {
|
import {
|
||||||
AlbumListSort,
|
AlbumListSort,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
@ -7,7 +6,15 @@ import {
|
|||||||
SongListSort,
|
SongListSort,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/renderer/api/types';
|
} from '/@/renderer/api/types';
|
||||||
import { FeatureCarousel, NativeScrollArea, Spinner, TextTitle } from '/@/renderer/components';
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
FeatureCarousel,
|
||||||
|
Group,
|
||||||
|
NativeScrollArea,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
TextTitle,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import { useAlbumList } from '/@/renderer/features/albums';
|
import { useAlbumList } from '/@/renderer/features/albums';
|
||||||
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
|
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
|
||||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
@ -244,10 +251,10 @@ const HomeRoute = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
|
gap="lg"
|
||||||
mb="5rem"
|
mb="5rem"
|
||||||
pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'}
|
pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'}
|
||||||
px="2rem"
|
px="2rem"
|
||||||
spacing="lg"
|
|
||||||
>
|
>
|
||||||
<FeatureCarousel data={featureItemsWithImage} />
|
<FeatureCarousel data={featureItemsWithImage} />
|
||||||
{sortedCarousel.map((carousel) => (
|
{sortedCarousel.map((carousel) => (
|
||||||
|
@ -304,7 +304,7 @@ export const ItemDetailsModal = ({ item }: ItemDetailsModalProps) => {
|
|||||||
<Table
|
<Table
|
||||||
highlightOnHover
|
highlightOnHover
|
||||||
horizontalSpacing="sm"
|
horizontalSpacing="sm"
|
||||||
sx={{ userSelect: 'text' }}
|
style={{ userSelect: 'text' }}
|
||||||
verticalSpacing="sm"
|
verticalSpacing="sm"
|
||||||
>
|
>
|
||||||
<tbody>{body}</tbody>
|
<tbody>{body}</tbody>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Divider, Group, Stack } from '@mantine/core';
|
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
@ -12,7 +11,15 @@ import {
|
|||||||
LyricsOverride,
|
LyricsOverride,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { useLyricSearch } from '../queries/lyric-search-query';
|
import { useLyricSearch } from '../queries/lyric-search-query';
|
||||||
import { ScrollArea, Spinner, Text, TextInput } from '/@/renderer/components';
|
import {
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
ScrollArea,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
} from '/@/renderer/components';
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
|
||||||
const SearchItem = styled.button`
|
const SearchItem = styled.button`
|
||||||
@ -47,12 +54,12 @@ const SearchResult = ({ data, onClick }: SearchResultProps) => {
|
|||||||
return (
|
return (
|
||||||
<SearchItem onClick={onClick}>
|
<SearchItem onClick={onClick}>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
justify="space-between"
|
||||||
position="apart"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
|
gap={0}
|
||||||
maw="65%"
|
maw="65%"
|
||||||
spacing={0}
|
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
size="md"
|
size="md"
|
||||||
@ -62,8 +69,8 @@ const SearchResult = ({ data, onClick }: SearchResultProps) => {
|
|||||||
</Text>
|
</Text>
|
||||||
<Text $secondary>{artist}</Text>
|
<Text $secondary>{artist}</Text>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
gap="sm"
|
||||||
spacing="sm"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
$secondary
|
$secondary
|
||||||
@ -146,7 +153,7 @@ export const LyricsSearchForm = ({ artist, name, onSearchOverride }: LyricSearch
|
|||||||
type="auto"
|
type="auto"
|
||||||
w="100%"
|
w="100%"
|
||||||
>
|
>
|
||||||
<Stack spacing="md">
|
<Stack gap="md">
|
||||||
{searchResults.map((result) => (
|
{searchResults.map((result) => (
|
||||||
<SearchResult
|
<SearchResult
|
||||||
key={`${result.source}-${result.id}`}
|
key={`${result.source}-${result.id}`}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ComponentPropsWithoutRef } from 'react';
|
import { ComponentPropsWithoutRef } from 'react';
|
||||||
import { TextTitle } from '/@/renderer/components/text-title';
|
import { TextTitle, TextTitleProps } from '/@/renderer/components/text-title';
|
||||||
import { TitleProps } from '@mantine/core';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
|
interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
|
||||||
@ -9,7 +8,7 @@ interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledText = styled(TextTitle)<TitleProps & { $alignment: string; $fontSize: number }>`
|
const StyledText = styled(TextTitle)<TextTitleProps & { $alignment: string; $fontSize: number }>`
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
font-size: ${(props) => props.$fontSize}px;
|
font-size: ${(props) => props.$fontSize}px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Box, Center, Group, Select, SelectItem } from '@mantine/core';
|
import { ComboboxItem } from '@mantine/core';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RiAddFill, RiSubtractFill } from 'react-icons/ri';
|
import { RiAddFill, RiSubtractFill } from 'react-icons/ri';
|
||||||
import { LyricsOverride } from '/@/renderer/api/types';
|
import { LyricsOverride } from '/@/renderer/api/types';
|
||||||
import { Button, NumberInput, Tooltip } from '/@/renderer/components';
|
import { Box, Button, Center, Group, NumberInput, Select, Tooltip } from '/@/renderer/components';
|
||||||
import { openLyricSearchModal } from '/@/renderer/features/lyrics/components/lyrics-search-form';
|
import { openLyricSearchModal } from '/@/renderer/features/lyrics/components/lyrics-search-form';
|
||||||
import {
|
import {
|
||||||
useCurrentSong,
|
useCurrentSong,
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
|
|
||||||
interface LyricsActionsProps {
|
interface LyricsActionsProps {
|
||||||
index: number;
|
index: number;
|
||||||
languages: SelectItem[];
|
languages: ComboboxItem[];
|
||||||
|
|
||||||
onRemoveLyric: () => void;
|
onRemoveLyric: () => void;
|
||||||
onResetLyric: () => void;
|
onResetLyric: () => void;
|
||||||
@ -61,11 +61,11 @@ export const LyricsActions = ({
|
|||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
{isDesktop && sources.length ? (
|
{isDesktop && sources.length ? (
|
||||||
<Button
|
<Button
|
||||||
uppercase
|
|
||||||
disabled={isActionsDisabled}
|
disabled={isActionsDisabled}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openLyricSearchModal({
|
openLyricSearchModal({
|
||||||
@ -94,7 +94,7 @@ export const LyricsActions = ({
|
|||||||
styles={{ input: { textAlign: 'center' } }}
|
styles={{ input: { textAlign: 'center' } }}
|
||||||
value={delayMs || 0}
|
value={delayMs || 0}
|
||||||
width={55}
|
width={55}
|
||||||
onChange={handleLyricOffset}
|
onChange={(e) => handleLyricOffset(Number(e))}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
@ -106,8 +106,8 @@ export const LyricsActions = ({
|
|||||||
</Button>
|
</Button>
|
||||||
{isDesktop && sources.length ? (
|
{isDesktop && sources.length ? (
|
||||||
<Button
|
<Button
|
||||||
uppercase
|
|
||||||
disabled={isActionsDisabled}
|
disabled={isActionsDisabled}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={onResetLyric}
|
onClick={onResetLyric}
|
||||||
>
|
>
|
||||||
@ -119,9 +119,9 @@ export const LyricsActions = ({
|
|||||||
<Box style={{ position: 'absolute', right: 0, top: 0 }}>
|
<Box style={{ position: 'absolute', right: 0, top: 0 }}>
|
||||||
{isDesktop && sources.length ? (
|
{isDesktop && sources.length ? (
|
||||||
<Button
|
<Button
|
||||||
uppercase
|
|
||||||
color="red"
|
color="red"
|
||||||
disabled={isActionsDisabled}
|
disabled={isActionsDisabled}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={onRemoveLyric}
|
onClick={onRemoveLyric}
|
||||||
>
|
>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Center, Group } from '@mantine/core';
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { RiInformationFill } from 'react-icons/ri';
|
import { RiInformationFill } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useSongLyricsByRemoteId, useSongLyricsBySong } from './queries/lyric-query';
|
import { useSongLyricsByRemoteId, useSongLyricsBySong } from './queries/lyric-query';
|
||||||
import { SynchronizedLyrics, SynchronizedLyricsProps } from './synchronized-lyrics';
|
import { SynchronizedLyrics, SynchronizedLyricsProps } from './synchronized-lyrics';
|
||||||
import { Spinner, TextTitle } from '/@/renderer/components';
|
import { Center, Group, Spinner, TextTitle } from '/@/renderer/components';
|
||||||
import { ErrorFallback } from '/@/renderer/features/action-required';
|
import { ErrorFallback } from '/@/renderer/features/action-required';
|
||||||
import {
|
import {
|
||||||
UnsynchronizedLyrics,
|
UnsynchronizedLyrics,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Box, Flex } from '@mantine/core';
|
|
||||||
import { PlayQueueListControls } from './play-queue-list-controls';
|
import { PlayQueueListControls } from './play-queue-list-controls';
|
||||||
import { Song } from '/@/renderer/api/types';
|
import { Song } from '/@/renderer/api/types';
|
||||||
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
||||||
|
import { Box, Flex } from '/@/renderer/components';
|
||||||
|
|
||||||
export const DrawerPlayQueue = () => {
|
export const DrawerPlayQueue = () => {
|
||||||
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
|
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
|
||||||
@ -15,7 +15,7 @@ export const DrawerPlayQueue = () => {
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
bg="var(--main-bg)"
|
bg="var(--main-bg)"
|
||||||
sx={{ borderRadius: '10px' }}
|
style={{ borderRadius: '10px' }}
|
||||||
>
|
>
|
||||||
<PlayQueueListControls
|
<PlayQueueListControls
|
||||||
tableRef={queueRef}
|
tableRef={queueRef}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { MutableRefObject } from 'react';
|
import type { MutableRefObject } from 'react';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
import { Group } from '@mantine/core';
|
import { Button, Group, Popover } from '/@/renderer/components';
|
||||||
import { Button, Popover } from '/@/renderer/components';
|
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -107,16 +106,15 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
position="apart"
|
justify="space-between"
|
||||||
px="1rem"
|
px="1rem"
|
||||||
py="1rem"
|
py="1rem"
|
||||||
sx={{ alignItems: 'center' }}
|
style={{ alignItems: 'center' }}
|
||||||
w="100%"
|
w="100%"
|
||||||
>
|
>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('player.shuffle', { postProcess: 'sentenceCase' }) }}
|
tooltip={{ label: t('player.shuffle', { postProcess: 'sentenceCase' }) }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleShuffleQueue}
|
onClick={handleShuffleQueue}
|
||||||
@ -124,8 +122,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
<RiShuffleLine size="1.1rem" />
|
<RiShuffleLine size="1.1rem" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('action.moveToBottom', { postProcess: 'sentenceCase' }) }}
|
tooltip={{ label: t('action.moveToBottom', { postProcess: 'sentenceCase' }) }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleMoveToBottom}
|
onClick={handleMoveToBottom}
|
||||||
@ -133,8 +130,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
<RiArrowDownLine size="1.1rem" />
|
<RiArrowDownLine size="1.1rem" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('action.moveToTop', { postProcess: 'sentenceCase' }) }}
|
tooltip={{ label: t('action.moveToTop', { postProcess: 'sentenceCase' }) }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleMoveToTop}
|
onClick={handleMoveToTop}
|
||||||
@ -142,8 +138,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
<RiArrowUpLine size="1.1rem" />
|
<RiArrowUpLine size="1.1rem" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('action.removeFromQueue', { postProcess: 'sentenceCase' }),
|
label: t('action.removeFromQueue', { postProcess: 'sentenceCase' }),
|
||||||
}}
|
}}
|
||||||
@ -153,8 +148,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
<RiEraserLine size="1.1rem" />
|
<RiEraserLine size="1.1rem" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('action.clearQueue', { postProcess: 'sentenceCase' }) }}
|
tooltip={{ label: t('action.clearQueue', { postProcess: 'sentenceCase' }) }}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleClearQueue}
|
onClick={handleClearQueue}
|
||||||
@ -169,8 +163,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
>
|
>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('common.configure', { postProcess: 'sentenceCase' }),
|
label: t('common.configure', { postProcess: 'sentenceCase' }),
|
||||||
}}
|
}}
|
||||||
|
@ -15,7 +15,7 @@ const NowPlayingRoute = () => {
|
|||||||
<AnimatedPage>
|
<AnimatedPage>
|
||||||
<VirtualGridContainer>
|
<VirtualGridContainer>
|
||||||
<NowPlayingHeader />
|
<NowPlayingHeader />
|
||||||
<Paper sx={{ borderTop: '1px solid var(--generic-border-color)' }}>
|
<Paper style={{ borderTop: '1px solid var(--generic-border-color)' }}>
|
||||||
<PlayQueueListControls
|
<PlayQueueListControls
|
||||||
tableRef={queueRef}
|
tableRef={queueRef}
|
||||||
type="nowPlaying"
|
type="nowPlaying"
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Flex, Stack, Group, Center } from '@mantine/core';
|
|
||||||
import { useSetState } from '@mantine/hooks';
|
import { useSetState } from '@mantine/hooks';
|
||||||
import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'framer-motion';
|
import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'framer-motion';
|
||||||
import { useEffect, useRef, useLayoutEffect, useState, useCallback, Fragment } from 'react';
|
import { useEffect, useRef, useLayoutEffect, useState, useCallback, Fragment } from 'react';
|
||||||
@ -7,7 +6,7 @@ import { generatePath } from 'react-router';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { QueueSong } from '/@/renderer/api/types';
|
import { QueueSong } from '/@/renderer/api/types';
|
||||||
import { Badge, Text, TextTitle } from '/@/renderer/components';
|
import { Badge, Center, Flex, Group, Stack, Text, TextTitle } from '/@/renderer/components';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import {
|
import {
|
||||||
@ -104,7 +103,7 @@ const ImageWithPlaceholder = ({
|
|||||||
if (!props.src) {
|
if (!props.src) {
|
||||||
return (
|
return (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
borderRadius: 'var(--card-default-radius)',
|
borderRadius: 'var(--card-default-radius)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -246,11 +245,10 @@ export const FullScreenPlayerImage = () => {
|
|||||||
</ImageContainer>
|
</ImageContainer>
|
||||||
<MetadataContainer
|
<MetadataContainer
|
||||||
className="full-screen-player-image-metadata"
|
className="full-screen-player-image-metadata"
|
||||||
|
gap="xs"
|
||||||
maw="100%"
|
maw="100%"
|
||||||
spacing="xs"
|
|
||||||
>
|
>
|
||||||
<TextTitle
|
<TextTitle
|
||||||
align="center"
|
|
||||||
order={1}
|
order={1}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
style={{
|
style={{
|
||||||
@ -263,7 +261,6 @@ export const FullScreenPlayerImage = () => {
|
|||||||
</TextTitle>
|
</TextTitle>
|
||||||
<TextTitle
|
<TextTitle
|
||||||
$link
|
$link
|
||||||
align="center"
|
|
||||||
component={Link}
|
component={Link}
|
||||||
order={3}
|
order={3}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
@ -280,7 +277,6 @@ export const FullScreenPlayerImage = () => {
|
|||||||
</TextTitle>
|
</TextTitle>
|
||||||
<TextTitle
|
<TextTitle
|
||||||
key="fs-artists"
|
key="fs-artists"
|
||||||
align="center"
|
|
||||||
order={3}
|
order={3}
|
||||||
style={{
|
style={{
|
||||||
textShadow: 'var(--fullscreen-player-text-shadow)',
|
textShadow: 'var(--fullscreen-player-text-shadow)',
|
||||||
@ -291,7 +287,7 @@ export const FullScreenPlayerImage = () => {
|
|||||||
{index > 0 && (
|
{index > 0 && (
|
||||||
<Text
|
<Text
|
||||||
$secondary
|
$secondary
|
||||||
sx={{
|
style={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
padding: '0 0.5rem',
|
padding: '0 0.5rem',
|
||||||
}}
|
}}
|
||||||
@ -317,8 +313,8 @@ export const FullScreenPlayerImage = () => {
|
|||||||
))}
|
))}
|
||||||
</TextTitle>
|
</TextTitle>
|
||||||
<Group
|
<Group
|
||||||
|
justify="center"
|
||||||
mt="sm"
|
mt="sm"
|
||||||
position="center"
|
|
||||||
>
|
>
|
||||||
{currentSong?.container && (
|
{currentSong?.container && (
|
||||||
<Badge size="lg">
|
<Badge size="lg">
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { Group } from '@mantine/core';
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { HiOutlineQueueList } from 'react-icons/hi2';
|
import { HiOutlineQueueList } from 'react-icons/hi2';
|
||||||
import { RiFileMusicLine, RiFileTextLine } from 'react-icons/ri';
|
import { RiFileMusicLine, RiFileTextLine } from 'react-icons/ri';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button } from '/@/renderer/components';
|
import { Button, Group } from '/@/renderer/components';
|
||||||
import { PlayQueue } from '/@/renderer/features/now-playing';
|
import { PlayQueue } from '/@/renderer/features/now-playing';
|
||||||
import {
|
import {
|
||||||
useFullScreenPlayerStore,
|
useFullScreenPlayerStore,
|
||||||
@ -91,23 +90,23 @@ export const FullScreenPlayerQueue = () => {
|
|||||||
<Group
|
<Group
|
||||||
grow
|
grow
|
||||||
align="center"
|
align="center"
|
||||||
position="center"
|
justify="center"
|
||||||
>
|
>
|
||||||
{headerItems.map((item) => (
|
{headerItems.map((item) => (
|
||||||
<HeaderItemWrapper key={`tab-${item.label}`}>
|
<HeaderItemWrapper key={`tab-${item.label}`}>
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
uppercase
|
|
||||||
fw="600"
|
fw="600"
|
||||||
pos="relative"
|
pos="relative"
|
||||||
size="lg"
|
size="lg"
|
||||||
sx={{
|
style={{
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
color: item.active
|
color: item.active
|
||||||
? 'var(--main-fg) !important'
|
? 'var(--main-fg) !important'
|
||||||
: 'var(--main-fg-secondary) !important',
|
: 'var(--main-fg-secondary) !important',
|
||||||
letterSpacing: '1px',
|
letterSpacing: '1px',
|
||||||
}}
|
}}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useLayoutEffect, useRef } from 'react';
|
import { useLayoutEffect, useRef } from 'react';
|
||||||
import { Divider, Group } from '@mantine/core';
|
|
||||||
import { useHotkeys } from '@mantine/hooks';
|
import { useHotkeys } from '@mantine/hooks';
|
||||||
import { Variants, motion } from 'framer-motion';
|
import { Variants, motion } from 'framer-motion';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -8,6 +7,8 @@ import { useLocation } from 'react-router';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
Option,
|
Option,
|
||||||
Popover,
|
Popover,
|
||||||
@ -108,17 +109,16 @@ const Controls = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
|
gap="sm"
|
||||||
p="1rem"
|
p="1rem"
|
||||||
pos="absolute"
|
pos="absolute"
|
||||||
spacing="sm"
|
style={{
|
||||||
sx={{
|
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 10,
|
top: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-sm"
|
||||||
size="sm"
|
|
||||||
tooltip={{ label: t('common.minimize', { postProcess: 'titleCase' }) }}
|
tooltip={{ label: t('common.minimize', { postProcess: 'titleCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleToggleFullScreenPlayer}
|
onClick={handleToggleFullScreenPlayer}
|
||||||
@ -128,8 +128,7 @@ const Controls = () => {
|
|||||||
<Popover position="bottom-start">
|
<Popover position="bottom-start">
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-sm"
|
||||||
size="sm"
|
|
||||||
tooltip={{ label: t('common.configure', { postProcess: 'titleCase' }) }}
|
tooltip={{ label: t('common.configure', { postProcess: 'titleCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
@ -229,7 +228,7 @@ const Controls = () => {
|
|||||||
/>
|
/>
|
||||||
</Option.Control>
|
</Option.Control>
|
||||||
</Option>
|
</Option>
|
||||||
<Divider my="sm" />
|
<Divider marginY="sm" />
|
||||||
<Option>
|
<Option>
|
||||||
<Option.Label>
|
<Option.Label>
|
||||||
{t('page.fullscreenPlayer.config.followCurrentLyric', {
|
{t('page.fullscreenPlayer.config.followCurrentLyric', {
|
||||||
@ -283,8 +282,8 @@ const Controls = () => {
|
|||||||
</Option.Label>
|
</Option.Label>
|
||||||
<Option.Control>
|
<Option.Control>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
|
||||||
w="100%"
|
w="100%"
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={lyricConfig.fontSize}
|
defaultValue={lyricConfig.fontSize}
|
||||||
@ -323,8 +322,8 @@ const Controls = () => {
|
|||||||
</Option.Label>
|
</Option.Label>
|
||||||
<Option.Control>
|
<Option.Control>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
|
||||||
w="100%"
|
w="100%"
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={lyricConfig.gap}
|
defaultValue={lyricConfig.gap}
|
||||||
@ -393,7 +392,7 @@ const Controls = () => {
|
|||||||
/>
|
/>
|
||||||
</Option.Control>
|
</Option.Control>
|
||||||
</Option>
|
</Option>
|
||||||
<Divider my="sm" />
|
<Divider marginY="sm" />
|
||||||
<TableConfigDropdown type="fullScreen" />
|
<TableConfigDropdown type="fullScreen" />
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -162,7 +162,7 @@ export const LeftControls = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center
|
<Center
|
||||||
sx={{
|
style={{
|
||||||
background: 'var(--placeholder-bg)',
|
background: 'var(--placeholder-bg)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
@ -177,11 +177,10 @@ export const LeftControls = () => {
|
|||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
opacity={0.8}
|
opacity={0.8}
|
||||||
radius={50}
|
radius={50}
|
||||||
size="md"
|
size="compact-md"
|
||||||
sx={{
|
style={{
|
||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 2,
|
right: 2,
|
||||||
@ -209,9 +208,9 @@ export const LeftControls = () => {
|
|||||||
<MetadataStack layout="position">
|
<MetadataStack layout="position">
|
||||||
<LineItem>
|
<LineItem>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
|
||||||
align="flex-start"
|
align="flex-start"
|
||||||
spacing="xs"
|
gap="xs"
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
$link
|
$link
|
||||||
@ -225,7 +224,7 @@ export const LeftControls = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
{isSongDefined && (
|
{isSongDefined && (
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={(e) => handleGeneralContextMenu(e, [currentSong!])}
|
onClick={(e) => handleGeneralContextMenu(e, [currentSong!])}
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Flex, Group } from '@mantine/core';
|
|
||||||
import { useHotkeys, useMediaQuery } from '@mantine/hooks';
|
import { useHotkeys, useMediaQuery } from '@mantine/hooks';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -26,7 +25,7 @@ import { useRightControls } from '../hooks/use-right-controls';
|
|||||||
import { PlayerButton } from './player-button';
|
import { PlayerButton } from './player-button';
|
||||||
import { LibraryItem, QueueSong, ServerType, Song } from '/@/renderer/api/types';
|
import { LibraryItem, QueueSong, ServerType, Song } from '/@/renderer/api/types';
|
||||||
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
|
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { DropdownMenu, Rating } from '/@/renderer/components';
|
import { DropdownMenu, Flex, Group, Rating } from '/@/renderer/components';
|
||||||
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
|
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
|
||||||
import { Slider } from '/@/renderer/components/slider';
|
import { Slider } from '/@/renderer/components/slider';
|
||||||
|
|
||||||
@ -213,9 +212,9 @@ export const RightControls = () => {
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
|
||||||
align="center"
|
align="center"
|
||||||
spacing="xs"
|
gap="xs"
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
withArrow
|
withArrow
|
||||||
@ -272,7 +271,7 @@ export const RightControls = () => {
|
|||||||
<RiHeartLine size="1.1rem" />
|
<RiHeartLine size="1.1rem" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sx={{
|
style={{
|
||||||
svg: {
|
svg: {
|
||||||
fill: !currentSong?.userFavorite
|
fill: !currentSong?.userFavorite
|
||||||
? undefined
|
? undefined
|
||||||
@ -297,8 +296,8 @@ export const RightControls = () => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
gap="xs"
|
||||||
spacing="xs"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<PlayerButton
|
<PlayerButton
|
||||||
icon={
|
icon={
|
||||||
|
@ -129,7 +129,7 @@ export const ShuffleAllModal = ({
|
|||||||
}, [musicFolders]);
|
}, [musicFolders]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="md">
|
<Stack gap="md">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label="How many tracks?"
|
label="How many tracks?"
|
||||||
@ -188,7 +188,7 @@ export const ShuffleAllModal = ({
|
|||||||
<Divider />
|
<Divider />
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<RiAddBoxFill size="1rem" />}
|
leftSection={<RiAddBoxFill size="1rem" />}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => handlePlay(Play.LAST)}
|
onClick={() => handlePlay(Play.LAST)}
|
||||||
@ -196,7 +196,7 @@ export const ShuffleAllModal = ({
|
|||||||
Add
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<RiAddCircleFill size="1rem" />}
|
leftSection={<RiAddCircleFill size="1rem" />}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => handlePlay(Play.NEXT)}
|
onClick={() => handlePlay(Play.NEXT)}
|
||||||
@ -205,7 +205,7 @@ export const ShuffleAllModal = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<RiPlayFill size="1rem" />}
|
leftSection={<RiPlayFill size="1rem" />}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onClick={() => handlePlay(Play.NOW)}
|
onClick={() => handlePlay(Play.NOW)}
|
||||||
|
@ -229,7 +229,7 @@ export const AddToPlaylistContextModal = ({
|
|||||||
})}
|
})}
|
||||||
{...form.getInputProps('skipDuplicates', { type: 'checkbox' })}
|
{...form.getInputProps('skipDuplicates', { type: 'checkbox' })}
|
||||||
/>
|
/>
|
||||||
<Group position="right">
|
<Group align="flex-end">
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
disabled={addToPlaylistMutation.isLoading}
|
disabled={addToPlaylistMutation.isLoading}
|
||||||
|
@ -144,7 +144,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Group position="right">
|
<Group align="flex-end">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
|
@ -159,15 +159,15 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
|||||||
return (
|
return (
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<Group
|
<Group
|
||||||
|
justify="space-between"
|
||||||
p="1rem"
|
p="1rem"
|
||||||
position="apart"
|
|
||||||
>
|
>
|
||||||
<Group>
|
<Group>
|
||||||
<PlayButton onClick={() => handlePlay()} />
|
<PlayButton onClick={() => handlePlay()} />
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size={20} />
|
<RiMoreFill size={20} />
|
||||||
@ -199,10 +199,10 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
|||||||
</DropdownMenu.Dropdown>
|
</DropdownMenu.Dropdown>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
uppercase
|
|
||||||
component={Link}
|
component={Link}
|
||||||
|
size="compact-md"
|
||||||
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
|
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId })}
|
||||||
|
tt="uppercase"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
View full playlist
|
View full playlist
|
||||||
@ -234,15 +234,15 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<MotionGroup
|
<MotionGroup
|
||||||
|
justify="center"
|
||||||
p="2rem"
|
p="2rem"
|
||||||
position="center"
|
|
||||||
onViewportEnter={handleLoadMore}
|
onViewportEnter={handleLoadMore}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
ref={loadMoreRef}
|
ref={loadMoreRef}
|
||||||
compact
|
|
||||||
disabled={!playlistSongsQueryInfinite.hasNextPage}
|
disabled={!playlistSongsQueryInfinite.hasNextPage}
|
||||||
loading={playlistSongsQueryInfinite.isFetchingNextPage}
|
loading={playlistSongsQueryInfinite.isFetchingNextPage}
|
||||||
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleLoadMore}
|
onClick={handleLoadMore}
|
||||||
>
|
>
|
||||||
|
@ -51,7 +51,7 @@ export const PlaylistDetailHeader = forwardRef(
|
|||||||
title={detailQuery?.data?.name || ''}
|
title={detailQuery?.data?.name || ''}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
{metadataItems.map((item, index) => (
|
{metadataItems.map((item, index) => (
|
||||||
<Fragment key={`item-${item.id}-${index}`}>
|
<Fragment key={`item-${item.id}-${index}`}>
|
||||||
{index > 0 && <Text $noSelect>•</Text>}
|
{index > 0 && <Text $noSelect>•</Text>}
|
||||||
|
@ -318,7 +318,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
[page, setPage],
|
[page, setPage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTableColumns = (values: TableColumn[]) => {
|
const handleTableColumns = (values: TableColumn[] | any[]) => {
|
||||||
const existingColumns = page.table.columns;
|
const existingColumns = page.table.columns;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -394,15 +394,14 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
<Group
|
<Group
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing="sm"
|
gap="sm"
|
||||||
w="100%"
|
w="100%"
|
||||||
>
|
>
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{sortByLabel}
|
{sortByLabel}
|
||||||
@ -430,9 +429,8 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size="1.3rem" />
|
<RiMoreFill size="1.3rem" />
|
||||||
@ -440,26 +438,26 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiPlayFill />}
|
leftSection={<RiPlayFill />}
|
||||||
onClick={() => handlePlay(Play.NOW)}
|
onClick={() => handlePlay(Play.NOW)}
|
||||||
>
|
>
|
||||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddBoxFill />}
|
leftSection={<RiAddBoxFill />}
|
||||||
onClick={() => handlePlay(Play.LAST)}
|
onClick={() => handlePlay(Play.LAST)}
|
||||||
>
|
>
|
||||||
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiAddCircleFill />}
|
leftSection={<RiAddCircleFill />}
|
||||||
onClick={() => handlePlay(Play.NEXT)}
|
onClick={() => handlePlay(Play.NEXT)}
|
||||||
>
|
>
|
||||||
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Divider />
|
<DropdownMenu.Divider />
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiEditFill />}
|
leftSection={<RiEditFill />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openUpdatePlaylistModal({
|
openUpdatePlaylistModal({
|
||||||
playlist: detailQuery.data!,
|
playlist: detailQuery.data!,
|
||||||
@ -470,14 +468,14 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
{t('action.editPlaylist', { postProcess: 'sentenceCase' })}
|
{t('action.editPlaylist', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiDeleteBinFill />}
|
leftSection={<RiDeleteBinFill />}
|
||||||
onClick={openDeletePlaylistModal}
|
onClick={openDeletePlaylistModal}
|
||||||
>
|
>
|
||||||
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })}
|
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Divider />
|
<DropdownMenu.Divider />
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiRefreshLine />}
|
leftSection={<RiRefreshLine />}
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
>
|
>
|
||||||
{t('action.refresh', { postProcess: 'sentenceCase' })}
|
{t('action.refresh', { postProcess: 'sentenceCase' })}
|
||||||
@ -505,8 +503,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
>
|
>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiSettings3Fill size="1.3rem" />
|
<RiSettings3Fill size="1.3rem" />
|
||||||
@ -548,7 +545,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
closeMenuOnClick={false}
|
closeMenuOnClick={false}
|
||||||
component="div"
|
component="div"
|
||||||
sx={{ cursor: 'default' }}
|
style={{ cursor: 'default' }}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -560,7 +557,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||||||
width={300}
|
width={300}
|
||||||
onChange={handleTableColumns}
|
onChange={handleTableColumns}
|
||||||
/>
|
/>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text>Auto Fit Columns</Text>
|
<Text>Auto Fit Columns</Text>
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={page.table.autoFit}
|
defaultChecked={page.table.autoFit}
|
||||||
|
@ -43,7 +43,7 @@ export const PlaylistDetailSongListHeader = ({
|
|||||||
const isSmartPlaylist = detailQuery?.data?.rules;
|
const isSmartPlaylist = detailQuery?.data?.rules;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={0}>
|
<Stack gap={0}>
|
||||||
<PageHeader backgroundColor="var(--titlebar-bg)">
|
<PageHeader backgroundColor="var(--titlebar-bg)">
|
||||||
<LibraryHeaderBar>
|
<LibraryHeaderBar>
|
||||||
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
|
||||||
|
@ -235,7 +235,7 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
[pageKey, setDisplayType],
|
[pageKey, setDisplayType],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTableColumns = (values: TableColumn[]) => {
|
const handleTableColumns = (values: TableColumn[] | any[]) => {
|
||||||
const existingColumns = table.columns;
|
const existingColumns = table.columns;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -288,15 +288,14 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
<Group
|
<Group
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing="sm"
|
gap="sm"
|
||||||
w="100%"
|
w="100%"
|
||||||
>
|
>
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
{sortByLabel}
|
{sortByLabel}
|
||||||
@ -322,8 +321,7 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
/>
|
/>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
@ -334,9 +332,8 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
fw="600"
|
fw="600"
|
||||||
size="md"
|
size="compact-md"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiMoreFill size="1.3rem" />
|
<RiMoreFill size="1.3rem" />
|
||||||
@ -344,7 +341,7 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
icon={<RiRefreshLine />}
|
leftSection={<RiRefreshLine />}
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
>
|
>
|
||||||
{t('common.refresh', { postProcess: 'titleCase' })}
|
{t('common.refresh', { postProcess: 'titleCase' })}
|
||||||
@ -359,8 +356,7 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
>
|
>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
size="md"
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
>
|
>
|
||||||
<RiSettings3Fill size="1.3rem" />
|
<RiSettings3Fill size="1.3rem" />
|
||||||
@ -437,7 +433,7 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
closeMenuOnClick={false}
|
closeMenuOnClick={false}
|
||||||
component="div"
|
component="div"
|
||||||
sx={{ cursor: 'default' }}
|
style={{ cursor: 'default' }}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -449,7 +445,7 @@ export const PlaylistListHeaderFilters = ({
|
|||||||
width={300}
|
width={300}
|
||||||
onChange={handleTableColumns}
|
onChange={handleTableColumns}
|
||||||
/>
|
/>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Text>
|
<Text>
|
||||||
{t('table.config.general.autoFitColumns', {
|
{t('table.config.general.autoFitColumns', {
|
||||||
postProcess: 'titleCase',
|
postProcess: 'titleCase',
|
||||||
|
@ -52,7 +52,7 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
ref={cq.ref}
|
ref={cq.ref}
|
||||||
spacing={0}
|
gap={0}
|
||||||
>
|
>
|
||||||
<PageHeader backgroundColor="var(--titlebar-bg)">
|
<PageHeader backgroundColor="var(--titlebar-bg)">
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -411,9 +411,11 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MotionFlex
|
<MotionFlex
|
||||||
direction="column"
|
style={{
|
||||||
h="calc(100% - 3.5rem)"
|
flexDirection: 'column',
|
||||||
justify="space-between"
|
height: 'calc(100% - 3.5rem)',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
h="100%"
|
h="100%"
|
||||||
@ -446,15 +448,15 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||||||
/>
|
/>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
|
||||||
align="flex-end"
|
align="flex-end"
|
||||||
|
justify="space-between"
|
||||||
m="1rem"
|
m="1rem"
|
||||||
position="apart"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
gap="sm"
|
||||||
spacing="sm"
|
|
||||||
w="100%"
|
w="100%"
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
searchable
|
searchable
|
||||||
@ -489,8 +491,8 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||||||
</Group>
|
</Group>
|
||||||
{onSave && onSaveAs && (
|
{onSave && onSaveAs && (
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
gap="sm"
|
||||||
spacing="sm"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
loading={isSaving}
|
loading={isSaving}
|
||||||
@ -519,7 +521,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
$danger
|
$danger
|
||||||
icon={<RiSaveLine color="var(--danger-color)" />}
|
leftSection={<RiSaveLine color="var(--danger-color)" />}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
>
|
>
|
||||||
{t('common.saveAndReplace', { postProcess: 'titleCase' })}
|
{t('common.saveAndReplace', { postProcess: 'titleCase' })}
|
||||||
|
@ -89,7 +89,7 @@ export const SaveAsPlaylistForm = ({
|
|||||||
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
|
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Group position="right">
|
<Group justify="flex-end">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
|
@ -117,7 +117,7 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
|
|||||||
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
|
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Group position="right">
|
<Group justify="flex-end">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
|
@ -179,7 +179,7 @@ const PlaylistDetailSongListRoute = () => {
|
|||||||
>
|
>
|
||||||
<Group p="1rem">
|
<Group p="1rem">
|
||||||
<Button
|
<Button
|
||||||
compact
|
size="compact-md"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={handleToggleExpand}
|
onClick={handleToggleExpand}
|
||||||
>
|
>
|
||||||
|
@ -93,15 +93,15 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
<Group
|
<Group
|
||||||
|
gap="sm"
|
||||||
mb="1rem"
|
mb="1rem"
|
||||||
spacing="sm"
|
|
||||||
>
|
>
|
||||||
{pages.map((page, index) => (
|
{pages.map((page, index) => (
|
||||||
<Fragment key={page}>
|
<Fragment key={page}>
|
||||||
{index > 0 && ' > '}
|
{index > 0 && ' > '}
|
||||||
<Button
|
<Button
|
||||||
compact
|
|
||||||
disabled
|
disabled
|
||||||
|
size="compact-md"
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
{page?.toLocaleUpperCase()}
|
{page?.toLocaleUpperCase()}
|
||||||
@ -122,7 +122,7 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
data-autofocus
|
data-autofocus
|
||||||
icon={<RiSearchLine />}
|
leftSection={<RiSearchLine />}
|
||||||
rightSection={
|
rightSection={
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -261,11 +261,11 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
|||||||
mt="0.5rem"
|
mt="0.5rem"
|
||||||
p="0.5rem"
|
p="0.5rem"
|
||||||
>
|
>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Command.Loading>
|
<Command.Loading>
|
||||||
{isHome && isLoading && query !== '' && <Spinner />}
|
{isHome && isLoading && query !== '' && <Spinner />}
|
||||||
</Command.Loading>
|
</Command.Loading>
|
||||||
<Group spacing="sm">
|
<Group gap="sm">
|
||||||
<Kbd size="md">ESC</Kbd>
|
<Kbd size="md">ESC</Kbd>
|
||||||
<Kbd size="md">↑</Kbd>
|
<Kbd size="md">↑</Kbd>
|
||||||
<Kbd size="md">↓</Kbd>
|
<Kbd size="md">↓</Kbd>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user