feat: Add file color coding in file manager

- Files: blue (text-blue-400)
- Folders: red (text-red-400)
- Symlinks: green (text-green-400)
- Add toggle in User Profile settings
- Store preference in localStorage
- Support i18n (en, zh)
- Can be disabled via settings
- Matches dark theme color scheme
This commit is contained in:
ZacharyZcR
2025-11-09 15:17:11 +08:00
parent 9a0933bf2f
commit 417df31aa7
4 changed files with 62 additions and 12 deletions

View File

@@ -1479,6 +1479,8 @@
"local": "Local",
"external": "External (OIDC)",
"selectPreferredLanguage": "Select your preferred language for the interface",
"fileColorCoding": "File Color Coding",
"fileColorCodingDesc": "Color-code files by type: folders (red), files (blue), symlinks (green)",
"currentPassword": "Current Password",
"passwordChangedSuccess": "Password changed successfully! Please log in again.",
"failedToChangePassword": "Failed to change password. Please check your current password and try again."

View File

@@ -1442,6 +1442,8 @@
"local": "本地",
"external": "外部 (OIDC)",
"selectPreferredLanguage": "选择您的界面首选语言",
"fileColorCoding": "文件颜色编码",
"fileColorCodingDesc": "按类型对文件进行颜色编码:文件夹(红色)、文件(蓝色)、符号链接(绿色)",
"currentPassword": "当前密码",
"passwordChangedSuccess": "密码修改成功!请重新登录。",
"failedToChangePassword": "修改密码失败。请检查您当前的密码并重试。"

View File

@@ -96,15 +96,33 @@ interface FileManagerGridProps {
onNewFolder?: () => void;
}
const getFileIcon = (file: FileItem, viewMode: "grid" | "list" = "grid") => {
const iconClass = viewMode === "grid" ? "w-8 h-8" : "w-6 h-6";
const getFileTypeColor = (file: FileItem): string => {
const colorEnabled = localStorage.getItem("fileColorCoding") !== "false";
if (!colorEnabled) {
return "text-muted-foreground";
}
if (file.type === "directory") {
return <Folder className={`${iconClass} text-muted-foreground`} />;
return "text-red-400";
}
if (file.type === "link") {
return <FileSymlink className={`${iconClass} text-muted-foreground`} />;
return "text-green-400";
}
return "text-blue-400";
};
const getFileIcon = (file: FileItem, viewMode: "grid" | "list" = "grid") => {
const iconClass = viewMode === "grid" ? "w-8 h-8" : "w-6 h-6";
const colorClass = getFileTypeColor(file);
if (file.type === "directory") {
return <Folder className={`${iconClass} ${colorClass}`} />;
}
if (file.type === "link") {
return <FileSymlink className={`${iconClass} ${colorClass}`} />;
}
const ext = file.name.split(".").pop()?.toLowerCase();
@@ -113,30 +131,30 @@ const getFileIcon = (file: FileItem, viewMode: "grid" | "list" = "grid") => {
case "txt":
case "md":
case "readme":
return <FileText className={`${iconClass} text-muted-foreground`} />;
return <FileText className={`${iconClass} ${colorClass}`} />;
case "png":
case "jpg":
case "jpeg":
case "gif":
case "bmp":
case "svg":
return <FileImage className={`${iconClass} text-muted-foreground`} />;
return <FileImage className={`${iconClass} ${colorClass}`} />;
case "mp4":
case "avi":
case "mkv":
case "mov":
return <FileVideo className={`${iconClass} text-muted-foreground`} />;
return <FileVideo className={`${iconClass} ${colorClass}`} />;
case "mp3":
case "wav":
case "flac":
case "ogg":
return <FileAudio className={`${iconClass} text-muted-foreground`} />;
return <FileAudio className={`${iconClass} ${colorClass}`} />;
case "zip":
case "tar":
case "gz":
case "rar":
case "7z":
return <Archive className={`${iconClass} text-muted-foreground`} />;
return <Archive className={`${iconClass} ${colorClass}`} />;
case "js":
case "ts":
case "jsx":
@@ -150,7 +168,7 @@ const getFileIcon = (file: FileItem, viewMode: "grid" | "list" = "grid") => {
case "rb":
case "go":
case "rs":
return <Code className={`${iconClass} text-muted-foreground`} />;
return <Code className={`${iconClass} ${colorClass}`} />;
case "json":
case "xml":
case "yaml":
@@ -159,9 +177,9 @@ const getFileIcon = (file: FileItem, viewMode: "grid" | "list" = "grid") => {
case "ini":
case "conf":
case "config":
return <Settings className={`${iconClass} text-muted-foreground`} />;
return <Settings className={`${iconClass} ${colorClass}`} />;
default:
return <File className={`${iconClass} text-muted-foreground`} />;
return <File className={`${iconClass} ${colorClass}`} />;
}
};

View File

@@ -10,6 +10,7 @@ import {
TabsTrigger,
} from "@/components/ui/tabs.tsx";
import { Separator } from "@/components/ui/separator.tsx";
import { Switch } from "@/components/ui/switch.tsx";
import { User, Shield, AlertCircle } from "lucide-react";
import { TOTPSetup } from "@/ui/desktop/user/TOTPSetup.tsx";
import {
@@ -87,6 +88,9 @@ export function UserProfile({ isTopbarOpen = true }: UserProfileProps) {
const [deletePassword, setDeletePassword] = useState("");
const [deleteLoading, setDeleteLoading] = useState(false);
const [deleteError, setDeleteError] = useState<string | null>(null);
const [fileColorCoding, setFileColorCoding] = useState<boolean>(
localStorage.getItem("fileColorCoding") !== "false"
);
useEffect(() => {
fetchUserInfo();
@@ -128,6 +132,13 @@ export function UserProfile({ isTopbarOpen = true }: UserProfileProps) {
}
};
const handleFileColorCodingToggle = (enabled: boolean) => {
setFileColorCoding(enabled);
localStorage.setItem("fileColorCoding", enabled.toString());
// Trigger a re-render by dispatching a custom event
window.dispatchEvent(new Event("fileColorCodingChanged"));
};
const handleDeleteAccount = async (e: React.FormEvent) => {
e.preventDefault();
setDeleteLoading(true);
@@ -325,6 +336,23 @@ export function UserProfile({ isTopbarOpen = true }: UserProfileProps) {
</div>
</div>
<div className="mt-6 pt-6 border-t border-dark-border">
<div className="flex items-center justify-between">
<div>
<Label className="text-gray-300">
{t("profile.fileColorCoding")}
</Label>
<p className="text-sm text-gray-400 mt-1">
{t("profile.fileColorCodingDesc")}
</p>
</div>
<Switch
checked={fileColorCoding}
onCheckedChange={handleFileColorCodingToggle}
/>
</div>
</div>
<div className="mt-6 pt-6 border-t border-dark-border">
<div className="flex items-center justify-between">
<div>