Преглед изворни кода

[DRAFT] feat(menu): use material icons for files and folders (#2739)

feat(menu): use material icons for files and folders
Dicha Zelianivan Arkana пре 8 месеци
родитељ
комит
1a376c262e
4 измењених фајлова са 67 додато и 19 уклоњено
  1. 7 0
      package-lock.json
  2. 1 0
      package.json
  3. 18 1
      src/core/webview/ClineProvider.ts
  4. 41 18
      webview-ui/src/components/chat/ContextMenu.tsx

+ 7 - 0
package-lock.json

@@ -61,6 +61,7 @@
 				"tmp": "^0.2.3",
 				"tree-sitter-wasms": "^0.1.11",
 				"turndown": "^7.2.0",
+				"vscode-material-icons": "^0.1.1",
 				"web-tree-sitter": "^0.22.6",
 				"zod": "^3.23.8"
 			},
@@ -21550,6 +21551,12 @@
 				"node": ">= 0.8"
 			}
 		},
+		"node_modules/vscode-material-icons": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/vscode-material-icons/-/vscode-material-icons-0.1.1.tgz",
+			"integrity": "sha512-GsoEEF8Tbb0yUFQ6N6FPvh11kFkL9F95x0FkKlbbfRQN9eFms67h+L3t6b9cUv58dSn2gu8kEhNfoESVCrz4ag==",
+			"license": "MIT"
+		},
 		"node_modules/walker": {
 			"version": "1.0.8",
 			"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",

+ 1 - 0
package.json

@@ -453,6 +453,7 @@
 		"tmp": "^0.2.3",
 		"tree-sitter-wasms": "^0.1.11",
 		"turndown": "^7.2.0",
+		"vscode-material-icons": "^0.1.1",
 		"web-tree-sitter": "^0.22.6",
 		"zod": "^3.23.8"
 	},

+ 18 - 1
src/core/webview/ClineProvider.ts

@@ -622,6 +622,13 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			"codicon.css",
 		])
 
+		const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [
+			"node_modules",
+			"vscode-material-icons",
+			"generated",
+			"icons",
+		])
+
 		const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
 
 		const file = "src/index.tsx"
@@ -656,7 +663,8 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 					<link rel="stylesheet" type="text/css" href="${stylesUri}">
 					<link href="${codiconsUri}" rel="stylesheet" />
 					<script nonce="${nonce}">
-						window.IMAGES_BASE_URI = "${imagesUri}"					
+						window.IMAGES_BASE_URI = "${imagesUri}"
+						window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
 					</script>
 					<title>Roo Code</title>
 				</head>
@@ -706,6 +714,14 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			"codicon.css",
 		])
 
+		// The material icons from the React build output
+		const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [
+			"node_modules",
+			"vscode-material-icons",
+			"generated",
+			"icons",
+		])
+
 		const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
 
 		// const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js"))
@@ -742,6 +758,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 			<link href="${codiconsUri}" rel="stylesheet" />
 			<script nonce="${nonce}">
 				window.IMAGES_BASE_URI = "${imagesUri}"
+				window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
 			</script>
             <title>Roo Code</title>
           </head>

+ 41 - 18
webview-ui/src/components/chat/ContextMenu.tsx

@@ -1,4 +1,5 @@
-import React, { useEffect, useMemo, useRef } from "react"
+import React, { useEffect, useMemo, useRef, useState } from "react"
+import { getIconForFilePath, getIconUrlByName, getIconForDirectoryPath } from "vscode-material-icons"
 import {
 	ContextMenuOptionType,
 	ContextMenuQueryItem,
@@ -35,6 +36,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
 	loading = false,
 	dynamicSearchResults = [],
 }) => {
+	const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("")
 	const menuRef = useRef<HTMLDivElement>(null)
 
 	const filteredOptions = useMemo(() => {
@@ -57,6 +59,12 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
 		}
 	}, [selectedIndex])
 
+	// get the icons base uri on mount
+	useEffect(() => {
+		const w = window as any
+		setMaterialIconsBaseUri(w.MATERIAL_ICONS_BASE_URI)
+	}, [])
+
 	const renderOptionContent = (option: ContextMenuQueryItem) => {
 		switch (option.type) {
 			case ContextMenuOptionType.Mode:
@@ -175,6 +183,15 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
 		}
 	}
 
+	const getMaterialIconForOption = (option: ContextMenuQueryItem): string => {
+		// only take the last part of the path to handle both file and folder icons
+		// since material-icons have specific folder icons, we use them if available
+		const name = option.value?.split("/").filter(Boolean).at(-1) ?? ""
+		const iconName =
+			option.type === ContextMenuOptionType.Folder ? getIconForDirectoryPath(name) : getIconForFilePath(name)
+		return getIconUrlByName(iconName, materialIconsBaseUri)
+	}
+
 	const isOptionSelectable = (option: ContextMenuQueryItem): boolean => {
 		return option.type !== ContextMenuOptionType.NoResults && option.type !== ContextMenuOptionType.URL
 	}
@@ -231,17 +248,35 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
 									overflow: "hidden",
 									paddingTop: 0,
 								}}>
-								{option.type !== ContextMenuOptionType.Mode && getIconForOption(option) && (
-									<i
-										className={`codicon codicon-${getIconForOption(option)}`}
+								{(option.type === ContextMenuOptionType.File ||
+									option.type === ContextMenuOptionType.Folder ||
+									option.type === ContextMenuOptionType.OpenedFile) && (
+									<img
+										src={getMaterialIconForOption(option)}
+										alt="Mode"
 										style={{
 											marginRight: "6px",
 											flexShrink: 0,
-											fontSize: "14px",
-											marginTop: 0,
+											width: "16px",
+											height: "16px",
 										}}
 									/>
 								)}
+								{option.type !== ContextMenuOptionType.Mode &&
+									option.type !== ContextMenuOptionType.File &&
+									option.type !== ContextMenuOptionType.Folder &&
+									option.type !== ContextMenuOptionType.OpenedFile &&
+									getIconForOption(option) && (
+										<i
+											className={`codicon codicon-${getIconForOption(option)}`}
+											style={{
+												marginRight: "6px",
+												flexShrink: 0,
+												fontSize: "14px",
+												marginTop: 0,
+											}}
+										/>
+									)}
 								{renderOptionContent(option)}
 							</div>
 							{(option.type === ContextMenuOptionType.File ||
@@ -253,18 +288,6 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
 										style={{ fontSize: "10px", flexShrink: 0, marginLeft: 8 }}
 									/>
 								)}
-							{(option.type === ContextMenuOptionType.Problems ||
-								option.type === ContextMenuOptionType.Terminal ||
-								((option.type === ContextMenuOptionType.File ||
-									option.type === ContextMenuOptionType.Folder ||
-									option.type === ContextMenuOptionType.OpenedFile ||
-									option.type === ContextMenuOptionType.Git) &&
-									option.value)) && (
-								<i
-									className="codicon codicon-add"
-									style={{ fontSize: "10px", flexShrink: 0, marginLeft: 8 }}
-								/>
-							)}
 						</div>
 					))
 				) : (