diff --git a/src/providers/tasksProvider.ts b/src/providers/tasksProvider.ts index bbe6c0b..a515fd3 100644 --- a/src/providers/tasksProvider.ts +++ b/src/providers/tasksProvider.ts @@ -128,13 +128,13 @@ export class MiseTasksProvider implements vscode.TreeDataProvider { for (const flag of spec.flags) { if (flag.arg) { const shouldProvide = await vscode.window.showQuickPick(["Yes", "No"], { - placeHolder: `Do you want to provide ${flag.name}?`, + placeHolder: `Do you want to provide "--${flag.name}" option?`, ignoreFocusOut: true, }); if (shouldProvide === "Yes") { const value = await vscode.window.showInputBox({ - prompt: `Enter value for ${flag.name}`, + prompt: `Enter value for --${flag.name}=?`, placeHolder: flag.arg, ignoreFocusOut: true, }); diff --git a/src/webviewPanel.ts b/src/webviewPanel.ts index 4bfe2fa..291a83a 100644 --- a/src/webviewPanel.ts +++ b/src/webviewPanel.ts @@ -1,4 +1,6 @@ import { readFileSync, writeFileSync } from "node:fs"; +import { homedir } from "node:os"; +import * as os from "node:os"; import path from "node:path"; import * as cheerio from "cheerio"; import * as vscode from "vscode"; @@ -248,7 +250,9 @@ export default class WebViewPanel { ); }); - $("head").append(``); + $("head") + .append(``) + .append(``); return $.html(); } diff --git a/src/webviews/Tools.tsx b/src/webviews/Tools.tsx index 2a6a89c..43412eb 100644 --- a/src/webviews/Tools.tsx +++ b/src/webviews/Tools.tsx @@ -1,12 +1,141 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { VscodeButton } from "@vscode-elements/react-elements"; +import { VscodeButton, VscodeCheckbox } from "@vscode-elements/react-elements"; +import { useState } from "react"; import CustomTable from "./components/CustomTable"; -import { vscodeClient } from "./webviewVsCodeApi"; +import { IconButton } from "./components/IconButton"; +import { + toDisplayPath, + trackedConfigsQueryOptions, + vscodeClient, +} from "./webviewVsCodeApi"; + +const styles = { + infoPanel: { padding: "10px" }, + header: { marginBottom: "10px" }, + title: { + display: "flex", + alignItems: "center", + gap: "8px", + fontSize: "16px", + fontWeight: "bold", + color: "var(--vscode-foreground)", + }, + label: { + fontSize: "12px", + color: "var(--vscode-descriptionForeground)", + marginBottom: "4px", + }, + value: { + fontSize: "14px", + wordBreak: "break-all", + color: "var(--vscode-foreground)", + }, +} as const; + +const ToolInfo = ({ + onClose, + selectedTool, +}: { selectedTool: MiseTool; onClose: () => void }) => { + const trackedConfigQuery = useQuery(trackedConfigsQueryOptions); + const configs = trackedConfigQuery.data || []; + + if (!selectedTool) { + return null; + } + + const configsWithTool = configs.filter((config) => { + return Object.keys(config.tools).includes(selectedTool.name); + }); + + return ( +
+
+
+ + {selectedTool.name} +
+ +
+ +
+
+
+

Version

+

{selectedTool.version}

+
+
+

Requested Version

+

+ {selectedTool.requested_version || "None"} +

+
+
+

Status

+

+ {selectedTool.installed ? "Installed" : "Not Installed"} +

+
+ + {selectedTool.install_path && ( +
+

Install Path

+

+ {toDisplayPath(selectedTool.install_path)} +

+
+ )} + + {selectedTool.source?.path && ( +
+

Requested by

+

+ {toDisplayPath(selectedTool.source.path)} +

+
+ )} +
+
+ + {configsWithTool.length > 0 && ( +
+

Used in

+
+ {configsWithTool + .map((config) => { + // @ts-ignore + const toolInfo = config.tools[selectedTool.name]; + return `${toDisplayPath(config.path)} (${JSON.stringify(toolInfo)})`; + }) + .join(", ")} +
+
+ )} +
+ ); +}; const ActionCell = ({ tool, outdatedToolInfo, -}: { tool: MiseTool; outdatedToolInfo?: MiseToolUpdate }) => { + onSelect, +}: { + tool: MiseTool; + outdatedToolInfo?: MiseToolUpdate; + onSelect: (tool: MiseTool) => void; +}) => { const queryClient = useQueryClient(); const mutationKey = ["uninstallTool", tool.name, tool.version]; const removeToolMutation = useMutation({ @@ -49,6 +178,15 @@ const ActionCell = ({ return (
+ { + onSelect(tool); + }} + > + + {outdatedToolInfo && ( { const queryClient = useQueryClient(); + const [selectedTool, setSelectedTool] = useState(null); + const [showOutdatedOnly, setShowOutdatedOnly] = useState(false); + const [activeOnly, setActiveOnly] = useState(false); + const toolsQuery = useQuery({ queryKey: ["tools"], queryFn: ({ queryKey }) => @@ -105,11 +247,64 @@ export const Tools = () => { return
Error: {toolsQuery.error.message}
; } + const rows: MiseTool[] = []; + for (const tool of toolsQuery.data || []) { + if ( + showOutdatedOnly && + !outdatedToolsQuery.data?.find( + (outdatedTool) => + outdatedTool.name === tool.name && + outdatedTool.version === tool.version, + ) + ) { + continue; + } + + if (activeOnly && !tool.active) { + continue; + } + + rows.push(tool); + } + return (
+ {selectedTool && ( + setSelectedTool(null)} + selectedTool={selectedTool} + /> + )} +
+ outdatedToolsQuery.refetch()} + /> + { + setActiveOnly(!activeOnly); + }} + /> + { + setShowOutdatedOnly(!showOutdatedOnly); + }} + /> { }); }} > - {pruneToolsMutations.isPending - ? "Pruning..." - : "Prune unused tools"} - - { - return outdatedToolsQuery.refetch(); - }} - style={{ background: "none", border: "none" }} - > - + {pruneToolsMutations.isPending ? "Pruning..." : "Prune tools"}

{outdatedToolsQuery.isLoading ? "Loading outdated tools..." : ""} @@ -144,6 +328,19 @@ export const Tools = () => { id: "name", header: "Name", accessorKey: "name", + cell: ({ row }) => { + return ( + // biome-ignore lint/a11y/useValidAnchor: + { + setSelectedTool(row.original); + }} + > + {row.original.name} + + ); + }, }, { id: "version", @@ -208,12 +405,13 @@ export const Tools = () => { outdatedTool.name === props.row.original.name && outdatedTool.version === props.row.original.version, )} + onSelect={setSelectedTool} tool={props.row.original} /> ), }, ]} - data={toolsQuery?.data || []} + data={rows} />

); diff --git a/src/webviews/TrackedConfigs.tsx b/src/webviews/TrackedConfigs.tsx index 33345af..03c3f46 100644 --- a/src/webviews/TrackedConfigs.tsx +++ b/src/webviews/TrackedConfigs.tsx @@ -1,15 +1,13 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import CustomTable from "./components/CustomTable"; -import { vscodeClient } from "./webviewVsCodeApi"; +import { + toDisplayPath, + trackedConfigsQueryOptions, + vscodeClient, +} from "./webviewVsCodeApi"; export const TrackedConfigs = () => { - const settingsQuery = useQuery({ - queryKey: ["trackedConfigs"], - queryFn: ({ queryKey }) => - vscodeClient.request({ queryKey }) as Promise< - Array<{ path: string; tools: object }> - >, - }); + const trackedConfigQuery = useQuery(trackedConfigsQueryOptions); const openFileMutation = useMutation({ mutationKey: ["openFile"], @@ -17,16 +15,16 @@ export const TrackedConfigs = () => { vscodeClient.request({ mutationKey: ["openFile"], variables: { path } }), }); - if (settingsQuery.isError) { - return
Error: {settingsQuery.error.message}
; + if (trackedConfigQuery.isError) { + return
Error: {trackedConfigQuery.error.message}
; } return (
{ }} title={row.original.path} > - {row.original.path} + {toDisplayPath(row.original.path)} ); }, diff --git a/src/webviews/components/CustomTable.tsx b/src/webviews/components/CustomTable.tsx index c77c603..203b597 100644 --- a/src/webviews/components/CustomTable.tsx +++ b/src/webviews/components/CustomTable.tsx @@ -19,7 +19,7 @@ import { VscodeTableHeaderCell, VscodeTableRow, } from "@vscode-elements/react-elements"; -import { type ReactElement, useState } from "react"; +import { type CSSProperties, type ReactElement, useState } from "react"; import { DebouncedInput } from "./DebouncedInput"; type VSCodeTableProps = { @@ -34,7 +34,8 @@ export default function CustomTable({ columns, isLoading = false, filterRowElement, -}: VSCodeTableProps) { + style, +}: VSCodeTableProps & { style?: CSSProperties }) { const [sorting, setSorting] = useState([]); const [globalFilter, setGlobalFilter] = useState(""); @@ -100,7 +101,8 @@ export default function CustomTable({ // @ts-ignore zebra responsive - breakpoint={400} + breakpoint={500} + style={style} > {table.getHeaderGroups().map((headerGroup) => ( diff --git a/src/webviews/components/IconButton.tsx b/src/webviews/components/IconButton.tsx new file mode 100644 index 0000000..d0139bd --- /dev/null +++ b/src/webviews/components/IconButton.tsx @@ -0,0 +1,19 @@ +import { VscodeButton } from "@vscode-elements/react-elements"; +import type { ComponentProps } from "react"; + +export function IconButton({ + iconName, + ...props +}: { + iconName: string; +} & ComponentProps) { + return ( + + + + ); +} diff --git a/src/webviews/webviewVsCodeApi.ts b/src/webviews/webviewVsCodeApi.ts index 737d968..ca27628 100644 --- a/src/webviews/webviewVsCodeApi.ts +++ b/src/webviews/webviewVsCodeApi.ts @@ -1,3 +1,5 @@ +import { queryOptions } from "@tanstack/react-query"; + declare global { interface Window { acquireVsCodeApi(): { @@ -48,3 +50,22 @@ export const vscodeClient = { }); }, }; + +const homeDir = document + .querySelector("meta[name='homeDir']") + ?.getAttribute("content"); + +export const toDisplayPath = (path: string) => { + if (!homeDir) { + return path; + } + return path.replace(homeDir, "~"); +}; + +export const trackedConfigsQueryOptions = queryOptions({ + queryKey: ["trackedConfigs"], + queryFn: ({ queryKey }) => + vscodeClient.request({ queryKey }) as Promise< + Array<{ path: string; tools: object }> + >, +});