From 6e69f8960dae7580411776011e74d8bcb1e7c730 Mon Sep 17 00:00:00 2001 From: elianiva <51877647+elianiva@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:58:10 +0700 Subject: [PATCH] feat(menu): use material icons for files and folders --- package-lock.json | 7 +++ package.json | 1 + src/core/webview/ClineProvider.ts | 17 ++++++ .../src/components/chat/ContextMenu.tsx | 59 +++++++++++++------ 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51ad221702..5b8d449ee3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,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" }, @@ -21570,6 +21571,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", diff --git a/package.json b/package.json index 720efbea71..641470c649 100644 --- a/package.json +++ b/package.json @@ -454,6 +454,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" }, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index dba450f9ae..b336ab3594 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -621,6 +621,13 @@ export class ClineProvider extends EventEmitter 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,6 +663,7 @@ export class ClineProvider extends EventEmitter implements Roo Code @@ -705,6 +713,14 @@ export class ClineProvider extends EventEmitter 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")) @@ -741,6 +757,7 @@ export class ClineProvider extends EventEmitter implements Roo Code diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index a353a97be6..415eadef4f 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/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, @@ -33,6 +34,7 @@ const ContextMenu: React.FC = ({ loading = false, dynamicSearchResults = [], }) => { + const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("") const menuRef = useRef(null) const filteredOptions = useMemo(() => { @@ -55,6 +57,12 @@ const ContextMenu: React.FC = ({ } }, [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: @@ -173,6 +181,15 @@ const ContextMenu: React.FC = ({ } } + 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 } @@ -229,17 +246,35 @@ const ContextMenu: React.FC = ({ overflow: "hidden", paddingTop: 0, }}> - {option.type !== ContextMenuOptionType.Mode && getIconForOption(option) && ( - )} + {option.type !== ContextMenuOptionType.Mode && + option.type !== ContextMenuOptionType.File && + option.type !== ContextMenuOptionType.Folder && + option.type !== ContextMenuOptionType.OpenedFile && + getIconForOption(option) && ( + + )} {renderOptionContent(option)} {(option.type === ContextMenuOptionType.File || @@ -251,18 +286,6 @@ const ContextMenu: React.FC = ({ 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)) && ( - - )} )) ) : (