143 lines
5.7 KiB
TypeScript
143 lines
5.7 KiB
TypeScript
"use server";
|
|
import { revalidatePath } from "next/cache";
|
|
import { setAppSetting, type AppSettings, type WhisperJavSettings, APP_SETTINGS_DEFAULTS } from "@/lib/db/appSettings";
|
|
|
|
export async function setBoolSetting(
|
|
key: "fadeTransitions" | "purgeFilesOnDelete" | "useRecycleBin",
|
|
value: boolean,
|
|
) {
|
|
setAppSetting(key, value);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setTranscodeMode(value: "off" | "always" | "auto-predicate" | "auto-runtime") {
|
|
if (value !== "off" && value !== "always" && value !== "auto-predicate" && value !== "auto-runtime") return;
|
|
setAppSetting("transcodeMode", value);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setNumberSetting(
|
|
key: "fadeDurationMs" | "trashRetentionDays" | "gridColumns" | "gridColumnsPortrait" | "supersededRetentionDays" | "coverPageSize",
|
|
value: number,
|
|
) {
|
|
if (!Number.isFinite(value)) return;
|
|
if (key === "gridColumns" && (value < 2 || value > 4)) return;
|
|
if (key === "gridColumnsPortrait" && (value < 4 || value > 10)) return;
|
|
if (key === "trashRetentionDays" && value < 0) return;
|
|
if (key === "supersededRetentionDays" && value < 0) return;
|
|
if (key === "coverPageSize" && (value < 25 || value > 500)) return;
|
|
setAppSetting(key, value);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
const HEX_RE = /^#[0-9a-fA-F]{6}$/;
|
|
export async function setColorSetting(
|
|
key: "accentPrimary" | "accentSecondary",
|
|
value: string,
|
|
) {
|
|
const normalized = value === "" ? "" : value.toLowerCase();
|
|
if (normalized !== "" && !HEX_RE.test(normalized)) return;
|
|
setAppSetting(key, normalized);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setPaginationMode(value: "url" | "scroll") {
|
|
if (value !== "url" && value !== "scroll") return;
|
|
setAppSetting("paginationMode", value);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setSettingsLayout(value: "sidebar" | "three-column") {
|
|
if (value !== "sidebar" && value !== "three-column") return;
|
|
setAppSetting("settingsLayout", value);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setVideoLibraryPath(value: string) {
|
|
setAppSetting("videoLibraryPath", value.trim());
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setPartSuffixPatterns(values: string[]) {
|
|
// Trim, drop blanks, preserve order. Validation of token grammar
|
|
// (e.g. `{N}`, `{L}`) happens client-side; storage accepts whatever
|
|
// the user typed so a malformed pattern doesn't silently disappear.
|
|
const cleaned = (values ?? []).map((v) => (v ?? "").trim()).filter(Boolean);
|
|
setAppSetting("partSuffixPatterns", cleaned);
|
|
// Reclassify on the next video scan; trigger a rescan so the change
|
|
// takes effect without a manual refresh.
|
|
try {
|
|
const { rescanVideoIndex } = await import("@/lib/video");
|
|
await rescanVideoIndex();
|
|
} catch (e) {
|
|
console.error("[settings] failed to rescan video index after pattern change:", e);
|
|
}
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setWhisperJavSettings(values: Partial<WhisperJavSettings>) {
|
|
const sanitized: WhisperJavSettings = {
|
|
...APP_SETTINGS_DEFAULTS.whisperjav,
|
|
...values,
|
|
cliPath: typeof values.cliPath === "string" ? values.cliPath.trim() : APP_SETTINGS_DEFAULTS.whisperjav.cliPath,
|
|
};
|
|
// Validate enum members so a bad client payload can't poison the row.
|
|
const QUALITIES: WhisperJavSettings["quality"][] = ["fast", "balanced", "qwen"];
|
|
const SOURCE_LANGS: WhisperJavSettings["sourceLanguage"][] = ["japanese", "korean", "chinese", "english"];
|
|
const OUTPUT_MODES: WhisperJavSettings["outputMode"][] = ["native", "direct-to-english"];
|
|
const SENSITIVITIES: WhisperJavSettings["sensitivity"][] = ["conservative", "balanced", "aggressive"];
|
|
const LOCATIONS: WhisperJavSettings["outputLocation"][] = ["beside-video", "data-folder"];
|
|
if (!QUALITIES.includes(sanitized.quality)) sanitized.quality = "balanced";
|
|
if (!SOURCE_LANGS.includes(sanitized.sourceLanguage)) sanitized.sourceLanguage = "japanese";
|
|
if (!OUTPUT_MODES.includes(sanitized.outputMode)) sanitized.outputMode = "native";
|
|
if (!SENSITIVITIES.includes(sanitized.sensitivity)) sanitized.sensitivity = "balanced";
|
|
if (!LOCATIONS.includes(sanitized.outputLocation)) sanitized.outputLocation = "beside-video";
|
|
sanitized.noSignature = sanitized.noSignature !== false;
|
|
const retention = Number(sanitized.retentionDays);
|
|
sanitized.retentionDays = Number.isFinite(retention) && retention >= 0 ? Math.floor(retention) : 30;
|
|
setAppSetting("whisperjav", sanitized);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setSubtitleCacheLimitMb(value: number) {
|
|
if (!Number.isFinite(value) || value < 0) return;
|
|
setAppSetting("subtitleCacheLimitMb", Math.floor(value));
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setSubtitleExtraPaths(values: string[]) {
|
|
const seen = new Set<string>();
|
|
const cleaned: string[] = [];
|
|
for (const v of values) {
|
|
const t = (v ?? "").trim();
|
|
if (!t) continue;
|
|
const key = t.toLowerCase();
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
cleaned.push(t);
|
|
}
|
|
setAppSetting("subtitleExtraPaths", cleaned);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export async function setVideoExtraPaths(values: string[]) {
|
|
// Trim, drop blanks, dedupe (case-insensitive on Windows-friendly compare).
|
|
const seen = new Set<string>();
|
|
const cleaned: string[] = [];
|
|
for (const v of values) {
|
|
const t = (v ?? "").trim();
|
|
if (!t) continue;
|
|
const key = t.toLowerCase();
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
cleaned.push(t);
|
|
}
|
|
setAppSetting("videoExtraPaths", cleaned);
|
|
revalidatePath("/");
|
|
}
|
|
|
|
export type WritableBoolKey = Parameters<typeof setBoolSetting>[0];
|
|
export type WritableNumberKey = Parameters<typeof setNumberSetting>[0];
|
|
export type WritableColorKey = Parameters<typeof setColorSetting>[0];
|
|
export type WritableSettings = Pick<AppSettings, WritableBoolKey | WritableNumberKey | WritableColorKey>;
|