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:
@@ -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."
|
||||
|
||||
@@ -1442,6 +1442,8 @@
|
||||
"local": "本地",
|
||||
"external": "外部 (OIDC)",
|
||||
"selectPreferredLanguage": "选择您的界面首选语言",
|
||||
"fileColorCoding": "文件颜色编码",
|
||||
"fileColorCodingDesc": "按类型对文件进行颜色编码:文件夹(红色)、文件(蓝色)、符号链接(绿色)",
|
||||
"currentPassword": "当前密码",
|
||||
"passwordChangedSuccess": "密码修改成功!请重新登录。",
|
||||
"failedToChangePassword": "修改密码失败。请检查您当前的密码并重试。"
|
||||
|
||||
@@ -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}`} />;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user