add minimal cards setting

This commit is contained in:
Pas 2025-12-24 10:12:49 -07:00
parent 41947f8da8
commit 342219b461
8 changed files with 122 additions and 42 deletions

View file

@ -1104,6 +1104,9 @@
"carouselView": "Carousel view", "carouselView": "Carousel view",
"carouselViewDescription": "Display your currently watching and bookmark sections as carousels instead of a grid. Disabled by default.", "carouselViewDescription": "Display your currently watching and bookmark sections as carousels instead of a grid. Disabled by default.",
"carouselViewLabel": "Carousel view", "carouselViewLabel": "Carousel view",
"minimalCards": "Minimal cards",
"minimalCardsDescription": "Hide text content (title, type, year) on media cards, showing only the poster image.",
"minimalCardsLabel": "Minimal cards",
"forceCompactEpisodeView": "Force compact episode view", "forceCompactEpisodeView": "Force compact episode view",
"forceCompactEpisodeViewDescription": "Force the episode carousel in the player to use the \"classic\" compact vertical view. Disabled by default.", "forceCompactEpisodeViewDescription": "Force the episode carousel in the player to use the \"classic\" compact vertical view. Disabled by default.",
"homeSectionOrder": "Home section order", "homeSectionOrder": "Home section order",

View file

@ -20,6 +20,7 @@ export interface SettingsInput {
enableDetailsModal?: boolean; enableDetailsModal?: boolean;
enableImageLogos?: boolean; enableImageLogos?: boolean;
enableCarouselView?: boolean; enableCarouselView?: boolean;
enableMinimalCards?: boolean;
forceCompactEpisodeView?: boolean; forceCompactEpisodeView?: boolean;
sourceOrder?: string[] | null; sourceOrder?: string[] | null;
enableSourceOrder?: boolean; enableSourceOrder?: boolean;
@ -56,6 +57,7 @@ export interface SettingsResponse {
enableDetailsModal?: boolean; enableDetailsModal?: boolean;
enableImageLogos?: boolean; enableImageLogos?: boolean;
enableCarouselView?: boolean; enableCarouselView?: boolean;
enableMinimalCards?: boolean;
forceCompactEpisodeView?: boolean; forceCompactEpisodeView?: boolean;
sourceOrder?: string[] | null; sourceOrder?: string[] | null;
enableSourceOrder?: boolean; enableSourceOrder?: boolean;

View file

@ -136,6 +136,7 @@ function MediaCardContent({
const dotListContent = [t(`media.types.${media.type}`)]; const dotListContent = [t(`media.types.${media.type}`)];
const [searchQuery] = useSearchQuery(); const [searchQuery] = useSearchQuery();
const enableMinimalCards = usePreferencesStore((s) => s.enableMinimalCards);
// Simple intersection observer for lazy loading images // Simple intersection observer for lazy loading images
const { targetRef, isIntersecting } = useIntersectionObserver({ const { targetRef, isIntersecting } = useIntersectionObserver({
@ -185,10 +186,11 @@ function MediaCardContent({
> >
<div <div
className={classNames( className={classNames(
"relative mb-4 pb-[150%] w-full overflow-hidden rounded-xl bg-mediaCard-hoverBackground bg-cover bg-center transition-[border-radius] duration-300", "relative pb-[150%] w-full overflow-hidden rounded-xl bg-mediaCard-hoverBackground bg-cover bg-center transition-[border-radius] duration-300",
{ {
"group-hover:rounded-lg": canLink, "group-hover:rounded-lg": canLink,
}, },
enableMinimalCards ? "" : "mb-4",
)} )}
style={{ style={{
backgroundImage: isIntersecting backgroundImage: isIntersecting
@ -272,48 +274,52 @@ function MediaCardContent({
</div> </div>
</div> </div>
<h1 className="mb-1 line-clamp-3 max-h-[4.5rem] text-ellipsis break-words font-bold text-white"> {!enableMinimalCards && (
<span>{media.title}</span> <>
</h1> <h1 className="mb-1 line-clamp-3 max-h-[4.5rem] text-ellipsis break-words font-bold text-white">
<div className="media-info-container justify-content-center flex flex-wrap"> <span>{media.title}</span>
<DotList className="text-xs" content={dotListContent} /> </h1>
</div> <div className="media-info-container justify-content-center flex flex-wrap">
<DotList className="text-xs" content={dotListContent} />
</div>
{!closable && ( {!closable && (
<div className="absolute bottom-0 translate-y-1 right-1"> <div className="absolute bottom-0 translate-y-1 right-1">
<button <button
className="media-more-button p-2" className="media-more-button p-2"
type="button" type="button"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onShowDetails?.(media); onShowDetails?.(media);
}} }}
> >
<Icon <Icon
className="text-xs font-semibold text-type-secondary" className="text-xs font-semibold text-type-secondary"
icon={Icons.ELLIPSIS} icon={Icons.ELLIPSIS}
/> />
</button> </button>
</div> </div>
)} )}
{editable && closable && ( {editable && closable && (
<div className="absolute bottom-0 translate-y-1 right-1"> <div className="absolute bottom-0 translate-y-1 right-1">
<button <button
className="media-more-button p-2" className="media-more-button p-2"
type="button" type="button"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onEdit?.(); onEdit?.();
}} }}
> >
<Icon <Icon
className="text-xs font-semibold text-type-secondary" className="text-xs font-semibold text-type-secondary"
icon={Icons.EDIT} icon={Icons.EDIT}
/> />
</button> </button>
</div> </div>
)}
</>
)} )}
</Flare.Child> </Flare.Child>
</Flare.Base> </Flare.Base>

View file

@ -94,6 +94,9 @@ export function useAuthData() {
const setKeyboardShortcuts = usePreferencesStore( const setKeyboardShortcuts = usePreferencesStore(
(s) => s.setKeyboardShortcuts, (s) => s.setKeyboardShortcuts,
); );
const setEnableMinimalCards = usePreferencesStore(
(s) => s.setEnableMinimalCards,
);
const login = useCallback( const login = useCallback(
async ( async (
@ -282,6 +285,10 @@ export function useAuthData() {
if (settings.keyboardShortcuts !== undefined) { if (settings.keyboardShortcuts !== undefined) {
setKeyboardShortcuts(settings.keyboardShortcuts); setKeyboardShortcuts(settings.keyboardShortcuts);
} }
if (settings.enableMinimalCards !== undefined) {
setEnableMinimalCards(settings.enableMinimalCards);
}
}, },
[ [
replaceBookmarks, replaceBookmarks,
@ -319,6 +326,7 @@ export function useAuthData() {
setEnableDoubleClickToSeek, setEnableDoubleClickToSeek,
setEnableAutoResumeOnPlaybackError, setEnableAutoResumeOnPlaybackError,
setKeyboardShortcuts, setKeyboardShortcuts,
setEnableMinimalCards,
], ],
); );

View file

@ -72,6 +72,7 @@ export function useSettingsState(
enableSkipCredits: boolean, enableSkipCredits: boolean,
enableImageLogos: boolean, enableImageLogos: boolean,
enableCarouselView: boolean, enableCarouselView: boolean,
enableMinimalCards: boolean,
forceCompactEpisodeView: boolean, forceCompactEpisodeView: boolean,
enableLowPerformanceMode: boolean, enableLowPerformanceMode: boolean,
enableNativeSubtitles: boolean, enableNativeSubtitles: boolean,
@ -221,6 +222,12 @@ export function useSettingsState(
resetEnableCarouselView, resetEnableCarouselView,
enableCarouselViewChanged, enableCarouselViewChanged,
] = useDerived(enableCarouselView); ] = useDerived(enableCarouselView);
const [
enableMinimalCardsState,
setEnableMinimalCardsState,
resetEnableMinimalCards,
enableMinimalCardsChanged,
] = useDerived(enableMinimalCards);
const [ const [
forceCompactEpisodeViewState, forceCompactEpisodeViewState,
setForceCompactEpisodeViewState, setForceCompactEpisodeViewState,
@ -299,6 +306,7 @@ export function useSettingsState(
resetDisabledEmbeds(); resetDisabledEmbeds();
resetProxyTmdb(); resetProxyTmdb();
resetEnableCarouselView(); resetEnableCarouselView();
resetEnableMinimalCards();
resetForceCompactEpisodeView(); resetForceCompactEpisodeView();
resetEnableLowPerformanceMode(); resetEnableLowPerformanceMode();
resetEnableNativeSubtitles(); resetEnableNativeSubtitles();
@ -338,6 +346,7 @@ export function useSettingsState(
disabledEmbedsChanged || disabledEmbedsChanged ||
proxyTmdbChanged || proxyTmdbChanged ||
enableCarouselViewChanged || enableCarouselViewChanged ||
enableMinimalCardsChanged ||
forceCompactEpisodeViewChanged || forceCompactEpisodeViewChanged ||
enableLowPerformanceModeChanged || enableLowPerformanceModeChanged ||
enableNativeSubtitlesChanged || enableNativeSubtitlesChanged ||
@ -490,6 +499,11 @@ export function useSettingsState(
set: setEnableCarouselViewState, set: setEnableCarouselViewState,
changed: enableCarouselViewChanged, changed: enableCarouselViewChanged,
}, },
enableMinimalCards: {
state: enableMinimalCardsState,
set: setEnableMinimalCardsState,
changed: enableMinimalCardsChanged,
},
forceCompactEpisodeView: { forceCompactEpisodeView: {
state: forceCompactEpisodeViewState, state: forceCompactEpisodeViewState,
set: setForceCompactEpisodeViewState, set: setForceCompactEpisodeViewState,

View file

@ -448,6 +448,11 @@ export function SettingsPage() {
(s) => s.setEnableCarouselView, (s) => s.setEnableCarouselView,
); );
const enableMinimalCards = usePreferencesStore((s) => s.enableMinimalCards);
const setEnableMinimalCards = usePreferencesStore(
(s) => s.setEnableMinimalCards,
);
const forceCompactEpisodeView = usePreferencesStore( const forceCompactEpisodeView = usePreferencesStore(
(s) => s.forceCompactEpisodeView, (s) => s.forceCompactEpisodeView,
); );
@ -563,6 +568,7 @@ export function SettingsPage() {
enableSkipCredits, enableSkipCredits,
enableImageLogos, enableImageLogos,
enableCarouselView, enableCarouselView,
enableMinimalCards,
forceCompactEpisodeView, forceCompactEpisodeView,
enableLowPerformanceMode, enableLowPerformanceMode,
enableNativeSubtitles, enableNativeSubtitles,
@ -631,6 +637,7 @@ export function SettingsPage() {
state.disabledSources.changed || state.disabledSources.changed ||
state.proxyTmdb.changed || state.proxyTmdb.changed ||
state.enableCarouselView.changed || state.enableCarouselView.changed ||
state.enableMinimalCards.changed ||
state.forceCompactEpisodeView.changed || state.forceCompactEpisodeView.changed ||
state.enableLowPerformanceMode.changed || state.enableLowPerformanceMode.changed ||
state.enableHoldToBoost.changed || state.enableHoldToBoost.changed ||
@ -660,6 +667,7 @@ export function SettingsPage() {
disabledSources: state.disabledSources.state, disabledSources: state.disabledSources.state,
proxyTmdb: state.proxyTmdb.state, proxyTmdb: state.proxyTmdb.state,
enableCarouselView: state.enableCarouselView.state, enableCarouselView: state.enableCarouselView.state,
enableMinimalCards: state.enableMinimalCards.state,
forceCompactEpisodeView: state.forceCompactEpisodeView.state, forceCompactEpisodeView: state.forceCompactEpisodeView.state,
enableLowPerformanceMode: state.enableLowPerformanceMode.state, enableLowPerformanceMode: state.enableLowPerformanceMode.state,
enableHoldToBoost: state.enableHoldToBoost.state, enableHoldToBoost: state.enableHoldToBoost.state,
@ -716,6 +724,7 @@ export function SettingsPage() {
setdebridService(state.debridService.state); setdebridService(state.debridService.state);
setProxyTmdb(state.proxyTmdb.state); setProxyTmdb(state.proxyTmdb.state);
setEnableCarouselView(state.enableCarouselView.state); setEnableCarouselView(state.enableCarouselView.state);
setEnableMinimalCards(state.enableMinimalCards.state);
setForceCompactEpisodeView(state.forceCompactEpisodeView.state); setForceCompactEpisodeView(state.forceCompactEpisodeView.state);
setEnableLowPerformanceMode(state.enableLowPerformanceMode.state); setEnableLowPerformanceMode(state.enableLowPerformanceMode.state);
setEnableHoldToBoost(state.enableHoldToBoost.state); setEnableHoldToBoost(state.enableHoldToBoost.state);
@ -771,6 +780,7 @@ export function SettingsPage() {
setBackendUrl, setBackendUrl,
setProxyTmdb, setProxyTmdb,
setEnableCarouselView, setEnableCarouselView,
setEnableMinimalCards,
setForceCompactEpisodeView, setForceCompactEpisodeView,
setEnableLowPerformanceMode, setEnableLowPerformanceMode,
setEnableHoldToBoost, setEnableHoldToBoost,
@ -886,6 +896,8 @@ export function SettingsPage() {
setEnableImageLogos={state.enableImageLogos.set} setEnableImageLogos={state.enableImageLogos.set}
enableCarouselView={state.enableCarouselView.state} enableCarouselView={state.enableCarouselView.state}
setEnableCarouselView={state.enableCarouselView.set} setEnableCarouselView={state.enableCarouselView.set}
enableMinimalCards={state.enableMinimalCards.state}
setEnableMinimalCards={state.enableMinimalCards.set}
forceCompactEpisodeView={state.forceCompactEpisodeView.state} forceCompactEpisodeView={state.forceCompactEpisodeView.state}
setForceCompactEpisodeView={state.forceCompactEpisodeView.set} setForceCompactEpisodeView={state.forceCompactEpisodeView.set}
homeSectionOrder={state.homeSectionOrder.state} homeSectionOrder={state.homeSectionOrder.state}

View file

@ -245,6 +245,9 @@ export function AppearancePart(props: {
enableCarouselView: boolean; enableCarouselView: boolean;
setEnableCarouselView: (v: boolean) => void; setEnableCarouselView: (v: boolean) => void;
enableMinimalCards: boolean;
setEnableMinimalCards: (v: boolean) => void;
forceCompactEpisodeView: boolean; forceCompactEpisodeView: boolean;
setForceCompactEpisodeView: (v: boolean) => void; setForceCompactEpisodeView: (v: boolean) => void;
@ -510,6 +513,30 @@ export function AppearancePart(props: {
</div> </div>
</div> </div>
{/* Minimal Cards */}
<div>
<p className="text-white font-bold mb-3">
{t("settings.appearance.options.minimalCards")}
</p>
<p className="max-w-[25rem] font-medium">
{t("settings.appearance.options.minimalCardsDescription")}
</p>
<div
onClick={() =>
props.setEnableMinimalCards(!props.enableMinimalCards)
}
className={classNames(
"bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg",
"cursor-pointer opacity-100 pointer-events-auto",
)}
>
<Toggle enabled={props.enableMinimalCards} />
<p className="flex-1 text-white font-bold">
{t("settings.appearance.options.minimalCardsLabel")}
</p>
</div>
</div>
{/* Force Compact Episode View */} {/* Force Compact Episode View */}
<div> <div>
<p className="text-white font-bold mb-3"> <p className="text-white font-bold mb-3">

View file

@ -16,6 +16,7 @@ export interface PreferencesStore {
enableDetailsModal: boolean; enableDetailsModal: boolean;
enableImageLogos: boolean; enableImageLogos: boolean;
enableCarouselView: boolean; enableCarouselView: boolean;
enableMinimalCards: boolean;
forceCompactEpisodeView: boolean; forceCompactEpisodeView: boolean;
sourceOrder: string[]; sourceOrder: string[];
enableSourceOrder: boolean; enableSourceOrder: boolean;
@ -46,6 +47,7 @@ export interface PreferencesStore {
setEnableDetailsModal(v: boolean): void; setEnableDetailsModal(v: boolean): void;
setEnableImageLogos(v: boolean): void; setEnableImageLogos(v: boolean): void;
setEnableCarouselView(v: boolean): void; setEnableCarouselView(v: boolean): void;
setEnableMinimalCards(v: boolean): void;
setForceCompactEpisodeView(v: boolean): void; setForceCompactEpisodeView(v: boolean): void;
setSourceOrder(v: string[]): void; setSourceOrder(v: string[]): void;
setEnableSourceOrder(v: boolean): void; setEnableSourceOrder(v: boolean): void;
@ -80,6 +82,7 @@ export const usePreferencesStore = create(
enableDetailsModal: false, enableDetailsModal: false,
enableImageLogos: true, enableImageLogos: true,
enableCarouselView: false, enableCarouselView: false,
enableMinimalCards: false,
forceCompactEpisodeView: false, forceCompactEpisodeView: false,
sourceOrder: [], sourceOrder: [],
enableSourceOrder: false, enableSourceOrder: false,
@ -141,6 +144,11 @@ export const usePreferencesStore = create(
s.enableCarouselView = v; s.enableCarouselView = v;
}); });
}, },
setEnableMinimalCards(v) {
set((s) => {
s.enableMinimalCards = v;
});
},
setForceCompactEpisodeView(v) { setForceCompactEpisodeView(v) {
set((s) => { set((s) => {
s.forceCompactEpisodeView = v; s.forceCompactEpisodeView = v;