From 9c89e1a4043e9c9192b224f07d4e67bec887e60b Mon Sep 17 00:00:00 2001 From: MasonDye Date: Sat, 30 Aug 2025 00:13:48 +0800 Subject: [PATCH] centralize thumbnail ext logic and use site config API --- src/api/api.ts | 125 +++--------------- src/api/explorer.ts | 5 + .../FileManager/ContextMenu/ContextMenu.tsx | 2 +- .../ContextMenu/useActionDisplayOpt.ts | 2 +- src/component/FileManager/FileManager.tsx | 2 +- src/redux/thunks/file.ts | 2 +- src/redux/thunks/thumb.ts | 50 +++++++ 7 files changed, 74 insertions(+), 114 deletions(-) create mode 100644 src/redux/thunks/thumb.ts diff --git a/src/api/api.ts b/src/api/api.ts index 1b2391f..f958f51 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -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 { return async (dispatch, _getState) => { return await dispatch( @@ -314,115 +310,24 @@ export function sendResetFileThumbs(req: ResetThumbRequest): ThunkResponse }; } -// Get supported thumbnail file extensions by reading enabled generators' settings -export function getSupportedThumbExts(): ThunkResponse { +// 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(); - - 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 | null | undefined = undefined; // undefined: not fetched, null: unknown/fallback - -// Prime cache once per page. Safe to call multiple times. -export function primeThumbExtsCache(): ThunkResponse { - 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 | 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 { return async (dispatch, _getState) => { return await dispatch( diff --git a/src/api/explorer.ts b/src/api/explorer.ts index 65a6fb6..b7e22c5 100644 --- a/src/api/explorer.ts +++ b/src/api/explorer.ts @@ -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; diff --git a/src/component/FileManager/ContextMenu/ContextMenu.tsx b/src/component/FileManager/ContextMenu/ContextMenu.tsx index 6896e76..7776f02 100644 --- a/src/component/FileManager/ContextMenu/ContextMenu.tsx +++ b/src/component/FileManager/ContextMenu/ContextMenu.tsx @@ -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"; diff --git a/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts b/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts index 0fe30d1..e7bf63f 100644 --- a/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts +++ b/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts @@ -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"; diff --git a/src/component/FileManager/FileManager.tsx b/src/component/FileManager/FileManager.tsx index 417b1d0..995e0cf 100644 --- a/src/component/FileManager/FileManager.tsx +++ b/src/component/FileManager/FileManager.tsx @@ -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"; diff --git a/src/redux/thunks/file.ts b/src/redux/thunks/file.ts index fe4def9..d8ca74c 100644 --- a/src/redux/thunks/file.ts +++ b/src/redux/thunks/file.ts @@ -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, diff --git a/src/redux/thunks/thumb.ts b/src/redux/thunks/thumb.ts new file mode 100644 index 0000000..0fb3278 --- /dev/null +++ b/src/redux/thunks/thumb.ts @@ -0,0 +1,50 @@ +import { AppThunk } from "../store.ts"; +import { getThumbExts } from "../../api/api.ts"; + +// --- Cached supported thumbnail extensions helpers --- +let __thumbExtsCache: Set | null | undefined = undefined; // undefined: not fetched, null: unknown/fallback + +// Get supported thumbnail file extensions by reading enabled generators' settings +export function getSupportedThumbExts(): AppThunk> { + 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> { + 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 | 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); +}