style(frontend): Prettier config + one-time repo-wide format pass

Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing
hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass
(`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a
feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest
all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
This commit is contained in:
2026-07-21 02:26:40 +02:00
parent 04ecd8b3a7
commit e257e2aca0
101 changed files with 4388 additions and 2641 deletions
+151 -37
View File
@@ -1,7 +1,20 @@
import { lazy, Suspense, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from "react";
import {
lazy,
Suspense,
useEffect,
useLayoutEffect,
useRef,
useState,
type CSSProperties,
} from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { useInfiniteQuery, useQuery, useQueryClient, type InfiniteData } from "@tanstack/react-query";
import {
useInfiniteQuery,
useQuery,
useQueryClient,
type InfiniteData,
} from "@tanstack/react-query";
import {
ArrowLeft,
Check,
@@ -32,7 +45,12 @@ import {
import { formatRuntime } from "../lib/format";
import { useDebounced } from "../lib/useDebounced";
import { useHistorySubview } from "../lib/history";
import { DetailCustomizeMenu, Filterable, useArtBackdrop, useDetailPrefs } from "../lib/plexDetailUi";
import {
DetailCustomizeMenu,
Filterable,
useArtBackdrop,
useDetailPrefs,
} from "../lib/plexDetailUi";
import PlexPlaylistAdd, { type PlexAddTarget } from "./PlexPlaylistAdd";
import PlexCollectionEditor from "./PlexCollectionEditor";
import { PageToolbar } from "./PageShell";
@@ -95,7 +113,8 @@ export default function PlexBrowse() {
const el = scroller();
if (!el) return;
if (sub.view.kind === "grid" && scrollRef.current) el.scrollTop = scrollRef.current;
else if (sub.view.kind === "info" && infoScrollRef.current) el.scrollTop = infoScrollRef.current;
else if (sub.view.kind === "info" && infoScrollRef.current)
el.scrollTop = infoScrollRef.current;
}, [sub.view.kind]);
// Backspace steps back one drill-down level (grid ← show ← season, and out of info/playlist),
@@ -106,7 +125,8 @@ export default function PlexBrowse() {
if (e.key !== "Backspace") return;
const el = document.activeElement as HTMLElement | null;
const tag = (el?.tagName || "").toLowerCase();
if (tag === "input" || tag === "textarea" || tag === "select" || el?.isContentEditable) return;
if (tag === "input" || tag === "textarea" || tag === "select" || el?.isContentEditable)
return;
if (["show", "season", "info", "playlist"].includes(sub.view.kind)) {
e.preventDefault();
sub.back();
@@ -227,7 +247,9 @@ export default function PlexBrowse() {
// The fallback is portaled for the same reason the player itself is: it is a fixed
// full-viewport overlay, and this tree renders inside the page scroller.
return (
<Suspense fallback={createPortal(<div className="fixed inset-0 z-overlay bg-black" />, document.body)}>
<Suspense
fallback={createPortal(<div className="fixed inset-0 z-overlay bg-black" />, document.body)}
>
<PlexPlayer itemId={sub.view.id} queue={sub.view.queue} onClose={sub.back} />
</Suspense>
);
@@ -288,7 +310,11 @@ export default function PlexBrowse() {
<PageToolbar>
<div className="px-4 pt-3 pb-1 max-w-[1600px] mx-auto">
<p className="text-xs text-muted">
{browseQ.isLoading ? " " : dq ? t("plex.searchCount", { count: total }) : t("plex.count", { count: total })}
{browseQ.isLoading
? " "
: dq
? t("plex.searchCount", { count: total })
: t("plex.count", { count: total })}
</p>
</div>
</PageToolbar>
@@ -314,7 +340,9 @@ export default function PlexBrowse() {
)}
<div className="min-w-0">
<div className="text-sm font-medium truncate max-w-[150px]">{p.name}</div>
<div className="text-[11px] text-muted">{t("plex.people.count", { count: p.count })}</div>
<div className="text-[11px] text-muted">
{t("plex.people.count", { count: p.count })}
</div>
</div>
<button
onClick={() => removeActor(p.name)}
@@ -389,7 +417,9 @@ export default function PlexBrowse() {
)}
<div ref={setSentinelEl} className="h-8" />
{isFetchingNextPage && <p className="text-center text-xs text-muted pb-4">{t("plex.loading")}</p>}
{isFetchingNextPage && (
<p className="text-center text-xs text-muted pb-4">{t("plex.loading")}</p>
)}
</div>
);
}
@@ -448,7 +478,11 @@ function PlexPosterCard({
<div className="flex flex-col items-center gap-1 text-white">
<Play className="w-10 h-10" fill="currentColor" />
<span className="text-xs font-medium">
{card.type === "show" ? t("plex.openShow") : inProgress ? t("plex.resume") : t("plex.play")}
{card.type === "show"
? t("plex.openShow")
: inProgress
? t("plex.resume")
: t("plex.play")}
</span>
</div>
</div>
@@ -462,7 +496,9 @@ function PlexPosterCard({
title={card.status === "watched" ? t("plex.markUnwatched") : t("plex.markWatched")}
className="absolute top-1.5 right-1.5 p-1 rounded-full bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80 transition"
>
<CheckCircle2 className={`w-4 h-4 ${card.status === "watched" ? "text-emerald-400" : "text-white"}`} />
<CheckCircle2
className={`w-4 h-4 ${card.status === "watched" ? "text-emerald-400" : "text-white"}`}
/>
</button>
)}
{/* Media info page (movies/episodes). */}
@@ -504,7 +540,9 @@ function PlexPosterCard({
{/* Type tag — tell a movie card from a show card at a glance (unified feed). */}
<span
className="absolute bottom-1.5 right-1.5 rounded bg-black/65 p-0.5 text-white/90"
title={t(card.type === "show" ? "plex.filter.scopeOpt.show" : "plex.filter.scopeOpt.movie")}
title={t(
card.type === "show" ? "plex.filter.scopeOpt.show" : "plex.filter.scopeOpt.movie",
)}
>
{card.type === "show" ? <Tv2 className="w-3 h-3" /> : <Film className="w-3 h-3" />}
</span>
@@ -519,8 +557,12 @@ function PlexPosterCard({
className="mt-1.5 rounded-lg px-2 py-1.5 cursor-pointer"
style={{ background: "color-mix(in srgb, var(--card) 60%, transparent)" }}
>
<div className="text-sm font-medium line-clamp-2 leading-tight hover:text-accent">{card.title}</div>
<div className="text-xs text-muted">{[card.year, formatRuntime(card.duration_seconds)].filter(Boolean).join(" · ")}</div>
<div className="text-sm font-medium line-clamp-2 leading-tight hover:text-accent">
{card.title}
</div>
<div className="text-xs text-muted">
{[card.year, formatRuntime(card.duration_seconds)].filter(Boolean).join(" · ")}
</div>
</div>
</div>
);
@@ -581,7 +623,8 @@ function PlexInfoView({
function epLabel(ep?: PlexCard | null): string | undefined {
if (!ep) return undefined;
if (ep.season_number != null && ep.episode_number != null) return `S${ep.season_number} · E${ep.episode_number}`;
if (ep.season_number != null && ep.episode_number != null)
return `S${ep.season_number} · E${ep.episode_number}`;
return ep.title;
}
@@ -675,7 +718,9 @@ function SeasonCard({
e.stopPropagation();
onToggleWatched();
}}
title={watched ? t("plex.series.markSeasonUnwatched") : t("plex.series.markSeasonWatched")}
title={
watched ? t("plex.series.markSeasonUnwatched") : t("plex.series.markSeasonWatched")
}
className="absolute top-1.5 right-1.5 p-1 rounded-full bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80 transition"
>
<CheckCircle2 className={`w-4 h-4 ${watched ? "text-emerald-400" : "text-white"}`} />
@@ -719,7 +764,13 @@ function SeasonCard({
);
}
function CastStrip({ cast, onFilter }: { cast: PlexCastMember[]; onFilter?: (patch: Partial<PlexFilters>) => void }) {
function CastStrip({
cast,
onFilter,
}: {
cast: PlexCastMember[];
onFilter?: (patch: Partial<PlexFilters>) => void;
}) {
const { t } = useTranslation();
return (
<section
@@ -731,14 +782,20 @@ function CastStrip({ cast, onFilter }: { cast: PlexCastMember[]; onFilter?: (pat
{cast.map((c, i) => {
const inner = (
<>
<div className={`mx-auto h-20 w-20 overflow-hidden rounded-full border bg-surface ${onFilter ? "border-border group-hover/cast:border-accent" : "border-border"}`}>
<div
className={`mx-auto h-20 w-20 overflow-hidden rounded-full border bg-surface ${onFilter ? "border-border group-hover/cast:border-accent" : "border-border"}`}
>
{c.thumb ? (
<img src={c.thumb} alt="" loading="lazy" className="h-full w-full object-cover" />
) : (
<div className="grid h-full w-full place-items-center opacity-40">{c.name.charAt(0)}</div>
<div className="grid h-full w-full place-items-center opacity-40">
{c.name.charAt(0)}
</div>
)}
</div>
<div className={`mt-1.5 text-xs font-medium line-clamp-2 leading-tight ${onFilter ? "group-hover/cast:text-accent" : ""}`}>
<div
className={`mt-1.5 text-xs font-medium line-clamp-2 leading-tight ${onFilter ? "group-hover/cast:text-accent" : ""}`}
>
{c.name}
</div>
{c.role && <div className="text-[11px] text-muted line-clamp-1">{c.role}</div>}
@@ -774,7 +831,11 @@ function RelatedStrip({ related, onOpen }: { related: PlexCard[]; onOpen: (id: s
<h2 className="text-sm font-semibold mb-3">{t("plex.series.related")}</h2>
<div className="flex gap-3 overflow-x-auto pb-2">
{related.map((r) => (
<button key={r.id} onClick={() => onOpen(r.id)} className="group w-[130px] shrink-0 text-left">
<button
key={r.id}
onClick={() => onOpen(r.id)}
className="group w-[130px] shrink-0 text-left"
>
<div className="relative aspect-[2/3] rounded-xl overflow-hidden bg-card border border-border">
<img
src={r.thumb}
@@ -848,7 +909,9 @@ function EpisodeCard({
title={ep.status === "watched" ? t("plex.markUnwatched") : t("plex.markWatched")}
className="absolute top-1.5 right-1.5 p-1 rounded-full bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80 transition"
>
<CheckCircle2 className={`w-4 h-4 ${ep.status === "watched" ? "text-emerald-400" : "text-white"}`} />
<CheckCircle2
className={`w-4 h-4 ${ep.status === "watched" ? "text-emerald-400" : "text-white"}`}
/>
</button>
)}
{ep.status === "watched" && (
@@ -989,19 +1052,37 @@ function PlexShowView({
<h1 className="text-2xl font-semibold leading-tight">{show.title}</h1>
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm text-muted">
{show.year != null && (
<Filterable onClick={onFilter ? () => onFilter({ yearMin: show.year, yearMax: show.year }) : undefined}>
<Filterable
onClick={
onFilter
? () => onFilter({ yearMin: show.year, yearMax: show.year })
: undefined
}
>
{show.year}
</Filterable>
)}
{show.content_rating && (
<Filterable onClick={onFilter ? () => onFilter({ contentRatings: [show.content_rating!] }) : undefined}>
<Filterable
onClick={
onFilter
? () => onFilter({ contentRatings: [show.content_rating!] })
: undefined
}
>
· {show.content_rating}
</Filterable>
)}
{show.season_count != null && <span>· {t("plex.seasons", { count: show.season_count })}</span>}
{show.season_count != null && (
<span>· {t("plex.seasons", { count: show.season_count })}</span>
)}
{show.imdb_rating != null && (
<button
onClick={onFilter ? () => onFilter({ ratingMin: Math.floor(show.imdb_rating!) }) : undefined}
onClick={
onFilter
? () => onFilter({ ratingMin: Math.floor(show.imdb_rating!) })
: undefined
}
className={`inline-flex items-center gap-1 ${onFilter ? "cursor-pointer hover:text-accent" : "cursor-default"}`}
>
· <Star className="w-3.5 h-3.5 text-amber-400" fill="currentColor" />
@@ -1023,7 +1104,9 @@ function PlexShowView({
</div>
)}
{show.summary && (
<p className="text-sm text-muted mt-3 leading-relaxed line-clamp-5">{show.summary}</p>
<p className="text-sm text-muted mt-3 leading-relaxed line-clamp-5">
{show.summary}
</p>
)}
<div className="mt-4 flex flex-wrap gap-2">
{show.resume && (
@@ -1036,23 +1119,35 @@ function PlexShowView({
/>
)}
{show.first && show.status !== "new" && (
<ActionBtn onClick={() => onPlay(show.first!.id, allKeys)} icon={RotateCcw} label={t("plex.series.playFromStart")} />
<ActionBtn
onClick={() => onPlay(show.first!.id, allKeys)}
icon={RotateCcw}
label={t("plex.series.playFromStart")}
/>
)}
<ActionBtn
onClick={() => markAll(!watched)}
disabled={busy}
icon={watched ? CheckCheck : Check}
label={watched ? t("plex.series.markShowUnwatched") : t("plex.series.markShowWatched")}
label={
watched ? t("plex.series.markShowUnwatched") : t("plex.series.markShowWatched")
}
/>
{allKeys.length > 0 && (
<ActionBtn
onClick={() => setAddTarget({ kind: "group", ratingKeys: allKeys, title: show.title })}
onClick={() =>
setAddTarget({ kind: "group", ratingKeys: allKeys, title: show.title })
}
icon={ListPlus}
label={t("plex.playlist.addShow")}
/>
)}
{isAdmin && showLib && (
<ActionBtn onClick={() => setCollOpen(true)} icon={Layers} label={t("plex.series.addShowCollection")} />
<ActionBtn
onClick={() => setCollOpen(true)}
icon={Layers}
label={t("plex.series.addShowCollection")}
/>
)}
</div>
</div>
@@ -1087,7 +1182,9 @@ function PlexShowView({
</section>
{show.cast.length > 0 && showCast && <CastStrip cast={show.cast} onFilter={onFilter} />}
{d.related.length > 0 && showRelated && <RelatedStrip related={d.related} onOpen={onOpenShow} />}
{d.related.length > 0 && showRelated && (
<RelatedStrip related={d.related} onOpen={onOpenShow} />
)}
</>
)}
@@ -1180,11 +1277,16 @@ function PlexSeasonView({
)}
</button>
<div className="min-w-0 flex-1">
<button onClick={onBack} className="text-sm text-muted hover:text-accent transition text-left">
<button
onClick={onBack}
className="text-sm text-muted hover:text-accent transition text-left"
>
{d.show.title}
</button>
<h1 className="text-2xl font-semibold leading-tight">{se.title}</h1>
<p className="text-sm text-muted mt-1">{t("plex.series.episodeCount", { count: se.episode_count })}</p>
<p className="text-sm text-muted mt-1">
{t("plex.series.episodeCount", { count: se.episode_count })}
</p>
<div className="mt-4 flex flex-wrap gap-2">
{se.resume && (
<ActionBtn
@@ -1196,18 +1298,30 @@ function PlexSeasonView({
/>
)}
{se.first && se.status !== "new" && (
<ActionBtn onClick={() => onPlay(se.first!.id, seasonKeys)} icon={RotateCcw} label={t("plex.series.playFromStart")} />
<ActionBtn
onClick={() => onPlay(se.first!.id, seasonKeys)}
icon={RotateCcw}
label={t("plex.series.playFromStart")}
/>
)}
<ActionBtn
onClick={() => markAll(!watched)}
disabled={busy}
icon={watched ? CheckCheck : Check}
label={watched ? t("plex.series.markSeasonUnwatched") : t("plex.series.markSeasonWatched")}
label={
watched
? t("plex.series.markSeasonUnwatched")
: t("plex.series.markSeasonWatched")
}
/>
{seasonKeys.length > 0 && (
<ActionBtn
onClick={() =>
setAddTarget({ kind: "group", ratingKeys: seasonKeys, title: `${d.show.title} — ${se.title}` })
setAddTarget({
kind: "group",
ratingKeys: seasonKeys,
title: `${d.show.title} — ${se.title}`,
})
}
icon={ListPlus}
label={t("plex.playlist.addSeason")}