add toggle for single/multiple home feature carousel (#1581) (#1412)

This commit is contained in:
jeffvli
2026-01-22 01:18:34 -08:00
parent 39d691d528
commit a5541745c3
4 changed files with 66 additions and 1 deletions
+4
View File
@@ -796,6 +796,10 @@
"homeConfiguration": "home page configuration", "homeConfiguration": "home page configuration",
"homeFeature_description": "controls whether to show the large featured carousel on the home page", "homeFeature_description": "controls whether to show the large featured carousel on the home page",
"homeFeature": "home featured carousel", "homeFeature": "home featured carousel",
"homeFeatureStyle_description": "controls the style of the home featured carousel",
"homeFeatureStyle": "home featured carousel style",
"homeFeatureStyle_optionMultiple": "multiple",
"homeFeatureStyle_optionSingle": "single",
"hotkey_browserBack": "browser back", "hotkey_browserBack": "browser back",
"hotkey_browserForward": "browser forward", "hotkey_browserForward": "browser forward",
"hotkey_favoriteCurrentSong": "favorite $t(common.currentSong)", "hotkey_favoriteCurrentSong": "favorite $t(common.currentSong)",
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useGridCarouselContainerQuery } from '/@/renderer/components/grid-carousel/grid-carousel-v2'; import { useGridCarouselContainerQuery } from '/@/renderer/components/grid-carousel/grid-carousel-v2';
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area'; import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel'; import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
import { AlbumInfiniteFeatureCarousel } from '/@/renderer/features/home/components/album-infinite-feature-carousel';
import { AlbumInfiniteSingleFeatureCarousel } from '/@/renderer/features/home/components/album-infinite-single-feature-carousel'; import { AlbumInfiniteSingleFeatureCarousel } from '/@/renderer/features/home/components/album-infinite-single-feature-carousel';
import { FeaturedGenres } from '/@/renderer/features/home/components/featured-genres'; import { FeaturedGenres } from '/@/renderer/features/home/components/featured-genres';
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
@@ -12,9 +13,11 @@ import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
import { SongInfiniteCarousel } from '/@/renderer/features/songs/components/song-infinite-carousel'; import { SongInfiniteCarousel } from '/@/renderer/features/songs/components/song-infinite-carousel';
import { import {
HomeFeatureStyle,
HomeItem, HomeItem,
useCurrentServer, useCurrentServer,
useHomeFeature, useHomeFeature,
useHomeFeatureStyle,
useHomeItems, useHomeItems,
useWindowSettings, useWindowSettings,
} from '/@/renderer/store'; } from '/@/renderer/store';
@@ -35,6 +38,7 @@ const HomeRoute = () => {
const server = useCurrentServer(); const server = useCurrentServer();
const { windowBarStyle } = useWindowSettings(); const { windowBarStyle } = useWindowSettings();
const homeFeature = useHomeFeature(); const homeFeature = useHomeFeature();
const homeFeatureStyle = useHomeFeatureStyle();
const homeItems = useHomeItems(); const homeItems = useHomeItems();
const containerQuery = useGridCarouselContainerQuery(); const containerQuery = useGridCarouselContainerQuery();
@@ -116,7 +120,12 @@ const HomeRoute = () => {
px="2rem" px="2rem"
ref={containerQuery.ref} ref={containerQuery.ref}
> >
{homeFeature && <AlbumInfiniteSingleFeatureCarousel />} {homeFeature && homeFeatureStyle === HomeFeatureStyle.SINGLE && (
<AlbumInfiniteSingleFeatureCarousel />
)}
{homeFeature && homeFeatureStyle === HomeFeatureStyle.MULTIPLE && (
<AlbumInfiniteFeatureCarousel />
)}
{sortedItems.map((item) => { {sortedItems.map((item) => {
if (item.id === HomeItem.GENRES) { if (item.id === HomeItem.GENRES) {
return <FeaturedGenres key="featured-genres" />; return <FeaturedGenres key="featured-genres" />;
@@ -18,6 +18,7 @@ import {
SettingsSection, SettingsSection,
} from '/@/renderer/features/settings/components/settings-section'; } from '/@/renderer/features/settings/components/settings-section';
import { import {
HomeFeatureStyle,
SideQueueType, SideQueueType,
useFontSettings, useFontSettings,
useGeneralSettings, useGeneralSettings,
@@ -26,6 +27,7 @@ import {
import { type Font, FONT_OPTIONS } from '/@/renderer/types/fonts'; import { type Font, FONT_OPTIONS } from '/@/renderer/types/fonts';
import { FileInput } from '/@/shared/components/file-input/file-input'; import { FileInput } from '/@/shared/components/file-input/file-input';
import { NumberInput } from '/@/shared/components/number-input/number-input'; import { NumberInput } from '/@/shared/components/number-input/number-input';
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
import { Select } from '/@/shared/components/select/select'; import { Select } from '/@/shared/components/select/select';
import { Slider } from '/@/shared/components/slider/slider'; import { Slider } from '/@/shared/components/slider/slider';
import { Switch } from '/@/shared/components/switch/switch'; import { Switch } from '/@/shared/components/switch/switch';
@@ -37,6 +39,23 @@ const ipc = isElectron() ? window.api.ipc : null;
// Electron 32+ removed file.path, use this which is exposed in preload to get real path // Electron 32+ removed file.path, use this which is exposed in preload to get real path
const webUtils = isElectron() ? window.electron.webUtils : null; const webUtils = isElectron() ? window.electron.webUtils : null;
const HOME_FEATURE_STYLE_OPTIONS = [
{
label: t('setting.homeFeatureStyle', {
context: 'optionSingle',
postProcess: 'sentenceCase',
}),
value: HomeFeatureStyle.SINGLE,
},
{
label: t('setting.homeFeatureStyle', {
context: 'optionMultiple',
postProcess: 'sentenceCase',
}),
value: HomeFeatureStyle.MULTIPLE,
},
];
const SIDE_QUEUE_OPTIONS = [ const SIDE_QUEUE_OPTIONS = [
{ {
label: t('setting.sidePlayQueueStyle', { label: t('setting.sidePlayQueueStyle', {
@@ -356,6 +375,29 @@ export const ApplicationSettings = memo(() => {
isHidden: false, isHidden: false,
title: t('setting.homeFeature', { postProcess: 'sentenceCase' }), title: t('setting.homeFeature', { postProcess: 'sentenceCase' }),
}, },
{
control: (
<SegmentedControl
aria-label={t('setting.homeFeatureStyle', { postProcess: 'sentenceCase' })}
data={HOME_FEATURE_STYLE_OPTIONS}
defaultValue={settings.homeFeatureStyle}
onChange={(e) =>
setSettings({
general: {
...settings,
homeFeatureStyle: e as HomeFeatureStyle,
},
})
}
/>
),
description: t('setting.homeFeature', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: false,
title: t('setting.homeFeature', { postProcess: 'sentenceCase' }),
},
{ {
control: ( control: (
<Switch <Switch
+10
View File
@@ -385,6 +385,11 @@ const VisualizerSettingsSchema = z.object({
type: z.enum(['audiomotionanalyzer', 'butterchurn']), type: z.enum(['audiomotionanalyzer', 'butterchurn']),
}); });
export enum HomeFeatureStyle {
MULTIPLE = 'multiple',
SINGLE = 'single',
}
export const GeneralSettingsSchema = z.object({ export const GeneralSettingsSchema = z.object({
accent: z accent: z
.string() .string()
@@ -409,6 +414,7 @@ export const GeneralSettingsSchema = z.object({
followSystemTheme: z.boolean(), followSystemTheme: z.boolean(),
genreTarget: GenreTargetSchema, genreTarget: GenreTargetSchema,
homeFeature: z.boolean(), homeFeature: z.boolean(),
homeFeatureStyle: z.nativeEnum(HomeFeatureStyle),
homeItems: z.array(SortableItemSchema(HomeItemSchema)), homeItems: z.array(SortableItemSchema(HomeItemSchema)),
imageRes: z.object({ imageRes: z.object({
fullScreenPlayer: z.number(), fullScreenPlayer: z.number(),
@@ -964,6 +970,7 @@ const initialState: SettingsState = {
followSystemTheme: false, followSystemTheme: false,
genreTarget: GenreTarget.TRACK, genreTarget: GenreTarget.TRACK,
homeFeature: true, homeFeature: true,
homeFeatureStyle: HomeFeatureStyle.SINGLE,
homeItems, homeItems,
imageRes: { imageRes: {
fullScreenPlayer: 0, fullScreenPlayer: 0,
@@ -2143,6 +2150,9 @@ export const useExternalLinks = () =>
export const useHomeFeature = () => useSettingsStore((state) => state.general.homeFeature, shallow); export const useHomeFeature = () => useSettingsStore((state) => state.general.homeFeature, shallow);
export const useHomeFeatureStyle = () =>
useSettingsStore((state) => state.general.homeFeatureStyle);
export const useHomeItems = () => useSettingsStore((state) => state.general.homeItems, shallow); export const useHomeItems = () => useSettingsStore((state) => state.general.homeItems, shallow);
export const useArtistItems = () => useSettingsStore((state) => state.general.artistItems, shallow); export const useArtistItems = () => useSettingsStore((state) => state.general.artistItems, shallow);