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
+109 -31
View File
@@ -1,7 +1,17 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Crop, Eye, EyeOff, Pause, Play, Scissors, SplitSquareHorizontal, Loader2, X } from "lucide-react";
import {
Crop,
Eye,
EyeOff,
Pause,
Play,
Scissors,
SplitSquareHorizontal,
Loader2,
X,
} from "lucide-react";
import clsx from "clsx";
import Modal from "./Modal";
import { notify } from "../lib/notifications";
@@ -19,7 +29,8 @@ function parseTc(v: string): number | null {
if (!v) return null;
if (v.includes(":")) {
const [m, s] = v.split(":");
const mm = Number(m), ss = Number(s);
const mm = Number(m),
ss = Number(s);
if (!isFinite(mm) || !isFinite(ss)) return null;
return mm * 60 + ss;
}
@@ -149,7 +160,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
const r = el.getBoundingClientRect();
return Math.max(0, Math.min(1, (clientX - r.left) / r.width)) * duration;
},
[duration]
[duration],
);
useEffect(() => {
if (!drag) return;
@@ -171,7 +182,9 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
// --- crop drag ---
const overlayRef = useRef<HTMLDivElement>(null);
const cropDrag = useRef<null | { mode: "move" | "resize"; px: number; py: number; orig: Frac }>(null);
const cropDrag = useRef<null | { mode: "move" | "resize"; px: number; py: number; orig: Frac }>(
null,
);
const onCropDown = (e: React.PointerEvent, mode: "move" | "resize") => {
e.preventDefault();
e.stopPropagation();
@@ -233,8 +246,8 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
0,
Math.min(
Math.max(0, (trackW || HOVER_W) - HOVER_W),
hoverX - (trackRef.current?.getBoundingClientRect().left ?? 0) - HOVER_W / 2
)
hoverX - (trackRef.current?.getBoundingClientRect().left ?? 0) - HOVER_W / 2,
),
)
: 0;
@@ -242,7 +255,10 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
const kept = segments.filter((s) => s.keep);
const keptDur = kept.reduce((a, s) => a + (s.end - s.start), 0);
const isFullSingle =
segments.length === 1 && kept.length === 1 && kept[0].start <= 0.01 && kept[0].end >= duration - 0.01;
segments.length === 1 &&
kept.length === 1 &&
kept[0].start <= 0.01 &&
kept[0].end >= duration - 0.01;
const valid = kept.length >= 1 && keptDur >= MIN_SEG && (cropOn || !isFullSingle);
const willJoin = output === "join" && kept.length > 1;
const reencode = cropOn || accurate;
@@ -253,7 +269,12 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
const vh = v?.videoHeight || job.asset?.height || 0;
if (!cropOn || !vw || !vh) return undefined;
const even = (n: number) => Math.max(2, Math.round(n / 2) * 2);
return { x: even(crop.x * vw), y: even(crop.y * vh), w: even(crop.w * vw), h: even(crop.h * vh) };
return {
x: even(crop.x * vw),
y: even(crop.y * vh),
w: even(crop.w * vw),
h: even(crop.h * vh),
};
};
const create = useMutation({
@@ -266,7 +287,11 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
accurate,
};
if (cp) spec.crop = cp;
await api.enqueueEdit({ source_job_id: job.id, edit_spec: spec, display_name: name.trim() || undefined });
await api.enqueueEdit({
source_job_id: job.id,
edit_spec: spec,
display_name: name.trim() || undefined,
});
return { files: 1 };
}
const N = kept.length;
@@ -331,12 +356,21 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
{/* transport */}
<div className="flex items-center gap-3">
<button onClick={playPause} className="p-2 rounded-lg bg-surface hover:bg-card transition" title={t("editor.playPause")}>
<button
onClick={playPause}
className="p-2 rounded-lg bg-surface hover:bg-card transition"
title={t("editor.playPause")}
>
{playing ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
</button>
<div className="text-xs text-muted tabular-nums">{tc(current)} / {tc(duration)}</div>
<div className="text-xs text-muted tabular-nums">
{tc(current)} / {tc(duration)}
</div>
<div className="flex-1" />
<button onClick={splitAtPlayhead} className={clsx(btnCls, "bg-surface hover:bg-card inline-flex items-center gap-1.5")}>
<button
onClick={splitAtPlayhead}
className={clsx(btnCls, "bg-surface hover:bg-card inline-flex items-center gap-1.5")}
>
<SplitSquareHorizontal className="w-4 h-4" /> {t("editor.splitHere")}
</button>
</div>
@@ -360,7 +394,9 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
<div
key={i}
className="h-full flex-1"
style={tileStyle(Math.round(((i + 0.5) / stripCells) * ((strip!.count ?? 1) - 1)))}
style={tileStyle(
Math.round(((i + 0.5) / stripCells) * ((strip!.count ?? 1) - 1)),
)}
/>
))}
</div>
@@ -372,7 +408,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
className={clsx(
"absolute inset-y-0 border-r border-black/40 last:border-r-0",
s.keep ? "bg-accent/10" : "bg-black/60",
s.id === selId && "ring-2 ring-inset ring-white/70"
s.id === selId && "ring-2 ring-inset ring-white/70",
)}
style={{
left: pct(s.start),
@@ -395,7 +431,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
}}
className={clsx(
"absolute top-1 left-1 p-0.5 rounded bg-black/50 backdrop-blur-sm",
s.keep ? "text-accent" : "text-muted"
s.keep ? "text-accent" : "text-muted",
)}
title={s.keep ? t("editor.drop") : t("editor.keep")}
>
@@ -405,7 +441,11 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
))}
{/* draggable boundary markers */}
{boundaries.map((b, bi) => (
<div key={bi} className="absolute inset-y-0 -ml-1.5 w-3 flex items-center justify-center z-base" style={{ left: pct(b) }}>
<div
key={bi}
className="absolute inset-y-0 -ml-1.5 w-3 flex items-center justify-center z-base"
style={{ left: pct(b) }}
>
<div
className="w-1 h-full bg-white/90 cursor-ew-resize rounded"
onPointerDown={(e) => {
@@ -426,7 +466,10 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
</div>
))}
{/* playhead */}
<div className="absolute inset-y-0 w-0.5 bg-white pointer-events-none z-menu" style={{ left: pct(current) }} />
<div
className="absolute inset-y-0 w-0.5 bg-white pointer-events-none z-menu"
style={{ left: pct(current) }}
/>
</div>
{/* hover-scrub thumbnail */}
@@ -435,8 +478,16 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
className="absolute bottom-full mb-2 pointer-events-none z-chrome rounded-md overflow-hidden border border-border shadow-xl bg-black"
style={{ left: hoverLeft, width: HOVER_W }}
>
<div style={{ width: HOVER_W, height: HOVER_W / aspect, ...tileStyle(Math.floor(hoverTime / (strip.interval_s || 1))) }} />
<div className="text-[10px] text-center text-muted py-0.5 tabular-nums">{tc(hoverTime)}</div>
<div
style={{
width: HOVER_W,
height: HOVER_W / aspect,
...tileStyle(Math.floor(hoverTime / (strip.interval_s || 1))),
}}
/>
<div className="text-[10px] text-center text-muted py-0.5 tabular-nums">
{tc(hoverTime)}
</div>
</div>
)}
</div>
@@ -449,7 +500,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
onClick={() => toggleKeep(sel.id)}
className={clsx(
"inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium",
sel.keep ? "bg-accent/15 text-accent" : "bg-surface text-muted"
sel.keep ? "bg-accent/15 text-accent" : "bg-surface text-muted",
)}
>
{sel.keep ? <Eye className="w-3.5 h-3.5" /> : <EyeOff className="w-3.5 h-3.5" />}
@@ -482,7 +533,9 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
className={numCls}
/>
</label>
<span className="text-xs text-muted tabular-nums w-16 text-right">{tc(sel.end - sel.start)}</span>
<span className="text-xs text-muted tabular-nums w-16 text-right">
{tc(sel.end - sel.start)}
</span>
</div>
)}
@@ -496,7 +549,9 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
disabled={m === "join" && kept.length < 2}
className={clsx(
"px-3 py-1.5 rounded-lg border transition disabled:opacity-40",
output === m ? "border-accent bg-accent/10 text-accent" : "border-border text-muted hover:text-fg"
output === m
? "border-accent bg-accent/10 text-accent"
: "border-border text-muted hover:text-fg",
)}
>
{t(`editor.output.${m}`)}
@@ -514,7 +569,9 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
onClick={() => setCropOn((v) => !v)}
className={clsx(
"flex items-center gap-2 px-3 py-2 rounded-lg border text-sm transition",
cropOn ? "border-accent text-accent bg-accent/10" : "border-border text-muted hover:text-fg"
cropOn
? "border-accent text-accent bg-accent/10"
: "border-border text-muted hover:text-fg",
)}
>
<Crop className="w-4 h-4" /> {cropOn ? t("editor.cropOn") : t("editor.cropOff")}
@@ -529,37 +586,58 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
onClick={() => setAccurate(m === "accurate")}
className={clsx(
"flex-1 px-3 py-2 rounded-lg border text-left transition",
on ? "border-accent bg-accent/10" : "border-border hover:border-muted"
on ? "border-accent bg-accent/10" : "border-border hover:border-muted",
)}
title={t(`editor.${m}.hint`)}
>
<div className={clsx("text-sm font-medium", on ? "text-accent" : "text-fg")}>{t(`editor.${m}.label`)}</div>
<div className={clsx("text-sm font-medium", on ? "text-accent" : "text-fg")}>
{t(`editor.${m}.label`)}
</div>
</button>
);
})}
</div>
) : (
<div className="flex items-center px-3 text-xs text-muted">{t("editor.cropReencode")}</div>
<div className="flex items-center px-3 text-xs text-muted">
{t("editor.cropReencode")}
</div>
)}
</div>
<div>
<label className="block text-sm font-medium mb-1">{t("editor.nameLabel")}</label>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder={defaultName} className={inputCls} />
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={defaultName}
className={inputCls}
/>
</div>
<div className="flex items-center justify-between pt-1">
<span className="text-xs text-muted">{reencode ? t("editor.willReencode") : t("editor.willCopy")}</span>
<span className="text-xs text-muted">
{reencode ? t("editor.willReencode") : t("editor.willCopy")}
</span>
<div className="flex gap-2">
<button onClick={onClose} className={clsx(btnCls, "text-muted hover:text-fg hover:bg-surface")}>
<button
onClick={onClose}
className={clsx(btnCls, "text-muted hover:text-fg hover:bg-surface")}
>
{t("common.cancel")}
</button>
<button
onClick={() => create.mutate()}
disabled={!valid || create.isPending}
className={clsx(btnCls, "bg-accent text-accent-fg hover:opacity-90 inline-flex items-center gap-1.5")}
className={clsx(
btnCls,
"bg-accent text-accent-fg hover:opacity-90 inline-flex items-center gap-1.5",
)}
>
{create.isPending ? <Loader2 className="w-4 h-4 animate-spin" /> : <Scissors className="w-4 h-4" />}
{create.isPending ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Scissors className="w-4 h-4" />
)}
{willJoin
? t("editor.createJoin")
: kept.length > 1