centralize thumbnail ext logic and use site config API

This commit is contained in:
MasonDye 2025-08-30 00:13:48 +08:00
parent 77ebb6d90b
commit 9c89e1a404
7 changed files with 74 additions and 114 deletions

View File

@ -67,6 +67,7 @@ import {
VersionControlService,
ViewerGroup,
ViewerSessionResponse,
ResetThumbRequest,
} from "./explorer.ts";
import { AppError, Code, CrHeaders, defaultOpts, send, ThunkResponse } from "./request.ts";
import { CreateDavAccountService, DavAccount, ListDavAccountsResponse, ListDavAccountsService } from "./setting.ts";
@ -292,11 +293,6 @@ export function getFileThumb(path: string, contextHint?: string): ThunkResponse<
};
}
// Reset thumbnails for given file URIs by clearing thumb:disabled metadata
export interface ResetThumbRequest {
uris: string[];
}
export function sendResetFileThumbs(req: ResetThumbRequest): ThunkResponse<void> {
return async (dispatch, _getState) => {
return await dispatch(
@ -314,115 +310,24 @@ export function sendResetFileThumbs(req: ResetThumbRequest): ThunkResponse<void>
};
}
// Get supported thumbnail file extensions by reading enabled generators' settings
export function getSupportedThumbExts(): ThunkResponse<string[]> {
// Thin wrapper to query supported thumbnail extensions from backend
export function getThumbExts(): ThunkResponse<{ thumb_exts?: string[] }> {
return async (dispatch, _getState) => {
// Try backend endpoint first if available
try {
const remote = await dispatch(
send<{ exts?: string[] }>(
"/file/thumb/exts",
{
method: "GET",
},
{
...defaultOpts,
},
),
);
if (remote && Array.isArray(remote.exts)) {
return remote.exts;
}
} catch (_e) {
// Fallback to compute from settings below
}
const keys = {
keys: [
"thumb_builtin_enabled",
"thumb_vips_enabled",
"thumb_vips_exts",
"thumb_ffmpeg_enabled",
"thumb_ffmpeg_exts",
"thumb_libreoffice_enabled",
"thumb_libreoffice_exts",
"thumb_music_cover_enabled",
"thumb_music_cover_exts",
"thumb_libraw_enabled",
"thumb_libraw_exts",
],
};
const settings = await dispatch(getSettings(keys));
const enabled = (k: string) => {
const v = (settings?.[k] ?? "").toString().toLowerCase();
return v === "1" || v === "true";
};
const parseExts = (v?: string) =>
(v || "")
.split(",")
.map((s) => s.trim().toLowerCase())
.filter((s) => !!s);
const exts = new Set<string>();
if (enabled("thumb_vips_enabled")) {
parseExts(settings?.["thumb_vips_exts"]).forEach((e) => exts.add(e));
}
if (enabled("thumb_ffmpeg_enabled")) {
parseExts(settings?.["thumb_ffmpeg_exts"]).forEach((e) => exts.add(e));
}
if (enabled("thumb_libreoffice_enabled")) {
parseExts(settings?.["thumb_libreoffice_exts"]).forEach((e) => exts.add(e));
}
if (enabled("thumb_music_cover_enabled")) {
parseExts(settings?.["thumb_music_cover_exts"]).forEach((e) => exts.add(e));
}
if (enabled("thumb_libraw_enabled")) {
parseExts(settings?.["thumb_libraw_exts"]).forEach((e) => exts.add(e));
}
// Note: builtin generator does not expose explicit extensions list in settings
// so we do not add extra exts for it to keep behavior consistent with backend.
return Array.from(exts);
return await dispatch(
send<{ thumb_exts?: string[] }>(
"/site/config/thumb",
{
method: "GET",
},
{
...defaultOpts,
noCredential: true,
},
),
);
};
}
// --- Cached supported thumbnail extensions helpers ---
let __thumbExtsCache: Set<string> | null | undefined = undefined; // undefined: not fetched, null: unknown/fallback
// Prime cache once per page. Safe to call multiple times.
export function primeThumbExtsCache(): ThunkResponse<void> {
return async (dispatch, _getState) => {
if (__thumbExtsCache !== undefined) return;
try {
const exts = await dispatch(getSupportedThumbExts());
__thumbExtsCache = new Set(exts.map((e) => e.toLowerCase()));
} catch (_e) {
// Mark as unknown to fall back to legacy behavior
__thumbExtsCache = null;
}
};
}
export function getCachedThumbExts(): Set<string> | null | undefined {
return __thumbExtsCache;
}
// Check if a file name is likely supported based on cached exts
// Returns undefined if cache is not ready (treat as supported by caller).
export function isThumbExtSupportedSync(fileName: string): boolean | undefined {
const cache = __thumbExtsCache;
if (cache === undefined) return undefined;
if (cache === null) return true; // unknown => allow
const idx = fileName.lastIndexOf(".");
const ext = idx >= 0 ? fileName.substring(idx + 1).toLowerCase() : "";
if (!ext) return false;
return cache.has(ext);
}
export function getUserInfo(uid: string): ThunkResponse<User> {
return async (dispatch, _getState) => {
return await dispatch(

View File

@ -217,6 +217,11 @@ export interface FileThumbResponse {
expires?: string;
}
// Request body for resetting thumbnails for given file URIs
export interface ResetThumbRequest {
uris: string[];
}
export interface DeleteFileService {
uris: string[];
unlink?: boolean;

View File

@ -22,7 +22,7 @@ import {
} from "../../../redux/thunks/file.ts";
import { refreshFileList, uploadClicked, uploadFromClipboard } from "../../../redux/thunks/filemanager.ts";
import { openViewers } from "../../../redux/thunks/viewer.ts";
import { primeThumbExtsCache } from "../../../api/api.ts";
import { primeThumbExtsCache } from "../../../redux/thunks/thumb.ts";
import AppFolder from "../../Icons/AppFolder.tsx";
import ArchiveArrow from "../../Icons/ArchiveArrow.tsx";
import ArrowSync from "../../Icons/ArrowSync.tsx";

View File

@ -1,6 +1,6 @@
import { useMemo } from "react";
import { FileResponse, FileType, Metadata, NavigatorCapability } from "../../../api/explorer.ts";
import { getCachedThumbExts } from "../../../api/api.ts";
import { getCachedThumbExts } from "../../../redux/thunks/thumb.ts";
import { GroupPermission } from "../../../api/user.ts";
import { defaultPath } from "../../../hooks/useNavigation.tsx";
import { ContextMenuTypes } from "../../../redux/fileManagerSlice.ts";

View File

@ -6,7 +6,7 @@ import { clearSelected } from "../../redux/fileManagerSlice.ts";
import { resetDialogs } from "../../redux/globalStateSlice.ts";
import { useAppDispatch } from "../../redux/hooks.ts";
import { resetFm, selectAll, shortCutDelete } from "../../redux/thunks/filemanager.ts";
import { primeThumbExtsCache } from "../../api/api.ts";
import { primeThumbExtsCache } from "../../redux/thunks/thumb.ts";
import ImageViewer from "../Viewers/ImageViewer/ImageViewer.tsx";
import Explorer from "./Explorer/Explorer.tsx";
import { FmIndexContext } from "./FmIndexContext.tsx";

View File

@ -6,7 +6,6 @@ import {
getFileEntityUrl,
getFileList,
getFileThumb,
getCachedThumbExts,
sendResetFileThumbs,
sendCreateFile,
sendDeleteFiles,
@ -17,6 +16,7 @@ import {
sendUnlockFiles,
setCurrentVersion,
} from "../../api/api.ts";
import { getCachedThumbExts } from "./thumb.ts";
import {
ConflictDetail,
DirectLink,

50
src/redux/thunks/thumb.ts Normal file
View File

@ -0,0 +1,50 @@
import { AppThunk } from "../store.ts";
import { getThumbExts } from "../../api/api.ts";
// --- Cached supported thumbnail extensions helpers ---
let __thumbExtsCache: Set<string> | null | undefined = undefined; // undefined: not fetched, null: unknown/fallback
// Get supported thumbnail file extensions by reading enabled generators' settings
export function getSupportedThumbExts(): AppThunk<Promise<string[]>> {
return async (dispatch, _getState) => {
// Read from new site config endpoint; fallback to empty list.
try {
const remote = await dispatch(getThumbExts());
const list = remote?.thumb_exts ?? [];
return Array.isArray(list) ? list : [];
} catch (_e) {
// Treat as unsupported when not available
return [];
}
};
}
// Prime cache once per page. Safe to call multiple times.
export function primeThumbExtsCache(): AppThunk<Promise<void>> {
return async (dispatch, _getState) => {
if (__thumbExtsCache !== undefined) return;
try {
const exts = await dispatch(getSupportedThumbExts());
__thumbExtsCache = new Set(exts.map((e) => e.toLowerCase()));
} catch (_e) {
// Mark as unknown to fall back to legacy behavior
__thumbExtsCache = null;
}
};
}
export function getCachedThumbExts(): Set<string> | null | undefined {
return __thumbExtsCache;
}
// Check if a file name is likely supported based on cached exts
// Returns undefined if cache is not ready (treat as supported by caller).
export function isThumbExtSupportedSync(fileName: string): boolean | undefined {
const cache = __thumbExtsCache;
if (cache === undefined) return undefined;
if (cache === null) return true; // unknown => allow
const idx = fileName.lastIndexOf(".");
const ext = idx >= 0 ? fileName.substring(idx + 1).toLowerCase() : "";
if (!ext) return false;
return cache.has(ext);
}