mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
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:
parent
beb21a6dbc
commit
30290d774f
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@
|
||||||
"clickToRefresh": "CAPTCHAを再読み込み",
|
"clickToRefresh": "CAPTCHAを再読み込み",
|
||||||
"switchLanguage": "言語を切り替え"
|
"switchLanguage": "言語を切り替え"
|
||||||
},
|
},
|
||||||
|
"resetThumbnail": "壊れたサムネイルをリセット",
|
||||||
|
"resetThumbnailRequested": "サムネイルのリセットをリクエストしました。",
|
||||||
|
"noFileCanResetThumbnail": "サムネイルをリセットできるファイルがありません。",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"notBefore": "~より前",
|
"notBefore": "~より前",
|
||||||
"notAfter": "~より後",
|
"notAfter": "~より後",
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@
|
||||||
"photos": "사진",
|
"photos": "사진",
|
||||||
"music": "음악",
|
"music": "음악",
|
||||||
"documents": "문서",
|
"documents": "문서",
|
||||||
|
"resetThumbnail": "깨진 썸네일 재설정",
|
||||||
|
"resetThumbnailRequested": "썸네일 재설정이 요청되었습니다.",
|
||||||
|
"noFileCanResetThumbnail": "썸네일을 재설정할 수 있는 파일이 없습니다.",
|
||||||
"addATag": "태그 추가...",
|
"addATag": "태그 추가...",
|
||||||
"addTagDialog": {
|
"addTagDialog": {
|
||||||
"selectFolder": "폴더 선택",
|
"selectFolder": "폴더 선택",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,9 @@
|
||||||
"notNameOpOr": "Должны содержаться все ключевые слова",
|
"notNameOpOr": "Должны содержаться все ключевые слова",
|
||||||
"caseFolding": "Игнорировать регистр",
|
"caseFolding": "Игнорировать регистр",
|
||||||
"keywords": "Ключевые слова",
|
"keywords": "Ключевые слова",
|
||||||
|
"resetThumbnail": "Сбросить повреждённую миниатюру",
|
||||||
|
"resetThumbnailRequested": "Запрошен сброс миниатюры.",
|
||||||
|
"noFileCanResetThumbnail": "Нет файлов для сброса миниатюры.",
|
||||||
"fileNameKeywordsHelp": "Нажмите Enter для добавления ключевого слова",
|
"fileNameKeywordsHelp": "Нажмите Enter для добавления ключевого слова",
|
||||||
"advancedSearch": "Расширенный поиск",
|
"advancedSearch": "Расширенный поиск",
|
||||||
"searchFilesTitle": "Поиск файлов",
|
"searchFilesTitle": "Поиск файлов",
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,9 @@
|
||||||
"moreActions": "更多操作",
|
"moreActions": "更多操作",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"createArchive": "创建压缩文件",
|
"createArchive": "创建压缩文件",
|
||||||
|
"resetThumbnail": "重置失败的缩略图",
|
||||||
|
"resetThumbnailRequested": "已请求重置缩略图。",
|
||||||
|
"noFileCanResetThumbnail": "没有可重置缩略图的文件。",
|
||||||
"newFolder": "创建文件夹",
|
"newFolder": "创建文件夹",
|
||||||
"newFile": "创建文件",
|
"newFile": "创建文件",
|
||||||
"showFullPath": "显示路径",
|
"showFullPath": "显示路径",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@
|
||||||
"notBefore": "不早於",
|
"notBefore": "不早於",
|
||||||
"notAfter": "不晚於",
|
"notAfter": "不晚於",
|
||||||
"minimum": "最小",
|
"minimum": "最小",
|
||||||
|
"resetThumbnail": "重設失敗的縮圖",
|
||||||
|
"resetThumbnailRequested": "已請求重設縮圖。",
|
||||||
|
"noFileCanResetThumbnail": "沒有可重設縮圖的檔案。",
|
||||||
"maximum": "最大",
|
"maximum": "最大",
|
||||||
"fileSize": "檔案大小",
|
"fileSize": "檔案大小",
|
||||||
"searchBase": "搜尋路徑",
|
"searchBase": "搜尋路徑",
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 = {};
|
||||||
|
|
|
||||||
|
|
@ -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>> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue