diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json
index 5a5c691..80d3bad 100644
--- a/public/locales/en-US/dashboard.json
+++ b/public/locales/en-US/dashboard.json
@@ -112,6 +112,8 @@
"retryDelayDes": "Initial delay time (seconds) for task retries."
},
"settings": {
+ "imageUrl": "Image URL",
+ "iconifyName": "Iconify icon name",
"oidc": "OpenID Connect (OIDC)",
"oidcDes": "OpenID Connect (OIDC) is an open authentication protocol for identity verification between different systems. After creating an application in a third-party identity platform, please add <0>{{url}}0> to the \"Redirect URI\" field. For more details, please refer to the <1>documentation1>.",
"clientID": "Client ID",
diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json
index d3dcd26..de0d1bc 100644
--- a/public/locales/ja-JP/dashboard.json
+++ b/public/locales/ja-JP/dashboard.json
@@ -112,6 +112,8 @@
"retryDelayDes": "タスク再試行の初期遅延時間(秒)"
},
"settings": {
+ "imageUrl": "画像 URL",
+ "iconifyName": "Iconify アイコン名",
"oidc": "OpenID Connect (OIDC)",
"oidcDes": "OpenID Connect (OIDC) は、異なるシステム間で認証を行うためのオープンな認証プロトコルです。サードパーティーのアイデンティティプラットフォームでアプリケーションを作成したら、<0>{{url}}0> を「リダイレクト URI」に追加してください。詳細は<1>公式ドキュメント1>を参照してください。",
"clientID": "クライアント ID",
diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json
index 9b600a5..914ae6e 100644
--- a/public/locales/zh-CN/dashboard.json
+++ b/public/locales/zh-CN/dashboard.json
@@ -112,6 +112,8 @@
"retryDelayDes": "任务重试的初始延迟时间(秒)。"
},
"settings": {
+ "imageUrl": "图片 URL",
+ "iconifyName": "Iconify 图标名",
"oidc": "OpenID Connect (OIDC)",
"oidcDes": "OpenID Connect (OIDC) 是一种开放的认证协议,用于在不同的系统之间进行身份验证。在第三方身份平台中创建应用后,请将 <0>{{url}}0> 添加到 “重定向 URI” 中。详情请参考 <1>官方文档1>。",
"clientID": "客户端 ID",
diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json
index 18a2856..670b315 100644
--- a/public/locales/zh-TW/dashboard.json
+++ b/public/locales/zh-TW/dashboard.json
@@ -112,6 +112,8 @@
"retryDelayDes": "任務重試的初始延遲時間(秒)。"
},
"settings": {
+ "imageUrl": "圖片 URL",
+ "iconifyName": "Iconify 圖標名",
"oidc": "OpenID Connect (OIDC)",
"oidcDes": "OpenID Connect (OIDC) 是一種開放的認證身份驗證協定,用於在不同的系統之間進行身份驗證。在第三方身份平台中創建應用後,請將 <0>{{url}}0> 添加到 “重定向 URI” 中。詳細請參閱 <1>官方文檔1>。",
"clientID": "客戶端 ID",
diff --git a/src/component/Admin/FileSystem/Icons/FileIconList.tsx b/src/component/Admin/FileSystem/Icons/FileIconList.tsx
index da101d1..8a50f61 100644
--- a/src/component/Admin/FileSystem/Icons/FileIconList.tsx
+++ b/src/component/Admin/FileSystem/Icons/FileIconList.tsx
@@ -1,14 +1,28 @@
-import { Box, IconButton, Table, TableBody, TableContainer, TableHead, TableRow } from "@mui/material";
-import { useTheme } from "@mui/material/styles";
+import { Icon } from "@iconify/react/dist/iconify.js";
+import {
+ Box,
+ IconButton,
+ InputAdornment,
+ ListItemText,
+ Table,
+ TableBody,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import { styled, useTheme } from "@mui/material/styles";
import { memo, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
DenseFilledTextField,
+ DenseSelect,
NoWrapCell,
NoWrapTableCell,
SecondaryButton,
StyledTableContainerPaper,
} from "../../../Common/StyledComponents.tsx";
+import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
import { builtInIcons, FileTypeIconSetting } from "../../../FileManager/Explorer/FileTypeIcon.tsx";
import Add from "../../../Icons/Add.tsx";
import Dismiss from "../../../Icons/Dismiss.tsx";
@@ -19,6 +33,20 @@ export interface FileIconListProps {
onChange: (value: string) => void;
}
+export enum IconType {
+ Image = "imageUrl",
+ Iconify = "iconifyName",
+}
+
+const StyledDenseSelect = styled(DenseSelect)(() => ({
+ "& .MuiFilledInput-input": {
+ "&:focus": {
+ backgroundColor: "initial",
+ },
+ },
+ backgroundColor: "initial",
+}));
+
const IconPreview = ({ icon }: { icon: FileTypeIconSetting }) => {
const theme = useTheme();
const IconComponent = useMemo(() => {
@@ -45,6 +73,12 @@ const IconPreview = ({ icon }: { icon: FileTypeIconSetting }) => {
/>
);
}
+
+ // Handle iconify icons
+ if (icon.iconify) {
+ return ;
+ }
+
return (
{
const [inputCache, setInputCache] = useState<{
[key: number]: string | undefined;
}>({});
+ const [iconUrlCache, setIconUrlCache] = useState<{
+ [key: number]: string | undefined;
+ }>({});
+ const [iconTypeCache, setIconTypeCache] = useState<{
+ [key: number]: IconType | undefined;
+ }>({});
+
return (
{configParsed?.length > 0 && (
@@ -71,123 +112,215 @@ const FileIconList = memo(({ config, onChange }: FileIconListProps) => {
{t("settings.icon")}
- {t("settings.iconUrl")}
+ {t("settings.iconUrl")}
{t("settings.iconColor")}
{t("settings.iconColorDark")}
- {t("settings.exts")}
+ {t("settings.exts")}
- {configParsed.map((r, i) => (
-
-
-
-
-
- {!r.icon ? (
+ {configParsed.map((r, i) => {
+ const currentIconType =
+ iconTypeCache[i] ?? (r.img ? IconType.Image : r.iconify ? IconType.Iconify : IconType.Image);
+ const currentIconUrl =
+ iconUrlCache[i] ?? (currentIconType === IconType.Image ? r.img : r.iconify) ?? "";
+
+ return (
+
+
+
+
+
+ {!r.icon ? (
+ {
+ const newConfig = [...configParsed];
+ const updatedItem = { ...r };
+
+ if (currentIconType === IconType.Image) {
+ updatedItem.img = currentIconUrl;
+ updatedItem.iconify = undefined;
+ } else {
+ updatedItem.iconify = currentIconUrl;
+ updatedItem.img = undefined;
+ }
+
+ newConfig[i] = updatedItem;
+ onChange(JSON.stringify(newConfig));
+
+ setIconUrlCache({
+ ...iconUrlCache,
+ [i]: undefined,
+ });
+ }}
+ onChange={(e) =>
+ setIconUrlCache({
+ ...iconUrlCache,
+ [i]: e.target.value,
+ })
+ }
+ slotProps={{
+ input: {
+ startAdornment: (
+
+ {
+ const newType = e.target.value as IconType;
+ setIconTypeCache({
+ ...iconTypeCache,
+ [i]: newType,
+ });
+
+ // Clear the URL cache when switching types
+ setIconUrlCache({
+ ...iconUrlCache,
+ [i]: "",
+ });
+
+ // Update the config immediately
+ const newConfig = [...configParsed];
+ const updatedItem = { ...r };
+
+ if (newType === IconType.Image) {
+ updatedItem.img = "";
+ updatedItem.iconify = undefined;
+ } else {
+ updatedItem.iconify = "";
+ updatedItem.img = undefined;
+ }
+
+ newConfig[i] = updatedItem;
+ onChange(JSON.stringify(newConfig));
+ }}
+ renderValue={(value) => (
+ {t(`settings.${value}`)}
+ )}
+ size={"small"}
+ variant="filled"
+ >
+
+
+ {t(`settings.${IconType.Image}`)}
+
+
+
+
+ {t(`settings.${IconType.Iconify}`)}
+
+
+
+
+ ),
+ },
+ }}
+ />
+ ) : (
+ t("settings.builtinIcon")
+ )}
+
+
+ {!r.icon && !r.iconify ? (
+ "-"
+ ) : (
+
+ onChange(
+ JSON.stringify([
+ ...configParsed.slice(0, i),
+ {
+ ...r,
+ color: color,
+ },
+ ...configParsed.slice(i + 1),
+ ]),
+ )
+ }
+ />
+ )}
+
+
+ {!r.icon && !r.iconify ? (
+ "-"
+ ) : (
+
+ onChange(
+ JSON.stringify([
+ ...configParsed.slice(0, i),
+ {
+ ...r,
+ color_dark: color,
+ },
+ ...configParsed.slice(i + 1),
+ ]),
+ )
+ }
+ />
+ )}
+
+
{
+ onChange(
+ JSON.stringify([
+ ...configParsed.slice(0, i),
+ {
+ ...r,
+ exts: inputCache[i]?.split(",") ?? r.exts,
+ },
+ ...configParsed.slice(i + 1),
+ ]),
+ );
+ setInputCache({
+ ...inputCache,
+ [i]: undefined,
+ });
+ }}
onChange={(e) =>
- onChange(
- JSON.stringify([
- ...configParsed.slice(0, i),
- { ...r, img: e.target.value as string },
- ...configParsed.slice(i + 1),
- ]),
- )
+ setInputCache({
+ ...inputCache,
+ [i]: e.target.value,
+ })
}
/>
- ) : (
- t("settings.builtinIcon")
- )}
-
-
- {!r.icon ? (
- "-"
- ) : (
-
- onChange(
- JSON.stringify([
- ...configParsed.slice(0, i),
- {
- ...r,
- color: color,
- },
- ...configParsed.slice(i + 1),
- ]),
- )
- }
- />
- )}
-
-
- {!r.icon ? (
- "-"
- ) : (
-
- onChange(
- JSON.stringify([
- ...configParsed.slice(0, i),
- {
- ...r,
- color_dark: color,
- },
- ...configParsed.slice(i + 1),
- ]),
- )
- }
- />
- )}
-
-
- {
- onChange(
- JSON.stringify([
- ...configParsed.slice(0, i),
- {
- ...r,
- exts: inputCache[i]?.split(",") ?? r.exts,
- },
- ...configParsed.slice(i + 1),
- ]),
- );
- setInputCache({
- ...inputCache,
- [i]: undefined,
- });
- }}
- onChange={(e) =>
- setInputCache({
- ...inputCache,
- [i]: e.target.value,
- })
- }
- />
-
-
- {!r.icon && (
- onChange(JSON.stringify(configParsed.filter((_, index) => index !== i)))}
- size={"small"}
- >
-
-
- )}
-
-
- ))}
+
+
+ {!r.icon && (
+ onChange(JSON.stringify(configParsed.filter((_, index) => index !== i)))}
+ size={"small"}
+ >
+
+
+ )}
+
+
+ );
+ })}
diff --git a/src/component/Common/SizeInput.tsx b/src/component/Common/SizeInput.tsx
index 1d1f7a9..3ac98bf 100644
--- a/src/component/Common/SizeInput.tsx
+++ b/src/component/Common/SizeInput.tsx
@@ -42,7 +42,7 @@ export interface SizeInputProps {
allowZero?: boolean;
}
-const StyledSelect = styled(Select)(() => ({
+export const StyledSelect = styled(Select)(() => ({
"& .MuiFilledInput-input": {
paddingTop: "5px",
"&:focus": {
diff --git a/src/component/FileManager/Explorer/FileTypeIcon.tsx b/src/component/FileManager/Explorer/FileTypeIcon.tsx
index 1778de4..9d0c5fa 100644
--- a/src/component/FileManager/Explorer/FileTypeIcon.tsx
+++ b/src/component/FileManager/Explorer/FileTypeIcon.tsx
@@ -1,3 +1,4 @@
+import { Icon as IconifyIcon } from "@iconify/react/dist/iconify.js";
import { Android } from "@mui/icons-material";
import { Box, SvgIconProps, useTheme } from "@mui/material";
import SvgIcon from "@mui/material/SvgIcon/SvgIcon";
@@ -41,6 +42,7 @@ export interface FileTypeIconProps extends SvgIconProps {
export interface FileTypeIconSetting {
exts: string[];
icon?: string;
+ iconify?: string;
img?: string;
color?: string;
color_dark?: string;
@@ -87,6 +89,15 @@ interface TypeIcon {
reverseDarkMode?: boolean;
}
+interface IconComponentProps {
+ icon?: typeof SvgIcon | ((props: SvgIconProps) => JSX.Element);
+ color?: string;
+ color_dark?: string;
+ isDefault?: boolean;
+ img?: string;
+ iconify?: string;
+}
+
const FileTypeIcon = ({
name,
fileType,
@@ -99,7 +110,7 @@ const FileTypeIcon = ({
}: FileTypeIconProps) => {
const theme = useTheme();
const iconOptions = useAppSelector((state) => state.siteConfig.explorer.typed?.icons) as ExpandedIconSettings;
- const IconComponent = useMemo(() => {
+ const IconComponent: IconComponentProps = useMemo(() => {
if (fileType === 1) {
return notLoaded ? { icon: FolderOutlined } : { icon: Folder };
}
@@ -109,7 +120,7 @@ const FileTypeIcon = ({
if (fileSuffix && iconOptions) {
const options = iconOptions[fileSuffix];
if (options) {
- const { icon, color, color_dark, img } = options;
+ const { icon, color, color_dark, img, iconify } = options;
if (icon) {
return {
icon: builtInIcons[icon],
@@ -120,6 +131,12 @@ const FileTypeIcon = ({
return {
img,
};
+ } else if (iconify) {
+ return {
+ iconify,
+ color,
+ color_dark,
+ };
}
}
}
@@ -152,6 +169,21 @@ const FileTypeIcon = ({
{...rest}
/>
);
+ } else if (IconComponent.iconify) {
+ return (
+ //@ts-ignore
+
+ );
} else {
return (
//@ts-ignore