Initial commit
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"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>;
|
||||
Reference in New Issue
Block a user