feat: update reset thumbnail feature (#306)

* update reset thumbnail feature

* fix the Translation issues

* centralize thumbnail ext logic and use site config API

* drop reset API; use PATCH metadata and reload only selected thumbnails

* Improve handling of resetting thumbnails

* Remove unused code

---------

Co-authored-by: Aaron Liu <abslant.liu@gmail.com>
This commit is contained in:
Mason Liu 2025-09-23 11:24:33 +08:00 committed by GitHub
parent beb21a6dbc
commit 30290d774f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 165 additions and 18 deletions

View File

@ -318,6 +318,9 @@
"moreActions": "Weitere Aktionen", "moreActions": "Weitere Aktionen",
"refresh": "Aktualisieren", "refresh": "Aktualisieren",
"createArchive": "Archiv erstellen", "createArchive": "Archiv erstellen",
"resetThumbnail": "Beschädigte Miniaturansicht zurücksetzen",
"resetThumbnailRequested": "Zurücksetzen der Miniaturansicht angefordert.",
"noFileCanResetThumbnail": "Keine Dateien zum Zurücksetzen der Miniaturansicht verfügbar.",
"newFolder": "Neuen Ordner erstellen", "newFolder": "Neuen Ordner erstellen",
"newFile": "Neue Datei erstellen", "newFile": "Neue Datei erstellen",
"showFullPath": "Pfad anzeigen", "showFullPath": "Pfad anzeigen",

View File

@ -284,6 +284,9 @@
"moreActions": "More actions", "moreActions": "More actions",
"refresh": "Refresh", "refresh": "Refresh",
"createArchive": "Create archive file", "createArchive": "Create archive file",
"resetThumbnail": "Reset broken thumbnail",
"resetThumbnailRequested": "Thumbnail reset requested.",
"noFileCanResetThumbnail": "No files available for thumbnail reset.",
"newFolder": "New folder", "newFolder": "New folder",
"newFile": "New file", "newFile": "New file",
"showFullPath": "Show full path", "showFullPath": "Show full path",

View File

@ -318,6 +318,9 @@
"moreActions": "Más acciones", "moreActions": "Más acciones",
"refresh": "Actualizar", "refresh": "Actualizar",
"createArchive": "Crear archivo comprimido", "createArchive": "Crear archivo comprimido",
"resetThumbnail": "Restablecer miniatura dañada",
"resetThumbnailRequested": "Se solicitó restablecer la miniatura.",
"noFileCanResetThumbnail": "No hay archivos disponibles para restablecer la miniatura.",
"newFolder": "Nueva carpeta", "newFolder": "Nueva carpeta",
"newFile": "Nuevo archivo", "newFile": "Nuevo archivo",
"showFullPath": "Mostrar ruta completa", "showFullPath": "Mostrar ruta completa",

View File

@ -19,6 +19,9 @@
"password": "Mot de passe", "password": "Mot de passe",
"captcha": "CAPTCHA", "captcha": "CAPTCHA",
"captchaError": "Impossible de charger le CAPTCHA : {{message}}", "captchaError": "Impossible de charger le CAPTCHA : {{message}}",
"resetThumbnail": "Réinitialiser la miniature cassée",
"resetThumbnailRequested": "Réinitialisation de la miniature demandée.",
"noFileCanResetThumbnail": "Aucun fichier pouvant réinitialiser la miniature.",
"signIn": "Se connecter", "signIn": "Se connecter",
"signUp": "S'inscrire", "signUp": "S'inscrire",
"signUpAccount": "S'inscrire", "signUpAccount": "S'inscrire",

View File

@ -54,6 +54,9 @@
"clickToRefresh": "Clicca per aggiornare il CAPTCHA", "clickToRefresh": "Clicca per aggiornare il CAPTCHA",
"switchLanguage": "Cambia lingua" "switchLanguage": "Cambia lingua"
}, },
"resetThumbnail": "Reimposta miniatura danneggiata",
"resetThumbnailRequested": "Reimpostazione della miniatura richiesta.",
"noFileCanResetThumbnail": "Nessun file può reimpostare la miniatura.",
"navbar": { "navbar": {
"notBefore": "Non prima di", "notBefore": "Non prima di",
"notAfter": "Non dopo", "notAfter": "Non dopo",

View File

@ -54,6 +54,9 @@
"clickToRefresh": "CAPTCHAを再読み込み", "clickToRefresh": "CAPTCHAを再読み込み",
"switchLanguage": "言語を切り替え" "switchLanguage": "言語を切り替え"
}, },
"resetThumbnail": "壊れたサムネイルをリセット",
"resetThumbnailRequested": "サムネイルのリセットをリクエストしました。",
"noFileCanResetThumbnail": "サムネイルをリセットできるファイルがありません。",
"navbar": { "navbar": {
"notBefore": "~より前", "notBefore": "~より前",
"notAfter": "~より後", "notAfter": "~より後",

View File

@ -88,6 +88,9 @@
"photos": "사진", "photos": "사진",
"music": "음악", "music": "음악",
"documents": "문서", "documents": "문서",
"resetThumbnail": "깨진 썸네일 재설정",
"resetThumbnailRequested": "썸네일 재설정이 요청되었습니다.",
"noFileCanResetThumbnail": "썸네일을 재설정할 수 있는 파일이 없습니다.",
"addATag": "태그 추가...", "addATag": "태그 추가...",
"addTagDialog": { "addTagDialog": {
"selectFolder": "폴더 선택", "selectFolder": "폴더 선택",

View File

@ -75,6 +75,9 @@
"recentlyViewed": "Visualizados recentemente", "recentlyViewed": "Visualizados recentemente",
"searchFiles": "Buscar arquivos...", "searchFiles": "Buscar arquivos...",
"showMore": "Mais", "showMore": "Mais",
"resetThumbnail": "Redefinir miniatura quebrada",
"resetThumbnailRequested": "Redefinição de miniatura solicitada.",
"noFileCanResetThumbnail": "Nenhum arquivo pode redefinir a miniatura.",
"myFiles": "Meus arquivos", "myFiles": "Meus arquivos",
"hisFiles": "Arquivos dele/dela", "hisFiles": "Arquivos dele/dela",
"trash": "Lixeira", "trash": "Lixeira",

View File

@ -68,6 +68,9 @@
"notNameOpOr": "Должны содержаться все ключевые слова", "notNameOpOr": "Должны содержаться все ключевые слова",
"caseFolding": "Игнорировать регистр", "caseFolding": "Игнорировать регистр",
"keywords": "Ключевые слова", "keywords": "Ключевые слова",
"resetThumbnail": "Сбросить повреждённую миниатюру",
"resetThumbnailRequested": "Запрошен сброс миниатюры.",
"noFileCanResetThumbnail": "Нет файлов для сброса миниатюры.",
"fileNameKeywordsHelp": "Нажмите Enter для добавления ключевого слова", "fileNameKeywordsHelp": "Нажмите Enter для добавления ключевого слова",
"advancedSearch": "Расширенный поиск", "advancedSearch": "Расширенный поиск",
"searchFilesTitle": "Поиск файлов", "searchFilesTitle": "Поиск файлов",

View File

@ -284,6 +284,9 @@
"moreActions": "更多操作", "moreActions": "更多操作",
"refresh": "刷新", "refresh": "刷新",
"createArchive": "创建压缩文件", "createArchive": "创建压缩文件",
"resetThumbnail": "重置失败的缩略图",
"resetThumbnailRequested": "已请求重置缩略图。",
"noFileCanResetThumbnail": "没有可重置缩略图的文件。",
"newFolder": "创建文件夹", "newFolder": "创建文件夹",
"newFile": "创建文件", "newFile": "创建文件",
"showFullPath": "显示路径", "showFullPath": "显示路径",

View File

@ -58,6 +58,9 @@
"notBefore": "不早於", "notBefore": "不早於",
"notAfter": "不晚於", "notAfter": "不晚於",
"minimum": "最小", "minimum": "最小",
"resetThumbnail": "重設失敗的縮圖",
"resetThumbnailRequested": "已請求重設縮圖。",
"noFileCanResetThumbnail": "沒有可重設縮圖的檔案。",
"maximum": "最大", "maximum": "最大",
"fileSize": "檔案大小", "fileSize": "檔案大小",
"searchBase": "搜尋路徑", "searchBase": "搜尋路徑",

View File

@ -3,6 +3,8 @@ import i18n from "../i18n.ts";
import { import {
AdminListGroupResponse, AdminListGroupResponse,
AdminListService, AdminListService,
ListShareResponse as AdminListShareResponse,
StoragePolicy as AdminStoragePolicy,
BatchIDService, BatchIDService,
CleanupTaskService, CleanupTaskService,
CreateStoragePolicyCorsService, CreateStoragePolicyCorsService,
@ -17,7 +19,6 @@ import {
ListEntityResponse, ListEntityResponse,
ListFileResponse, ListFileResponse,
ListNodeResponse, ListNodeResponse,
ListShareResponse as AdminListShareResponse,
ListStoragePolicyResponse, ListStoragePolicyResponse,
ListTaskResponse, ListTaskResponse,
ListUserResponse, ListUserResponse,
@ -26,7 +27,6 @@ import {
QueueMetric, QueueMetric,
SetSettingService, SetSettingService,
Share as ShareEnt, Share as ShareEnt,
StoragePolicy as AdminStoragePolicy,
Task, Task,
TestNodeDownloaderService, TestNodeDownloaderService,
TestNodeService, TestNodeService,
@ -78,7 +78,6 @@ import {
Capacity, Capacity,
FinishPasskeyLoginService, FinishPasskeyLoginService,
FinishPasskeyRegistrationService, FinishPasskeyRegistrationService,
Group,
LoginResponse, LoginResponse,
Passkey, Passkey,
PasskeyCredentialOption, PasskeyCredentialOption,
@ -304,7 +303,7 @@ export function getUserInfo(uid: string): ThunkResponse<User> {
}, },
{ {
...defaultOpts, ...defaultOpts,
bypassSnackbar: (_e) => skipSnackbar, bypassSnackbar: (_e) => true,
}, },
), ),
); );

View File

@ -44,6 +44,7 @@ export interface SiteConfig {
custom_props?: CustomProps[]; custom_props?: CustomProps[];
custom_nav_items?: CustomNavItem[]; custom_nav_items?: CustomNavItem[];
custom_html?: CustomHTML; custom_html?: CustomHTML;
thumb_exts?: string[];
} }
export interface CaptchaResponse { export interface CaptchaResponse {

View File

@ -9,9 +9,11 @@ import {
setVersionControlDialog, setVersionControlDialog,
} from "../../../redux/globalStateSlice.ts"; } from "../../../redux/globalStateSlice.ts";
import { useAppDispatch } from "../../../redux/hooks.ts"; import { useAppDispatch } from "../../../redux/hooks.ts";
import { resetThumbnails } from "../../../redux/thunks/file.ts";
import Archive from "../../Icons/Archive.tsx"; import Archive from "../../Icons/Archive.tsx";
import BranchForkLink from "../../Icons/BranchForkLink.tsx"; import BranchForkLink from "../../Icons/BranchForkLink.tsx";
import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; import HistoryOutlined from "../../Icons/HistoryOutlined.tsx";
import ImageArrowCounterclockwise from "../../Icons/ImageAarowCounterclockwise.tsx";
import LinkSetting from "../../Icons/LinkSetting.tsx"; import LinkSetting from "../../Icons/LinkSetting.tsx";
import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx";
import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx";
@ -105,6 +107,14 @@ const MoreMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => {
<ListItemText>{t("application:fileManager.createArchive")}</ListItemText> <ListItemText>{t("application:fileManager.createArchive")}</ListItemText>
</CascadingMenuItem> </CascadingMenuItem>
)} )}
{displayOpt.showResetThumb && (
<CascadingMenuItem onClick={onClick(() => dispatch(resetThumbnails(targets)))}>
<ListItemIcon>
<ImageArrowCounterclockwise fontSize="small" />
</ListItemIcon>
<ListItemText>{t("application:fileManager.resetThumbnail")}</ListItemText>
</CascadingMenuItem>
)}
</> </>
); );
}; };

View File

@ -45,6 +45,7 @@ export interface DisplayOption {
hasFile?: boolean; hasFile?: boolean;
hasFolder?: boolean; hasFolder?: boolean;
hasOwned?: boolean; hasOwned?: boolean;
hasFailedThumb?: boolean;
showEnter?: boolean; showEnter?: boolean;
showOpen?: boolean; showOpen?: boolean;
@ -77,6 +78,7 @@ export interface DisplayOption {
showDirectLinkManagement?: boolean; showDirectLinkManagement?: boolean;
showManageShares?: boolean; showManageShares?: boolean;
showCreateArchive?: boolean; showCreateArchive?: boolean;
showResetThumb?: boolean;
andCapability?: Boolset; andCapability?: Boolset;
orCapability?: Boolset; orCapability?: Boolset;
@ -154,8 +156,13 @@ export const getActionOpt = (
display.hasUpdatable = true; display.hasUpdatable = true;
} }
if (target.metadata && target.metadata[Metadata.restore_uri]) { if (target.metadata) {
display.hasTrashFile = true; if (target.metadata[Metadata.restore_uri]) {
display.hasTrashFile = true;
}
if (target.metadata[Metadata.thumbDisabled] !== undefined) {
display.hasFailedThumb = true;
}
} }
if (target.type == FileType.file) { if (target.type == FileType.file) {
@ -290,12 +297,20 @@ export const getActionOpt = (
groupBs.enabled(GroupPermission.archive_task) && groupBs.enabled(GroupPermission.archive_task) &&
display.orCapability && display.orCapability &&
display.orCapability.enabled(NavigatorCapability.download_file); display.orCapability.enabled(NavigatorCapability.download_file);
display.showResetThumb =
display.hasFile &&
!display.hasFolder &&
display.hasFailedThumb &&
display.allUpdatable &&
display.orCapability &&
display.orCapability.enabled(NavigatorCapability.update_metadata);
display.showMore = display.showMore =
display.showVersionControl || display.showVersionControl ||
display.showManageShares || display.showManageShares ||
display.showCreateArchive || display.showCreateArchive ||
display.showDirectLinkManagement; display.showDirectLinkManagement ||
display.showResetThumb;
return display; return display;
}; };

View File

@ -1,18 +1,18 @@
import { useDrag, useDrop } from "react-dnd";
import { memo, useCallback, useContext, useEffect } from "react"; import { memo, useCallback, useContext, useEffect } from "react";
import { FileResponse, FileType } from "../../../api/explorer.ts"; import { useDrag, useDrop } from "react-dnd";
import { getEmptyImage } from "react-dnd-html5-backend"; import { getEmptyImage } from "react-dnd-html5-backend";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import { FileResponse, FileType } from "../../../api/explorer.ts";
import { setDragging } from "../../../redux/globalStateSlice.ts"; import { setDragging } from "../../../redux/globalStateSlice.ts";
import { getFileLinkedUri, mergeRefs } from "../../../util"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import { processDnd } from "../../../redux/thunks/file.ts"; import { processDnd } from "../../../redux/thunks/file.ts";
import { getFileLinkedUri, mergeRefs } from "../../../util";
import { FmIndexContext } from "../FmIndexContext.tsx";
import { FileManagerIndex } from "../FileManager.tsx";
import { FileBlockProps } from "../Explorer/Explorer.tsx";
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery"; import useMediaQuery from "@mui/material/useMediaQuery";
import CrUri, { Filesystem } from "../../../util/uri.ts"; import CrUri, { Filesystem } from "../../../util/uri.ts";
import { FileBlockProps } from "../Explorer/Explorer.tsx";
import { FileManagerIndex } from "../FileManager.tsx";
import { FmIndexContext } from "../FmIndexContext.tsx";
export interface DragItem { export interface DragItem {
target: FileResponse; target: FileResponse;

View File

@ -1,8 +1,8 @@
import { Box, Grid, Stack, styled, Typography } from "@mui/material";
import React, { useContext, useMemo } from "react"; import React, { useContext, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Box, Grid, Stack, styled, Typography } from "@mui/material";
import { useAppSelector } from "../../../../redux/hooks.ts";
import { FileResponse, FileType } from "../../../../api/explorer.ts"; import { FileResponse, FileType } from "../../../../api/explorer.ts";
import { useAppSelector } from "../../../../redux/hooks.ts";
import DndWrappedFile from "../../Dnd/DndWrappedFile.tsx"; import DndWrappedFile from "../../Dnd/DndWrappedFile.tsx";
import { FmIndexContext } from "../../FmIndexContext.tsx"; import { FmIndexContext } from "../../FmIndexContext.tsx";

View File

@ -0,0 +1,9 @@
import { SvgIcon, SvgIconProps } from "@mui/material";
export default function ImageArrowCounterclockwise(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M12 6.5a5.5 5.5 0 1 0-11 0a5.5 5.5 0 0 0 11 0m-8-3v.551a3.5 3.5 0 1 1-.187 4.691C3.55 8.427 3.811 8 4.221 8c.176 0 .339.085.46.213A2.5 2.5 0 1 0 4.5 5h1a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 1 0m14.75 2h-5.826a6.5 6.5 0 0 0-.422-1.5h6.248A3.25 3.25 0 0 1 22 7.25v11.5A3.25 3.25 0 0 1 18.75 22H7.25A3.25 3.25 0 0 1 4 18.75v-6.248c.474.198.977.34 1.5.422v5.826q.001.313.103.594l5.823-5.701a2.25 2.25 0 0 1 3.02-.116l.128.116l5.822 5.702q.102-.28.104-.595V7.25a1.75 1.75 0 0 0-1.75-1.75m.58 14.901l-5.805-5.686a.75.75 0 0 0-.966-.071l-.084.07l-5.807 5.687q.274.097.582.099h11.5c.203 0 .399-.035.58-.099M16.253 7.5a2.252 2.252 0 1 1 0 4.504a2.252 2.252 0 0 1 0-4.504m0 1.5a.752.752 0 1 0 0 1.504a.752.752 0 0 0 0-1.504" />
</SvgIcon>
);
}

View File

@ -127,6 +127,11 @@ export const fileManagerSlice = createSlice({
state[index].listViewColumns = action.payload; state[index].listViewColumns = action.payload;
}); });
}, },
removeThumbCache: (state, action: PayloadAction<FileManagerArgsBase<string[]>>) => {
action.payload.value.forEach((path) => {
state[action.payload.index].tree[path].thumb = undefined;
});
},
resetFileManager: (state, action: PayloadAction<number>) => { resetFileManager: (state, action: PayloadAction<number>) => {
state[action.payload].path = undefined; state[action.payload].path = undefined;
state[action.payload].previous_path = undefined; state[action.payload].previous_path = undefined;
@ -498,6 +503,7 @@ export const fileManagerSlice = createSlice({
export default fileManagerSlice.reducer; export default fileManagerSlice.reducer;
export const { export const {
removeThumbCache,
clearMultiSelectHovered, clearMultiSelectHovered,
setGalleryWidth, setGalleryWidth,
setListViewColumns, setListViewColumns,

View File

@ -47,6 +47,10 @@ const initialState: SiteConfigSlice = {
loaded: ConfigLoadState.NotLoaded, loaded: ConfigLoadState.NotLoaded,
config: {}, config: {},
}, },
thumb: {
loaded: ConfigLoadState.NotLoaded,
config: {},
},
}; };
export let Viewers: ExpandedViewerSetting = {}; export let Viewers: ExpandedViewerSetting = {};

View File

@ -39,7 +39,7 @@ import { loadingDebounceMs } from "../../constants";
import { defaultPath } from "../../hooks/useNavigation.tsx"; import { defaultPath } from "../../hooks/useNavigation.tsx";
import SessionManager, { UserSettings } from "../../session"; import SessionManager, { UserSettings } from "../../session";
import { addRecentUsedColor, addUsedTags } from "../../session/utils.ts"; import { addRecentUsedColor, addUsedTags } from "../../session/utils.ts";
import { getFileLinkedUri } from "../../util"; import { fileExtension, getFileLinkedUri } from "../../util";
import Boolset from "../../util/boolset.ts"; import Boolset from "../../util/boolset.ts";
import { canCopyMoveTo } from "../../util/permission.ts"; import { canCopyMoveTo } from "../../util/permission.ts";
import CrUri, { Filesystem } from "../../util/uri.ts"; import CrUri, { Filesystem } from "../../util/uri.ts";
@ -52,6 +52,7 @@ import {
ContextMenuTypes, ContextMenuTypes,
fileUpdated, fileUpdated,
removeSelected, removeSelected,
removeThumbCache,
removeTreeCache, removeTreeCache,
setContextMenu, setContextMenu,
setFileDeleteModal, setFileDeleteModal,
@ -71,12 +72,13 @@ import {
setSidebar, setSidebar,
updateLockConflicts, updateLockConflicts,
} from "../globalStateSlice.ts"; } from "../globalStateSlice.ts";
import { Viewers } from "../siteConfigSlice.ts"; import { ConfigLoadState, Viewers } from "../siteConfigSlice.ts";
import { AppThunk } from "../store.ts"; import { AppThunk } from "../store.ts";
import { confirmOperation, deleteConfirmation, renameForm, requestCreateNew, selectPath } from "./dialog.ts"; import { confirmOperation, deleteConfirmation, renameForm, requestCreateNew, selectPath } from "./dialog.ts";
import { downloadSingleFile } from "./download.ts"; import { downloadSingleFile } from "./download.ts";
import { navigateToPath, refreshFileList, updateUserCapacity } from "./filemanager.ts"; import { navigateToPath, refreshFileList, updateUserCapacity } from "./filemanager.ts";
import { queueLoadShareInfo } from "./share.ts"; import { queueLoadShareInfo } from "./share.ts";
import { loadSiteConfig } from "./site.ts";
import { openViewer, openViewers } from "./viewer.ts"; import { openViewer, openViewers } from "./viewer.ts";
const contextMenuCloseAnimationDelay = 250; const contextMenuCloseAnimationDelay = 250;
@ -114,6 +116,18 @@ export function loadFileThumb(index: number, file: FileResponse): AppThunk<Promi
dispatch(setThumbCache({ index, value: [file.path, thumb] })); dispatch(setThumbCache({ index, value: [file.path, thumb] }));
return thumb.url; return thumb.url;
} catch (e) { } catch (e) {
dispatch(
fileUpdated({
index,
value: [
{
file: { ...file, metadata: { ...file.metadata, [Metadata.thumbDisabled]: "" } },
oldPath: file.path,
includeMetadata: true,
},
],
}),
);
console.warn("Failed to load thumb", e); console.warn("Failed to load thumb", e);
return null; return null;
} }
@ -1160,6 +1174,62 @@ export function batchGetDirectLinks(index: number, files: FileResponse[]): AppTh
}; };
} }
export function resetThumbnails(files: FileResponse[]): AppThunk {
return async (dispatch, getState) => {
const thumbConfigLoaded = getState().siteConfig.thumb.loaded;
if (thumbConfigLoaded == ConfigLoadState.NotLoaded) {
await dispatch(loadSiteConfig("thumb"));
}
const thumbExts = getState().siteConfig.thumb.config.thumb_exts ?? [];
const targetFiles = files
.filter((f) => f.type == FileType.file)
.filter(
(f) => f.metadata?.[Metadata.thumbDisabled] !== undefined && thumbExts.includes(fileExtension(f.name) ?? ""),
);
if (targetFiles.length === 0) {
enqueueSnackbar({
message: i18next.t("application:fileManager.noFileCanResetThumbnail"),
preventDuplicate: true,
variant: "warning",
action: DefaultCloseAction,
});
return;
}
try {
// Re-enable thumbnails by removing the disable mark, and update local metadata/cache.
await dispatch(
patchFileMetadata(FileManagerIndex.main, targetFiles, [
{
key: Metadata.thumbDisabled,
remove: true,
},
]),
);
dispatch(
removeThumbCache({
index: FileManagerIndex.main,
value: targetFiles.map((file) => file.path),
}),
);
// refresh file list
dispatch(refreshFileList(FileManagerIndex.main));
// 成功信息
enqueueSnackbar({
message: i18next.t("application:fileManager.resetThumbnailRequested"),
variant: "success",
action: DefaultCloseAction,
});
} catch (_e) {
// Error snackbar is handled in send()
}
};
}
// Single file symbolic links might be invalid if original file is renamed by its owner, // Single file symbolic links might be invalid if original file is renamed by its owner,
// we need to refresh the symbolic links by getting the latest file list // we need to refresh the symbolic links by getting the latest file list
export function refreshSingleFileSymbolicLinks(file: FileResponse): AppThunk<Promise<FileResponse>> { export function refreshSingleFileSymbolicLinks(file: FileResponse): AppThunk<Promise<FileResponse>> {