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",
"carouselViewDescription": "Display your currently watching and bookmark sections as carousels instead of a grid. Disabled by default.",
"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",
"forceCompactEpisodeViewDescription": "Force the episode carousel in the player to use the \"classic\" compact vertical view. Disabled by default.",
"homeSectionOrder": "Home section order",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -245,6 +245,9 @@ export function AppearancePart(props: {
enableCarouselView: boolean;
setEnableCarouselView: (v: boolean) => void;
enableMinimalCards: boolean;
setEnableMinimalCards: (v: boolean) => void;
forceCompactEpisodeView: boolean;
setForceCompactEpisodeView: (v: boolean) => void;
@ -510,6 +513,30 @@ export function AppearancePart(props: {
</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 */}
<div>
<p className="text-white font-bold mb-3">

View file

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