diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b7d10eaa..8f87e224 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -714,9 +714,10 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
- '@xmldom/xmldom@0.8.11':
- resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
+ '@xmldom/xmldom@0.8.12':
+ resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
engines: {node: '>=10.0.0'}
+ deprecated: this version has critical issues, please update to the latest version
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -866,6 +867,9 @@ packages:
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+ brace-expansion@2.0.3:
+ resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
+
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
@@ -1374,11 +1378,12 @@ packages:
glob@10.5.0:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
@@ -1410,8 +1415,8 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
- handlebars@4.7.8:
- resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+ handlebars@4.7.9:
+ resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
engines: {node: '>=0.4.7'}
hasBin: true
@@ -1682,8 +1687,8 @@ packages:
lodash.union@4.6.0:
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
- lodash@4.17.21:
- resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ lodash@4.18.1:
+ resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
@@ -1753,6 +1758,10 @@ packages:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
+ minimatch@5.1.9:
+ resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
+ engines: {node: '>=10'}
+
minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -2207,6 +2216,7 @@ packages:
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
+ deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
temp-file@3.4.0:
resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
@@ -2675,7 +2685,7 @@ snapshots:
dependencies:
debug: 4.4.3
fs-extra: 9.1.0
- lodash: 4.17.21
+ lodash: 4.18.1
tmp-promise: 3.0.3
transitivePeerDependencies:
- supports-color
@@ -3032,7 +3042,7 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
- '@xmldom/xmldom@0.8.11': {}
+ '@xmldom/xmldom@0.8.12': {}
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
@@ -3076,7 +3086,7 @@ snapshots:
didyoumean: 1.2.2
inquirer: 7.3.3
json-fixer: 1.6.15
- lodash: 4.17.21
+ lodash: 4.18.1
node-fetch: 2.7.0
pify: 5.0.0
yargs: 15.4.1
@@ -3198,7 +3208,7 @@ snapshots:
auto-changelog@2.5.0:
dependencies:
commander: 7.2.0
- handlebars: 4.7.8
+ handlebars: 4.7.9
import-cwd: 3.0.0
node-fetch: 2.7.0
parse-github-url: 1.0.3
@@ -3234,6 +3244,10 @@ snapshots:
dependencies:
balanced-match: 1.0.2
+ brace-expansion@2.0.3:
+ dependencies:
+ balanced-match: 1.0.2
+
braces@3.0.3:
dependencies:
fill-range: 7.1.1
@@ -3954,7 +3968,7 @@ snapshots:
graphemer@1.4.0: {}
- handlebars@4.7.8:
+ handlebars@4.7.9:
dependencies:
minimist: 1.2.8
neo-async: 2.6.2
@@ -4058,7 +4072,7 @@ snapshots:
cli-width: 3.0.0
external-editor: 3.1.0
figures: 3.2.0
- lodash: 4.17.21
+ lodash: 4.18.1
mute-stream: 0.0.8
run-async: 2.4.1
rxjs: 6.6.7
@@ -4225,7 +4239,7 @@ snapshots:
lodash.union@4.6.0: {}
- lodash@4.17.21: {}
+ lodash@4.18.1: {}
lowercase-keys@2.0.0: {}
@@ -4275,6 +4289,10 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
+ minimatch@5.1.9:
+ dependencies:
+ brace-expansion: 2.0.3
+
minimatch@9.0.3:
dependencies:
brace-expansion: 2.0.2
@@ -4417,7 +4435,7 @@ snapshots:
plist@3.1.0:
dependencies:
- '@xmldom/xmldom': 0.8.11
+ '@xmldom/xmldom': 0.8.12
base64-js: 1.5.1
xmlbuilder: 15.1.1
@@ -4479,7 +4497,7 @@ snapshots:
readdir-glob@1.1.3:
dependencies:
- minimatch: 5.1.6
+ minimatch: 5.1.9
readdirp@4.1.2: {}
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 613331e8..9f58292b 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -58,9 +58,12 @@
-
+
+
+
+
@@ -175,9 +181,12 @@
-
+
+
+
+
diff --git a/src/renderer/modules/connectionManagement.ts b/src/renderer/modules/connectionManagement.ts
index 149fd456..2b4e1380 100644
--- a/src/renderer/modules/connectionManagement.ts
+++ b/src/renderer/modules/connectionManagement.ts
@@ -1948,6 +1948,17 @@ export async function loadSidebarConnections(): Promise
{
// Category filter
const selectedCategory = categoryFilter?.value || "";
+ // Update filter button indicator and one-click clear button visibility
+ const hasDropdownFilters = !!(selectedEnvironment || selectedAuthType || selectedCategory);
+ const connectionsFilterBtn = document.getElementById("connections-filter-btn");
+ if (connectionsFilterBtn) {
+ connectionsFilterBtn.classList.toggle("has-active-filters", hasDropdownFilters);
+ }
+ const connectionsFilterClearBtn = document.getElementById("connections-filter-clear-btn") as HTMLButtonElement | null;
+ if (connectionsFilterClearBtn) {
+ connectionsFilterClearBtn.style.display = hasDropdownFilters ? "flex" : "none";
+ }
+
// Apply filters
const filteredConnections = connections.filter((conn: DataverseConnection) => {
// Search filter (name or URL)
@@ -2241,3 +2252,30 @@ export async function loadSidebarConnections(): Promise {
logError("Failed to load connections", error);
}
}
+
+/**
+ * Clear only the dropdown filter selections (environment, auth type, category) for connections.
+ * Leaves the search input unchanged.
+ */
+export function clearConnectionDropdownFilters(): void {
+ // Reset environment filter
+ const environmentFilter = document.getElementById("connections-environment-filter") as HTMLSelectElement | null;
+ if (environmentFilter) {
+ environmentFilter.value = "";
+ }
+
+ // Reset auth type filter
+ const authFilter = document.getElementById("connections-auth-filter") as HTMLSelectElement | null;
+ if (authFilter) {
+ authFilter.value = "";
+ }
+
+ // Reset category filter
+ const categoryFilter = document.getElementById("connections-category-filter") as HTMLSelectElement | null;
+ if (categoryFilter) {
+ categoryFilter.value = "";
+ }
+
+ // Reload the connections list to reflect the cleared filters
+ loadSidebarConnections();
+}
diff --git a/src/renderer/modules/homepageManagement.ts b/src/renderer/modules/homepageManagement.ts
index 26e36cd3..c91f1960 100644
--- a/src/renderer/modules/homepageManagement.ts
+++ b/src/renderer/modules/homepageManagement.ts
@@ -5,6 +5,7 @@
import type { LastUsedToolEntry } from "../../common/types";
import { applyToolIconMasks, generateToolIconHtml } from "../utils/toolIconResolver";
+import { filterMarketplaceByNew } from "./marketplaceManagement";
import { switchSidebar } from "./sidebarManagement";
import { launchTool, LaunchToolOptions } from "./toolManagement";
import { logError } from "../../common/logger";
@@ -113,6 +114,7 @@ async function loadNewToolsNotification(): Promise {
notificationBar.style.cursor = "pointer";
notificationBar.onclick = () => {
switchSidebar("marketplace");
+ filterMarketplaceByNew();
};
} else if (notificationBar) {
notificationBar.style.display = "none";
diff --git a/src/renderer/modules/initialization.ts b/src/renderer/modules/initialization.ts
index 34b24a22..7e8ab23f 100644
--- a/src/renderer/modules/initialization.ts
+++ b/src/renderer/modules/initialization.ts
@@ -17,6 +17,7 @@ import {
import { setupAutoUpdateListeners } from "./autoUpdateManagement";
import { initializeBrowserWindowModals } from "./browserWindowModals";
import {
+ clearConnectionDropdownFilters,
exportConnections,
handleReauthentication,
importConnections,
@@ -27,7 +28,7 @@ import {
} from "./connectionManagement";
import { initializeGlobalSearch } from "./globalSearchManagement";
import { loadHomepageData, setupHomepageActions } from "./homepageManagement";
-import { handleProtocolInstallToolRequest, loadMarketplace, loadToolsLibrary } from "./marketplaceManagement";
+import { clearMarketplaceDropdownFilters, handleProtocolInstallToolRequest, loadMarketplace, loadToolsLibrary } from "./marketplaceManagement";
import { closeModal, openModal } from "./modalManagement";
import { setDefaultNotificationDuration, showPPTBNotification } from "./notifications";
import { openSettingsTab } from "./settingsManagement";
@@ -35,7 +36,7 @@ import { switchSidebar } from "./sidebarManagement";
import { handleTerminalClosed, handleTerminalCommandCompleted, handleTerminalCreated, handleTerminalError, handleTerminalOutput, setupTerminalPanel } from "./terminalManagement";
import { applyDebugMenuVisibility, applyTerminalFont, applyTheme } from "./themeManagement";
import { applyAppearanceSettings, closeAllTools, initializeTabScrollButtons, launchTool, restoreSession, setupKeyboardShortcuts, showHomePage } from "./toolManagement";
-import { loadSidebarTools } from "./toolsSidebarManagement";
+import { clearInstalledToolsDropdownFilters, loadSidebarTools } from "./toolsSidebarManagement";
/**
* Initialize the application
@@ -819,6 +820,15 @@ function setupFilterDropdownToggles(): void {
});
}
+ // Tools filter clear button
+ const toolsFilterClearBtn = document.getElementById("tools-filter-clear-btn");
+ if (toolsFilterClearBtn) {
+ toolsFilterClearBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ clearInstalledToolsDropdownFilters();
+ });
+ }
+
// Connections filter dropdown
const connectionsFilterBtn = document.getElementById("connections-filter-btn");
const connectionsFilterDropdown = document.getElementById("connections-filter-dropdown");
@@ -840,6 +850,15 @@ function setupFilterDropdownToggles(): void {
});
}
+ // Connections filter clear button
+ const connectionsFilterClearBtn = document.getElementById("connections-filter-clear-btn");
+ if (connectionsFilterClearBtn) {
+ connectionsFilterClearBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ clearConnectionDropdownFilters();
+ });
+ }
+
// Marketplace filter dropdown
const marketplaceFilterBtn = document.getElementById("marketplace-filter-btn");
const marketplaceFilterDropdown = document.getElementById("marketplace-filter-dropdown");
@@ -861,10 +880,19 @@ function setupFilterDropdownToggles(): void {
});
}
+ // Marketplace filter clear button
+ const marketplaceFilterClearBtn = document.getElementById("marketplace-filter-clear-btn");
+ if (marketplaceFilterClearBtn) {
+ marketplaceFilterClearBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ clearMarketplaceDropdownFilters();
+ });
+ }
+
// Close dropdowns when clicking outside
document.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
- if (!target.closest(".filter-dropdown") && !target.closest(".search-filter-btn")) {
+ if (!target.closest(".filter-dropdown") && !target.closest(".search-filter-btn") && !target.closest(".filter-clear-btn")) {
document.querySelectorAll(".filter-dropdown").forEach((dropdown) => {
(dropdown as HTMLElement).style.display = "none";
});
diff --git a/src/renderer/modules/marketplaceManagement.ts b/src/renderer/modules/marketplaceManagement.ts
index 5fb00019..a47cdc49 100644
--- a/src/renderer/modules/marketplaceManagement.ts
+++ b/src/renderer/modules/marketplaceManagement.ts
@@ -8,7 +8,7 @@ import type { Tool } from "../../common/types";
import type { ToolDetail } from "../types/index";
import { renderMarkdownToSafeHtml, wireExternalLinks } from "../utils/markdown";
import { getUnsupportedBadgeTitle, getUnsupportedRequirement } from "../utils/toolCompatibility";
-import { applyToolIconMasks, escapeHtml, generateToolIconHtml, resolveToolIconUrl } from "../utils/toolIconResolver";
+import { applyToolIconMasks, escapeHtml, generateToolIconHtml } from "../utils/toolIconResolver";
import { openToolDetailTab } from "./toolManagement";
import { loadSidebarTools } from "./toolsSidebarManagement";
@@ -21,10 +21,6 @@ interface InstalledTool {
// Tool library loaded from registry
let toolLibrary: ToolDetail[] = [];
-const DEFAULT_TOOL_ICON_DARK_SVG = ``;
-
/**
* Get tool library
*/
@@ -112,6 +108,17 @@ export async function loadMarketplace(): Promise {
const showNewOnly = newFilter?.checked || false;
const deprecatedToolsVisibility = (await window.toolboxAPI.getSetting("deprecatedToolsVisibility")) || "hide-all";
+ // Update filter button indicator and one-click clear button visibility
+ const hasDropdownFilters = !!(selectedCategory || selectedAuthor || showNewOnly);
+ const marketplaceFilterBtn = document.getElementById("marketplace-filter-btn");
+ if (marketplaceFilterBtn) {
+ marketplaceFilterBtn.classList.toggle("has-active-filters", hasDropdownFilters);
+ }
+ const marketplaceFilterClearBtn = document.getElementById("marketplace-filter-clear-btn") as HTMLButtonElement | null;
+ if (marketplaceFilterClearBtn) {
+ marketplaceFilterClearBtn.style.display = hasDropdownFilters ? "flex" : "none";
+ }
+
// Get saved sort preference or default
const savedSort = await window.toolboxAPI.getSetting("marketplaceSort");
const sortOption = (sortSelect?.value as any) || savedSort || "name-asc";
@@ -187,7 +194,7 @@ export async function loadMarketplace(): Promise {
// Show empty state if no tools match the search
if (filteredTools.length === 0) {
const hasSearchTerm = searchTerm.length > 0;
- const hasActiveFilters = hasSearchTerm || selectedCategory || selectedAuthor;
+ const hasActiveFilters = hasSearchTerm || selectedCategory || selectedAuthor || showNewOnly;
const emptyMessage = hasSearchTerm ? "Try a different search term." : hasActiveFilters ? "No tools match the current filters." : "Check back later for new tools.";
marketplaceList.innerHTML = `
@@ -601,24 +608,9 @@ async function loadToolReadme(panel: HTMLElement, readmeUrl: string | undefined,
}
function buildToolIconHtml(tool: ToolDetail): string {
- // defaultToolIcon is a safe data:image/svg+xml URI generated from application constant
- const defaultToolIcon = svgToDataUri(DEFAULT_TOOL_ICON_DARK_SVG);
- const resolvedIconUrl = resolveToolIconUrl(tool.id, tool.icon);
-
- // Validate the generated data URI is safe (defensive check)
- const escapedDefaultIcon = defaultToolIcon.startsWith("data:image/") ? escapeHtml(defaultToolIcon) : "";
-
- if (!resolvedIconUrl) {
- return escapedDefaultIcon ? `

` : "";
- }
-
- const escapedResolvedUrl = escapeHtml(resolvedIconUrl);
- const onerrorAttr = escapedDefaultIcon ? ` onerror="this.src='${escapedDefaultIcon}'"` : "";
- return `

`;
-}
-
-function svgToDataUri(svgContent: string): string {
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
+ const isDarkTheme = document.body.classList.contains("dark-theme");
+ const defaultToolIcon = isDarkTheme ? "icons/dark/tool-default.svg" : "icons/light/tool-default.svg";
+ return generateToolIconHtml(tool.id, tool.icon, tool.name, defaultToolIcon);
}
function formatError(error: unknown): string {
@@ -653,10 +645,55 @@ function clearMarketplaceFilters(): void {
authorFilter.value = "";
}
+ // Reset new tools filter
+ const newFilter = document.getElementById("marketplace-new-filter") as HTMLInputElement | null;
+ if (newFilter) {
+ newFilter.checked = false;
+ }
+
// Reload the marketplace to reflect the cleared filters
loadMarketplace();
}
+/**
+ * Clear only the dropdown filter selections (category, author, new) for the marketplace.
+ * Leaves the search input unchanged.
+ */
+export function clearMarketplaceDropdownFilters(): void {
+ // Reset category filter
+ const categoryFilter = document.getElementById("marketplace-category-filter") as HTMLSelectElement | null;
+ if (categoryFilter) {
+ categoryFilter.value = "";
+ }
+
+ // Reset author filter
+ const authorFilter = document.getElementById("marketplace-author-filter") as HTMLSelectElement | null;
+ if (authorFilter) {
+ authorFilter.value = "";
+ }
+
+ // Reset "new only" checkbox
+ const newFilter = document.getElementById("marketplace-new-filter") as HTMLInputElement | null;
+ if (newFilter) {
+ newFilter.checked = false;
+ }
+
+ // Reload the marketplace to reflect the cleared filters
+ loadMarketplace();
+}
+
+/**
+ * Apply the "new tools only" filter to the marketplace and reload it.
+ * Used when navigating to the marketplace from the new-tools notification banner.
+ */
+export function filterMarketplaceByNew(): void {
+ const newFilter = document.getElementById("marketplace-new-filter") as HTMLInputElement | null;
+ if (newFilter) {
+ newFilter.checked = true;
+ }
+ loadMarketplace();
+}
+
/**
* Handle protocol deep link install request
* Called when user clicks pptb://install?toolId={toolId}&toolName={toolName}
diff --git a/src/renderer/modules/themeManagement.ts b/src/renderer/modules/themeManagement.ts
index 2151ac72..4722e2a8 100644
--- a/src/renderer/modules/themeManagement.ts
+++ b/src/renderer/modules/themeManagement.ts
@@ -39,6 +39,9 @@ export function applyTheme(theme: string): void {
// Update marketplace icons when theme changes
updateMarketplaceIconsForTheme();
+ // Update tool detail tab icons when theme changes
+ updateToolDetailIconsForTheme();
+
// Update homepage icon when theme changes
updateHomepageIconForTheme();
@@ -268,6 +271,27 @@ export function updateFilterIconsForTheme(): void {
}
}
+/**
+ * Update tool detail tab fallback icons to match current theme
+ * Called when theme changes to update default tool icons in any open detail tab
+ */
+export function updateToolDetailIconsForTheme(): void {
+ const isDarkTheme = document.body.classList.contains("dark-theme");
+ const cacheBuster = `?t=${Date.now()}`;
+ const defaultToolIcon = isDarkTheme ? "icons/dark/tool-default.svg" : "icons/light/tool-default.svg";
+
+ const detailPanel = document.getElementById("tool-detail-content-panel");
+ if (!detailPanel) return;
+
+ // Update fallback img icons (only default tool icons, not custom tool icons)
+ detailPanel.querySelectorAll(".tool-detail-tab-icon img").forEach((img) => {
+ const currentSrc = (img as HTMLImageElement).src;
+ if (currentSrc.includes("tool-default.svg")) {
+ (img as HTMLImageElement).src = defaultToolIcon + cacheBuster;
+ }
+ });
+}
+
/**
* Apply terminal font family
*/
diff --git a/src/renderer/modules/toolsSidebarManagement.ts b/src/renderer/modules/toolsSidebarManagement.ts
index 03bba5ee..0f4e347a 100644
--- a/src/renderer/modules/toolsSidebarManagement.ts
+++ b/src/renderer/modules/toolsSidebarManagement.ts
@@ -3,6 +3,7 @@
* Handles the display and management of installed tools in the sidebar
*/
+import { logError, logInfo } from "../../common/logger";
import { ToolDetail } from "../types/index";
import { getUnsupportedBadgeTitle, getUnsupportedRequirement } from "../utils/toolCompatibility";
import { applyToolIconMasks, generateToolIconHtml } from "../utils/toolIconResolver";
@@ -10,7 +11,6 @@ import { getToolSourceIconHtml } from "../utils/toolSourceIcon";
import { loadMarketplace, openToolDetail } from "./marketplaceManagement";
import { switchSidebar } from "./sidebarManagement";
import { launchTool } from "./toolManagement";
-import { logInfo, logError } from "../../common/logger";
let activeToolContextMenu: { menu: HTMLElement; anchor: HTMLElement; cleanup: () => void } | null = null;
@@ -67,6 +67,17 @@ export async function loadSidebarTools(): Promise
{
const selectedCategory = categoryFilter?.value || "";
const selectedAuthor = authorFilter?.value || "";
+ // Update filter button indicator and one-click clear button visibility
+ const hasDropdownFilters = !!(selectedCategory || selectedAuthor);
+ const toolsFilterBtn = document.getElementById("tools-filter-btn");
+ if (toolsFilterBtn) {
+ toolsFilterBtn.classList.toggle("has-active-filters", hasDropdownFilters);
+ }
+ const toolsFilterClearBtn = document.getElementById("tools-filter-clear-btn") as HTMLButtonElement | null;
+ if (toolsFilterClearBtn) {
+ toolsFilterClearBtn.style.display = hasDropdownFilters ? "flex" : "none";
+ }
+
// Get saved sort preference or default
const savedSort = await window.toolboxAPI.getSetting("installedToolsSort");
const sortOption = (sortSelect?.value as any) || savedSort || "name-asc";
@@ -145,12 +156,17 @@ export async function loadSidebarTools(): Promise {
toolsList.innerHTML = `
No matching tools
-
${emptyMessage}
+
${hasActiveFilters ? '
Clear all filters' : ""}
`;
+ const emptyStateHint = document.getElementById("empty-state-hint");
+ if (emptyStateHint) {
+ emptyStateHint.textContent = emptyMessage;
+ }
+
// Add event listener for the marketplace search button
attachMarketplaceNavigationButton("search-marketplace-btn", searchTerm);
@@ -712,6 +728,27 @@ function clearAllFilters(): void {
loadSidebarTools();
}
+/**
+ * Clear only the dropdown filter selections (category, author) for installed tools.
+ * Leaves the search input unchanged.
+ */
+export function clearInstalledToolsDropdownFilters(): void {
+ // Reset category filter
+ const categoryFilter = document.getElementById("tools-category-filter") as HTMLSelectElement | null;
+ if (categoryFilter) {
+ categoryFilter.value = "";
+ }
+
+ // Reset author filter
+ const authorFilter = document.getElementById("tools-author-filter") as HTMLSelectElement | null;
+ if (authorFilter) {
+ authorFilter.value = "";
+ }
+
+ // Reload the sidebar tools to reflect the cleared filters
+ loadSidebarTools();
+}
+
/**
* Attach click event listener to a marketplace navigation button
*/
diff --git a/src/renderer/styles.scss b/src/renderer/styles.scss
index 57ef3ff2..2854c056 100644
--- a/src/renderer/styles.scss
+++ b/src/renderer/styles.scss
@@ -2230,9 +2230,7 @@ body.dark-theme .settings-vscode-item:hover {
.tool-detail-tab-icon span {
width: 48px;
height: 48px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
+ display: inline-block;
}
.tool-detail-tab-meta {
@@ -2483,6 +2481,15 @@ body.dark-theme .settings-vscode-item:hover {
box-shadow: 0 0 0 1px var(--accent-color);
}
+/* Filter button group: filter icon + clear button side-by-side */
+.filter-btn-group {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ flex-shrink: 0;
+}
+
+
.search-filter-btn {
padding: 6px 8px;
background: none;
@@ -2518,6 +2525,32 @@ body.dark-theme .search-filter-btn.active img {
filter: none;
}
+/* Filter button active-filters indicator dot */
+.search-filter-btn.has-active-filters {
+ position: relative;
+ border-color: var(--accent-color);
+}
+
+.search-filter-btn.has-active-filters::after {
+ content: "";
+ position: absolute;
+ top: 3px;
+ right: 3px;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background-color: var(--accent-color);
+ /* 1.5px outline gap separates the dot from the button border */
+ box-shadow: 0 0 0 1.5px var(--sidebar-bg);
+ pointer-events: none;
+}
+
+/* When dropdown is open and filters are active, show a contrasting dot */
+.search-filter-btn.has-active-filters.active::after {
+ background-color: var(--filter-btn-active-color);
+ box-shadow: 0 0 0 1.5px var(--filter-btn-active-bg);
+}
+
/* Filter dropdown - VSCode style */
.filter-dropdown {
position: absolute;