This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
The following commit(s) were added to refs/heads/main by this push: new d828ba2 Fix #367 (#369) d828ba2 is described below commit d828ba218e57f63f76f95e800605ba85bba46c36 Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Tue Jun 14 18:33:10 2022 -0400 Fix #367 (#369) --- karavan-vscode/src/designerView.ts | 144 +++++++++++--------- karavan-vscode/src/extension.ts | 139 +++++++++++-------- karavan-vscode/src/helpView.ts | 24 ++-- karavan-vscode/src/integrationView.ts | 50 ++++--- karavan-vscode/src/{commands.ts => jbang.ts} | 48 +++---- karavan-vscode/src/openapiView.ts | 56 ++++---- karavan-vscode/src/utils.ts | 147 +++++++++++++-------- karavan-vscode/webview/builder/PropertiesTable.tsx | 26 ++-- 8 files changed, 368 insertions(+), 266 deletions(-) diff --git a/karavan-vscode/src/designerView.ts b/karavan-vscode/src/designerView.ts index af8059e..aa89553 100644 --- a/karavan-vscode/src/designerView.ts +++ b/karavan-vscode/src/designerView.ts @@ -14,42 +14,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as vscode from "vscode"; -import * as fs from "fs"; +import { workspace, Uri, window, commands, WebviewPanel, ExtensionContext, ViewColumn, WebviewPanelOnDidChangeViewStateEvent } from "vscode"; import * as path from "path"; import * as utils from "./utils"; -import * as commands from "./commands"; +import * as jbang from "./jbang"; import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml"; import { Integration } from "karavan-core/lib/model/IntegrationDefinition"; const KARAVAN_LOADED = "karavan:loaded"; -const KARAVAN_PANELS: Map<string, vscode.WebviewPanel> = new Map<string, vscode.WebviewPanel>(); +const KARAVAN_PANELS: Map<string, WebviewPanel> = new Map<string, WebviewPanel>(); const extension = '.properties'; export class DesignerView { - constructor(private context: vscode.ExtensionContext, private webviewContent: string, private rootPath?: string) { + constructor(private context: ExtensionContext, private webviewContent: string, private rootPath?: string) { } karavanOpen(fullPath: string, tab?: string) { - const yaml = fs.readFileSync(path.resolve(fullPath)).toString('utf8'); - const filename = path.basename(fullPath); - const relativePath = utils.getRalativePath(fullPath); - const integration = utils.parceYaml(filename, yaml); + utils.readFile(path.resolve(fullPath)).then(readData => { + const yaml = Buffer.from(readData).toString('utf8'); + const filename = path.basename(fullPath); + const relativePath = utils.getRalativePath(fullPath); + const integration = utils.parceYaml(filename, yaml); - if (integration[0]) { - this.openKaravanWebView(filename, relativePath, fullPath, integration[1], tab); - } else { - vscode.window.showErrorMessage("File is not Camel Integration!") - } + if (integration[0]) { + this.openKaravanWebView(filename, relativePath, fullPath, integration[1], tab); + } else { + window.showErrorMessage("File is not Camel Integration!") + } + }) } jbangRun(fullPath: string) { const filename = this.getFilename(fullPath); - if (filename && this.rootPath){ + if (filename && this.rootPath) { this.selectProfile(this.rootPath, filename); - } + } } getFilename(fullPath: string) { @@ -59,37 +60,40 @@ export class DesignerView { return filename; } } else { - const yaml = fs.readFileSync(path.resolve(fullPath)).toString('utf8'); - const relativePath = utils.getRalativePath(fullPath); - const filename = path.basename(fullPath); - const integration = utils.parceYaml(filename, yaml); - if (integration[0] && this.rootPath) { - return relativePath; - } else { - vscode.window.showErrorMessage("File is not Camel Integration!") - } + utils.readFile(path.resolve(fullPath)).then(readData => { + const yaml = Buffer.from(readData).toString('utf8'); + const relativePath = utils.getRalativePath(fullPath); + const filename = path.basename(fullPath); + const integration = utils.parceYaml(filename, yaml); + if (integration[0] && this.rootPath) { + return relativePath; + } else { + window.showErrorMessage("File is not Camel Integration!") + } + }); } } selectProfile(rootPath: string, filename?: string) { if (this.rootPath) { - const profiles: string [] = utils.getProfiles(); - if (profiles && profiles.length > 0){ - vscode.window.showQuickPick(profiles).then((profile) => { - if (!profile) { - return - } else { - commands.camelJbangRun(rootPath, profile, filename); - } - }) - } else { - commands.camelJbangRun(rootPath, "application", filename); - } + utils.getProfiles().then(profiles => { + if (profiles && profiles.length > 0) { + window.showQuickPick(profiles).then((profile) => { + if (!profile) { + return + } else { + jbang.camelJbangRun(rootPath, profile, filename); + } + }) + } else { + jbang.camelJbangRun(rootPath, "application", filename); + } + }); } } createIntegration(crd: boolean, rootPath?: string) { - vscode.window + window .showInputBox({ title: crd ? "Create Camel Integration CRD" : "Create Camel Integration YAML", ignoreFocusOut: true, @@ -109,32 +113,32 @@ export class DesignerView { const yaml = CamelDefinitionYaml.integrationToYaml(i); const filename = name.toLocaleLowerCase().endsWith('.yaml') ? name : name + '.yaml'; const relativePath = (this.rootPath ? rootPath?.replace(this.rootPath, "") : rootPath) + path.sep + filename; - const fullPath = (rootPath ? rootPath : this.rootPath) + path.sep + filename; + const fullPath = (rootPath ? rootPath : this.rootPath) + path.sep + filename; utils.save(relativePath, yaml); this.openKaravanWebView(filename, filename, fullPath, yaml); - vscode.commands.executeCommand('integrations.refresh'); + commands.executeCommand('integrations.refresh'); } }); } - openKaravanWebView(filename: string, relativePath: string, fullPath: string, yaml?: string, tab?: string) { + openKaravanWebView(filename: string, relativePath: string, fullPath: string, yaml?: string, tab?: string) { console.log("openKaravanWebView"); if (!KARAVAN_PANELS.has(relativePath)) { // Karavan webview - const panel = vscode.window.createWebviewPanel( + const panel = window.createWebviewPanel( "karavan", filename, - vscode.ViewColumn.One, + ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [ - vscode.Uri.joinPath(this.context.extensionUri, "dist"), + Uri.joinPath(this.context.extensionUri, "dist"), ], } ); panel.webview.html = this.webviewContent; - panel.iconPath = vscode.Uri.joinPath( + panel.iconPath = Uri.joinPath( this.context.extensionUri, "icons/karavan.svg" ); @@ -152,7 +156,7 @@ export class DesignerView { break; case 'disableStartHelp': utils.disableStartHelp(); - break; + break; } }, undefined, @@ -164,7 +168,7 @@ export class DesignerView { }, null, this.context.subscriptions); // Handle reopen - panel.onDidChangeViewState((e: vscode.WebviewPanelOnDidChangeViewStateEvent) => { + panel.onDidChangeViewState((e: WebviewPanelOnDidChangeViewStateEvent) => { console.log(e); if (e.webviewPanel.active || e.webviewPanel.reveal) { e.webviewPanel.webview.postMessage({ command: 'activate', tab: tab }); @@ -174,7 +178,7 @@ export class DesignerView { }); KARAVAN_PANELS.set(relativePath, panel); - vscode.commands.executeCommand("setContext", KARAVAN_LOADED, true); + commands.executeCommand("setContext", KARAVAN_LOADED, true); } else { const panel = KARAVAN_PANELS.get(relativePath); panel?.reveal(undefined, true); @@ -182,24 +186,38 @@ export class DesignerView { } } - sendData(panel: vscode.WebviewPanel, filename: string, relativePath: string, fullPath: string, reread: boolean, yaml?: string, tab?: string) { - - // Read and send Kamelets - panel.webview.postMessage({ command: 'kamelets', kamelets: utils.readKamelets(this.context) }); - - // Read and send Components - panel.webview.postMessage({ command: 'components', components: utils.readComponents(this.context) }); - - // Send showStartHelp - const showStartHelp = vscode.workspace.getConfiguration().get("Karavan.showStartHelp"); - panel.webview.postMessage({ command: 'showStartHelp', showStartHelp: showStartHelp}); + sendData(panel: WebviewPanel, filename: string, relativePath: string, fullPath: string, reread: boolean, yaml?: string, tab?: string) { + Promise.all([ + // Read Kamelets + utils.readKamelets(this.context), + // Read components + utils.readComponents(this.context) + ]).then(results => { + // Send Kamelets + panel.webview.postMessage({ command: 'kamelets', kamelets: results[0] }); + // Send components + panel.webview.postMessage({ command: 'components', components: results[1] }); + // Send showStartHelp + const showStartHelp = workspace.getConfiguration().get("Karavan.showStartHelp"); + panel.webview.postMessage({ command: 'showStartHelp', showStartHelp: showStartHelp }); + // Send integration + this.sendIntegrationData(panel, filename, relativePath, fullPath, reread, yaml, tab) + }) + } + sendIntegrationData(panel: WebviewPanel, filename: string, relativePath: string, fullPath: string, reread: boolean, yaml?: string, tab?: string) { // Read file if required - if (reread){ - yaml = fs.readFileSync(path.resolve(fullPath)).toString('utf8'); + if (reread) { + utils.readFile(path.resolve(fullPath)).then(readData => { + const yaml = Buffer.from(readData).toString('utf8'); + // Send integration + panel.webview.postMessage({ command: 'open', page: "designer", filename: filename, relativePath: relativePath, yaml: yaml, tab: tab }); + }); + } else { + // Send integration + panel.webview.postMessage({ command: 'open', page: "designer", filename: filename, relativePath: relativePath, yaml: yaml, tab: tab }); } - // Send integration - panel.webview.postMessage({ command: 'open', page: "designer", filename: filename, relativePath: relativePath, yaml: yaml, tab: tab }); + } } \ No newline at end of file diff --git a/karavan-vscode/src/extension.ts b/karavan-vscode/src/extension.ts index 4fd268a..b1baa2f 100644 --- a/karavan-vscode/src/extension.ts +++ b/karavan-vscode/src/extension.ts @@ -14,82 +14,104 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import vscode, { window } from 'vscode'; -import * as fs from "fs"; +import { ExtensionContext, Uri, window, workspace, commands } from 'vscode'; import { DesignerView } from "./designerView"; import { IntegrationView } from "./integrationView"; import { HelpView } from "./helpView"; import { selectFileName, inputFileName, OpenApiView, OpenApiItem } from "./openapiView"; import * as path from "path"; -import * as commands from "./commands"; +import * as jbang from "./jbang"; import * as utils from "./utils"; +import * as fs from "fs"; const KARAVAN_LOADED = "karavan:loaded"; -export function activate(context: vscode.ExtensionContext) { - const webviewContent = fs - .readFileSync( - vscode.Uri.joinPath(context.extensionUri, "dist/index.html").fsPath, - { encoding: "utf-8" } - ) - .replace( - "styleUri", - vscode.Uri.joinPath(context.extensionUri, "/dist/main.css") - .with({ scheme: "vscode-resource" }) - .toString() - ) - .replace( - "scriptUri", - vscode.Uri.joinPath(context.extensionUri, "/dist/webview.js") - .with({ scheme: "vscode-resource" }) - .toString() - ); - - const rootPath = (vscode.workspace.workspaceFolders && (vscode.workspace.workspaceFolders.length > 0)) - ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined; +export function activate(context: ExtensionContext) { + const webviewContent = `<!DOCTYPE html> + <html lang="en"> + + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link href="styleUri" rel="stylesheet" type="text/css" /> + </head> + + <body> + <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="root"> + <div class="pf-c-page karavan"> + <main class="pf-c-page__main" tabindex="-1"> + <section class="pf-c-page__main-section pf-m-dark-200 loading-page"><svg + class="pf-c-spinner pf-m-xl progress-stepper" role="progressbar" aria-valuetext="Loading..." + viewBox="0 0 100 100" style="--pf-c-spinner--diameter:80px" aria-label="Loading..."> + <circle class="pf-c-spinner__path" cx="50" cy="50" r="45" fill="none"></circle> + </svg></section> + </main> + </div> + </div> + <script> + </script> + <script src="scriptUri"></script> + </body> + + </html>` + .replace( + "styleUri", + Uri.joinPath(context.extensionUri, "/dist/main.css") + .with({ scheme: "vscode-resource" }) + .toString() + ) + .replace( + "scriptUri", + Uri.joinPath(context.extensionUri, "/dist/webview.js") + .with({ scheme: "vscode-resource" }) + .toString() + ); + const rootPath = (workspace.workspaceFolders && (workspace.workspaceFolders.length > 0)) + ? workspace.workspaceFolders[0].uri.fsPath : undefined; // Register views const designer = new DesignerView(context, webviewContent, rootPath); const integrationView = new IntegrationView(designer, rootPath); - vscode.window.registerTreeDataProvider('integrations', integrationView); - vscode.commands.registerCommand('integrations.refresh', () => integrationView.refresh()); + window.registerTreeDataProvider('integrations', integrationView); + commands.registerCommand('integrations.refresh', () => integrationView.refresh()); const openapiView = new OpenApiView(designer, rootPath); - vscode.window.registerTreeDataProvider('openapi', openapiView); - vscode.commands.registerCommand('openapi.refresh', () => openapiView.refresh()); + window.registerTreeDataProvider('openapi', openapiView); + commands.registerCommand('openapi.refresh', () => openapiView.refresh()); const helpView = new HelpView(context, webviewContent); - vscode.window.registerTreeDataProvider('help', helpView); - vscode.commands.registerCommand('karavan.openKamelets', () => helpView.openKaravanWebView("kamelets")); - vscode.commands.registerCommand('karavan.openComponents', () => helpView.openKaravanWebView("components")); - vscode.commands.registerCommand('karavan.openEip', () => helpView.openKaravanWebView("eip")); + window.registerTreeDataProvider('help', helpView); + commands.registerCommand('karavan.openKamelets', () => helpView.openKaravanWebView("kamelets")); + commands.registerCommand('karavan.openComponents', () => helpView.openKaravanWebView("components")); + commands.registerCommand('karavan.openEip', () => helpView.openKaravanWebView("eip")); // Create new Integration CRD command - const createCrd = vscode.commands.registerCommand("karavan.create-crd", (...args: any[]) => { + const createCrd = commands.registerCommand("karavan.create-crd", (...args: any[]) => { if (args.length > 0) designer.createIntegration(true, args[0].fsPath) else designer.createIntegration(true, rootPath) }); context.subscriptions.push(createCrd); // Create new Integration YAML command - const createYaml = vscode.commands.registerCommand("karavan.create-yaml", (...args: any[]) => designer.createIntegration(false, args[0].fsPath)); + const createYaml = commands.registerCommand("karavan.create-yaml", (...args: any[]) => designer.createIntegration(false, args[0].fsPath)); context.subscriptions.push(createYaml); // Open integration in designer command - const open = vscode.commands.registerCommand("karavan.open", (...args: any[]) => designer.karavanOpen(args[0].fsPath, args[0].tab)); + const open = commands.registerCommand("karavan.open", (...args: any[]) => designer.karavanOpen(args[0].fsPath, args[0].tab)); context.subscriptions.push(open); // Open integration in editor command - const openFile = vscode.commands.registerCommand("karavan.open-file", (...args: any[]) => { - let uri = vscode.Uri.file(args[0].fsPath); - vscode.window.showTextDocument(uri, { preserveFocus: false, preview: false }); + const openFile = commands.registerCommand("karavan.open-file", (...args: any[]) => { + let uri = Uri.file(args[0].fsPath); + window.showTextDocument(uri, { preserveFocus: false, preview: false }); }); context.subscriptions.push(openFile); // Export to Quarkus or Spring const exportOptions = ["Quarkus", "Spring"]; - const exportCommand = vscode.commands.registerCommand("karavan.jbang-export", (...args: any[]) => { + const exportCommand = commands.registerCommand("karavan.jbang-export", (...args: any[]) => { window.showQuickPick(exportOptions, { title: "Select Runtime", canPickMany: false }).then((value) => { if (value) inputExportFolder(value, rootPath); }) @@ -97,28 +119,29 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(exportCommand); // Run Integration in designer command - const run = vscode.commands.registerCommand("karavan.jbang-run-file", (...args: any[]) => designer.jbangRun(args[0].fsPath)); + const run = commands.registerCommand("karavan.jbang-run-file", (...args: any[]) => designer.jbangRun(args[0].fsPath)); context.subscriptions.push(run); // Run project - const runProjectCommand = vscode.commands.registerCommand("karavan.jbang-run-project", (...args: any[]) => { + const runProjectCommand = commands.registerCommand("karavan.jbang-run-project", (...args: any[]) => { console.log("RUN PROJECT") - const profiles = utils.getProfiles(rootPath); - console.log("profiles", profiles) - if (profiles && profiles.length > 0) { - profiles.push("Default"); - window.showQuickPick(profiles, { title: "Select Profile", canPickMany: false }).then((value) => { - if (value && rootPath) commands.camelJbangRun(rootPath, value !== "Default" ? value : undefined); - }) - } else { - if (rootPath) commands.camelJbangRun(rootPath); - } + utils.getProfiles(rootPath).then(profiles => { + console.log("profiles", profiles) + if (profiles && profiles.length > 0) { + profiles.push("Default"); + window.showQuickPick(profiles, { title: "Select Profile", canPickMany: false }).then((value) => { + if (value && rootPath) jbang.camelJbangRun(rootPath, value !== "Default" ? value : undefined); + }) + } else { + if (rootPath) jbang.camelJbangRun(rootPath); + } + }) }); context.subscriptions.push(runProjectCommand); // Generate RST API from OpenAPI specification command const generateOptions = ["Create new CRD", "Create new YAML", "Add to existing file"]; - const generateRest = vscode.commands.registerCommand('karavan.generate-rest', async (...args: any[]) => { + const generateRest = commands.registerCommand('karavan.generate-rest', async (...args: any[]) => { const openApi: OpenApiItem = args[0]; window.showQuickPick(generateOptions, { title: "Select REST Generator options", canPickMany: false }).then((value) => { switch (value) { @@ -131,8 +154,8 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(generateRest); // Create issue command - vscode.commands.registerCommand('karavan.reportIssue', () => { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/apache/camel-karavan/issues/new?title=[VS+Code]New+report&template=issue_template.md')); + commands.registerCommand('karavan.reportIssue', () => { + commands.executeCommand('open', Uri.parse('https://github.com/apache/camel-karavan/issues/new?title=[VS+Code]New+report&template=issue_template.md')); }); } @@ -140,7 +163,7 @@ export function activate(context: vscode.ExtensionContext) { * export into folder */ export async function inputExportFolder(runtime: string, rootPath?: string) { - vscode.window.showInputBox({ + window.showInputBox({ title: "Export project with " + runtime, ignoreFocusOut: true, prompt: "Export folder name", @@ -163,7 +186,7 @@ export async function inputExportFolder(runtime: string, rootPath?: string) { * export with gav */ export async function inputExportGav(runtime: string, folder: string) { - vscode.window.showInputBox({ + window.showInputBox({ title: "Export project with " + runtime, ignoreFocusOut: true, prompt: "groupId:artifactId:version", @@ -176,13 +199,13 @@ export async function inputExportGav(runtime: string, folder: string) { } }).then(gav => { if (gav) { - commands.camelJbangExport(runtime.toLowerCase(), folder, gav); + jbang.camelJbangExport(runtime.toLowerCase(), folder, gav); } }); } export function deactivate() { - vscode.commands.executeCommand("setContext", KARAVAN_LOADED, false); + commands.executeCommand("setContext", KARAVAN_LOADED, false); } diff --git a/karavan-vscode/src/helpView.ts b/karavan-vscode/src/helpView.ts index 4bf321b..70dec55 100644 --- a/karavan-vscode/src/helpView.ts +++ b/karavan-vscode/src/helpView.ts @@ -33,10 +33,10 @@ export class HelpView implements vscode.TreeDataProvider<HelpItem> { } getChildren(element?: HelpItem): vscode.ProviderResult<HelpItem[]> { const helpItems: HelpItem[] = []; - helpItems.push(new HelpItem("Enterprise Integration Patterns", "Enterprise Integration Patterns", "eip", 'combine', { command: 'karavan.openEip' , title: ''})); + helpItems.push(new HelpItem("Enterprise Integration Patterns", "Enterprise Integration Patterns", "eip", 'combine', { command: 'karavan.openEip', title: '' })); helpItems.push(new HelpItem("Kamelet catalog", "Kamelet Catalog", "kamelets", 'extensions', { command: 'karavan.openKamelets', title: '' })); helpItems.push(new HelpItem("Component catalog", "Component Catalog", "component", 'extensions', { command: 'karavan.openComponents', title: '' })); - helpItems.push(new HelpItem("Report issue", "Report Issue", "issue", 'comment', { command: 'karavan.reportIssue' , title: ''})); + helpItems.push(new HelpItem("Report issue", "Report Issue", "issue", 'comment', { command: 'karavan.reportIssue', title: '' })); return Promise.resolve(helpItems); } @@ -95,15 +95,19 @@ export class HelpView implements vscode.TreeDataProvider<HelpItem> { } } - sendData(panel: vscode.WebviewPanel, page: string) { + sendData(panel: vscode.WebviewPanel, page: string) { // Read and send Kamelets - if (page === 'kamelets') panel.webview.postMessage({ command: 'kamelets', kamelets: utils.readKamelets(this.context) }); - - // Read and send Components - if (page === 'components') panel.webview.postMessage({ command: 'components', components: utils.readComponents(this.context) }); - - // Send integration - panel.webview.postMessage({ command: 'open', page: page }); + utils.readKamelets(this.context).then(kamelets => { + if (page === 'kamelets') panel.webview.postMessage({ command: 'kamelets', kamelets: kamelets }); + }).finally(() => { + utils.readComponents(this.context).then(components => { + // Read and send Components + if (page === 'components') panel.webview.postMessage({ command: 'components', components: components }); + }).finally(() => { + // Send integration + panel.webview.postMessage({ command: 'open', page: page }); + }) + }) } } diff --git a/karavan-vscode/src/integrationView.ts b/karavan-vscode/src/integrationView.ts index e1188f4..25a9225 100644 --- a/karavan-vscode/src/integrationView.ts +++ b/karavan-vscode/src/integrationView.ts @@ -14,40 +14,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as vscode from "vscode"; +import { workspace, TreeDataProvider, EventEmitter, Event, TreeItem, ProviderResult, Command, ThemeIcon, TreeItemCollapsibleState } from "vscode"; import * as path from "path"; import * as utils from "./utils"; -import * as fs from "fs"; import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml"; import { DesignerView } from "./designerView"; import { Integration } from "karavan-core/lib/model/IntegrationDefinition"; -export class IntegrationView implements vscode.TreeDataProvider<IntegrationItem> { +export class IntegrationView implements TreeDataProvider<IntegrationItem> { constructor(private designer: DesignerView, private rootPath: string | undefined) { } - private _onDidChangeTreeData: vscode.EventEmitter<IntegrationItem | undefined | void> = new vscode.EventEmitter<IntegrationItem | undefined | void>(); - readonly onDidChangeTreeData: vscode.Event<IntegrationItem | undefined | void> = this._onDidChangeTreeData.event; + private _onDidChangeTreeData: EventEmitter<IntegrationItem | undefined | void> = new EventEmitter<IntegrationItem | undefined | void>(); + readonly onDidChangeTreeData: Event<IntegrationItem | undefined | void> = this._onDidChangeTreeData.event; - getTreeItem(element: IntegrationItem): vscode.TreeItem | Thenable<vscode.TreeItem> { + getTreeItem(element: IntegrationItem): TreeItem | Thenable<TreeItem> { return element; } - getChildren(element?: IntegrationItem): vscode.ProviderResult<IntegrationItem[]> { - const integrations: IntegrationItem[] = []; + getChildren(element?: IntegrationItem): ProviderResult<IntegrationItem[]> { if (element === undefined && this.rootPath) { - utils.getIntegrationFiles(this.rootPath).forEach(f => { - const yaml = fs.readFileSync(path.resolve(f)).toString('utf8'); - const filename = path.basename(f); - const i = CamelDefinitionYaml.yamlToIntegration(filename, yaml); - integrations.push(new IntegrationItem(i.metadata.name, f, i.crd ? "CRD" : "", i, { command: 'karavan.open', title: '', arguments: [{ fsPath: f }] })); - }) + return this.getIntegrations(this.rootPath); } else if (element && element.integration) { + const integrations: IntegrationItem[] = []; element.integration.spec.flows?.forEach(f => { integrations.push(new IntegrationItem(f.dslName.replace("Definition", ""), "", f.id, undefined, undefined)); }) + return integrations; + } + return Promise.resolve([]); + } + + async getIntegrations(dir: string) { + const result:IntegrationItem[] = [] + const files = await utils.getYamlFiles(dir); + for (let x in files){ + const filename = files[x]; + const readData = await utils.readFile(path.resolve(filename)); + const yaml = Buffer.from(readData).toString('utf8'); + if (!filename.startsWith(dir + path.sep + "target") && CamelDefinitionYaml.yamlIsIntegration(yaml)){ + const basename = path.basename(filename); + const i = CamelDefinitionYaml.yamlToIntegration(basename, yaml); + result.push(new IntegrationItem(i.metadata.name, filename, i.crd ? "CRD" : "", i, { command: 'karavan.open', title: '', arguments: [{ fsPath: filename }] })); + } + } - return Promise.resolve(integrations); + return result; } refresh(): void { @@ -55,23 +67,23 @@ export class IntegrationView implements vscode.TreeDataProvider<IntegrationItem> } } -export class IntegrationItem extends vscode.TreeItem { +export class IntegrationItem extends TreeItem { constructor( public readonly title: string, public readonly fsPath: string, public readonly description: string, public readonly integration?: Integration, - public readonly command?: vscode.Command + public readonly command?: Command ) { - super(title, integration ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None); + super(title, integration ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None); this.tooltip = this.fsPath; } iconPath = this.integration ? { light: path.join(__filename, '..', '..', 'icons', 'light', this.integration?.crd ? 'crd.svg' : 'karavan.svg'), dark: path.join(__filename, '..', '..', 'icons', 'dark', this.integration?.crd ? 'crd.svg' : 'karavan.svg') - } : vscode.ThemeIcon.File; + } : ThemeIcon.File; contextValue = this.integration ? 'integration' : "route"; } \ No newline at end of file diff --git a/karavan-vscode/src/commands.ts b/karavan-vscode/src/jbang.ts similarity index 72% rename from karavan-vscode/src/commands.ts rename to karavan-vscode/src/jbang.ts index 3aa04c0..c34a8bd 100644 --- a/karavan-vscode/src/commands.ts +++ b/karavan-vscode/src/jbang.ts @@ -14,40 +14,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as vscode from "vscode"; -import * as fs from "fs"; +import { workspace, window, Terminal } from "vscode"; import * as path from "path"; import * as shell from 'shelljs'; import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml"; -import { ProjectModel } from "karavan-core/lib/model/ProjectModel"; +import * as utils from "./utils"; -const TERMINALS: Map<string, vscode.Terminal> = new Map<string, vscode.Terminal>(); +const TERMINALS: Map<string, Terminal> = new Map<string, Terminal>(); -export function camelJbangGenerate(rootPath: string, openApiFullPath: string, fullPath: string, add: boolean, crd?: boolean, generateRoutes?: boolean) { +export async function camelJbangGenerate(rootPath: string, openApiFullPath: string, fullPath: string, add: boolean, crd?: boolean, generateRoutes?: boolean) { let command = prepareCommand("generate rest -i " + openApiFullPath, "application"); // TODO: set profile configurable if (generateRoutes === true) command = command + " --routes"; executeJbangCommand(rootPath, command, (code, stdout, stderr) => { console.log('Exit code:', code); if (code === 0) { - // console.log('Program output:', stdout); const filename = path.basename(fullPath); let yaml; if (add) { - const camelYaml = fs.readFileSync(path.resolve(fullPath)).toString('utf8'); - yaml = createYaml(filename, stdout, camelYaml, undefined); + utils.readFile(fullPath).then(readData => { + const camelYaml = Buffer.from(readData).toString('utf8'); + yaml = createYaml(filename, stdout, camelYaml, undefined); + utils.write(fullPath, yaml); + }); } else { yaml = createYaml(filename, stdout, undefined, crd); + utils.write(fullPath, yaml); } - const uriFile: vscode.Uri = vscode.Uri.file(fullPath); - fs.writeFile(uriFile.fsPath, yaml, err => { - if (err) vscode.window.showErrorMessage("Error: " + err?.message); - else { - vscode.commands.executeCommand('integrations.refresh') - .then(x => vscode.commands.executeCommand('karavan.open', { fsPath: fullPath, tab: 'rest' })); - } - }); } else { - vscode.window.showErrorMessage(stderr); + window.showErrorMessage(stderr); } }); } @@ -78,33 +72,33 @@ export function cacheClear(rootPath: string, callback: (code: number) => any) { } function prepareCommand(command: string, profile?: string): string { - const version = vscode.workspace.getConfiguration().get("camel.version"); + const version = workspace.getConfiguration().get("camel.version"); const p = profile ? " --profile " + profile : ""; return "jbang -Dcamel.jbang.version=" + version + " camel@apache/camel " + command + p; } export function camelJbangRun(rootPath: string, profile?: string, filename?: string) { - const maxMessages: number = vscode.workspace.getConfiguration().get("camel.maxMessages") || -1; - const cmd = (filename ? "run " + filename : "run * " ) + (maxMessages > -1 ? " --max-messages=" + maxMessages : ""); + const maxMessages: number = workspace.getConfiguration().get("camel.maxMessages") || -1; + const cmd = (filename ? "run " + filename : "run * ") + (maxMessages > -1 ? " --max-messages=" + maxMessages : ""); const command = prepareCommand(cmd, profile); const terminalId = "run_" + profile + "_" + filename; const existTerminal = TERMINALS.get(terminalId); if (existTerminal) existTerminal.dispose(); - const terminal = vscode.window.createTerminal('Camel run: ' + filename ? filename : "project"); + const terminal = window.createTerminal('Camel run: ' + filename ? filename : "project"); TERMINALS.set(terminalId, terminal); terminal.show(); terminal.sendText(command); } export function camelJbangExport(runtime: string, directory: string, gav: string) { - const cmd = "export " + runtime - + (directory !== undefined ? " --directory="+directory : "") - + (gav !== undefined ? " --gav=" + gav : ""); + const cmd = "export " + runtime + + (directory !== undefined ? " --directory=" + directory : "") + + (gav !== undefined ? " --gav=" + gav : ""); const command = prepareCommand(cmd); const terminalId = "export " + runtime; const existTerminal = TERMINALS.get(terminalId); if (existTerminal) existTerminal.dispose(); - const terminal = vscode.window.createTerminal('Camel export'); + const terminal = window.createTerminal('Camel export'); TERMINALS.set(terminalId, terminal); terminal.show(); terminal.sendText(command); @@ -120,12 +114,12 @@ function executeJbangCommand(rootPath: string, command: string, callback: (code: if (code === 0) { // vscode.window.showInformationMessage(stdout); } else { - vscode.window.showErrorMessage(stderr); + window.showErrorMessage(stderr); } callback(code, stdout, stderr); }); } else { - vscode.window.showErrorMessage("JBang not found!"); + window.showErrorMessage("JBang not found!"); } } diff --git a/karavan-vscode/src/openapiView.ts b/karavan-vscode/src/openapiView.ts index f0c34b6..437cdd5 100644 --- a/karavan-vscode/src/openapiView.ts +++ b/karavan-vscode/src/openapiView.ts @@ -14,40 +14,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import vscode, { window } from "vscode"; +import { Command, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState, window, Event } from "vscode"; import * as path from "path"; import * as utils from "./utils"; -import * as commands from "./commands"; -import * as fs from "fs"; +import * as jbang from "./jbang"; import { ThemeIcon } from "vscode"; -import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml"; import { DesignerView } from "./designerView"; -import { Integration } from "karavan-core/lib/model/IntegrationDefinition"; -export class OpenApiView implements vscode.TreeDataProvider<OpenApiItem> { +export class OpenApiView implements TreeDataProvider<OpenApiItem> { constructor(private designer: DesignerView, private rootPath: string | undefined) { } - private _onDidChangeTreeData: vscode.EventEmitter<OpenApiItem | undefined | void> = new vscode.EventEmitter<OpenApiItem | undefined | void>(); - readonly onDidChangeTreeData: vscode.Event<OpenApiItem | undefined | void> = this._onDidChangeTreeData.event; + private _onDidChangeTreeData: EventEmitter<OpenApiItem | undefined | void> = new EventEmitter<OpenApiItem | undefined | void>(); + readonly onDidChangeTreeData: Event<OpenApiItem | undefined | void> = this._onDidChangeTreeData.event; - getTreeItem(element: OpenApiItem): vscode.TreeItem | Thenable<vscode.TreeItem> { + getTreeItem(element: OpenApiItem): TreeItem | Thenable<TreeItem> { return element; } - getChildren(element?: OpenApiItem): vscode.ProviderResult<OpenApiItem[]> { - const openapis: OpenApiItem[] = []; + getChildren(element?: OpenApiItem): ProviderResult<OpenApiItem[]> { if (this.rootPath) { - utils.getJsonFiles(this.rootPath).forEach(f => { - const json = fs.readFileSync(path.resolve(f)).toString('utf8'); - const api = JSON.parse(json); - if (api.openapi) { - const filename = path.basename(f); - openapis.push(new OpenApiItem(filename, f, api?.info?.title, { command: 'karavan.open-file', title: 'Open file', arguments: [{ fsPath: f }] })); - } - }) + return this.getOpenApiItems(this.rootPath); + } else { + return Promise.resolve([]); } - return Promise.resolve(openapis); + } + + async getOpenApiItems(dir: string) { + const result:OpenApiItem[] = [] + const files = await utils.getJsonFiles(dir); + for (let x in files){ + const filename = files[x]; + const readData = await utils.readFile(path.resolve(filename)); + const json = Buffer.from(readData).toString('utf8'); + const api = JSON.parse(json); + if (api.openapi) { + const basename = path.basename(filename); + result.push(new OpenApiItem(basename, filename, api?.info?.title, { command: 'karavan.open-file', title: 'Open file', arguments: [{ fsPath: filename }] })); + } + } + return result; } refresh(): void { @@ -55,15 +61,15 @@ export class OpenApiView implements vscode.TreeDataProvider<OpenApiItem> { } } -export class OpenApiItem extends vscode.TreeItem { +export class OpenApiItem extends TreeItem { constructor( public readonly title: string, public readonly fsPath: string, public readonly description: string, - public readonly command?: vscode.Command + public readonly command?: Command ) { - super(title, vscode.TreeItemCollapsibleState.None); + super(title, TreeItemCollapsibleState.None); this.tooltip = this.fsPath; } @@ -82,7 +88,7 @@ export async function selectRouteGeneration(rootPath: string, openApiFullPath: s placeHolder: 'Select option', }).then(option => { const generateRoutes: boolean = option !== undefined && option === options[0]; - commands.camelJbangGenerate(rootPath, openApiFullPath, fullPath, add, crd, generateRoutes); + jbang.camelJbangGenerate(rootPath, openApiFullPath, fullPath, add, crd, generateRoutes); }); } @@ -107,7 +113,7 @@ export async function selectFileName(rootPath?: string, openApi?: OpenApiItem) { * Create new file and add REST API */ export async function inputFileName(crd: boolean, rootPath?: string, openApi?: OpenApiItem) { - vscode.window.showInputBox({ + window.showInputBox({ title: "Generate REST API from " + openApi?.title, ignoreFocusOut: true, prompt: "Integration file name", diff --git a/karavan-vscode/src/utils.ts b/karavan-vscode/src/utils.ts index 4d55663..73f11a5 100644 --- a/karavan-vscode/src/utils.ts +++ b/karavan-vscode/src/utils.ts @@ -14,56 +14,61 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as vscode from "vscode"; -import * as fs from "fs"; import * as path from "path"; -import * as shell from 'shelljs'; +import { workspace, Uri, window, ExtensionContext, FileType} from "vscode"; import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml"; -import { ProjectModel } from "karavan-core/lib/model/ProjectModel"; import { ProjectModelApi } from "karavan-core/lib/api/ProjectModelApi"; export function save(relativePath: string, text: string) { - if (vscode.workspace.workspaceFolders) { - const uriFolder: vscode.Uri = vscode.workspace.workspaceFolders[0].uri; - const uriFile: vscode.Uri = vscode.Uri.file(path.join(uriFolder.path, relativePath)); - fs.writeFile(uriFile.fsPath, text, err => { - if (err) vscode.window.showErrorMessage("Error: " + err?.message); - }); + if (workspace.workspaceFolders) { + const uriFolder: Uri = workspace.workspaceFolders[0].uri; + write(path.join(uriFolder.path, relativePath), text); } } export function deleteFile(fullPath: string) { - if (vscode.workspace.workspaceFolders) { - fs.rmSync(path.resolve(fullPath)); + if (workspace.workspaceFolders) { + const uriFile: Uri = Uri.file(path.resolve(fullPath)); + workspace.fs.delete(uriFile); } } export function getRalativePath(fullPath: string): string { - const root = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.path : ""; - const normalizedRoot = vscode.Uri.file(root).fsPath; + const root = workspace.workspaceFolders ? workspace.workspaceFolders[0].uri.path : ""; + const normalizedRoot = Uri.file(root).fsPath; const relativePath = path.resolve(fullPath).replace(normalizedRoot + path.sep, ''); return relativePath; } -export function readKamelets(context: vscode.ExtensionContext): string[] { +export async function readKamelets(context: ExtensionContext) { const dir = path.join(context.extensionPath, 'kamelets'); - const yamls: string[] = fs.readdirSync(dir).filter(file => file.endsWith("yaml")).map(file => fs.readFileSync(dir + "/" + file, 'utf-8')); - try { - const kameletsPath: string | undefined = vscode.workspace.getConfiguration().get("Karavan.kameletsPath"); - if (kameletsPath && kameletsPath.trim().length > 0) { - const kameletsDir = path.isAbsolute(kameletsPath) ? kameletsPath : path.resolve(kameletsPath); - const customKamelets: string[] = fs.readdirSync(kameletsDir).filter(file => file.endsWith("yaml")).map(file => fs.readFileSync(kameletsDir + "/" + file, 'utf-8')); - if (customKamelets && customKamelets.length > 0) yamls.push(...customKamelets); - } - } catch (e) { - + const yamls: string[] = await readFilesInDirByExtension(dir, "yaml"); + const kameletsPath: string | undefined = workspace.getConfiguration().get("Karavan.kameletsPath"); + if (kameletsPath && kameletsPath.trim().length > 0) { + const kameletsDir = path.isAbsolute(kameletsPath) ? kameletsPath : path.resolve(kameletsPath); + const customKamelets: string[] = await readFilesInDirByExtension(kameletsDir, "yaml"); + if (customKamelets && customKamelets.length > 0) yamls.push(...customKamelets); } return yamls; } -export function readComponents(context: vscode.ExtensionContext): string[] { +async function readFilesInDirByExtension(dir: string, extension: string) { + const result: string[] = []; + const dirs: [string, FileType][] = await readDirectory(dir); + for (let d in dirs) { + const filename = dirs[d][0]; + if (filename.endsWith(extension)){ + const file = await readFile(dir + "/" + filename); + const code = Buffer.from(file).toString('utf8'); + result.push(code); + } + } + return result; +} + +export async function readComponents(context: ExtensionContext) { const dir = path.join(context.extensionPath, 'components'); - const jsons: string[] = fs.readdirSync(dir).filter(file => file.endsWith("json")).map(file => fs.readFileSync(dir + "/" + file, 'utf-8')); + const jsons: string[] = await readFilesInDirByExtension(dir, "json"); return jsons; } @@ -77,7 +82,7 @@ export function parceYaml(filename: string, yaml: string): [boolean, string?] { } export function disableStartHelp() { - const config = vscode.workspace.getConfiguration(); + const config = workspace.getConfiguration(); config.update("Karavan.showStartHelp", false); } @@ -91,60 +96,98 @@ export function nameFromTitle(title: string): string { return title.replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); } -export function getAllFiles(dirPath, arrayOfFiles: string[]): string[] { - const files = fs.readdirSync(dirPath) +export async function getAllFiles(dirPath, arrayOfFiles: string[]) { + const files = await readDirectory(dirPath) - arrayOfFiles = arrayOfFiles || [] + arrayOfFiles = arrayOfFiles || []; - files.forEach(function (file) { - if (fs.statSync(dirPath + "/" + file).isDirectory()) { - arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles) + for (let x in files){ + const filename = files[x][0]; + const type = files[x][1]; + if (type === FileType.Directory) { + arrayOfFiles = await getAllFiles(dirPath + "/" + filename, arrayOfFiles) } else { - arrayOfFiles.push(path.join(dirPath, "/", file)) + arrayOfFiles.push(path.join(dirPath, "/", filename)) } - }) + } return arrayOfFiles } -export function getYamlFiles(baseDir: string): string[] { +export async function getYamlFiles(baseDir: string) { const result: string[] = []; - getAllFiles(baseDir, []).filter(f => f.endsWith(".yaml")).forEach(f => { + (await getAllFiles(baseDir, [])).filter(f => f.endsWith(".yaml")).forEach(f => { result.push(f); }) return result; } -export function getPropertyFiles(baseDir: string): string[] { +export async function getPropertyFiles(baseDir: string) { const result: string[] = []; - getAllFiles(baseDir, []).filter(f => f.endsWith(".properties")).forEach(f => { + (await getAllFiles(baseDir, [])).filter(f => f.endsWith(".properties")).forEach(f => { result.push(f); }) return result; } -export function getJsonFiles(baseDir: string): string[] { +export async function getJsonFiles(baseDir: string) { const result: string[] = []; - getAllFiles(baseDir, []).filter(f => f.endsWith(".json")).forEach(f => { + (await getAllFiles(baseDir, [])).filter(f => f.endsWith(".json")).forEach(f => { result.push(f); }) return result; } -export function getIntegrationFiles(baseDir: string): string[] { - return getYamlFiles(baseDir).filter(f => { - const yaml = fs.readFileSync(path.resolve(f)).toString('utf8'); - return !f.startsWith(baseDir + path.sep + "target") && CamelDefinitionYaml.yamlIsIntegration(yaml); - }); +export async function getIntegrationFiles(baseDir: string) { + const result: string[] = [] + const files = await getYamlFiles(baseDir); + for (let x in files){ + const filename = files[x]; + const readData = await readFile(path.resolve(filename)); + const yaml = Buffer.from(readData).toString('utf8'); + if (!filename.startsWith(baseDir + path.sep + "target") && CamelDefinitionYaml.yamlIsIntegration(yaml)){ + result.push(yaml); + } + } + return result; } -export function getProperties(rootPath?: string): string { - if (rootPath) return fs.readFileSync(path.resolve(rootPath, "application.properties")).toString('utf8'); - else return fs.readFileSync(path.resolve("application.properties")).toString('utf8'); +export async function getProperties(rootPath?: string) { + if (rootPath) { + const readData = await readFile(path.resolve(rootPath, "application.properties")); + return Buffer.from(readData).toString('utf8'); + } else { + const readData = await readFile(path.resolve("application.properties")); + return Buffer.from(readData).toString('utf8'); + } } -export function getProfiles(rootPath?: string): string[] { - const text = getProperties(rootPath); +export async function getProfiles(rootPath?: string) { + const text = await getProperties(rootPath); const project = ProjectModelApi.propertiesToProject(text); return ProjectModelApi.getProfiles(project.properties); +} + +export async function stat(fullPath: string) { + const uriFile: Uri = Uri.file(fullPath); + return workspace.fs.stat(uriFile); +} + +export async function readDirectory(fullPath: string) { + const uriFile: Uri = Uri.file(fullPath); + return workspace.fs.readDirectory(uriFile); +} + +export async function readFile(fullPath: string) { + const uriFile: Uri = Uri.file(fullPath); + return workspace.fs.readFile(uriFile); +} + +export async function write(fullPath: string, code: string) { + const uriFile: Uri = Uri.file(fullPath); + workspace.fs.writeFile(uriFile, Buffer.from(code, 'utf8')) + .then( + value => {}, + reason => window.showErrorMessage("Error: " + reason) + ); } \ No newline at end of file diff --git a/karavan-vscode/webview/builder/PropertiesTable.tsx b/karavan-vscode/webview/builder/PropertiesTable.tsx index 9ea6569..27d5a88 100644 --- a/karavan-vscode/webview/builder/PropertiesTable.tsx +++ b/karavan-vscode/webview/builder/PropertiesTable.tsx @@ -78,8 +78,8 @@ export class PropertiesTable extends React.Component<Props, State> { </Modal>) } - getTextInputField(property: ProjectProperty, field: "key" | "value") { - return (<TextInput isRequired={true} className="text-field" type={"text"} id={"key"} name={"key"} + getTextInputField(property: ProjectProperty, field: "key" | "value", readOnly: boolean) { + return (<TextInput isDisabled={readOnly} isRequired={true} className="text-field" type={"text"} id={"key"} name={"key"} value={field === "key" ? property.key : property.value} onChange={val => this.changeProperty(property, field, val)}/>) } @@ -99,16 +99,18 @@ export class PropertiesTable extends React.Component<Props, State> { </Tr> </Thead> <Tbody> - {properties.map((property, idx: number) => ( - <Tr key={property.id}> - <Td noPadding width={20} dataLabel="key">{this.getTextInputField(property, "key")}</Td> - <Td noPadding width={10} dataLabel="value">{this.getTextInputField(property, "value")}</Td> - <Td noPadding isActionCell dataLabel="delete"> - <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"} - onClick={event => this.startDelete(property.id)}/> - </Td> - </Tr> - ))} + {properties.map((property, idx: number) => { + const readOnly = ["camel.jbang.gav", "camel.jbang.runtime"].includes(property.key); + return ( + <Tr key={property.id}> + <Td noPadding width={20} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td> + <Td noPadding width={10} dataLabel="value">{this.getTextInputField(property, "value", readOnly)}</Td> + <Td noPadding isActionCell dataLabel="delete"> + {!readOnly && <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"} + onClick={event => this.startDelete(property.id)}/>} + </Td> + </Tr> + )})} </Tbody> </TableComposable>} <Panel>