diff --git a/package-lock.json b/package-lock.json
index 71b63e2..ad8b5d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"conditional-reduce": "^1.2.0",
"dayjs": "^1.10.7",
"dts-dom": "^3.6.0",
+ "dvdt-erd-generator": "^0.0.6",
"fast-xml-parser": "^4.5.0",
"fs-extra": "^10.0.0",
"lodash": "^4.17.21",
@@ -2641,6 +2642,20 @@
"node": ">= 0.4"
}
},
+ "node_modules/dvdt-erd-generator": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/dvdt-erd-generator/-/dvdt-erd-generator-0.0.6.tgz",
+ "integrity": "sha512-qQVSMPAU9sUzlhHYCyAmccxe7rBBcRAFH2DX1f+NdI6rxS2A9OKdhByRth3vLaVMOHuNGYblgKOk6lCBph3b9w==",
+ "license": "GPL-2.0",
+ "peerDependencies": {
+ "vscode": "^1.85.0"
+ },
+ "peerDependenciesMeta": {
+ "vscode": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
diff --git a/package.json b/package.json
index 278d0a4..a3f8b75 100644
--- a/package.json
+++ b/package.json
@@ -164,6 +164,12 @@
"when": "view == dvEntities",
"group": "navigation@2"
},
+ {
+ "command": "dvdt.commands.showToolsPage",
+ "when": "view == ppToolBox",
+ "group": "navigation@1",
+ "icon": "$(tools)"
+ },
{
"command": "dvdt.explorer.webresources.smartMatch",
"when": "view == dvWebResources",
@@ -506,6 +512,18 @@
"title": "Open Dataverse REST Builder (DRB)",
"category": "%dvdt.commands.category%"
},
+ {
+ "command": "dvdt.commands.showToolsPage",
+ "title": "Show Tools Page",
+ "category": "%dvdt.commands.category%",
+ "icon": "$(tools)"
+ },
+ {
+ "command": "dvdt.commands.launchToolByShortName",
+ "title": "Launch Tool",
+ "when": "false",
+ "category": "%dvdt.commands.category%"
+ },
{
"command": "dvdt.explorer.tools.launchTool",
"title": "Launch tool from ToolBox",
@@ -579,6 +597,7 @@
"conditional-reduce": "^1.2.0",
"dayjs": "^1.10.7",
"dts-dom": "^3.6.0",
+ "dvdt-erd-generator": "^0.0.6",
"fast-xml-parser": "^4.5.0",
"fs-extra": "^10.0.0",
"lodash": "^4.17.21",
diff --git a/resources/views/css/base.css b/resources/views/css/base.css
index fe1a21c..87206b4 100644
--- a/resources/views/css/base.css
+++ b/resources/views/css/base.css
@@ -69,6 +69,191 @@ body {
font-size: small;
}
+.btn {
+ padding: 4px 12px;
+ cursor: pointer;
+ background-color: var(--vscode-button-background);
+ color: var(--vscode-button-foreground);
+ border: none;
+ border-radius: 2px;
+ font-size: 0.8rem;
+}
+
+.btn:hover {
+ background-color: var(--vscode-button-hoverBackground);
+}
+
+.btn:active {
+ background-color: var(--vscode-button-hoverBackground);
+ opacity: 0.8;
+}
+
+/**** Tools Page Styling ****/
+.tools-header {
+ padding: 20px;
+ border-bottom: 1px solid var(--vscode-panel-border);
+ margin-bottom: 20px;
+}
+
+.tools-header h2 {
+ margin: 0 0 8px 0;
+ color: var(--vscode-foreground);
+ font-size: 1.5rem;
+ font-weight: 600;
+}
+
+.tools-description {
+ margin: 0 0 16px 0;
+ color: var(--vscode-descriptionForeground);
+ font-size: 0.9rem;
+}
+
+.search-container {
+ width: 100%;
+ max-width: 600px;
+}
+
+.tools-search {
+ width: 100%;
+ padding: 10px 16px;
+ background-color: var(--vscode-input-background);
+ color: var(--vscode-input-foreground);
+ border: 1px solid var(--vscode-input-border);
+ border-radius: 4px;
+ font-size: 0.9rem;
+ outline: none;
+ transition: border-color 0.2s;
+}
+
+.tools-search:focus {
+ border-color: var(--vscode-focusBorder);
+}
+
+.tools-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 16px;
+ padding: 0 20px 20px 20px;
+}
+
+.tool-card {
+ background-color: var(--vscode-editor-background);
+ border: 1px solid var(--vscode-panel-border);
+ border-radius: 6px;
+ padding: 20px;
+ transition: all 0.2s ease;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.tool-card:hover {
+ border-color: var(--vscode-focusBorder);
+ background-color: var(--vscode-list-hoverBackground);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+.tool-card-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.tool-icon {
+ width: 48px;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, var(--vscode-button-background), var(--vscode-button-hoverBackground));
+ border-radius: 8px;
+ font-size: 24px;
+ flex-shrink: 0;
+}
+
+.tool-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.tool-name {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--vscode-foreground);
+ margin: 0 0 4px 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.tool-author {
+ font-size: 0.85rem;
+ color: var(--vscode-descriptionForeground);
+ margin: 0;
+}
+
+.tool-description {
+ font-size: 0.9rem;
+ color: var(--vscode-foreground);
+ line-height: 1.4;
+ margin: 0;
+ height: 64px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-line-clamp: 3;
+ line-clamp: 3;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+
+.tool-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 8px;
+}
+
+.tool-tag {
+ display: inline-block;
+ padding: 4px 10px;
+ background-color: var(--vscode-badge-background);
+ color: var(--vscode-badge-foreground);
+ border-radius: 12px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ text-transform: uppercase;
+}
+
+.tool-launch-btn {
+ padding: 8px 16px;
+ background-color: var(--vscode-button-background);
+ color: var(--vscode-button-foreground);
+ border: none;
+ border-radius: 4px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.tool-launch-btn:hover {
+ background-color: var(--vscode-button-hoverBackground);
+ transform: scale(1.05);
+}
+
+.tool-launch-btn:active {
+ transform: scale(0.98);
+}
+
+.tool-card.hidden {
+ display: none;
+}
+
/**** Lables ****/
label {
font-size: 0.8rem;
diff --git a/resources/views/js/base.js b/resources/views/js/base.js
index fb11c16..cef3f29 100644
--- a/resources/views/js/base.js
+++ b/resources/views/js/base.js
@@ -17,6 +17,14 @@ $(document).ready(function () {
});
});
+ $("#toolsSearch").on("keyup", function () {
+ var value = $(this).val().toLowerCase();
+ $(".tool-card").filter(function () {
+ var isMatch = $(this).text().toLowerCase().indexOf(value) > -1;
+ $(this).toggleClass("hidden", !isMatch);
+ });
+ });
+
if ($matchTable && $matchTable.bootstrapTable) {
$matchTable.bootstrapTable();
$matchTable.bootstrapTable("refreshOptions", {
@@ -80,6 +88,13 @@ function link(fullPath, wrId) {
});
}
+function launchTool(toolShortName) {
+ vscode.postMessage({
+ command: "launchTool",
+ toolShortName: toolShortName,
+ });
+}
+
function sortTable(tableId, column) {
var table = document.getElementById(tableId);
var tbody = table.querySelector("tbody");
diff --git a/resources/views/toolslist.html b/resources/views/toolslist.html
new file mode 100644
index 0000000..8ee90b7
--- /dev/null
+++ b/resources/views/toolslist.html
@@ -0,0 +1,11 @@
+
+
+
+ !!{tools}
+
diff --git a/src/commands/registerToolsCommands.ts b/src/commands/registerToolsCommands.ts
index 30b425a..fc38124 100644
--- a/src/commands/registerToolsCommands.ts
+++ b/src/commands/registerToolsCommands.ts
@@ -4,6 +4,7 @@ import { CLIHelper } from "../helpers/cliHelper";
import { ErrorHandler } from "../helpers/errorHandler";
import { ToolsHelper } from "../helpers/toolsHelper";
import { ICommand } from "../utils/Interfaces";
+import { ToolsListView } from "../views/ToolsListView";
import { ViewBase } from "../views/ViewBase";
export async function registerToolsCommands(vscontext: vscode.ExtensionContext, tr: TelemetryReporter): Promise {
@@ -23,16 +24,16 @@ export async function registerToolsCommands(vscontext: vscode.ExtensionContext,
}
},
},
- // {
- // command: "dvdt.commands.openERDGenerator",
- // callback: async () => {
- // try {
- // toolHelper.openERDGenerator();
- // } catch (error) {
- // errorHandler.log(error, "openERDGenerator");
- // }
- // },
- // },
+ {
+ command: "dvdt.commands.openERDGenerator",
+ callback: async () => {
+ try {
+ toolHelper.openERDGenerator();
+ } catch (error) {
+ errorHandler.log(error, "openERDGenerator");
+ }
+ },
+ },
{
command: "dvdt.commands.launchPRT",
callback: async () => {
@@ -63,6 +64,27 @@ export async function registerToolsCommands(vscontext: vscode.ExtensionContext,
}
},
},
+ {
+ command: "dvdt.commands.showToolsPage",
+ callback: async () => {
+ try {
+ const webview = await views.getWebView({ type: "showToolsList", title: "Power Platform ToolBox" });
+ new ToolsListView(webview, vscontext);
+ } catch (error) {
+ errorHandler.log(error, "showToolsPage");
+ }
+ },
+ },
+ {
+ command: "dvdt.commands.launchToolByShortName",
+ callback: async (toolShortName: string) => {
+ try {
+ toolHelper.launchToolByShortName(toolShortName, cliHelper, views);
+ } catch (error) {
+ errorHandler.log(error, "launchToolByShortName");
+ }
+ },
+ },
);
cmds.forEach((c) => {
diff --git a/src/helpers/toolsHelper.ts b/src/helpers/toolsHelper.ts
index 2853281..8a7abb0 100644
--- a/src/helpers/toolsHelper.ts
+++ b/src/helpers/toolsHelper.ts
@@ -1,3 +1,4 @@
+import { showERDPanel } from "dvdt-erd-generator";
import * as vscode from "vscode";
import { ToolsTreeItem } from "../tools/toolsDataProvider";
import { connectionCurrentStoreKey } from "../utils/Constants";
@@ -21,14 +22,17 @@ export class ToolsHelper {
public openTool(toolItem: ToolsTreeItem) {
const views = new ViewBase(this.vscontext);
const cliHelper = new CLIHelper(this.vscontext);
+ this.launchToolByShortName(toolItem.toolShortName, cliHelper, views);
+ }
- switch (toolItem.toolShortName) {
+ public launchToolByShortName(shortName: string, cliHelper: CLIHelper, views: ViewBase) {
+ switch (shortName) {
case "drb":
this.openDRB(views);
break;
- // case "erd":
- // this.openERDGenerator();
- // break;
+ case "erd":
+ this.openERDGenerator();
+ break;
case "prt":
cliHelper.launchPRT();
break;
@@ -53,12 +57,12 @@ export class ToolsHelper {
}
}
- // public openERDGenerator(): void {
- // const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey);
- // if (connFromWS && connFromWS.currentAccessToken) {
- // showERDPanel(this.vscontext.extensionUri, connFromWS.environmentUrl, connFromWS.currentAccessToken);
- // } else {
- // vscode.window.showErrorMessage(ErrorMessages.commonToolsError);
- // }
- // }
+ public openERDGenerator(): void {
+ const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey);
+ if (connFromWS && connFromWS.currentAccessToken) {
+ showERDPanel(this.vscontext.extensionUri, connFromWS.environmentUrl, connFromWS.currentAccessToken);
+ } else {
+ vscode.window.showErrorMessage(ErrorMessages.commonToolsError);
+ }
+ }
}
diff --git a/src/tools/tools.json b/src/tools/tools.json
index af36cb3..65bbf21 100644
--- a/src/tools/tools.json
+++ b/src/tools/tools.json
@@ -4,25 +4,36 @@
"name": "Dataverse REST Builder",
"shortName": "drb",
"author": "Guido Preite",
- "icon": "drb.png"
+ "icon": "drb.png",
+ "description": "Build and test Dataverse Web API queries with an intuitive interface. Generate code snippets for JavaScript, C#, and more."
+ },
+ {
+ "name": "ERD Generator",
+ "shortName": "erd",
+ "author": "Power Maverick",
+ "icon": "erd.svg",
+ "description": "Generate Entity Relationship Diagrams for Dataverse models."
},
{
"name": "Plugin Registration",
"shortName": "prt",
"author": "Microsoft",
- "icon": "prt.png"
+ "icon": "prt.png",
+ "description": "Register and manage plugins, custom workflow activities, and service endpoints for Dataverse."
},
{
"name": "Configuration Migration",
"shortName": "cmt",
"author": "Microsoft",
- "icon": "cmt.png"
+ "icon": "cmt.png",
+ "description": "Move configuration data across Dataverse environments. Export and import customizations, reference data, and more."
},
{
"name": "Package Deployer",
"shortName": "pd",
"author": "Microsoft",
- "icon": "pd.png"
+ "icon": "pd.png",
+ "description": "Deploy packages to Dataverse environments with pre and post-deployment actions. Automate solution deployment."
}
]
}
\ No newline at end of file
diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts
index 3e5debc..d6272ba 100644
--- a/src/utils/Interfaces.ts
+++ b/src/utils/Interfaces.ts
@@ -298,6 +298,7 @@ export interface IToolDetails {
shortName: string;
author: string;
icon?: string;
+ description?: string;
}
export interface ICliCommandList {
commands: ICliCommand[];
diff --git a/src/views/ToolsListView.ts b/src/views/ToolsListView.ts
new file mode 100644
index 0000000..7152f00
--- /dev/null
+++ b/src/views/ToolsListView.ts
@@ -0,0 +1,68 @@
+import _ from "lodash";
+import * as path from "path";
+import * as vscode from "vscode";
+import toolsInJson from "../tools/tools.json";
+import { readFileSync } from "../utils/FileSystem";
+import { IToolDetails } from "../utils/Interfaces";
+import { VsCodePanel } from "./base/VsCodePanelBase";
+
+export class ToolsListView extends VsCodePanel {
+ private tools: IToolDetails[];
+
+ constructor(webview: vscode.WebviewPanel, vscontext: vscode.ExtensionContext) {
+ super({ panel: webview, extensionUri: vscontext.extensionUri, webViewFileName: "toolslist.html" });
+ this.tools = toolsInJson.tools;
+ // Set the webview's initial html content
+ super.update();
+
+ // Set up message listener for launching tools
+ this.webViewPanel.webview.onDidReceiveMessage((message) => {
+ switch (message.command) {
+ case "launchTool":
+ vscode.commands.executeCommand("dvdt.commands.launchToolByShortName", message.toolShortName);
+ return;
+ }
+ });
+ }
+
+ public getHtmlForWebview(webviewFileName: string): string {
+ const pathOnDisk = path.join(this.panelOptions.extensionUri.fsPath, "resources", "views", webviewFileName);
+ const fileHtml = readFileSync(pathOnDisk).toString();
+ _.templateSettings.interpolate = /!!{([\s\S]+?)}/g;
+ const compiled = _.template(fileHtml);
+
+ const viewModel = {
+ tools: this.tools
+ .map((tool) => {
+ const escapedToolName = _.escape(tool.name);
+ const escapedShortName = _.escape(tool.shortName);
+ const escapedAuthor = _.escape(tool.author);
+ const escapedDescription = _.escape(tool.description || "No description available");
+ const toolIcon = tool.icon
+ ? `
`
+ : `🛠️`;
+
+ return ``;
+ })
+ .join(""),
+ };
+
+ return super.render(compiled(viewModel));
+ }
+}