返回 DeepSeek-Reasonix
WorkspaceFileIcon.tsx
根目录 / desktop / frontend / src / components / WorkspaceFileIcon.tsx
1 import type { CSSProperties } from "react";
2 import setiTheme from "../assets/file-icons/seti/vs-seti-icon-theme.json";
3
4 type IconDefinition = { fontCharacter?: string; fontColor?: string };
5 type IconAssociations = {
6 file?: string;
7 fileExtensions?: Record<string, string>;
8 fileNames?: Record<string, string>;
9 languageIds?: Record<string, string>;
10 };
11
12 const theme = setiTheme as typeof setiTheme & IconAssociations & {
13 iconDefinitions: Record<string, IconDefinition>;
14 light?: IconAssociations;
15 };
16
17 const languageIdByExtension: Record<string, string> = {
18 bash: "shellscript",
19 c: "c",
20 cc: "cpp",
21 cpp: "cpp",
22 cs: "csharp",
23 css: "css",
24 dart: "dart",
25 dockerfile: "dockerfile",
26 go: "go",
27 gradle: "groovy",
28 h: "c",
29 hpp: "cpp",
30 html: "html",
31 java: "java",
32 js: "javascript",
33 json: "json",
34 jsonl: "jsonl",
35 jsx: "javascriptreact",
36 kt: "kotlin",
37 kts: "kotlin",
38 less: "less",
39 lua: "lua",
40 md: "markdown",
41 markdown: "markdown",
42 php: "php",
43 properties: "properties",
44 ps1: "powershell",
45 py: "python",
46 rb: "ruby",
47 rs: "rust",
48 scss: "scss",
49 sh: "shellscript",
50 sql: "sql",
51 svelte: "svelte",
52 swift: "swift",
53 toml: "toml",
54 ts: "typescript",
55 tsx: "typescriptreact",
56 vue: "vue",
57 xml: "xml",
58 yaml: "yaml",
59 yml: "yaml",
60 };
61
62 function extensionCandidates(fileName: string): string[] {
63 const parts = fileName.split(".");
64 if (parts.length < 2) return [fileName];
65 const candidates: string[] = [];
66 for (let index = 1; index < parts.length; index += 1) {
67 candidates.push(parts.slice(index).join("."));
68 }
69 return candidates;
70 }
71
72 function associationFor(fileName: string, associations: IconAssociations | undefined): string | undefined {
73 if (!associations) return undefined;
74 const normalized = fileName.toLowerCase();
75 const exact = associations.fileNames?.[normalized];
76 if (exact) return exact;
77 for (const extension of extensionCandidates(normalized)) {
78 const icon = associations.fileExtensions?.[extension];
79 if (icon) return icon;
80 }
81 const finalExtension = normalized.includes(".") ? normalized.slice(normalized.lastIndexOf(".") + 1) : normalized;
82 const languageId = languageIdByExtension[finalExtension];
83 return (languageId ? associations.languageIds?.[languageId] : undefined) ?? associations.file;
84 }
85
86 function glyphFor(definition: IconDefinition | undefined): string {
87 const cssEscape = definition?.fontCharacter;
88 if (!cssEscape?.startsWith("\\")) return "";
89 const codePoint = Number.parseInt(cssEscape.slice(1), 16);
90 return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : "";
91 }
92
93 export function workspaceFileIcon(fileName: string): {
94 glyph: string;
95 darkColor: string;
96 lightColor: string;
97 } {
98 const darkId = associationFor(fileName, theme) ?? theme.file ?? "_default";
99 const lightId = associationFor(fileName, theme.light) ?? `${darkId}_light`;
100 const darkDefinition = theme.iconDefinitions[darkId] ?? theme.iconDefinitions._default;
101 const lightDefinition = theme.iconDefinitions[lightId] ?? darkDefinition;
102 return {
103 glyph: glyphFor(darkDefinition),
104 darkColor: darkDefinition?.fontColor ?? "currentColor",
105 lightColor: lightDefinition?.fontColor ?? darkDefinition?.fontColor ?? "currentColor",
106 };
107 }
108
109 export function WorkspaceFileIcon({ fileName, className = "" }: { fileName: string; className?: string }) {
110 const icon = workspaceFileIcon(fileName);
111 const style = {
112 "--workspace-file-icon-dark": icon.darkColor,
113 "--workspace-file-icon-light": icon.lightColor,
114 } as CSSProperties;
115 return (
116 <span
117 aria-hidden="true"
118 className={`workspace-file-icon${className ? ` ${className}` : ""}`}
119 style={style}
120 >
121 {icon.glyph}
122 </span>
123 );
124 }
125
125 lines Plain Text