Album blur, allow clicking the playerbar to toggle the player, misc changes (#717)
* Album blur, allow clicking the playerbar to toggle the player * Fix stopProporagion, sync package with upsteam, update translation * recommit my existing changes * Update default albumBackgroundBlur to 6 * according to git this commit resets the package files * merge with our fork because pyx forgot to add it * try adding a setting * change the playerbar animation * make the animation quicker bc its choppy * change playerbar to use a bool instead * requested opacity fix * Refactor classes to use clsx --------- Co-authored-by: iiPython <ben@iipython.dev> Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com>
This commit is contained in:
parent
b93ad40571
commit
eb50c69a35
16 changed files with 197 additions and 29 deletions
|
@ -451,6 +451,10 @@
|
||||||
"setting": {
|
"setting": {
|
||||||
"accentColor": "accent color",
|
"accentColor": "accent color",
|
||||||
"accentColor_description": "sets the accent color for the application",
|
"accentColor_description": "sets the accent color for the application",
|
||||||
|
"albumBackground": "album background image",
|
||||||
|
"albumBackground_description": "adds a background image for album pages containing the album art",
|
||||||
|
"albumBackgroundBlur": "album background image blur size",
|
||||||
|
"albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image",
|
||||||
"applicationHotkeys": "application hotkeys",
|
"applicationHotkeys": "application hotkeys",
|
||||||
"applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)",
|
"applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)",
|
||||||
"audioDevice": "audio device",
|
"audioDevice": "audio device",
|
||||||
|
@ -587,6 +591,8 @@
|
||||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||||
"playerAlbumArtResolution": "player album art resolution",
|
"playerAlbumArtResolution": "player album art resolution",
|
||||||
"playerAlbumArtResolution_description": "the resolution for the large player's album art preview. larger makes it look more crisp, but may slow loading down. defaults to 0, meaning auto",
|
"playerAlbumArtResolution_description": "the resolution for the large player's album art preview. larger makes it look more crisp, but may slow loading down. defaults to 0, meaning auto",
|
||||||
|
"playerbarOpenDrawer": "playerbar fullscreen toggle",
|
||||||
|
"playerbarOpenDrawer_description": "allows clicking of the playerbar to open the full screen player",
|
||||||
"remotePassword": "remote control server password",
|
"remotePassword": "remote control server password",
|
||||||
"remotePassword_description": "sets the password for the remote control server. These credentials are by default transferred insecurely, so you should use a unique password that you do not care about",
|
"remotePassword_description": "sets the password for the remote control server. These credentials are by default transferred insecurely, so you should use a unique password that you do not care about",
|
||||||
"remotePort": "remote control server port",
|
"remotePort": "remote control server port",
|
||||||
|
|
|
@ -517,7 +517,7 @@
|
||||||
},
|
},
|
||||||
"albumArtistDetail": {
|
"albumArtistDetail": {
|
||||||
"viewAllTracks": "ver todo de $t(entity.track_other)",
|
"viewAllTracks": "ver todo de $t(entity.track_other)",
|
||||||
"relatedArtists": "similar a $t(entity.artist_other)",
|
"relatedArtists": "$t(entity.artist_other) similar",
|
||||||
"topSongs": "mejores canciones",
|
"topSongs": "mejores canciones",
|
||||||
"topSongsFrom": "las mejores canciones de {{title}}",
|
"topSongsFrom": "las mejores canciones de {{title}}",
|
||||||
"viewAll": "Ver todo",
|
"viewAll": "Ver todo",
|
||||||
|
|
|
@ -13,7 +13,10 @@ import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { formatDateAbsolute, formatDurationString } from '/@/renderer/utils';
|
import { formatDateAbsolute, formatDurationString } from '/@/renderer/utils';
|
||||||
|
|
||||||
interface AlbumDetailHeaderProps {
|
interface AlbumDetailHeaderProps {
|
||||||
background: string;
|
background: {
|
||||||
|
background: string;
|
||||||
|
blur: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlbumDetailHeader = forwardRef(
|
export const AlbumDetailHeader = forwardRef(
|
||||||
|
@ -24,6 +27,8 @@ export const AlbumDetailHeader = forwardRef(
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const showRating = detailQuery?.data?.serverType === ServerType.NAVIDROME;
|
||||||
|
|
||||||
const originalDifferentFromRelease =
|
const originalDifferentFromRelease =
|
||||||
detailQuery.data?.originalDate &&
|
detailQuery.data?.originalDate &&
|
||||||
detailQuery.data.originalDate !== detailQuery.data.releaseDate;
|
detailQuery.data.originalDate !== detailQuery.data.releaseDate;
|
||||||
|
@ -78,16 +83,14 @@ export const AlbumDetailHeader = forwardRef(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showRating = detailQuery?.data?.serverType === ServerType.NAVIDROME;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack ref={cq.ref}>
|
<Stack ref={cq.ref}>
|
||||||
<LibraryHeader
|
<LibraryHeader
|
||||||
ref={ref}
|
ref={ref}
|
||||||
background={background}
|
|
||||||
imageUrl={detailQuery?.data?.imageUrl}
|
imageUrl={detailQuery?.data?.imageUrl}
|
||||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||||
title={detailQuery?.data?.name || ''}
|
title={detailQuery?.data?.name || ''}
|
||||||
|
{...background}
|
||||||
>
|
>
|
||||||
<Stack spacing="sm">
|
<Stack spacing="sm">
|
||||||
<Group spacing="sm">
|
<Group spacing="sm">
|
||||||
|
|
|
@ -10,17 +10,18 @@ import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
|
||||||
|
|
||||||
const AlbumDetailRoute = () => {
|
const AlbumDetailRoute = () => {
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const headerRef = useRef<HTMLDivElement>(null);
|
const headerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { albumBackground, albumBackgroundBlur } = useGeneralSettings();
|
||||||
|
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
||||||
const { color: background, colorId } = useFastAverageColor({
|
const { color: backgroundColor, colorId } = useFastAverageColor({
|
||||||
id: albumId,
|
id: albumId,
|
||||||
src: detailQuery.data?.imageUrl,
|
src: detailQuery.data?.imageUrl,
|
||||||
srcLoaded: !detailQuery.isLoading,
|
srcLoaded: !detailQuery.isLoading,
|
||||||
|
@ -38,16 +39,19 @@ const AlbumDetailRoute = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!background || colorId !== albumId) {
|
if (!backgroundColor || colorId !== albumId) {
|
||||||
return <Spinner container />;
|
return <Spinner container />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const backgroundUrl = detailQuery.data?.imageUrl || '';
|
||||||
|
const background = (albumBackground && `url(${backgroundUrl})`) || backgroundColor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage key={`album-detail-${albumId}`}>
|
<AnimatedPage key={`album-detail-${albumId}`}>
|
||||||
<NativeScrollArea
|
<NativeScrollArea
|
||||||
ref={scrollAreaRef}
|
ref={scrollAreaRef}
|
||||||
pageHeaderProps={{
|
pageHeaderProps={{
|
||||||
backgroundColor: background,
|
backgroundColor: backgroundColor || undefined,
|
||||||
children: (
|
children: (
|
||||||
<LibraryHeaderBar>
|
<LibraryHeaderBar>
|
||||||
<LibraryHeaderBar.PlayButton onClick={handlePlay} />
|
<LibraryHeaderBar.PlayButton onClick={handlePlay} />
|
||||||
|
@ -62,7 +66,10 @@ const AlbumDetailRoute = () => {
|
||||||
>
|
>
|
||||||
<AlbumDetailHeader
|
<AlbumDetailHeader
|
||||||
ref={headerRef}
|
ref={headerRef}
|
||||||
background={background}
|
background={{
|
||||||
|
background,
|
||||||
|
blur: (albumBackground && albumBackgroundBlur) || 0,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<AlbumDetailContent
|
<AlbumDetailContent
|
||||||
background={background}
|
background={background}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { forwardRef, Fragment, Ref } from 'react';
|
import { forwardRef, Fragment, Ref } from 'react';
|
||||||
import { Group, Rating, Stack } from '@mantine/core';
|
import { Group, Rating, Stack } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
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 { Text } from '/@/renderer/components';
|
||||||
|
@ -17,6 +18,7 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||||
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
||||||
const { albumArtistId } = useParams() as { albumArtistId: string };
|
const { albumArtistId } = useParams() as { albumArtistId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
const { t } = useTranslation();
|
||||||
const detailQuery = useAlbumArtistDetail({
|
const detailQuery = useAlbumArtistDetail({
|
||||||
query: { id: albumArtistId },
|
query: { id: albumArtistId },
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
|
@ -26,12 +28,12 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||||
{
|
{
|
||||||
id: 'albumCount',
|
id: 'albumCount',
|
||||||
secondary: false,
|
secondary: false,
|
||||||
value: detailQuery?.data?.albumCount && `${detailQuery?.data?.albumCount} albums`,
|
value: t('entity.albumWithCount', { count: detailQuery?.data?.albumCount || 0 }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'songCount',
|
id: 'songCount',
|
||||||
secondary: false,
|
secondary: false,
|
||||||
value: detailQuery?.data?.songCount && `${detailQuery?.data?.songCount} songs`,
|
value: t('entity.trackWithCount', { count: detailQuery?.data?.songCount || 0 }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'duration',
|
id: 'duration',
|
||||||
|
|
|
@ -67,7 +67,7 @@ const PlayerbarImage = styled.img`
|
||||||
|
|
||||||
const LineItem = styled.div<{ $secondary?: boolean }>`
|
const LineItem = styled.div<{ $secondary?: boolean }>`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 95%;
|
width: fit-content;
|
||||||
max-width: 20vw;
|
max-width: 20vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
@ -122,6 +122,8 @@ export const LeftControls = () => {
|
||||||
setSideBar({ image: true });
|
setSideBar({ image: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const stopPropagation = (e?: MouseEvent) => e?.stopPropagation();
|
||||||
|
|
||||||
useHotkeys([
|
useHotkeys([
|
||||||
[
|
[
|
||||||
bindings.toggleFullscreenPlayer.allowGlobal
|
bindings.toggleFullscreenPlayer.allowGlobal
|
||||||
|
@ -207,7 +209,7 @@ export const LeftControls = () => {
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<MetadataStack layout="position">
|
<MetadataStack layout="position">
|
||||||
<LineItem>
|
<LineItem onClick={stopPropagation}>
|
||||||
<Group
|
<Group
|
||||||
noWrap
|
noWrap
|
||||||
align="flex-start"
|
align="flex-start"
|
||||||
|
@ -234,7 +236,10 @@ export const LeftControls = () => {
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</LineItem>
|
</LineItem>
|
||||||
<LineItem $secondary>
|
<LineItem
|
||||||
|
$secondary
|
||||||
|
onClick={stopPropagation}
|
||||||
|
>
|
||||||
{artists?.map((artist, index) => (
|
{artists?.map((artist, index) => (
|
||||||
<React.Fragment key={`bar-${artist.id}`}>
|
<React.Fragment key={`bar-${artist.id}`}>
|
||||||
{index > 0 && <Separator />}
|
{index > 0 && <Separator />}
|
||||||
|
@ -257,7 +262,10 @@ export const LeftControls = () => {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</LineItem>
|
</LineItem>
|
||||||
<LineItem $secondary>
|
<LineItem
|
||||||
|
$secondary
|
||||||
|
onClick={stopPropagation}
|
||||||
|
>
|
||||||
<Text
|
<Text
|
||||||
$link
|
$link
|
||||||
component={Link}
|
component={Link}
|
||||||
|
|
|
@ -132,6 +132,10 @@ export const PlayerButton = forwardRef<HTMLDivElement, PlayerButtonProps>(
|
||||||
<StyledPlayerButton
|
<StyledPlayerButton
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
rest.onClick?.(e);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</StyledPlayerButton>
|
</StyledPlayerButton>
|
||||||
|
@ -148,6 +152,10 @@ export const PlayerButton = forwardRef<HTMLDivElement, PlayerButtonProps>(
|
||||||
<StyledPlayerButton
|
<StyledPlayerButton
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
rest.onClick?.(e);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</StyledPlayerButton>
|
</StyledPlayerButton>
|
||||||
|
|
|
@ -41,6 +41,9 @@ export const PlayerbarSlider = ({ ...props }: SliderProps) => {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
|
onClick={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback, MouseEvent } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { usePlaybackType, useSettingsStore } from '/@/renderer/store/settings.store';
|
import {
|
||||||
|
usePlaybackType,
|
||||||
|
useSettingsStore,
|
||||||
|
useGeneralSettings,
|
||||||
|
} from '/@/renderer/store/settings.store';
|
||||||
import { PlaybackType } from '/@/renderer/types';
|
import { PlaybackType } from '/@/renderer/types';
|
||||||
import { AudioPlayer } from '/@/renderer/components';
|
import { AudioPlayer } from '/@/renderer/components';
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +15,8 @@ import {
|
||||||
usePlayer2Data,
|
usePlayer2Data,
|
||||||
usePlayerControls,
|
usePlayerControls,
|
||||||
useVolume,
|
useVolume,
|
||||||
|
useSetFullScreenPlayerStore,
|
||||||
|
useFullScreenPlayerStore,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { CenterControls } from './center-controls';
|
import { CenterControls } from './center-controls';
|
||||||
import { LeftControls } from './left-controls';
|
import { LeftControls } from './left-controls';
|
||||||
|
@ -62,6 +68,7 @@ const CenterGridItem = styled.div`
|
||||||
export const Playerbar = () => {
|
export const Playerbar = () => {
|
||||||
const playersRef = PlayersRef;
|
const playersRef = PlayersRef;
|
||||||
const settings = useSettingsStore((state) => state.playback);
|
const settings = useSettingsStore((state) => state.playback);
|
||||||
|
const { playerbarOpenDrawer } = useGeneralSettings();
|
||||||
const playbackType = usePlaybackType();
|
const playbackType = usePlaybackType();
|
||||||
const volume = useVolume();
|
const volume = useVolume();
|
||||||
const player1 = usePlayer1Data();
|
const player1 = usePlayer1Data();
|
||||||
|
@ -70,6 +77,13 @@ export const Playerbar = () => {
|
||||||
const player = useCurrentPlayer();
|
const player = useCurrentPlayer();
|
||||||
const muted = useMuted();
|
const muted = useMuted();
|
||||||
const { autoNext } = usePlayerControls();
|
const { autoNext } = usePlayerControls();
|
||||||
|
const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore();
|
||||||
|
const setFullScreenPlayerStore = useSetFullScreenPlayerStore();
|
||||||
|
|
||||||
|
const handleToggleFullScreenPlayer = (e?: MouseEvent<HTMLDivElement> | KeyboardEvent) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
|
||||||
|
};
|
||||||
|
|
||||||
const autoNextFn = useCallback(() => {
|
const autoNextFn = useCallback(() => {
|
||||||
const playerData = autoNext();
|
const playerData = autoNext();
|
||||||
|
@ -77,7 +91,9 @@ export const Playerbar = () => {
|
||||||
}, [autoNext]);
|
}, [autoNext]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlayerbarContainer>
|
<PlayerbarContainer
|
||||||
|
onClick={playerbarOpenDrawer ? handleToggleFullScreenPlayer : undefined}
|
||||||
|
>
|
||||||
<PlayerbarControlsGrid>
|
<PlayerbarControlsGrid>
|
||||||
<LeftGridItem>
|
<LeftGridItem>
|
||||||
<LeftControls />
|
<LeftControls />
|
||||||
|
|
|
@ -467,6 +467,76 @@ export const ControlSettings = () => {
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
title: t('setting.homeFeature', { postProcess: 'sentenceCase' }),
|
title: t('setting.homeFeature', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label={t('setting.albumBackground', { postProcess: 'sentenceCase' })}
|
||||||
|
defaultChecked={settings.albumBackground}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
albumBackground: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.albumBackground', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: false,
|
||||||
|
title: t('setting.albumBackground', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Slider
|
||||||
|
defaultValue={settings.albumBackgroundBlur}
|
||||||
|
label={(e) => `${e} rem`}
|
||||||
|
max={6}
|
||||||
|
min={0}
|
||||||
|
step={0.5}
|
||||||
|
w={100}
|
||||||
|
onChangeEnd={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
albumBackgroundBlur: e,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.albumBackgroundBlur', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: false,
|
||||||
|
title: t('setting.albumBackgroundBlur', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label={t('setting.playerbarOpenDrawer ', { postProcess: 'sentenceCase' })}
|
||||||
|
defaultChecked={settings.playerbarOpenDrawer}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
playerbarOpenDrawer: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.playerbarOpenDrawer', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: false,
|
||||||
|
title: t('setting.playerbarOpenDrawer', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return <SettingsSection options={controlOptions} />;
|
return <SettingsSection options={controlOptions} />;
|
||||||
|
|
|
@ -123,6 +123,8 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
|
background-size: cover !important;
|
||||||
|
background-position: center !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-overlay {
|
.background-overlay {
|
||||||
|
@ -135,6 +137,10 @@
|
||||||
background: var(--bg-header-overlay);
|
background: var(--bg-header-overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.opaque-overlay {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { forwardRef, ReactNode, Ref, useState } from 'react';
|
import { forwardRef, ReactNode, Ref, useState } from 'react';
|
||||||
import { Group } from '@mantine/core';
|
import { Group } from '@mantine/core';
|
||||||
import { AutoTextSize } from 'auto-text-size';
|
import { AutoTextSize } from 'auto-text-size';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styles from './library-header.module.scss';
|
import styles from './library-header.module.scss';
|
||||||
import { LibraryItem } from '/@/renderer/api/types';
|
import { LibraryItem } from '/@/renderer/api/types';
|
||||||
import { Text } from '/@/renderer/components';
|
import { Text } from '/@/renderer/components';
|
||||||
import { ItemImagePlaceholder } from '/@/renderer/features/shared/components/item-image-placeholder';
|
import { ItemImagePlaceholder } from '/@/renderer/features/shared/components/item-image-placeholder';
|
||||||
|
import { useGeneralSettings } from '/@/renderer/store';
|
||||||
|
|
||||||
interface LibraryHeaderProps {
|
interface LibraryHeaderProps {
|
||||||
background: string;
|
background: string;
|
||||||
|
blur?: number;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
imagePlaceholderUrl?: string | null;
|
imagePlaceholderUrl?: string | null;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
@ -19,11 +22,20 @@ interface LibraryHeaderProps {
|
||||||
|
|
||||||
export const LibraryHeader = forwardRef(
|
export const LibraryHeader = forwardRef(
|
||||||
(
|
(
|
||||||
{ imageUrl, imagePlaceholderUrl, background, title, item, children }: LibraryHeaderProps,
|
{
|
||||||
|
imageUrl,
|
||||||
|
imagePlaceholderUrl,
|
||||||
|
background,
|
||||||
|
blur,
|
||||||
|
title,
|
||||||
|
item,
|
||||||
|
children,
|
||||||
|
}: LibraryHeaderProps,
|
||||||
ref: Ref<HTMLDivElement>,
|
ref: Ref<HTMLDivElement>,
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isImageError, setIsImageError] = useState<boolean | null>(false);
|
const [isImageError, setIsImageError] = useState<boolean | null>(false);
|
||||||
|
const { albumBackground } = useGeneralSettings();
|
||||||
|
|
||||||
const onImageError = () => {
|
const onImageError = () => {
|
||||||
setIsImageError(true);
|
setIsImageError(true);
|
||||||
|
@ -53,9 +65,13 @@ export const LibraryHeader = forwardRef(
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={styles.background}
|
className={styles.background}
|
||||||
style={{ background }}
|
style={{ background, filter: `blur(${blur ?? 0}rem)` }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={clsx(styles.backgroundOverlay, {
|
||||||
|
[styles.opaqueOverlay]: albumBackground,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<div className={styles.backgroundOverlay} />
|
|
||||||
<div className={styles.imageSection}>
|
<div className={styles.imageSection}>
|
||||||
{imageUrl && !isImageError ? (
|
{imageUrl && !isImageError ? (
|
||||||
<img
|
<img
|
||||||
|
|
|
@ -1,15 +1,34 @@
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Playerbar } from '/@/renderer/features/player';
|
import { Playerbar } from '/@/renderer/features/player';
|
||||||
|
import { useGeneralSettings } from '/@/renderer/store/settings.store';
|
||||||
|
|
||||||
const PlayerbarContainer = styled.footer`
|
interface PlayerbarContainerProps {
|
||||||
|
drawerEffect: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlayerbarContainer = styled.footer<PlayerbarContainerProps>`
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
grid-area: player;
|
grid-area: player;
|
||||||
background: var(--playerbar-bg);
|
background: var(--playerbar-bg);
|
||||||
|
transition: background 0.5s;
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.drawerEffect &&
|
||||||
|
`
|
||||||
|
&:hover {
|
||||||
|
background: var(--playerbar-bg-active);
|
||||||
|
}
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PlayerBar = () => {
|
export const PlayerBar = () => {
|
||||||
|
const { playerbarOpenDrawer } = useGeneralSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlayerbarContainer id="player-bar">
|
<PlayerbarContainer
|
||||||
|
drawerEffect={playerbarOpenDrawer}
|
||||||
|
id="player-bar"
|
||||||
|
>
|
||||||
<Playerbar />
|
<Playerbar />
|
||||||
</PlayerbarContainer>
|
</PlayerbarContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -205,6 +205,8 @@ export interface SettingsState {
|
||||||
general: {
|
general: {
|
||||||
accent: string;
|
accent: string;
|
||||||
albumArtRes?: number | null;
|
albumArtRes?: number | null;
|
||||||
|
albumBackground: boolean;
|
||||||
|
albumBackgroundBlur: number;
|
||||||
buttonSize: number;
|
buttonSize: number;
|
||||||
defaultFullPlaylist: boolean;
|
defaultFullPlaylist: boolean;
|
||||||
disabledContextMenu: { [k in ContextMenuItemType]?: boolean };
|
disabledContextMenu: { [k in ContextMenuItemType]?: boolean };
|
||||||
|
@ -218,6 +220,7 @@ export interface SettingsState {
|
||||||
nativeAspectRatio: boolean;
|
nativeAspectRatio: boolean;
|
||||||
passwordStore?: string;
|
passwordStore?: string;
|
||||||
playButtonBehavior: Play;
|
playButtonBehavior: Play;
|
||||||
|
playerbarOpenDrawer: boolean;
|
||||||
resume: boolean;
|
resume: boolean;
|
||||||
showQueueDrawerButton: boolean;
|
showQueueDrawerButton: boolean;
|
||||||
sideQueueType: SideQueueType;
|
sideQueueType: SideQueueType;
|
||||||
|
@ -342,6 +345,8 @@ const initialState: SettingsState = {
|
||||||
general: {
|
general: {
|
||||||
accent: 'rgb(53, 116, 252)',
|
accent: 'rgb(53, 116, 252)',
|
||||||
albumArtRes: undefined,
|
albumArtRes: undefined,
|
||||||
|
albumBackground: false,
|
||||||
|
albumBackgroundBlur: 6,
|
||||||
buttonSize: 20,
|
buttonSize: 20,
|
||||||
defaultFullPlaylist: true,
|
defaultFullPlaylist: true,
|
||||||
disabledContextMenu: {},
|
disabledContextMenu: {},
|
||||||
|
@ -355,6 +360,7 @@ const initialState: SettingsState = {
|
||||||
nativeAspectRatio: false,
|
nativeAspectRatio: false,
|
||||||
passwordStore: undefined,
|
passwordStore: undefined,
|
||||||
playButtonBehavior: Play.NOW,
|
playButtonBehavior: Play.NOW,
|
||||||
|
playerbarOpenDrawer: false,
|
||||||
resume: false,
|
resume: false,
|
||||||
showQueueDrawerButton: false,
|
showQueueDrawerButton: false,
|
||||||
sideQueueType: 'sideQueue',
|
sideQueueType: 'sideQueue',
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
--sidebar-handle-bg: #4d4d4d;
|
--sidebar-handle-bg: #4d4d4d;
|
||||||
--sidebar-border: 2px rgba(18, 18, 18, 70%) solid;
|
--sidebar-border: 2px rgba(18, 18, 18, 70%) solid;
|
||||||
--playerbar-bg: rgb(16, 16, 16);
|
--playerbar-bg: rgb(16, 16, 16);
|
||||||
|
--playerbar-bg-active: rgb(11, 11, 11);
|
||||||
--playerbar-btn-main-fg: rgb(0, 0, 0);
|
--playerbar-btn-main-fg: rgb(0, 0, 0);
|
||||||
--playerbar-btn-main-fg-hover: rgb(0, 0, 0);
|
--playerbar-btn-main-fg-hover: rgb(0, 0, 0);
|
||||||
--playerbar-btn-main-bg: rgb(230, 230, 230);
|
--playerbar-btn-main-bg: rgb(230, 230, 230);
|
||||||
|
|
|
@ -15,11 +15,8 @@ body[data-theme='defaultLight'] {
|
||||||
--sidebar-fg-hover: rgb(85, 85, 85);
|
--sidebar-fg-hover: rgb(85, 85, 85);
|
||||||
--sidebar-handle-bg: rgb(220, 220, 220);
|
--sidebar-handle-bg: rgb(220, 220, 220);
|
||||||
--sidebar-border: 1px rgba(220, 220, 220, 70%) solid;
|
--sidebar-border: 1px rgba(220, 220, 220, 70%) solid;
|
||||||
--playerbar-bg: linear-gradient(
|
--playerbar-bg: rgb(220, 220, 220);
|
||||||
rgb(220, 220, 220) 0%,
|
--playerbar-bg-active: rgb(175, 175, 175);
|
||||||
rgb(240, 240, 240) 50%,
|
|
||||||
rgb(220, 220, 220) 100%
|
|
||||||
);
|
|
||||||
--playerbar-btn-main-fg: rgb(0, 0, 0);
|
--playerbar-btn-main-fg: rgb(0, 0, 0);
|
||||||
--playerbar-btn-main-fg-hover: rgb(0, 0, 0);
|
--playerbar-btn-main-fg-hover: rgb(0, 0, 0);
|
||||||
--playerbar-btn-main-bg: transparent;
|
--playerbar-btn-main-bg: transparent;
|
||||||
|
|
Reference in a new issue