diff --git a/packages/backend/src/studio.ts b/packages/backend/src/studio.ts index f7950141b..80f941bf3 100644 --- a/packages/backend/src/studio.ts +++ b/packages/backend/src/studio.ts @@ -16,12 +16,11 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import { Uri, window, env, version } from '@podman-desktop/api'; +import { env, version } from '@podman-desktop/api'; import { satisfies, minVersion, coerce } from 'semver'; import type { ExtensionContext, TelemetryLogger, - WebviewOptions, WebviewPanel, WebviewPanelOnDidChangeViewStateEvent, } from '@podman-desktop/api'; @@ -32,7 +31,6 @@ import { GitManager } from './managers/gitManager'; import { TaskRegistry } from './registries/TaskRegistry'; import { CatalogManager } from './managers/catalogManager'; import { ModelsManager } from './managers/modelsManager'; -import fs from 'node:fs'; import { ContainerRegistry } from './registries/ContainerRegistry'; import { PodmanConnection } from './managers/podmanConnection'; import { LocalRepositoryRegistry } from './registries/LocalRepositoryRegistry'; @@ -43,6 +41,7 @@ import { CancellationTokenRegistry } from './registries/CancellationTokenRegistr import { engines } from '../package.json'; import { BuilderManager } from './managers/recipes/BuilderManager'; import { PodManager } from './managers/recipes/PodManager'; +import { initWebview } from './webviewUtils'; export const AI_LAB_COLLECT_GPU_COMMAND = 'ai-lab.gpu.collect'; @@ -95,50 +94,8 @@ export class Studio { this.telemetry.logUsage('start'); - const extensionUri = this.#extensionContext.extensionUri; - - // register webview - this.#panel = window.createWebviewPanel('studio', 'AI Lab', this.getWebviewOptions(extensionUri)); - this.#extensionContext.subscriptions.push(this.#panel); - - // update html - - const indexHtmlUri = Uri.joinPath(extensionUri, 'media', 'index.html'); - const indexHtmlPath = indexHtmlUri.fsPath; - - let indexHtml = await fs.promises.readFile(indexHtmlPath, 'utf8'); - - // replace links with webView Uri links - // in the content replace src with webview.asWebviewUri - const scriptLink = indexHtml.match(//g); - if (scriptLink) { - scriptLink.forEach(link => { - const src = link.match(/src="(.*?)"/); - if (src) { - const webviewSrc = this.#panel?.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', src[1])); - if (!webviewSrc) throw new Error('undefined webviewSrc'); - indexHtml = indexHtml.replace(src[1], webviewSrc.toString()); - } - }); - } - - // and now replace for css file as well - const cssLink = indexHtml.match(//g); - if (cssLink) { - cssLink.forEach(link => { - const href = link.match(/href="(.*?)"/); - if (href) { - const webviewHref = this.#panel?.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', href[1])); - if (!webviewHref) - throw new Error('Something went wrong while replacing links with webView Uri links: undefined webviewHref'); - indexHtml = indexHtml.replace(href[1], webviewHref.toString()); - } - }); - } - - console.log('updated indexHtml to', indexHtml); - - this.#panel.webview.html = indexHtml; + // init webview + this.#panel = await initWebview(this.#extensionContext.extensionUri); // Creating cancellation token registry const cancellationTokenRegistry = new CancellationTokenRegistry(); @@ -251,14 +208,4 @@ export class Studio { console.log('stopping AI Lab extension'); this.telemetry?.logUsage('stop'); } - - getWebviewOptions(extensionUri: Uri): WebviewOptions { - return { - // Enable javascript in the webview - // enableScripts: true, - - // And restrict the webview to only loading content from our extension's `media` directory. - localResourceRoots: [Uri.joinPath(extensionUri, 'media')], - }; - } } diff --git a/packages/backend/src/webviewUtils.spec.ts b/packages/backend/src/webviewUtils.spec.ts new file mode 100644 index 000000000..b1e19d0bc --- /dev/null +++ b/packages/backend/src/webviewUtils.spec.ts @@ -0,0 +1,78 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { beforeEach, expect, test, vi } from 'vitest'; +import { initWebview } from './webviewUtils'; +import type { Uri } from '@podman-desktop/api'; +import { promises } from 'node:fs'; + +vi.mock('@podman-desktop/api', async () => { + return { + Uri: class { + static joinPath = () => ({ fsPath: '.' }); + }, + window: { + createWebviewPanel: () => ({ + webview: { + html: '', + onDidReceiveMessage: vi.fn(), + postMessage: vi.fn(), + asWebviewUri: () => 'dummy-src', + }, + onDidChangeViewState: vi.fn(), + }), + }, + }; +}); + +vi.mock('node:fs', () => ({ + promises: { + readFile: vi.fn(), + }, +})); + +beforeEach(() => { + vi.resetAllMocks(); +}); + +test('panel should have file content as html', async () => { + vi.mocked(promises.readFile).mockImplementation(() => { + return Promise.resolve(''); + }); + + const panel = await initWebview({} as unknown as Uri); + expect(panel.webview.html).toBe(''); +}); + +test('script src should be replaced with asWebviewUri result', async () => { + vi.mocked(promises.readFile).mockImplementation(() => { + return Promise.resolve(''); + }); + + const panel = await initWebview({} as unknown as Uri); + expect(panel.webview.html).toBe(''); +}); + +test('links src should be replaced with asWebviewUri result', async () => { + vi.mocked(promises.readFile).mockImplementation(() => { + return Promise.resolve(''); + }); + + const panel = await initWebview({} as unknown as Uri); + expect(panel.webview.html).toBe(''); +}); diff --git a/packages/backend/src/webviewUtils.ts b/packages/backend/src/webviewUtils.ts new file mode 100644 index 000000000..2aea9b848 --- /dev/null +++ b/packages/backend/src/webviewUtils.ts @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { Uri, type WebviewOptions, type WebviewPanel, window } from '@podman-desktop/api'; +import { promises } from 'node:fs'; + +function getWebviewOptions(extensionUri: Uri): WebviewOptions { + return { + // Enable javascript in the webview + // enableScripts: true, + + // And restrict the webview to only loading content from our extension's `media` directory. + localResourceRoots: [Uri.joinPath(extensionUri, 'media')], + }; +} + +export async function initWebview(extensionUri: Uri): Promise { + // register webview + const panel = window.createWebviewPanel('studio', 'AI Lab', getWebviewOptions(extensionUri)); + + // update html + const indexHtmlUri = Uri.joinPath(extensionUri, 'media', 'index.html'); + const indexHtmlPath = indexHtmlUri.fsPath; + + let indexHtml = await promises.readFile(indexHtmlPath, 'utf8'); + + // replace links with webView Uri links + // in the content replace src with webview.asWebviewUri + const scriptLink = indexHtml.match(//g); + if (scriptLink) { + scriptLink.forEach(link => { + const src = link.match(/src="(.*?)"/); + if (src) { + const webviewSrc = panel.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', src[1])); + if (!webviewSrc) throw new Error('undefined webviewSrc'); + indexHtml = indexHtml.replace(src[1], webviewSrc.toString()); + } + }); + } + + // and now replace for css file as well + const cssLink = indexHtml.match(//g); + if (cssLink) { + cssLink.forEach(link => { + const href = link.match(/href="(.*?)"/); + if (href) { + const webviewHref = panel.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', href[1])); + if (!webviewHref) + throw new Error('Something went wrong while replacing links with webView Uri links: undefined webviewHref'); + indexHtml = indexHtml.replace(href[1], webviewHref.toString()); + } + }); + } + + panel.webview.html = indexHtml; + + return panel; +}