diff --git a/.vscode/launch.json b/.vscode/launch.json index 87a7054c3..a5baa6769 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,21 +17,6 @@ "DEBUGTELEMETRY": "v" } }, - { - "name": "Launch Extension + web view", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/dist/**/*.{js,mjs,cjs}", - ], - "preLaunchTask": "Watch: ESBuild with Web View", - "env": { - "DEBUGTELEMETRY": "v" - } - }, { "name": "Launch Extension + Docker", "type": "extensionHost", diff --git a/esbuild.mjs b/esbuild.mjs index ff680aba4..ffede9c08 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -4,4 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { autoEsbuildOrWatch, autoSelectEsbuildConfig } from '@microsoft/vscode-azext-eng/esbuild'; -await autoEsbuildOrWatch(autoSelectEsbuildConfig()); +import { copyFileSync, mkdirSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; + +const require = createRequire(import.meta.url); + +const pkgDist = dirname(require.resolve('@microsoft/vscode-azext-webview/package.json')) + '/dist'; +const outDir = 'dist/webview-assets'; +mkdirSync(outDir, { recursive: true }); +copyFileSync(join(pkgDist, 'views.js'), join(outDir, 'views.js')); +copyFileSync(join(pkgDist, 'views.css'), join(outDir, 'views.css')); + +const configs = autoSelectEsbuildConfig(); +await autoEsbuildOrWatch(configs); diff --git a/esbuild.views.mjs b/esbuild.views.mjs deleted file mode 100644 index e1055b0b7..000000000 --- a/esbuild.views.mjs +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isAutoDebug, isAutoWatch } from '@microsoft/vscode-azext-eng/esbuild'; -import esbuild from 'esbuild'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const outdir = path.resolve(__dirname, 'dist'); - -const commonConfig = { - entryPoints: { - views: path.resolve(__dirname, 'src/webviews/index.tsx'), - }, - - bundle: true, - outdir: outdir, - format: 'esm', - platform: 'browser', //todo: remove (platform is auto browser if not specified) - target: 'es2022', - sourcemap: isAutoWatch, - minify: !isAutoWatch, - metafile: isAutoDebug, - splitting: false, - - inject: [path.resolve(__dirname, 'react-shim.js')], - - loader: { - '.ts': 'ts', - '.tsx': 'tsx', - '.css': 'css', - '.scss': 'css', - '.ttf': 'dataurl', - '.woff': 'dataurl', - '.woff2': 'dataurl', - }, - - plugins: [ - { - name: 'sass', - setup(build) { - build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => { - const sass = await import('sass'); - const result = sass.compile(args.path); - return { - contents: result.css, - loader: 'css', - }; - }); - }, - }, - ], - logLevel: 'info' -}; - -const ctx = await esbuild.context({ - ...commonConfig, - outdir, -}); - -// Always do an initial rebuild to ensure views.js exists -await ctx.rebuild(); - -if (isAutoWatch) { - await ctx.watch(); - console.log('Watching webview bundle...'); - await new Promise(() => { }); -} else { - await ctx.rebuild(); - await ctx.dispose(); -} diff --git a/package-lock.json b/package-lock.json index e1e1cc991..5ae20373f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@microsoft/vscode-azext-azureutils": "^4.2.0", "@microsoft/vscode-azext-github": "^2.0.1", "@microsoft/vscode-azext-utils": "^4.1.0", + "@microsoft/vscode-azext-webview": "^1.0.0", "@microsoft/vscode-azureresources-api": "^3.1.0", "@vscode/codicons": "^0.0.45", "dayjs": "^1.11.3", @@ -3472,7 +3473,7 @@ "dayjs": "^1.11.19", "html-to-text": "^9.0.5", "semver": "^7.7.4", - "vscode-tas-client": "^0.1.84" + "vscode-tas-client": "^0.2.1" }, "engines": { "vscode": "^1.105.0" @@ -3487,6 +3488,35 @@ } } }, + "node_modules/@microsoft/vscode-azext-webview": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-webview/-/vscode-azext-webview-1.1.0.tgz", + "integrity": "sha512-EYispeNjqx/6Kn5TfhPklDMXRd2t6xKBxaoSXfQNBhvF246ThA8DfZ++xh4PjU2DKPkZpSFdBwADafEnxsm0uQ==", + "license": "MIT", + "dependencies": { + "@fluentui/react-components": "^9.56.2", + "@fluentui/react-icons": "^2.0.265", + "@microsoft/vscode-azext-utils": "^4.1.0", + "@vscode/codicons": "0.0.38", + "dompurify": "^3.4.3", + "marked": "^18.0.3", + "monaco-editor": "~0.51.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "sass": "~1.79.6", + "sass-loader": "^16.0.2", + "uuid": "^14.0.0" + }, + "engines": { + "vscode": "^1.105.0" + } + }, + "node_modules/@microsoft/vscode-azext-webview/node_modules/@vscode/codicons": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.38.tgz", + "integrity": "sha512-g4uInb63ECu0URD0DlyBzwXu2TksWUOaMSzQcKRoc3mXSNrjR3wC7EB3PA0Iq83eK8KCEKoVzkC1clt/r1euqA==", + "license": "CC-BY-4.0" + }, "node_modules/@microsoft/vscode-azureresources-api": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@microsoft/vscode-azureresources-api/-/vscode-azureresources-api-3.1.0.tgz", @@ -4529,6 +4559,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -6278,6 +6315,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz", + "integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -8186,6 +8232,18 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10354,10 +10412,13 @@ } }, "node_modules/tas-client": { - "version": "0.2.33", - "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", - "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==", - "license": "MIT" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.4.1.tgz", + "integrity": "sha512-cNwPitLm+VvWtUYyZu6YOfP1qwgwRzqTHC7StkhCcI4zHp3l/uRAG+29LV4SpRnagd+jAEKmJFOIV6KwZDiA6w==", + "license": "MIT", + "engines": { + "node": ">=22" + } }, "node_modules/terminal-link": { "version": "4.0.0", @@ -10804,12 +10865,12 @@ "license": "MIT" }, "node_modules/vscode-tas-client": { - "version": "0.1.84", - "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz", - "integrity": "sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.2.1.tgz", + "integrity": "sha512-htmjTFhOkM213OKv42cyCboc6D4vhZTdGHLb+GiNvFeK66eBdTbec2P41rgIg9YxnBgcDOlkvTElRorm3VitXw==", "license": "MIT", "dependencies": { - "tas-client": "0.2.33" + "tas-client": "^0.4.0" }, "engines": { "vscode": "^1.85.0" diff --git a/package.json b/package.json index 893fcf441..9193cfb1e 100644 --- a/package.json +++ b/package.json @@ -823,10 +823,9 @@ } }, "scripts": { - "build": "npm run build:esbuild & npm run build:check & npm run build:webview", + "build": "npm run build:esbuild & npm run build:check", "build:esbuild": "node esbuild.mjs", "build:check": "tsc --noEmit", - "build:webview": "node esbuild.views.mjs", "package": "vsce package --githubBranch main --no-dependencies", "lint": "eslint --max-warnings 0", "test": "vscode-test" @@ -865,6 +864,7 @@ "@microsoft/vscode-azext-azureutils": "^4.2.0", "@microsoft/vscode-azext-github": "^2.0.1", "@microsoft/vscode-azext-utils": "^4.1.0", + "@microsoft/vscode-azext-webview": "^1.0.0", "@microsoft/vscode-azureresources-api": "^3.1.0", "@vscode/codicons": "^0.0.45", "dayjs": "^1.11.3", diff --git a/src/commands/copilot/deployWithCopilot.ts b/src/commands/copilot/deployWithCopilot.ts index 243ac2008..6cd4ac765 100644 --- a/src/commands/copilot/deployWithCopilot.ts +++ b/src/commands/copilot/deployWithCopilot.ts @@ -5,9 +5,9 @@ import { type IActionContext, } from "@microsoft/vscode-azext-utils"; import { CopilotUserInput, markAsCopilotUserInput } from "@microsoft/vscode-azext-utils/copilot"; +import { SharedState } from '@microsoft/vscode-azext-webview'; import * as vscode from 'vscode'; import { type ContainerAppItem } from "../../tree/ContainerAppItem"; -import { SharedState } from "../../webviews/OpenConfirmationViewStep"; import { deployContainerApp } from "../deployContainerApp/deployContainerApp"; export async function deployWithCopilot(context: IActionContext, node: ContainerAppItem): Promise { diff --git a/src/commands/deployContainerApp/deployContainerApp.ts b/src/commands/deployContainerApp/deployContainerApp.ts index af8b972d5..6ba383d96 100644 --- a/src/commands/deployContainerApp/deployContainerApp.ts +++ b/src/commands/deployContainerApp/deployContainerApp.ts @@ -5,6 +5,7 @@ import { KnownActiveRevisionsMode } from "@azure/arm-appcontainers"; import { AzExtUserInput, AzureWizard, createSubscriptionContext, isCopilotUserInput, nonNullProp, type AzureWizardPromptStep, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { OpenConfirmationViewStep, openLoadingViewPanel, SharedState } from '@microsoft/vscode-azext-webview'; import { type AzureSubscription } from "@microsoft/vscode-azureresources-api"; import { acrDomain, ImageSource, SupportedRegistries } from "../../constants"; import { type ContainerAppItem } from "../../tree/ContainerAppItem"; @@ -14,8 +15,6 @@ import { getManagedEnvironmentFromContainerApp } from "../../utils/getResourceUt import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep"; import { localize } from "../../utils/localize"; import { pickContainerApp } from "../../utils/pickItem/pickContainerApp"; -import { OpenConfirmationViewStep, SharedState } from "../../webviews/OpenConfirmationViewStep"; -import { openLoadingViewPanel } from "../../webviews/OpenLoadingViewStep"; import { CommandAttributes } from "../CommandAttributes"; import { ContainerAppOverwriteConfirmStep } from "../ContainerAppOverwriteConfirmStep"; import { ContainerAppListStep } from "../createContainerApp/ContainerAppListStep"; diff --git a/src/extension.ts b/src/extension.ts index 9d166085b..dd169b230 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,7 +8,9 @@ import { registerAzureUtilsExtensionVariables } from '@microsoft/vscode-azext-azureutils'; import { registerGitHubExtensionVariables } from '@microsoft/vscode-azext-github'; import { TreeElementStateManager, callWithTelemetryAndErrorHandling, createApiProvider, createAzExtOutputChannel, createExperimentationService, registerUIExtensionVariables, type IActionContext, type apiUtils } from '@microsoft/vscode-azext-utils'; +import { registerWebviewExtensionVariables } from '@microsoft/vscode-azext-webview'; import { AzExtResourceType, type AzureResourcesExtensionApi } from '@microsoft/vscode-azureresources-api'; +import * as path from 'path'; import { v4 as uuid } from 'uuid'; import * as vscode from 'vscode'; import { createContainerAppsApiProvider } from './commands/api/createContainerAppsApiProvider'; @@ -31,6 +33,10 @@ export async function activate(context: vscode.ExtensionContext, perfStats: { lo registerUIExtensionVariables(ext); registerAzureUtilsExtensionVariables(ext); registerGitHubExtensionVariables(ext); + registerWebviewExtensionVariables({ + context, + webviewAssetsDir: path.join(context.extensionPath, 'dist', 'webview-assets'), + }); return await callWithTelemetryAndErrorHandling('containerApps.activate', async (activateContext: IActionContext) => { activateContext.errorHandling.rethrow = true; diff --git a/src/webviews/Channel/Channel.ts b/src/webviews/Channel/Channel.ts deleted file mode 100644 index 049d46cd0..000000000 --- a/src/webviews/Channel/Channel.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type Transport } from "./Transport"; - -export const isChannelPayload = (payload: unknown): payload is ChannelPayload => { - return typeof payload === 'object' && payload !== null && 'type' in payload; -}; - -export type ChannelCallback = (...payload: unknown[]) => ReturnType | Promise; - -export type ChannelPayload = - | { - type: 'event'; - name: string; - params: unknown[]; - } - | { - type: 'request'; - name: string; - params: unknown[]; - } - | { - type: 'response'; - value: unknown; - } - | { - type: 'error'; - message: string; - }; - -export interface ChannelMessage { - id: string; // uuid to connect request and response - payload: ChannelPayload; -} - -// TODO: Ideally Channel should receive an API schema or API interface to check types -// Also constructor should receive API implementation of schema or interface -export interface Channel { - readonly name: string; - readonly transport: Transport; - - postMessage(message: ChannelMessage | ChannelPayload): PromiseLike; - on(event: string, callback: ChannelCallback): Channel; - once(event: string, callback: ChannelCallback): Channel; - off(event: string, callback: ChannelCallback): Channel; - removeAllListeners(event?: string): Channel; - dispose(): void; -} diff --git a/src/webviews/Channel/CommonChannel.ts b/src/webviews/Channel/CommonChannel.ts deleted file mode 100644 index 78ddbb51b..000000000 --- a/src/webviews/Channel/CommonChannel.ts +++ /dev/null @@ -1,257 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { v4 as uuid } from 'uuid'; -import { - isChannelPayload, - type Channel, - type ChannelCallback, - type ChannelMessage, - type ChannelPayload, -} from './Channel'; - -import { Deferred, type DeferredPromise } from './DeferredPromise'; -import { type Transport, type TransportMessage } from './Transport'; - -type ListenerCallback = { - type: 'on' | 'once'; - callback: ChannelCallback; - calledTimes?: number; -}; -type Request = { - expiresAt: number; - deferred: DeferredPromise; -}; - -type ErrorWithMessage = { - message: string; -}; - -function isErrorWithMessage(error: unknown): error is ErrorWithMessage { - return ( - typeof error === 'object' && - error !== null && - 'message' in error && - typeof (error as Record).message === 'string' - ); -} - -function toErrorWithMessage(maybeError: unknown): ErrorWithMessage { - if (isErrorWithMessage(maybeError)) {return maybeError;} - - try { - return new Error(JSON.stringify(maybeError)); - } catch { - // fallback in case there's an error stringifying the maybeError - // like with circular references for example. - return new Error(String(maybeError)); - } -} - -export function getErrorMessage(error: unknown) { - return toErrorWithMessage(error).message; -} - -export class CommonChannel implements Channel { - private listeners: Record = {}; - private pendingRequests: Record = {}; - - private readonly handleMessageInternal: (msg: TransportMessage) => void; - private readonly timeoutId: NodeJS.Timeout; - - private isDisposed = false; - - constructor( - public readonly name: string, - public readonly transport: Transport, - ) { - this.handleMessageInternal = (msg: TransportMessage) => this.handleMessage(msg); - this.transport.on(this.handleMessageInternal); - - // Clean up pending requests every 500ms (we don't expect a lot of requests and accuracy is not critical) - this.timeoutId = setInterval(() => { - const now = Date.now(); - Object.entries(this.pendingRequests).forEach(([id, request]) => { - if (request.expiresAt < now) { - request.deferred.reject(new Error(`Request timed out`)); - delete this.pendingRequests[id]; - } - }); - }, 500); - } - - postMessage(message: ChannelPayload): PromiseLike; - postMessage(message: ChannelMessage): PromiseLike; - postMessage(message: ChannelMessage | ChannelPayload): PromiseLike { - if (this.isDisposed) { - return Promise.reject(new Error('Channel disposed')); - } - - const now = Date.now(); - const id = 'id' in message ? message.id : uuid(); - const payload = 'id' in message ? message.payload : message; - - if (payload.type === 'request') { - const deferred = new Deferred(); - this.pendingRequests[id] = { expiresAt: now + 15000, deferred }; - // Automatically remove pending request from the list to clean up memory - void deferred.promise.then( - () => delete this.pendingRequests[id], - () => delete this.pendingRequests[id], - ); - } - - void this.transport.post({ id, payload }); - - return this.pendingRequests[id]?.deferred.promise ?? Promise.resolve(true); - } - - on(event: string, callback: ChannelCallback): Channel { - if (this.isDisposed) { - return this; - } - - if (!this.listeners[event]) { - this.listeners[event] = []; - } - this.listeners[event].push({ type: 'on', callback, calledTimes: 0 }); - - return this; - } - - once(event: string, callback: ChannelCallback): Channel { - if (this.isDisposed) { - return this; - } - - if (!this.listeners[event]) { - this.listeners[event] = []; - } - this.listeners[event].push({ type: 'once', callback, calledTimes: 0 }); - - return this; - } - - off(event: string, callback: ChannelCallback): Channel { - if (this.isDisposed) { - return this; - } - - if (this.listeners[event]) { - this.listeners[event] = this.listeners[event].filter((cb) => cb.callback !== callback); - } - - return this; - } - - removeAllListeners(event?: string): Channel { - if (this.isDisposed) { - return this; - } - - if (event) { - delete this.listeners[event]; - } else { - this.listeners = {}; - } - - return this; - } - - dispose(): void { - this.isDisposed = true; - - // Clean up listeners first to avoid any messages being processed - this.listeners = {}; - - // since the transport is shared, we don't dispose it here - this.transport.off(this.handleMessageInternal); - - // Clean up pending requests - clearTimeout(this.timeoutId); - Object.values(this.pendingRequests).forEach((request) => { - request.deferred.reject(new Error('Channel disposed')); - }); - - this.pendingRequests = {}; - } - - private handleMessage(msg: TransportMessage): void { - try { - if (typeof msg.payload !== 'object' || !msg.payload || !isChannelPayload(msg.payload)) { - console.warn('Received message with unknown payload', msg); - return; - } - - const payload = msg.payload as ChannelPayload; - - if (payload.type === 'response' || payload.type === 'error') { - const request = this.pendingRequests[msg.id]; - if (!request) { - console.warn('Received response for unknown request', msg); - return; - } - - if (payload.type === 'response') { - request.deferred.resolve(payload.value); - } else { - request.deferred.reject(new Error(payload.message)); - } - - delete this.pendingRequests[msg.id]; - } - - if (payload.type === 'event' || payload.type === 'request') { - const callbacks = this.listeners[payload.name]; - - callbacks.forEach((cb) => { - // One callback throwing an error should not prevent other callbacks from being called - try { - cb.calledTimes ??= 0; - cb.calledTimes++; - - if (cb.type === 'once' && cb.calledTimes > 1) { - return; - } - - void Promise.resolve(cb.callback.call(undefined, ...payload.params)) - .then((returnValue) => { - if (payload.type === 'request') { - void this.postMessage({ - id: msg.id, - payload: { type: 'response', value: returnValue }, - }); - } - }) - .catch((error) => { - if (payload.type === 'request') { - void this.postMessage({ - id: msg.id, - payload: { type: 'error', message: getErrorMessage(error) }, - }); - } - }) - .finally(() => { - if (cb.type === 'once') { - this.off(payload.name, cb.callback); - } - }); - } catch (error) { - const errorMessage = getErrorMessage(error); - console.error(`[VSCodeTransport] Error occurred calling callback`, errorMessage); - } - }); - } - } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (this.pendingRequests[msg.id]) { - this.pendingRequests[msg.id].deferred.reject( - new Error(`Error occurred handling received message : ${errorMessage}`), - ); - } - console.error(errorMessage); - } - } -} diff --git a/src/webviews/Channel/DeferredPromise.ts b/src/webviews/Channel/DeferredPromise.ts deleted file mode 100644 index 6d1d1583c..000000000 --- a/src/webviews/Channel/DeferredPromise.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export interface DeferredPromise { - /** - The deferred promise. - */ - readonly promise: Promise; - - /** - Resolves the promise with a value or the result of another promise. - - @param value - The value to resolve the promise with. - */ - resolve(this: void, value?: ValueType | PromiseLike): void; - - /** - Reject the promise with a provided reason or error. - - @param reason - The reason or error to reject the promise with. - */ - reject(this: void, reason?: unknown): void; -} - -export class Deferred implements DeferredPromise { - private _resolve!: (value?: ValueType | PromiseLike) => void; - private _reject!: (reason?: unknown) => void; - - public readonly promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - }); - - resolve(value?: ValueType | PromiseLike): void { - this._resolve(value); - } - - reject(reason?: unknown): void { - this._reject(reason); - } -} diff --git a/src/webviews/Channel/Transport.ts b/src/webviews/Channel/Transport.ts deleted file mode 100644 index a4c1a7e1b..000000000 --- a/src/webviews/Channel/Transport.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export const isTransportMessage = (msg: unknown): msg is TransportMessage => { - return typeof msg === 'object' && msg !== null && 'id' in msg && 'payload' in msg; -}; - -export interface TransportMessage { - id: string; // uuid to connect request and response - payload: unknown; -} - -export interface Transport { - readonly name: string; - - post(message: TransportMessage): PromiseLike; - on(callback: (message: TransportMessage) => void): void; - off(callback: (message: TransportMessage) => void): void; - dispose(): void; -} diff --git a/src/webviews/Channel/WebviewChannel.ts b/src/webviews/Channel/WebviewChannel.ts deleted file mode 100644 index 67bfcce6a..000000000 --- a/src/webviews/Channel/WebviewChannel.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type WebviewApi } from 'vscode-webview'; -import { CommonChannel } from './CommonChannel'; -import { WebviewTransport } from './WebviewTransport'; - -export class WebviewChannel extends CommonChannel { - constructor(webviewApi: WebviewApi) { - const transport = new WebviewTransport(webviewApi); - super('webview', transport); - } - - dispose(): void { - super.dispose(); - this.transport.dispose(); - } -} diff --git a/src/webviews/Channel/WebviewTransport.ts b/src/webviews/Channel/WebviewTransport.ts deleted file mode 100644 index cb66e28b8..000000000 --- a/src/webviews/Channel/WebviewTransport.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type WebviewApi } from 'vscode-webview'; -import { isTransportMessage, type Transport, type TransportMessage } from './Transport'; - -export class WebviewTransport implements Transport { - public readonly name = 'WebviewTransport'; - - private listeners: ((message: TransportMessage) => void)[] = []; - - constructor(private readonly vscodeApi: WebviewApi) { - window.addEventListener('message', (event) => this.handleMessage(event.data)); - } - - post(message: TransportMessage): PromiseLike { - this.vscodeApi.postMessage(message); - return Promise.resolve(true); // Can't actually know if the message was sent - } - - on(callback: (message: TransportMessage) => void): void { - this.listeners.push(callback); - } - - off(callback: (message: TransportMessage) => void): void { - this.listeners = this.listeners.filter((cb) => cb !== callback); - } - - dispose(): void { - this.listeners = []; - } - - private handleMessage(msg: unknown): void { - try { - if (!isTransportMessage(msg)) { - console.warn(`[WebviewTransport] Received message that is not a transport message`, msg); - return; - } - - const message = msg as TransportMessage; - this.listeners.forEach((cb) => { - // One callback throwing an error should not prevent other callbacks from being called - try { - cb(message); - } catch (error) { - console.error(`[WebviewTransport] Error occurred calling callback`, error); - } - }); - } catch (error) { - console.error(`[WebviewTransport] Error occurred handling received message`, error); - } - } -} diff --git a/src/webviews/ConfirmationView.tsx b/src/webviews/ConfirmationView.tsx deleted file mode 100644 index cad8795a8..000000000 --- a/src/webviews/ConfirmationView.tsx +++ /dev/null @@ -1,161 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Button, createTableColumn, DataGrid, DataGridBody, DataGridCell, DataGridRow, TableCellLayout, Tooltip, type OnSelectionChangeData, type SelectionItemId, type TableColumnDefinition } from '@fluentui/react-components'; -import * as React from 'react'; -import { useContext, useState, type JSX } from 'react'; -import './styles/confirmationView.scss'; -import { type ConfirmationViewControllerType } from './ConfirmationViewController'; -import { useConfiguration } from './useConfiguration'; -import { ConfirmationViewCommands } from './webviewConstants'; -import { WebviewContext } from './WebviewContext'; - -type Item = { - name: string; - value: string; -} - -function createNewSetWithItem(set: Set, first: boolean): Set { - if (first) { - const firstElement = Array.from(set)[0]; - return new Set([firstElement]); - } else { - const lastElement = Array.from(set)[set.size - 1]; - return new Set([lastElement]); - } -} - -export const ConfirmationView = (): JSX.Element => { - const [buttonName, setName] = useState('Confirm'); - const [selectedItems, setSelectedItems] = useState>(new Set()); - const { vscodeApi } = useContext(WebviewContext); - - const configuration = useConfiguration(); - const title = configuration.title; - const description = configuration.description; - const commandName = configuration.commandName; - const confirmClicked = () => { - const selectedItemIndex = Number(Array.from(selectedItems)[0]); - const itemsToClear = configuration.items.length - selectedItemIndex; - vscodeApi.postMessage({ - command: ConfirmationViewCommands.Confirm, - itemsToClear: itemsToClear - }); - console.log(itemsToClear); - }; - - const copilotClicked = (name: string, value: string) => { - vscodeApi.postMessage({ - command: ConfirmationViewCommands.Copilot, - name: name, - value: value, - commandName: commandName - }); - }; - - const cancelClicked = () => { - vscodeApi.postMessage({ - command: ConfirmationViewCommands.Cancel - }); - }; - - const onChange = (_ev: React.MouseEvent, data: OnSelectionChangeData) => { - setName(data.selectedItems.size > 0 ? 'Edit' : 'Confirm'); - setSelectedItems(data.selectedItems.size > 0 ? createNewSetWithItem(data.selectedItems, false) : createNewSetWithItem(data.selectedItems, true)); - }; - - const columnSizingOptions = { - name: { - defaultWidth: 200, - minWidth: 200, - }, - value: { - defaultWidth: 275, - minWidth: 275, - } - }; - - const columns: TableColumnDefinition[] = [ - createTableColumn({ - columnId: 'name', - renderCell: (item: Item) => { - return {item.name}; - }, - }), - createTableColumn({ - columnId: 'value', - renderCell: (item: Item) => { - return {item.value}; - }, - }), - createTableColumn({ - columnId: 'copilot', - renderCell: (item: Item) => { - return - - - - ; - }, - }) - ]; - - const DataGridComponent: React.FC<{ - context: ConfirmationViewControllerType, - selectedItems?: Set | undefined, - onSelectionChange: (_ev: React.MouseEvent, data: OnSelectionChangeData) => void, - }> = ({ context, selectedItems, onSelectionChange },) => { - return ( - - > - {({ item, rowId }) => ( - - key={rowId}> - {({ renderCell }) => ( - {renderCell(item)} - )} - - )} - - - ); - }; - - return ( -
-
-

{title}

-
{description}
-
- -
- -
- - -
-
-
- ); -}; diff --git a/src/webviews/ConfirmationViewController.ts b/src/webviews/ConfirmationViewController.ts deleted file mode 100644 index 9479c2a2d..000000000 --- a/src/webviews/ConfirmationViewController.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { type ConfirmationViewProperty } from "@microsoft/vscode-azext-utils"; -import { ViewColumn } from "vscode"; -import { ext } from "../extensionVariables"; -import { WebviewController } from "./extension-server/WebviewController"; - -export type ConfirmationViewControllerType = { - title: string; - tabTitle: string; - description: string; - commandName: string; // only used to help construct the copilot prompt - items: Array -} - -export class ConfirmationViewController extends WebviewController { - constructor(viewConfig: ConfirmationViewControllerType) { - super(ext.context, viewConfig.tabTitle, 'confirmationView', viewConfig, ViewColumn.Active); - } -} diff --git a/src/webviews/LoadingView.tsx b/src/webviews/LoadingView.tsx deleted file mode 100644 index 06c6c1b1b..000000000 --- a/src/webviews/LoadingView.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Spinner } from "@fluentui/react-components"; -import { useEffect, useState } from 'react'; -import { LoadingViewProgressItem } from "./LoadingViewController"; -import './styles/loadingView.scss'; -import { LoadingViewCommands } from './webviewConstants'; - -type ProgressMessage = { - command: LoadingViewCommands.AddProgressItem; - name: string; -}; - -export const LoadingView = () => { - const [progressItems, setProgressItems] = useState([]); - - useEffect(() => { - const handleMessage = (event: MessageEvent) => { - const message = event.data; - if (message.command === LoadingViewCommands.AddProgressItem) { - setProgressItems(prev => [ - ...prev, - { name: message.name } - ]); - } - }; - - window.addEventListener('message', handleMessage); - return () => window.removeEventListener('message', handleMessage); - }, []); - - return ( -
-
- -
- {progressItems.length > 0 ? ( - progressItems.map((item, index) => ( -
- - {item.name} -
- )) - ) : ( - Array.from({ length: 3 }).map((_, index) => ( -
- - -
- )) - )} -
-
-
- ); -}; - diff --git a/src/webviews/LoadingViewController.ts b/src/webviews/LoadingViewController.ts deleted file mode 100644 index 7b7d516f1..000000000 --- a/src/webviews/LoadingViewController.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { ViewColumn } from "vscode"; -import { ext } from "../extensionVariables"; -import { WebviewController } from "./extension-server/WebviewController"; -import { LoadingViewCommands } from "./webviewConstants"; - -export type LoadingViewProgressItem = { - name: string; -} - -export type LoadingViewControllerType = { - title: string; - items?: LoadingViewProgressItem[]; -} - -export class LoadingViewController extends WebviewController { - constructor(viewConfiguration: LoadingViewControllerType) { - super(ext.context, viewConfiguration.title, 'loadingView', viewConfiguration, ViewColumn.Active); - } - - public addProgressItem(name: string): void { - void this.panel.webview.postMessage({ - command: LoadingViewCommands.AddProgressItem, - name - }); - } -} diff --git a/src/webviews/OpenConfirmationViewStep.ts b/src/webviews/OpenConfirmationViewStep.ts deleted file mode 100644 index 4ca578af6..000000000 --- a/src/webviews/OpenConfirmationViewStep.ts +++ /dev/null @@ -1,131 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { AzExtUserInput, AzureWizardPromptStep, GoBackError, isCopilotUserInput, openUrl, UserCancelledError, type ConfirmationViewProperty, type IActionContext } from "@microsoft/vscode-azext-utils"; -import * as vscode from 'vscode'; -import { localize } from "../utils/localize"; -import { ConfirmationViewController } from "./ConfirmationViewController"; -import { resetSharedState, SharedState } from "./SharedViewState"; - -export { SharedState } from "./SharedViewState"; - -export class OpenConfirmationViewStep extends AzureWizardPromptStep { - private readonly viewConfig: () => ConfirmationViewProperty[]; - private readonly title: string; - private readonly tabTitle: string; - private readonly description: string; - private readonly commandName: string; - - public constructor(title: string, tabTitle: string, description: string, commandName: string, viewConfig: () => ConfirmationViewProperty[]) { - super(); - this.title = title; - this.tabTitle = tabTitle; - this.description = description; - this.viewConfig = viewConfig; - this.commandName = commandName; - } - - public async prompt(context: T): Promise { - if (SharedState.currentPanel) { - SharedState.currentPanel.dispose(); - SharedState.currentPanel = undefined; - } - - const confirmationView = new ConfirmationViewController({ - title: this.title, - tabTitle: this.tabTitle, - description: this.description, - commandName: this.commandName, - items: this.viewConfig() - }); - - confirmationView.revealToForeground(vscode.ViewColumn.Active); - - await new Promise((resolve, reject) => { - confirmationView.onDisposed(async () => { - try { - if (SharedState.itemsToClear > 0) { - context.telemetry.properties.editingPicks = 'true'; - SharedState.editingPicks = true; - if (isCopilotUserInput(context)) { - context.ui = new AzExtUserInput(context); - } - throw new GoBackError(SharedState.itemsToClear); - } - - if (SharedState.cancelled) { - context.telemetry.properties.cancelClicked = 'true'; - throw new UserCancelledError('openConfirmationViewStep'); - } - } catch (error) { - reject(error); - } finally { - const greatButton: vscode.MessageItem = { - title: 'Great', - }; - const notHelpfulButton: vscode.MessageItem = { - title: 'Unhelpful' - }; - const surveyButton: vscode.MessageItem = { - title: localize('provideFeedback', 'Provide Feedback'), - - }; - const buttons = [greatButton, notHelpfulButton, surveyButton]; - - if (SharedState.editingPicks && SharedState.itemsToClear === 0 && SharedState.copilotClicked) { - resolve(); - const message = localize('editingPicksandCopilotMessage', 'How was your experience using Copilot and revising your selections?'); - const result = await vscode.window.showInformationMessage(message, ...buttons); - await confirmationViewButtonActions(context, result); - } else if (!SharedState.cancelled && !SharedState.copilotClicked && !SharedState.editingPicks) { - resolve(); - const message = localize('confirmMessage', 'How was your experience using the summary view?'); - const result = await vscode.window.showInformationMessage(message, ...buttons); - await confirmationViewButtonActions(context, result); - } else if (SharedState.editingPicks && SharedState.itemsToClear === 0) { - resolve(); - const message = localize('editingPicksMessage', 'How was your experience revising your selections?'); - const result = await vscode.window.showInformationMessage(message, ...buttons); - await confirmationViewButtonActions(context, result); - } else if (SharedState.copilotClicked && !SharedState.cancelled) { - resolve(); - context.telemetry.properties.isCopilotEvent = 'true'; - context.telemetry.properties.copilotClicked = 'true'; - const message = localize('copilotMessage', 'How was your experience using Copilot and the summary view?'); - const result = await vscode.window.showInformationMessage(message, ...buttons); - if (result === surveyButton) { - await confirmationViewButtonActions(context, result); - } else if (result === greatButton) { - context.telemetry.properties.copilotButtonHelpful = 'true'; - } else if (result === notHelpfulButton) { - context.telemetry.properties.copilotButtonNotHelpful = 'true'; - } - } else { - resolve(); - } - } - }); - // reset the shared state - resetSharedState(); - }); - } - - public shouldPrompt(): boolean { - return this.viewConfig().length > 0; - } -} - -export async function confirmationViewButtonActions(context: IActionContext, result: vscode.MessageItem | undefined): Promise { - if (result) { - if (result.title === 'Great') { - context.telemetry.properties.confirmationViewHelpful = 'true'; - } else if (result.title === 'Unhelpful') { - context.telemetry.properties.confirmationViewNotHelpful = 'true'; - } else if (result.title === 'Provide Feedback') { - const confirmationViewSurveyLink = 'https://aka.ms/AAx0tw1'; - await openUrl(confirmationViewSurveyLink); - } - } -} diff --git a/src/webviews/OpenLoadingViewStep.ts b/src/webviews/OpenLoadingViewStep.ts deleted file mode 100644 index 9842f3c88..000000000 --- a/src/webviews/OpenLoadingViewStep.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { AzureWizardPromptStep, runGenericPromptStep, type IActionContext } from "@microsoft/vscode-azext-utils"; -import * as vscode from 'vscode'; -import { localize } from "../utils/localize"; -import { LoadingViewController } from "./LoadingViewController"; -import { SharedState } from "./SharedViewState"; - -export class OpenLoadingViewStep extends AzureWizardPromptStep { - public async prompt(): Promise { - const loadingView = new LoadingViewController({ title: localize('loadingViewTitle', 'Loading...') }); - loadingView.revealToForeground(vscode.ViewColumn.Active); - SharedState.currentPanel = loadingView.panel; - SharedState.loadingViewController = loadingView; - } - - public shouldPrompt(): boolean { - return true; - } -} - -export async function openLoadingViewPanel(context: IActionContext): Promise { - const promptSteps: AzureWizardPromptStep[] = [new OpenLoadingViewStep()]; - - return await runGenericPromptStep(context, { - promptSteps - }); -} diff --git a/src/webviews/WebviewContext.tsx b/src/webviews/WebviewContext.tsx deleted file mode 100644 index 7c5a6dd48..000000000 --- a/src/webviews/WebviewContext.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as React from 'react'; -import { createContext } from 'react'; -import { type WebviewApi } from 'vscode-webview'; -import { type Channel } from './Channel/Channel'; -import { WebviewChannel } from './Channel/WebviewChannel'; - -export type WebviewState = object; - -export type WebviewContextValue = { - channel: Channel; - vscodeApi: WebviewApi; -}; - -export const WebviewContext = createContext({} as WebviewContextValue); - -export const WithWebviewContext = ({ - vscodeApi, - children, -}: { - vscodeApi: WebviewApi; - children: React.ReactNode; -}) => { - const channel = new WebviewChannel(vscodeApi); - return {children}; -}; - diff --git a/src/webviews/WebviewRegistry.ts b/src/webviews/WebviewRegistry.ts deleted file mode 100644 index c61fc46a7..000000000 --- a/src/webviews/WebviewRegistry.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ConfirmationView } from "./ConfirmationView"; -import { LoadingView } from "./LoadingView"; - -export const WebviewRegistry = { - confirmationView: ConfirmationView, - loadingView: LoadingView -} as const; diff --git a/src/webviews/extension-server/WebviewBaseController.ts b/src/webviews/extension-server/WebviewBaseController.ts deleted file mode 100644 index 9e4abbe32..000000000 --- a/src/webviews/extension-server/WebviewBaseController.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { randomBytes } from 'crypto'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { ext } from "../../extensionVariables"; - -/** - * WebviewBaseController is a class that manages a vscode.Webview and provides - * a way to communicate with it. It provides a way to register request handlers and reducers - * that can be called from the webview. It also provides a way to post notifications to the webview. - * @template Configuration The type of the configuration object that the webview will receive - */ -export abstract class WebviewBaseController implements vscode.Disposable { - private _disposables: vscode.Disposable[] = []; - private _isDisposed: boolean = false; - - // private _isFirstLoad: boolean = true; - // private _loadStartTime: number = Date.now(); - private _onDisposed: vscode.EventEmitter = new vscode.EventEmitter(); - - public readonly onDisposed: vscode.Event = this._onDisposed.event; - - /** - * Creates a new ReactWebviewPanelController - * @param extensionContext The context of the extension-server - * @param _webviewName The source file that the webview will use - * @param configuration The initial state object that the webview will use - */ - constructor( - protected extensionContext: vscode.ExtensionContext, - private _webviewName: string, - protected configuration: Configuration, - ) { } - - protected registerDisposable(disposable: vscode.Disposable) { - this._disposables.push(disposable); - } - - protected getDocumentTemplate(webview?: vscode.Webview) { - const nonce = randomBytes(16).toString('base64'); - - const filename = 'views.js'; - const uri = (...parts: string[]) => webview?.asWebviewUri(vscode.Uri.file(path.join(ext.context.extensionPath, ...parts))).toString(true); - const srcUri = uri('dist', filename); - const cssUri = uri('dist', 'views.css'); - - const csp = [ - `form-action 'none';`, - `default-src ${webview?.cspSource};`, - `script-src ${webview?.cspSource} 'nonce-${nonce}';`, - `style-src ${webview?.cspSource} vscode-resource: 'unsafe-inline';`, - `img-src ${webview?.cspSource} data: vscode-resource:;`, - `connect-src ${webview?.cspSource} ws:;`, - `font-src ${webview?.cspSource} data:;`, - `worker-src ${webview?.cspSource} blob:;`, - ] - .join(' '); - - /** - * Note to code maintainers: - * encodeURIComponent(JSON.stringify(this.configuration)) below is crucial - * We want to avoid the webview from crashing when the configuration object contains 'unsupported' bytes - */ - - return ` - - - - - - - - -
- - - - `; - } - - /** - * Gets whether the controller has been disposed - */ - public get isDisposed(): boolean { - return this._isDisposed; - } - - /** - * Disposes the controller - */ - public dispose() { - this._onDisposed.fire(); - this._disposables.forEach((d) => d.dispose()); - this._isDisposed = true; - } -} diff --git a/src/webviews/extension-server/WebviewController.ts b/src/webviews/extension-server/WebviewController.ts deleted file mode 100644 index b56811eb3..000000000 --- a/src/webviews/extension-server/WebviewController.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { nonNullValue } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; -import { SharedState } from '../OpenConfirmationViewStep'; -import { WebviewBaseController } from './WebviewBaseController'; -/** - * WebviewController is a class that manages a vscode.WebviewPanel and provides - * a way to communicate with it. It uses tRPC to handle incoming requests (queries, - * mutations, and subscriptions) from the webview. Through this controller, the - * webview can call server-side procedures defined in the `appRouter`. - * - * @template Configuration - The type of the configuration object that the webview will receive. - */ -export class WebviewController extends WebviewBaseController { - private _panel: vscode.WebviewPanel; - - /** - * Creates a new WebviewController instance. - * - * @param context The extension context. - * @param title The title of the webview panel. - * @param webviewName The identifier/name for the webview resource. - * @param initialState The initial state object that the webview will use on startup. - * @param viewColumn The view column in which to show the new webview panel. - * @param _iconPath An optional icon to display in the tab of the webview. - */ - constructor( - context: vscode.ExtensionContext, - title: string, - webviewName: string, - initialState: Configuration, - viewColumn: vscode.ViewColumn = vscode.ViewColumn.One, - private _iconPath?: - | vscode.Uri - | { - readonly light: vscode.Uri; - readonly dark: vscode.Uri; - }, - ) { - super(context, webviewName, initialState); - - // Create the webview panel - this._panel = vscode.window.createWebviewPanel('react-webview-' + webviewName, title, viewColumn, { - enableScripts: true, - retainContextWhenHidden: true, - localResourceRoots: [vscode.Uri.file(this.extensionContext.extensionPath)], - }); - - this._panel.webview.html = this.getDocumentTemplate(this._panel.webview); - this._panel.iconPath = this._iconPath; - - this._panel.webview.onDidReceiveMessage( - async (message: { command: string, itemsToClear?: number, name?: string, value?: string, commandName?: string }) => { - // Todo: these are placeholders. May change to using trpc for the webview - switch (message.command) { - case 'cancel': - SharedState.cancelled = true; - this._panel.dispose(); - break; - case 'confirm': - SharedState.cancelled = false; - SharedState.itemsToClear = message.itemsToClear ?? 0; - this._panel.dispose(); - break; - case 'copilot': - SharedState.copilotClicked = true; - await vscode.commands.executeCommand("workbench.action.chat.open"); - await vscode.commands.executeCommand("workbench.action.chat.newChat"); - await vscode.commands.executeCommand("workbench.action.chat.open", { - mode: 'agent', - query: createCopilotPromptForConfirmationViewButton(nonNullValue(message.name), nonNullValue(message.value), nonNullValue(message.commandName), 'Azure Container Apps'), - }); - - break; - } - } - ); - - // Clean up when the panel is disposed - this.registerDisposable( - this._panel.onDidDispose(() => { - this.dispose(); - }), - ); - } - - /** - * Retrieves the vscode.Webview associated with this controller. - * @returns The webview being managed by this controller. - */ - protected _getWebview(): vscode.Webview { - return this._panel.webview; - } - - /** - * Gets the vscode.WebviewPanel that the controller is managing. - */ - public get panel(): vscode.WebviewPanel { - return this._panel; - } - - /** - * Reveals the webview in the given column, bringing it to the foreground. - * Useful if the webview is already open but hidden. - * - * @param viewColumn The column to reveal in. Defaults to ViewColumn.One. - */ - public revealToForeground(viewColumn: vscode.ViewColumn = vscode.ViewColumn.One): void { - this._panel.reveal(viewColumn, true); - } -} - -export function createCopilotPromptForConfirmationViewButton(name: string, value: string, commandName: string, extension: string): string { - return `Help explain what ${name}: ${value} means in the context of the "${commandName}" command using the ${extension} extension for VS Code.`; -} diff --git a/src/webviews/index.tsx b/src/webviews/index.tsx deleted file mode 100644 index d30e4c2a0..000000000 --- a/src/webviews/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import './styles/global.scss'; - -import * as React from 'react'; -import { createRoot } from 'react-dom/client'; -import { type WebviewApi } from 'vscode-webview'; -import { DynamicThemeProvider } from './theme/DynamicThemeProvider'; -import { type WebviewState, WithWebviewContext } from './WebviewContext'; -import { WebviewRegistry } from './WebviewRegistry'; - -export type ViewKey = keyof typeof WebviewRegistry; - -export function render(key: V, vscodeApi: WebviewApi, rootId = 'root'): void { - const container = document.getElementById(rootId); - if (!container) { - throw new Error(`Element with id of ${rootId} not found.`); - } - - const Component: React.ComponentType = WebviewRegistry[key]; - - const root = createRoot(container); - - root.render( - - - - - , - ); -} diff --git a/src/webviews/styles/confirmationView.scss b/src/webviews/styles/confirmationView.scss deleted file mode 100644 index ec18e1e0e..000000000 --- a/src/webviews/styles/confirmationView.scss +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.confirmationView { - display: flex; - flex-direction: column; - align-items: edge; - justify-content: flex-start; - height: 100vh; - padding: 20px; - width: 46vw; - - .header { - text-wrap: nowrap; - } - - .viewContent { - margin-top: 5%; - - .buttonsView { - display: flex; - justify-content: flex-start; - margin-top: 32px; - gap: 4px; - } - } -} diff --git a/src/webviews/styles/global.scss b/src/webviews/styles/global.scss deleted file mode 100644 index cf9dd8a90..000000000 --- a/src/webviews/styles/global.scss +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -@import '@vscode/codicons/dist/codicon.css'; - -.codicon, -.codicon::before { - font-family: codicon !important; -} diff --git a/src/webviews/styles/loadingView.scss b/src/webviews/styles/loadingView.scss deleted file mode 100644 index 14cdfa1a4..000000000 --- a/src/webviews/styles/loadingView.scss +++ /dev/null @@ -1,111 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.loadingView { - display: flex; - justify-content: center; - align-items: flex-start; - height: 100vh; - padding: 20px; - padding-top: 15vh; - - .loadingContent { - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; - max-width: 500px; - width: 100%; - } - - .progressList { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - margin-top: 16px; - padding: 16px; - border-radius: 4px; - width: 100%; - box-sizing: border-box; - } - - .progressItemPlaceholder { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 0; - width: 250px; - - .placeholderIcon { - width: 14px; - height: 14px; - border-radius: 50%; - background-color: var(--vscode-descriptionForeground, rgba(128, 128, 128, 0.3)); - opacity: 0.3; - flex-shrink: 0; - animation: shimmer 1.5s ease-in-out infinite; - } - - .placeholderText { - height: 12px; - border-radius: 4px; - background-color: var(--vscode-descriptionForeground, rgba(128, 128, 128, 0.3)); - opacity: 0.3; - animation: shimmer 1.5s ease-in-out infinite; - } - } - - .progressItem { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 0; - animation: fadeIn 0.3s ease-in-out; - - .checkmark { - color: var(--vscode-testing-iconPassed); - font-size: 14px; - flex-shrink: 0; - } - - .itemName { - color: var(--vscode-foreground); - font-weight: 600; - flex-shrink: 0; - } - - .itemValue { - color: var(--vscode-descriptionForeground); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-4px); - } - - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes shimmer { - - 0%, - 100% { - opacity: 0.3; - } - - 50% { - opacity: 0.6; - } -} diff --git a/src/webviews/theme/DynamicThemeProvider.tsx b/src/webviews/theme/DynamicThemeProvider.tsx deleted file mode 100644 index bea276ed5..000000000 --- a/src/webviews/theme/DynamicThemeProvider.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { FluentProvider } from '@fluentui/react-components'; -import { type PropsWithChildren, type ReactNode } from 'react'; -import { useThemeState, WithTheme } from './state/ThemeContext'; - - -export type DynamicThemeProviderProps = { - useAdaptive?: boolean; -}; - -const FluentUiProvider = ({ children }: { children: ReactNode }) => { - const themeState = useThemeState(); - - return {children}; -}; - -export const DynamicThemeProvider = ({ - children, - useAdaptive = false, -}: PropsWithChildren) => { - return ( - - {children} - - ); -}; diff --git a/src/webviews/theme/state/ThemeContext.tsx b/src/webviews/theme/state/ThemeContext.tsx deleted file mode 100644 index aee1a813c..000000000 --- a/src/webviews/theme/state/ThemeContext.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { teamsDarkTheme, teamsHighContrastTheme, teamsLightTheme } from '@fluentui/react-components'; -import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; -import { generateAdaptiveDarkTheme, generateAdaptiveLightTheme, generateMonacoTheme } from '../themeGenerator'; -import { defaultState, type MonacoBuiltinTheme, type MonacoTheme, type ThemeState } from './ThemeState'; - -export const ThemeContext = createContext(defaultState); - -export const useThemeMutationObserver = (callback: (themeKind: string) => void) => { - const observer = useMemo( - () => - new MutationObserver((mutations) => { - mutations.forEach(function (mutation) { - if (mutation.type === 'attributes' && mutation.attributeName === 'data-vscode-theme-kind') { - console.log('theme attributes changed'); - - const newValue = - (mutation.target as HTMLElement).getAttribute('data-vscode-theme-kind') ?? 'vscode-light'; - callback(newValue); - } - }); - }), - [callback], - ); - - useEffect(() => { - const targetNode = document.body; - observer.observe(targetNode, { - attributes: true, - }); - - return () => observer.disconnect(); - }, [observer]); -}; - -// get class value from body element -export const useVSCodeTheme = () => { - return document.body.getAttribute('data-vscode-theme-kind') ?? 'vscode-light'; -}; - -export const getFluentUiTheme = (useAdaptive: boolean = false, themeKind: string) => { - if (useAdaptive) { - return themeKind === 'vscode-light' - ? generateAdaptiveLightTheme() - : themeKind === 'vscode-dark' - ? generateAdaptiveDarkTheme() - : themeKind === 'vscode-high-contrast' - ? teamsHighContrastTheme - : themeKind === 'vscode-high-contrast-light' - ? teamsLightTheme // TODO: find a better theme for this - : undefined; - } - return themeKind === 'vscode-light' - ? teamsLightTheme - : themeKind === 'vscode-dark' - ? teamsDarkTheme - : themeKind === 'vscode-high-contrast' - ? teamsHighContrastTheme - : themeKind === 'vscode-high-contrast-light' - ? teamsLightTheme // TODO: find a better theme for this - : undefined; -}; - -export const getMonacoTheme = (useAdaptive: boolean = false, themeKind: string): MonacoTheme => { - const monacoBaseTheme: MonacoBuiltinTheme = - themeKind === 'vscode-light' - ? 'vs' - : themeKind === 'vscode-dark' - ? 'vs-dark' - : themeKind === 'vscode-high-contrast' - ? 'hc-black' - : themeKind === 'vscode-high-contrast-light' - ? 'hc-light' - : 'vs'; - - if (useAdaptive) { - return { - themeName: 'adaptive', - theme: generateMonacoTheme(monacoBaseTheme), - }; - } - - return { - themeName: monacoBaseTheme, - }; -}; - -export function useThemeState() { - return useContext(ThemeContext); -} - -export const ThemeProvider = ThemeContext.Provider; - -export const generateThemeContext = (useAdaptive: boolean = false, themeKind: string) => { - return { - fluentUI: { - theme: getFluentUiTheme(useAdaptive, themeKind), - themeKind, - }, - monaco: getMonacoTheme(useAdaptive, themeKind), - useAdaptive, - themeKind, - }; -}; - -export const WithTheme = ({ children, useAdaptive }: { children: ReactNode; useAdaptive: boolean }) => { - const [state, setState] = useState(generateThemeContext(useAdaptive, useVSCodeTheme())); - - function setThemeKind(themeKind: string) { - setState({ - ...state, - ...generateThemeContext(useAdaptive, themeKind), - }); - } - - useEffect(() => { - setThemeKind(useVSCodeTheme()); - }, [useAdaptive]); - - useThemeMutationObserver((themeKind) => { - setThemeKind(themeKind); - }); - - return {children}; -}; diff --git a/src/webviews/theme/state/ThemeState.tsx b/src/webviews/theme/state/ThemeState.tsx deleted file mode 100644 index 5fc7620fc..000000000 --- a/src/webviews/theme/state/ThemeState.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { teamsLightTheme, type Theme } from '@fluentui/react-components'; -import type * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; - -export type MonacoBuiltinTheme = monacoEditor.editor.BuiltinTheme; -export type MonacoThemeData = monacoEditor.editor.IStandaloneThemeData; -export type MonacoColors = monacoEditor.editor.IColors; -export type MonacoTheme = { - theme?: MonacoThemeData; - themeName: string; -}; - -export type ThemeState = { - themeKind: string; - useAdaptive: boolean; - fluentUI: { - theme?: Theme; - themeKind: string; - }; - monaco: MonacoTheme; -}; - -export const defaultState: ThemeState = { - themeKind: 'vscode-light', - useAdaptive: false, - fluentUI: { - theme: teamsLightTheme, - themeKind: 'vscode-light', - }, - monaco: { - theme: undefined, - themeName: 'vs', - }, -}; diff --git a/src/webviews/theme/themeGenerator.ts b/src/webviews/theme/themeGenerator.ts deleted file mode 100644 index 8771a1634..000000000 --- a/src/webviews/theme/themeGenerator.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type BrandVariants, createDarkTheme, createLightTheme, type Theme } from '@fluentui/react-components'; -import { type MonacoBuiltinTheme, type MonacoColors, type MonacoThemeData } from './state/ThemeState'; -import { hex_to_LCH, hexColorsFromPalette, type Palette, RGBAToHexA } from './utils'; -import { vscodeThemeTokens, vscodeThemeTokenToCSSVar } from './vscodeThemeTokens'; - -type Options = { - darkCp?: number; - lightCp?: number; - hueTorsion?: number; -}; - -/** - * A palette is represented as a continuous curve through LAB space, made of two quadratic bezier curves that start at - * 0L (black) and 100L (white) and meet at the LAB value of the provided key color. - * - * This function takes in a palette as input, which consists of: - * keyColor: The primary color in the LCH (Lightness Chroma Hue) color space - * darkCp, lightCp: The control point of the quadratic beizer curve towards black and white, respectively (between 0-1). - * Higher values move the control point toward the ends of the gamut causing chroma/saturation to - * diminish more slowly near the key color, and lower values move the control point toward the key - * color causing chroma/saturation to diminish more linearly. - * hueTorsion: Enables the palette to move through different hues by rotating the curve’s points in LAB space, - * creating a helical curve - - * The function returns a set of brand tokens. - */ -export function getBrandTokensFromPalette(keyColor: string, options: Options = {}) { - const { darkCp = 2 / 3, lightCp = 1 / 3, hueTorsion = 0 } = options; - - if (!keyColor.startsWith('#')) { - if (keyColor.startsWith('rgb')) { - keyColor = RGBAToHexA(keyColor); - } - - // TODO: If the color is not a hex value - } - - const brandPalette: Palette = { - keyColor: hex_to_LCH(keyColor), - darkCp, - lightCp, - hueTorsion, - }; - const hexColors = hexColorsFromPalette(keyColor, brandPalette, 16, 1); - return hexColors.reduce((acc: Record, hexColor, h) => { - acc[`${(h + 1) * 10}`] = hexColor; - return acc; - }, {}) as BrandVariants; -} - -// https://react.fluentui.dev/?path=/docs/concepts-developer-theming--page#overriding-existing-tokens -export const generateAdaptiveLightTheme = (): Theme => { - const style = getComputedStyle(document.documentElement); - const buttonBackground = style.getPropertyValue('--vscode-button-background'); - const brandVSCode: BrandVariants = getBrandTokensFromPalette(buttonBackground); - - return { - ...createLightTheme(brandVSCode), - ...{ - colorNeutralForeground1: 'var(--vscode-editor-foreground)', - colorNeutralForeground1Hover: 'var(--vscode-editor-foreground)', - colorNeutralForeground1Pressed: 'var(--vscode-editor-foreground)', - colorNeutralForeground1Selected: 'var(--vscode-editor-foreground)', - - colorNeutralBackground1: 'var(--vscode-editor-background)', - }, - }; -}; - -export const generateAdaptiveDarkTheme = (): Theme => { - const style = getComputedStyle(document.documentElement); - const buttonBackground = style.getPropertyValue('--vscode-button-background'); - const brandVSCode: BrandVariants = getBrandTokensFromPalette(buttonBackground); - - return { - ...createDarkTheme(brandVSCode), - ...{ - colorNeutralForeground1: 'var(--vscode-button-foreground)', - colorNeutralForeground1Hover: 'var(--vscode-button-foreground)', - colorNeutralForeground1Pressed: 'var(--vscode-button-foreground)', - colorNeutralForeground1Selected: 'var(--vscode-button-foreground)', - colorNeutralForeground2: 'var(--vscode-button-secondaryForeground)', - colorNeutralForeground2Hover: 'var(--vscode-button-secondaryForeground)', - colorNeutralForeground2Pressed: 'var(--vscode-button-secondaryForeground)', - colorNeutralForeground2Selected: 'var(--vscode-button-secondaryForeground)', - - colorNeutralBackground1: 'var(--vscode-editor-background)', - }, - }; -}; - -export const generateMonacoTheme = (baseTheme: MonacoBuiltinTheme): MonacoThemeData => { - const style = getComputedStyle(document.documentElement); - const colors = vscodeThemeTokens - .map((token) => { - let color = style.getPropertyValue(vscodeThemeTokenToCSSVar(token)); - if (!color.startsWith('#')) { - if (color.startsWith('rgb')) { - color = RGBAToHexA(color); - } - } - return [token, color]; - }) - .filter(([_, color]) => color !== ''); - - return { - base: baseTheme, - inherit: true, - rules: [], - colors: Object.fromEntries(colors) as MonacoColors, - }; -}; diff --git a/src/webviews/theme/utils/csswg.ts b/src/webviews/theme/utils/csswg.ts deleted file mode 100644 index 027e749ea..000000000 --- a/src/webviews/theme/utils/csswg.ts +++ /dev/null @@ -1,295 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* eslint-disable @typescript-eslint/naming-convention */ -// The following is a combination of several files retrieved from CSSWG’s -// CSS Color 4 module. It was modified to support TypeScript types adapted for -// the Fluent Blocks `colors` package and formatted to meet its style criteria. -import { type Vec3 } from './types'; - -/** - * Simple matrix (and vector) multiplication - * Warning: No error handling for incompatible dimensions! - * @author Lea Verou 2020 MIT License - */ - -type MatrixIO = number[][] | number[]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isFlat(A: any): A is number[] { - return !Array.isArray(A[0]); -} - -// A is m x n. B is n x p. product is m x p. -export function multiplyMatrices(AMatrixOrVector: MatrixIO, BMatrixOrVector: MatrixIO): MatrixIO { - const m = AMatrixOrVector.length; - - const A: number[][] = isFlat(AMatrixOrVector) - ? // A is vector, convert to [[a, b, c, ...]] - [AMatrixOrVector] - : AMatrixOrVector; - - const B: number[][] = isFlat(BMatrixOrVector) - ? // B is vector, convert to [[a], [b], [c], ...]] - BMatrixOrVector.map((x) => [x]) - : BMatrixOrVector; - - const p = B[0].length; - const B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B - let product: MatrixIO = A.map((row) => - B_cols.map((col) => { - if (!Array.isArray(row)) { - return col.reduce((a, c) => a + c * row, 0); - } - - return row.reduce((a, c, i) => a + c * (col[i] || 0), 0); - }), - ); - - if (m === 1) { - product = product[0]; // Avoid [[a, b, c, ...]] - } - - if (p === 1) { - return (product as number[][]).map((x) => x[0]); // Avoid [[a], [b], [c], ...]] - } - - return product; -} - -export function lin_sRGB(RGB: Vec3) { - // convert an array of sRGB values - // where in-gamut values are in the range [0 - 1] - // to linear light (un-companded) form. - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // for negative values, linear portion is extended on reflection of axis, - // then reflected power function is used. - return RGB.map((val) => { - const sign = val < 0 ? -1 : 1; - const abs = Math.abs(val); - - if (abs < 0.04045) { - return val / 12.92; - } - - return sign * Math.pow((abs + 0.055) / 1.055, 2.4); - }) as Vec3; -} - -export function gam_sRGB(RGB: Vec3) { - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - return RGB.map((val) => { - const sign = val < 0 ? -1 : 1; - const abs = Math.abs(val); - - if (abs > 0.0031308) { - return sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); - } - - return 12.92 * val; - }) as Vec3; -} - -export function lin_sRGB_to_XYZ(rgb: Vec3) { - // convert an array of linear-light sRGB values to CIE XYZ - // using sRGB's own white, D65 (no chromatic adaptation) - - const M = [ - [0.41239079926595934, 0.357584339383878, 0.1804807884018343], - [0.21263900587151027, 0.715168678767756, 0.07219231536073371], - [0.01933081871559182, 0.11919477979462598, 0.9505321522496607], - ]; - return multiplyMatrices(M, rgb) as Vec3; -} - -export function XYZ_to_lin_sRGB(XYZ: Vec3) { - // convert XYZ to linear-light sRGB - - const M = [ - [3.2409699419045226, -1.537383177570094, -0.4986107602930034], - [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559], - [0.05563007969699366, -0.20397695888897652, 1.0569715142428786], - ]; - - return multiplyMatrices(M, XYZ) as Vec3; -} - -// Chromatic adaptation - -export function D65_to_D50(XYZ: Vec3) { - // Bradford chromatic adaptation from D65 to D50 - // The matrix below is the result of three operations: - // - convert from XYZ to retinal cone domain - // - scale components from one reference white to another - // - convert back to XYZ - // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - const M = [ - [1.0479298208405488, 0.022946793341019088, -0.05019222954313557], - [0.029627815688159344, 0.990434484573249, -0.01707382502938514], - [-0.009243058152591178, 0.015055144896577895, 0.7518742899580008], - ]; - - return multiplyMatrices(M, XYZ) as Vec3; -} - -export function D50_to_D65(XYZ: Vec3) { - // Bradford chromatic adaptation from D50 to D65 - const M = [ - [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], - [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], - [0.012314001688319899, -0.020507696433477912, 1.3303659366080753], - ]; - - return multiplyMatrices(M, XYZ) as Vec3; -} - -// Lab and LCH - -export function XYZ_to_Lab(XYZ: Vec3) { - // Assuming XYZ is relative to D50, convert to CIE Lab - // from CIE standard, which now defines these as a rational fraction - const eps = 216 / 24389; // 6^3/29^3 - const kappa = 24389 / 27; // 29^3/3^3 - const white = [0.96422, 1.0, 0.82521]; // D50 reference white - - // compute xyz, which is XYZ scaled relative to reference white - const xyz = XYZ.map((value, i) => value / white[i]); - - // now compute f - const f = xyz.map((value) => (value > eps ? Math.cbrt(value) : (kappa * value + 16) / 116)); - - return [ - 116 * f[1] - 16, // L - 500 * (f[0] - f[1]), // a - 200 * (f[1] - f[2]), // b - ] as Vec3; -} - -export function Lab_to_XYZ(Lab: Vec3) { - // Convert Lab to D50-adapted XYZ - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - const kappa = 24389 / 27; // 29^3/3^3 - const eps = 216 / 24389; // 6^3/29^3 - const white = [0.96422, 1.0, 0.82521]; // D50 reference white - const f: number[] = []; - - // compute f, starting with the luminance-related term - f[1] = (Lab[0] + 16) / 116; - f[0] = Lab[1] / 500 + f[1]; - f[2] = f[1] - Lab[2] / 200; - - // compute xyz - const xyz = [ - Math.pow(f[0], 3) > eps ? Math.pow(f[0], 3) : (116 * f[0] - 16) / kappa, - Lab[0] > kappa * eps ? Math.pow((Lab[0] + 16) / 116, 3) : Lab[0] / kappa, - Math.pow(f[2], 3) > eps ? Math.pow(f[2], 3) : (116 * f[2] - 16) / kappa, - ]; - - // Compute XYZ by scaling xyz by reference white - return xyz.map((value, i) => value * white[i]) as Vec3; -} - -export function Lab_to_LCH(Lab: Vec3) { - // Convert to polar form - const hue = (Math.atan2(Lab[2], Lab[1]) * 180) / Math.PI; - return [ - Lab[0], // L is still L - Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)), // Chroma - hue >= 0 ? hue : hue + 360, // Hue, in degrees [0 to 360) - ] as Vec3; -} - -export function LCH_to_Lab(LCH: Vec3) { - // Convert from polar form - return [ - LCH[0], // L is still L - LCH[1] * Math.cos((LCH[2] * Math.PI) / 180), // a - LCH[1] * Math.sin((LCH[2] * Math.PI) / 180), // b - ] as Vec3; -} - -export function sRGB_to_LCH(RGB: Vec3) { - // convert an array of gamma-corrected sRGB values - // in the 0.0 to 1.0 range - // to linear-light sRGB, then to CIE XYZ, - // then adapt from D65 to D50, - // then convert XYZ to CIE Lab - // and finally, convert to CIE LCH - - return Lab_to_LCH(XYZ_to_Lab(D65_to_D50(lin_sRGB_to_XYZ(lin_sRGB(RGB))))); -} - -export function LCH_to_sRGB(LCH: Vec3) { - // convert an array of CIE LCH values - // to CIE Lab, and then to XYZ, - // adapt from D50 to D65, - // then convert XYZ to linear-light sRGB - // and finally to gamma corrected sRGB - // for in-gamut colors, components are in the 0.0 to 1.0 range - // out of gamut colors may have negative components - // or components greater than 1.0 - // so check for that :) - - return gam_sRGB(XYZ_to_lin_sRGB(D50_to_D65(Lab_to_XYZ(LCH_to_Lab(LCH))))); -} - -export function LAB_to_sRGB(LAB: Vec3) { - // convert an array of CIE Lab values to XYZ, - // adapt from D50 to D65, - // then convert XYZ to linear-light sRGB - // and finally to gamma corrected sRGB - // for in-gamut colors, components are in the 0.0 to 1.0 range - // out of gamut colors may have negative components - // or components greater than 1.0 - // so check for that :) - - return gam_sRGB(XYZ_to_lin_sRGB(D50_to_D65(Lab_to_XYZ(LAB)))); -} - -function is_LCH_inside_sRGB(l: number, c: number, h: number): boolean { - const eps = 0.000005; - const rgb = LCH_to_sRGB([+l, +c, +h]); - return rgb.reduce((a: boolean, b: number) => a && b >= 0 - eps && b <= 1 + eps, true); -} - -export function snap_into_gamut(Lab: Vec3): Vec3 { - // Moves an LCH color into the sRGB gamut - // by holding the l and h steady, - // and adjusting the c via binary-search - // until the color is on the sRGB boundary. - - // .0001 chosen fairly arbitrarily as "close enough" - const eps = 0.0001; - - const LCH = Lab_to_LCH(Lab); - const l = LCH[0]; - let c = LCH[1]; - const h = LCH[2]; - - if (is_LCH_inside_sRGB(l, c, h)) { - return Lab; - } - - let hiC = c; - let loC = 0; - c /= 2; - - while (hiC - loC > eps) { - if (is_LCH_inside_sRGB(l, c, h)) { - loC = c; - } else { - hiC = c; - } - c = (hiC + loC) / 2; - } - - return LCH_to_Lab([l, c, h]); -} diff --git a/src/webviews/theme/utils/geometry.ts b/src/webviews/theme/utils/geometry.ts deleted file mode 100644 index 295ee8db5..000000000 --- a/src/webviews/theme/utils/geometry.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type Curve, type CurvePath, type Vec3 } from './types'; - -const curveResolution = 128; - -// Many of these functions are ported from ThreeJS, which is distributed under -// the MIT license. Retrieved from https://github.com/mrdoob/three.js on -// 14 October 2021. - -function equals(v1: Vec3, v2: Vec3) { - return v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2]; -} - -function QuadraticBezierP0(t: number, p: number): number { - const k = 1 - t; - return k * k * p; -} - -function QuadraticBezierP1(t: number, p: number): number { - return 2 * (1 - t) * t * p; -} - -function QuadraticBezierP2(t: number, p: number): number { - return t * t * p; -} - -function QuadraticBezier(t: number, p0: number, p1: number, p2: number): number { - return QuadraticBezierP0(t, p0) + QuadraticBezierP1(t, p1) + QuadraticBezierP2(t, p2); -} - -function getPointOnCurve(curve: Curve, t: number) { - const [v0, v1, v2] = curve.points; - return [ - QuadraticBezier(t, v0[0], v1[0], v2[0]), - QuadraticBezier(t, v0[1], v1[1], v2[1]), - QuadraticBezier(t, v0[2], v1[2], v2[2]), - ] as Vec3; -} - -function getPointsOnCurve(curve: Curve, divisions: number): Vec3[] { - const points: Vec3[] = []; - for (let d = 0; d <= divisions; d++) { - points.push(getPointOnCurve(curve, d / divisions)); - } - return points; -} - -export function getPointsOnCurvePath(curvePath: CurvePath, divisions = curveResolution): Vec3[] { - const points: Vec3[] = []; - let last: Vec3 | undefined; - - for (let i = 0, curves = curvePath.curves; i < curves.length; i++) { - const curve = curves[i]; - const pts = getPointsOnCurve(curve, divisions); - - for (const point of pts) { - if (last && equals(last, point)) { - // ensures no consecutive points are duplicates - continue; - } - - points.push(point); - last = point; - } - } - - return points; -} diff --git a/src/webviews/theme/utils/hueMap.ts b/src/webviews/theme/utils/hueMap.ts deleted file mode 100644 index 8237f18a9..000000000 --- a/src/webviews/theme/utils/hueMap.ts +++ /dev/null @@ -1,403 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function hexToHue(hexColor: string): number { - // Parse the hex color string into its red, green, and blue components - const red = parseInt(hexColor.substring(1, 3), 16); - const green = parseInt(hexColor.substring(3, 5), 16); - const blue = parseInt(hexColor.substring(5, 7), 16); - - // Convert the RGB color to HSL color space - const r = red / 255; - const g = green / 255; - const b = blue / 255; - const cmax = Math.max(r, g, b); - const cmin = Math.min(r, g, b); - const delta = cmax - cmin; - let hue: number; - - // Calculate the hue value based on the RGB color values - if (delta === 0) { - hue = 0; - } else if (cmax === r) { - hue = ((g - b) / delta) % 6; - } else if (cmax === g) { - hue = (b - r) / delta + 2; - } else { - hue = (r - g) / delta + 4; - } - - // Convert the hue value to degrees and return it - hue = Math.round(hue * 60); - if (hue < 0) { - hue += 360; - } - - return hue; -} - -// map of hue to [min, center, max], generated from Arman -export const hueToSnappingPointsMap = [ - [0.0085504, 0.148504, 0.858504], - [0.00855388, 0.1485388, 0.8585388], - [0.0085582, 0.148582, 0.858582], - [0.00856192, 0.1486192, 0.8586192], - [0.00856644, 0.1486644, 0.8586644], - [0.00857184, 0.1487184, 0.8587184], - [0.0085802, 0.148802, 0.858802], - [0.00858752, 0.1488752, 0.8588752], - [0.00859616, 0.1489616, 0.8589616], - [0.00860584, 0.1490584, 0.8590584], - [0.00861948, 0.1491948, 0.8591948], - [0.00863172, 0.1493172, 0.8593172], - [0.00864508, 0.1494508, 0.8594508], - [0.00865968, 0.1495968, 0.8595968], - [0.00867968, 0.1497968, 0.8597968], - [0.00869708, 0.1499708, 0.8599708], - [0.00871576, 0.1501576, 0.8601576], - [0.0087358, 0.150358, 0.860358], - [0.00876272, 0.1506272, 0.8606272], - [0.0087858, 0.150858, 0.860858], - [0.00881028, 0.1511028, 0.8611028], - [0.0088362, 0.151362, 0.861362], - [0.0088706, 0.151706, 0.861706], - [0.0088998, 0.151998, 0.861998], - [0.00893052, 0.1523052, 0.8623052], - [0.00896272, 0.1526272, 0.8626272], - [0.00900516, 0.1530516, 0.8630516], - [0.00904084, 0.1534084, 0.8634084], - [0.00907812, 0.1537812, 0.8637812], - [0.00911704, 0.1541704, 0.8641704], - [0.00916792, 0.1546792, 0.8646792], - [0.00921048, 0.1551048, 0.8651048], - [0.00925472, 0.1555472, 0.8655472], - [0.00930064, 0.1560064, 0.8660064], - [0.00934824, 0.1564824, 0.8664824], - [0.0094102, 0.157102, 0.867102], - [0.00946168, 0.1576168, 0.8676168], - [0.00951496, 0.1581496, 0.8681496], - [0.00957, 0.1587, 0.8687], - [0.00964128, 0.1594128, 0.8694128], - [0.00970036, 0.1600036, 0.8700036], - [0.00976128, 0.1606128, 0.8706128], - [0.00982404, 0.1612404, 0.8712404], - [0.00990508, 0.1620508, 0.8720508], - [0.009972, 0.16272, 0.87272], - [0.01004084, 0.1634084, 0.8734084], - [0.0101296, 0.164296, 0.874296], - [0.01020272, 0.1650272, 0.8750272], - [0.01027784, 0.1657784, 0.8757784], - [0.01035488, 0.1665488, 0.8765488], - [0.01045392, 0.1675392, 0.8775392], - [0.0105354, 0.168354, 0.878354], - [0.01061892, 0.1691892, 0.8791892], - [0.0107044, 0.170044, 0.880044], - [0.01081412, 0.1711412, 0.8811412], - [0.0109042, 0.172042, 0.882042], - [0.01099636, 0.1729636, 0.8829636], - [0.01109056, 0.1739056, 0.8839056], - [0.01121124, 0.1751124, 0.8851124], - [0.01131016, 0.1761016, 0.8861016], - [0.0114112, 0.177112, 0.887112], - [0.01138116, 0.1768116, 0.8868116], - [0.01135176, 0.1765176, 0.8865176], - [0.01131588, 0.1761588, 0.8861588], - [0.01128788, 0.1758788, 0.8858788], - [0.01126048, 0.1756048, 0.8856048], - [0.01122712, 0.1752712, 0.8852712], - [0.01120108, 0.1750108, 0.8850108], - [0.01117568, 0.1747568, 0.8847568], - [0.01115088, 0.1745088, 0.8845088], - [0.01112068, 0.1742068, 0.8842068], - [0.0110972, 0.173972, 0.883972], - [0.01107428, 0.1737428, 0.8837428], - [0.01105196, 0.1735196, 0.8835196], - [0.01102488, 0.1732488, 0.8832488], - [0.01100384, 0.1730384, 0.8830384], - [0.0109834, 0.172834, 0.882834], - [0.01096348, 0.1726348, 0.8826348], - [0.01094416, 0.1724416, 0.8824416], - [0.01092072, 0.1722072, 0.8822072], - [0.01090264, 0.1720264, 0.8820264], - [0.01088508, 0.1718508, 0.8818508], - [0.01086388, 0.1716388, 0.8816388], - [0.01084752, 0.1714752, 0.8814752], - [0.01083168, 0.1713168, 0.8813168], - [0.01081636, 0.1711636, 0.8811636], - [0.010798, 0.17098, 0.88098], - [0.0107838, 0.170838, 0.880838], - [0.01077016, 0.1707016, 0.8807016], - [0.010757, 0.17057, 0.88057], - [0.01074436, 0.1704436, 0.8804436], - [0.01072924, 0.1702924, 0.8802924], - [0.01071768, 0.1701768, 0.8801768], - [0.01070656, 0.1700656, 0.8800656], - [0.010696, 0.16996, 0.87996], - [0.01068336, 0.1698336, 0.8798336], - [0.01067376, 0.1697376, 0.8797376], - [0.01066464, 0.1696464, 0.8796464], - [0.010656, 0.16956, 0.87956], - [0.01064572, 0.1694572, 0.8794572], - [0.01063804, 0.1693804, 0.8793804], - [0.01063076, 0.1693076, 0.8793076], - [0.01062224, 0.1692224, 0.8792224], - [0.01061588, 0.1691588, 0.8791588], - [0.01060996, 0.1690996, 0.8790996], - [0.0106044, 0.169044, 0.879044], - [0.0105992, 0.168992, 0.878992], - [0.01059328, 0.1689328, 0.8789328], - [0.01058892, 0.1688892, 0.8788892], - [0.01058496, 0.1688496, 0.8788496], - [0.01058132, 0.1688132, 0.8788132], - [0.01057728, 0.1687728, 0.8787728], - [0.0105744, 0.168744, 0.878744], - [0.01057184, 0.1687184, 0.8787184], - [0.01056956, 0.1686956, 0.8786956], - [0.01056716, 0.1686716, 0.8786716], - [0.01056556, 0.1686556, 0.8786556], - [0.0105642, 0.168642, 0.878642], - [0.01056284, 0.1686284, 0.8786284], - [0.0105618, 0.168618, 0.878618], - [0.0105608, 0.168608, 0.878608], - [0.01056112, 0.1686112, 0.8786112], - [0.01056148, 0.1686148, 0.8786148], - [0.01056196, 0.1686196, 0.8786196], - [0.0105624, 0.168624, 0.878624], - [0.01056296, 0.1686296, 0.8786296], - [0.0105636, 0.168636, 0.878636], - [0.01056452, 0.1686452, 0.8786452], - [0.0105654, 0.168654, 0.878654], - [0.0105664, 0.168664, 0.878664], - [0.01056748, 0.1686748, 0.8786748], - [0.010569, 0.16869, 0.87869], - [0.01057036, 0.1687036, 0.8787036], - [0.0105718, 0.168718, 0.878718], - [0.01057384, 0.1687384, 0.8787384], - [0.0105756, 0.168756, 0.878756], - [0.01057748, 0.1687748, 0.8787748], - [0.01057948, 0.1687948, 0.8787948], - [0.01058164, 0.1688164, 0.8788164], - [0.01058456, 0.1688456, 0.8788456], - [0.010587, 0.16887, 0.87887], - [0.01058964, 0.1688964, 0.8788964], - [0.0105924, 0.168924, 0.878924], - [0.01059604, 0.1689604, 0.8789604], - [0.01059916, 0.1689916, 0.8789916], - [0.0106024, 0.169024, 0.879024], - [0.01060668, 0.1690668, 0.8790668], - [0.01061028, 0.1691028, 0.8791028], - [0.01061408, 0.1691408, 0.8791408], - [0.010618, 0.16918, 0.87918], - [0.01062312, 0.1692312, 0.8792312], - [0.01062744, 0.1692744, 0.8792744], - [0.01063188, 0.1693188, 0.8793188], - [0.01063652, 0.1693652, 0.8793652], - [0.01064132, 0.1694132, 0.8794132], - [0.0106476, 0.169476, 0.879476], - [0.0106528, 0.169528, 0.879528], - [0.01065816, 0.1695816, 0.8795816], - [0.01066372, 0.1696372, 0.8796372], - [0.01067092, 0.1697092, 0.8797092], - [0.01067688, 0.1697688, 0.8797688], - [0.01068304, 0.1698304, 0.8798304], - [0.01068936, 0.1698936, 0.8798936], - [0.01069756, 0.1699756, 0.8799756], - [0.01070428, 0.1700428, 0.8800428], - [0.01071124, 0.1701124, 0.8801124], - [0.0107184, 0.170184, 0.880184], - [0.0107276, 0.170276, 0.880276], - [0.0107352, 0.170352, 0.880352], - [0.01074296, 0.1704296, 0.8804296], - [0.01075092, 0.1705092, 0.8805092], - [0.01076116, 0.1706116, 0.8806116], - [0.0107696, 0.170696, 0.880696], - [0.01077824, 0.1707824, 0.8807824], - [0.01078708, 0.1708708, 0.8808708], - [0.0107984, 0.170984, 0.880984], - [0.01080772, 0.1710772, 0.8810772], - [0.0108172, 0.171172, 0.881172], - [0.0108294, 0.171294, 0.881294], - [0.0108394, 0.171394, 0.881394], - [0.0108496, 0.171496, 0.881496], - [0.01074856, 0.1704856, 0.8804856], - [0.01064964, 0.1694964, 0.8794964], - [0.01052896, 0.1682896, 0.8782896], - [0.01043476, 0.1673476, 0.8773476], - [0.0103426, 0.166426, 0.876426], - [0.01025252, 0.1655252, 0.8755252], - [0.0101428, 0.164428, 0.874428], - [0.01005732, 0.1635732, 0.8735732], - [0.0099738, 0.162738, 0.872738], - [0.00987228, 0.1617228, 0.8717228], - [0.009716, 0.16016, 0.87016], - [0.0096412, 0.159412, 0.869412], - [0.009568, 0.15868, 0.86868], - [0.00947924, 0.1577924, 0.8677924], - [0.0094104, 0.157104, 0.867104], - [0.00934348, 0.1564348, 0.8664348], - [0.0092624, 0.155624, 0.865624], - [0.00919968, 0.1549968, 0.8649968], - [0.0091388, 0.154388, 0.864388], - [0.00907968, 0.1537968, 0.8637968], - [0.0090224, 0.153224, 0.863224], - [0.00895336, 0.1525336, 0.8625336], - [0.00890008, 0.1520008, 0.8620008], - [0.0088486, 0.151486, 0.861486], - [0.00878664, 0.1508664, 0.8608664], - [0.00873904, 0.1503904, 0.8603904], - [0.00869312, 0.1499312, 0.8599312], - [0.00864888, 0.1494888, 0.8594888], - [0.00860632, 0.1490632, 0.8590632], - [0.00855544, 0.1485544, 0.8585544], - [0.00851652, 0.1481652, 0.8581652], - [0.00851652, 0.1481652, 0.8581652], - [0.00847924, 0.1477924, 0.8577924], - [0.00844356, 0.1474356, 0.8574356], - [0.00840112, 0.1470112, 0.8570112], - [0.00836888, 0.1466888, 0.8566888], - [0.0083382, 0.146382, 0.856382], - [0.008309, 0.14609, 0.85609], - [0.00827456, 0.1457456, 0.8557456], - [0.00824868, 0.1454868, 0.8554868], - [0.00822416, 0.1452416, 0.8552416], - [0.00819556, 0.1449556, 0.8549556], - [0.00817416, 0.1447416, 0.8547416], - [0.00815416, 0.1445416, 0.8545416], - [0.00813544, 0.1443544, 0.8543544], - [0.00811804, 0.1441804, 0.8541804], - [0.00809808, 0.1439808, 0.8539808], - [0.00808348, 0.1438348, 0.8538348], - [0.00807012, 0.1437012, 0.8537012], - [0.00805504, 0.1435504, 0.8535504], - [0.00804424, 0.1434424, 0.8534424], - [0.00803456, 0.1433456, 0.8533456], - [0.00802592, 0.1432592, 0.8532592], - [0.00801832, 0.1431832, 0.8531832], - [0.00801024, 0.1431024, 0.8531024], - [0.0080048, 0.143048, 0.853048], - [0.00800028, 0.1430028, 0.8530028], - [0.00799572, 0.1429572, 0.8529572], - [0.00799224, 0.1429224, 0.8529224], - [0.0079888, 0.142888, 0.852888], - [0.0079898, 0.142898, 0.852898], - [0.00799084, 0.1429084, 0.8529084], - [0.0079922, 0.142922, 0.852922], - [0.0079936, 0.142936, 0.852936], - [0.0079952, 0.142952, 0.852952], - [0.00799704, 0.1429704, 0.8529704], - [0.00799984, 0.1429984, 0.8529984], - [0.0080024, 0.143024, 0.853024], - [0.00800528, 0.1430528, 0.8530528], - [0.00800848, 0.1430848, 0.8530848], - [0.00801296, 0.1431296, 0.8531296], - [0.00801692, 0.1431692, 0.8531692], - [0.00802128, 0.1432128, 0.8532128], - [0.0080272, 0.143272, 0.853272], - [0.0080324, 0.143324, 0.853324], - [0.00803796, 0.1433796, 0.8533796], - [0.00804388, 0.1434388, 0.8534388], - [0.00805024, 0.1435024, 0.8535024], - [0.0080588, 0.143588, 0.853588], - [0.00806604, 0.1436604, 0.8536604], - [0.00807372, 0.1437372, 0.8537372], - [0.008084, 0.14384, 0.85384], - [0.00809264, 0.1439264, 0.8539264], - [0.00810176, 0.1440176, 0.8540176], - [0.00811136, 0.1441136, 0.8541136], - [0.008124, 0.14424, 0.85424], - [0.0081346, 0.144346, 0.854346], - [0.00814568, 0.1444568, 0.8544568], - [0.0081572, 0.144572, 0.854572], - [0.00817236, 0.1447236, 0.8547236], - [0.008185, 0.14485, 0.85485], - [0.00819816, 0.1449816, 0.8549816], - [0.0082118, 0.145118, 0.855118], - [0.008226, 0.14526, 0.85526], - [0.0082444, 0.145444, 0.855444], - [0.00826, 0.1456, 0.8556], - [0.00827552, 0.1457552, 0.8557552], - [0.00829188, 0.1459188, 0.8559188], - [0.00831308, 0.1461308, 0.8561308], - [0.00833064, 0.1463064, 0.8563064], - [0.00834872, 0.1464872, 0.8564872], - [0.00837212, 0.1467212, 0.8567212], - [0.00839148, 0.1469148, 0.8569148], - [0.00841136, 0.1471136, 0.8571136], - [0.00843184, 0.1473184, 0.8573184], - [0.00845288, 0.1475288, 0.8575288], - [0.00848, 0.1478, 0.8578], - [0.00850228, 0.1480228, 0.8580228], - [0.0085252, 0.148252, 0.858252], - [0.00855464, 0.1485464, 0.8585464], - [0.00857884, 0.1487884, 0.8587884], - [0.00860368, 0.1490368, 0.8590368], - [0.00862908, 0.1492908, 0.8592908], - [0.00866172, 0.1496172, 0.8596172], - [0.00868848, 0.1498848, 0.8598848], - [0.00871588, 0.1501588, 0.8601588], - [0.00874388, 0.1504388, 0.8604388], - [0.00877976, 0.1507976, 0.8607976], - [0.0088092, 0.151092, 0.861092], - [0.0088392, 0.151392, 0.861392], - [0.008829, 0.15129, 0.86129], - [0.008819, 0.15119, 0.86119], - [0.0088068, 0.151068, 0.861068], - [0.00879732, 0.1509732, 0.8609732], - [0.008788, 0.15088, 0.86088], - [0.00877668, 0.1507668, 0.8607668], - [0.00876784, 0.1506784, 0.8606784], - [0.0087592, 0.150592, 0.860592], - [0.0087508, 0.150508, 0.860508], - [0.00874256, 0.1504256, 0.8604256], - [0.00873256, 0.1503256, 0.8603256], - [0.0087248, 0.150248, 0.860248], - [0.0087172, 0.150172, 0.860172], - [0.008708, 0.15008, 0.86008], - [0.00870084, 0.1500084, 0.8600084], - [0.00869388, 0.1499388, 0.8599388], - [0.00868712, 0.1498712, 0.8598712], - [0.00867896, 0.1497896, 0.8597896], - [0.00867264, 0.1497264, 0.8597264], - [0.00866648, 0.1496648, 0.8596648], - [0.00866052, 0.1496052, 0.8596052], - [0.00865332, 0.1495332, 0.8595332], - [0.00864776, 0.1494776, 0.8594776], - [0.0086424, 0.149424, 0.859424], - [0.0086372, 0.149372, 0.859372], - [0.00863092, 0.1493092, 0.8593092], - [0.00862612, 0.1492612, 0.8592612], - [0.00862148, 0.1492148, 0.8592148], - [0.008617, 0.14917, 0.85917], - [0.00861272, 0.1491272, 0.8591272], - [0.0086076, 0.149076, 0.859076], - [0.00860368, 0.1490368, 0.8590368], - [0.00859988, 0.1489988, 0.8589988], - [0.00859628, 0.1489628, 0.8589628], - [0.008592, 0.14892, 0.85892], - [0.00858876, 0.1488876, 0.8588876], - [0.00858564, 0.1488564, 0.8588564], - [0.00858272, 0.1488272, 0.8588272], - [0.00857924, 0.1487924, 0.8587924], - [0.0085766, 0.148766, 0.858766], - [0.00857416, 0.1487416, 0.8587416], - [0.0085718, 0.148718, 0.858718], - [0.00856908, 0.1486908, 0.8586908], - [0.00856708, 0.1486708, 0.8586708], - [0.0085652, 0.148652, 0.858652], - [0.008563, 0.14863, 0.85863], - [0.0085614, 0.148614, 0.858614], - [0.00856, 0.1486, 0.8586], - [0.0085586, 0.148586, 0.858586], - [0.00855736, 0.1485736, 0.8585736], - [0.008556, 0.14856, 0.85856], - [0.008555, 0.14855, 0.85855], - [0.00855412, 0.1485412, 0.8585412], - [0.0085532, 0.148532, 0.858532], - [0.00855256, 0.1485256, 0.8585256], - [0.008552, 0.14852, 0.85852], - [0.00855156, 0.1485156, 0.8585156], - [0.00855108, 0.1485108, 0.8585108], - [0.00855072, 0.1485072, 0.8585072], -]; diff --git a/src/webviews/theme/utils/index.ts b/src/webviews/theme/utils/index.ts deleted file mode 100644 index 868ff0bad..000000000 --- a/src/webviews/theme/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export * from './csswg'; -export * from './geometry'; -export * from './palettes'; -export * from './types'; - diff --git a/src/webviews/theme/utils/palettes.ts b/src/webviews/theme/utils/palettes.ts deleted file mode 100644 index dd39f5240..000000000 --- a/src/webviews/theme/utils/palettes.ts +++ /dev/null @@ -1,216 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* eslint-disable @typescript-eslint/naming-convention */ -import { Lab_to_LCH, LAB_to_sRGB, LCH_to_Lab, snap_into_gamut, sRGB_to_LCH } from './csswg'; -import { getPointsOnCurvePath } from './geometry'; -import { hexToHue, hueToSnappingPointsMap } from './hueMap'; -import { type CurvedHelixPath, type Palette, type Vec3 } from './types'; - -/** - * When distributing output shades along the curve, for each shade’s lightness a - * logarithmically distributed value is averaged with a linearly distributed - * value to this degree between zero and one, zero meaning use the logarithmic - * value, one meaning use the linear value. - */ -const defaultLinearity = 0.75; - -const snappingPointsForKeyColor = (keyColor: string): number[] => { - const hue = hexToHue(keyColor); - return [ - hueToSnappingPointsMap[hue][0] * 100, - hueToSnappingPointsMap[hue][1] * 100, - hueToSnappingPointsMap[hue][2] * 100, - ]; -}; - -const pointsForKeyColor = (keyColor: string, range: number[]): number[] => { - const hue = hexToHue(keyColor); - const center = hueToSnappingPointsMap[hue][1] * 100; - - return linearInterpolationThroughPoint(range[0], range[1], center, 16); -}; - -function linearInterpolationThroughPoint(start: number, end: number, inBetween: number, numSamples: number) { - if (numSamples < 3) { - throw new Error('Number of samples must be at least 3.'); - } - - // Find the ratio of the inBetween point - const inBetweenRatio = (inBetween - start) / (end - start); - - // Calculate the index of the inBetween point in the resulting array - const inBetweenIndex = Math.floor((numSamples - 1) * inBetweenRatio); - - // Initialize the output array - const result: number[] = new Array(numSamples); - - // Set start, inBetween and end points in the result array - result[0] = start; - result[inBetweenIndex] = inBetween; - result[numSamples - 1] = end; - - // Calculate the step size for each segment - const stepBefore = (inBetween - start) / inBetweenIndex; - const stepAfter = (end - inBetween) / (numSamples - 1 - inBetweenIndex); - - // Fill the array with interpolated values before the inBetween point - for (let i = 1; i < inBetweenIndex; i++) { - result[i] = start + i * stepBefore; - } - - // Fill the array with interpolated values after the inBetween point - for (let i = inBetweenIndex + 1; i < numSamples - 1; i++) { - result[i] = inBetween + (i - inBetweenIndex) * stepAfter; - } - - return result; -} - -const getLogSpace = (min: number, max: number, n: number) => { - const a = min <= 0 ? 0 : Math.log(min); - const b = Math.log(max); - const delta = (b - a) / n; - - const result = [Math.pow(Math.E, a)]; - for (let i = 1; i < n; i += 1) { - result.push(Math.pow(Math.E, a + delta * i)); - } - result.push(Math.pow(Math.E, b)); - return result; -}; - -function paletteShadesFromCurvePoints( - curvePoints: Vec3[], - nShades: number, - linearity = defaultLinearity, - keyColor: string, -): Vec3[] { - if (curvePoints.length <= 2) { - return []; - } - - const snappingPoints = snappingPointsForKeyColor(keyColor); - const paletteShades: Vec3[] = []; - const range = [snappingPoints[0], snappingPoints[2]]; - const logLightness = getLogSpace(Math.log10(0), Math.log10(100), nShades); - const linearLightness = pointsForKeyColor(keyColor, range); - let c = 0; - - // obtain 2d path through color space to grab points from - for (let i = 0; i < nShades; i++) { - const l = Math.min( - range[1], - Math.max(range[0], logLightness[i] * (1 - linearity) + linearLightness[i] * linearity), - ); - - while (l > curvePoints[c + 1][0]) { - c++; - } - - const [l1, a1, b1] = curvePoints[c]; - const [l2, a2, b2] = curvePoints[c + 1]; - - const u = (l - l1) / (l2 - l1); - - paletteShades[i] = [l1 + (l2 - l1) * u, a1 + (a2 - a1) * u, b1 + (b2 - b1) * u] as Vec3; - } - - return paletteShades.map(snap_into_gamut); -} - -export function paletteShadesFromCurve( - keyColor: string, - curve: CurvedHelixPath, - nShades = 16, - linearity = defaultLinearity, - curveDepth = 24, -): Vec3[] { - const points = getPointsOnCurvePath(curve, Math.ceil((curveDepth * (1 + Math.abs(curve.torsion || 1))) / 2)).map( - (curvePoint: Vec3) => getPointOnHelix(curvePoint, curve.torsion, curve.torsionT0), - ); - return paletteShadesFromCurvePoints(points, nShades, linearity, keyColor); -} - -export function sRGB_to_hex(rgb: Vec3): string { - return `#${rgb - .map((x) => { - const channel = x < 0 ? 0 : Math.floor(x >= 1.0 ? 255 : x * 256); - return channel.toString(16).padStart(2, '0'); - }) - .join('')}`; -} - -export function Lab_to_hex(lab: Vec3): string { - return sRGB_to_hex(LAB_to_sRGB(lab)); -} - -export function hex_to_sRGB(hex: string): Vec3 { - const aRgbHex = hex.match(/#?(..)(..)(..)/); - return aRgbHex - ? [parseInt(aRgbHex[1], 16) / 255, parseInt(aRgbHex[2], 16) / 255, parseInt(aRgbHex[3], 16) / 255] - : [0, 0, 0]; -} - -export function hex_to_LCH(hex: string): Vec3 { - return sRGB_to_LCH(hex_to_sRGB(hex)); -} - -function paletteShadesToHex(paletteShades: Vec3[]): string[] { - return paletteShades.map(Lab_to_hex); -} - -function getPointOnHelix(pointOnCurve: Vec3, torsion = 0, torsionT0 = 50): Vec3 { - const t = pointOnCurve[0]; - const [l, c, h] = Lab_to_LCH(pointOnCurve); - const hueOffset = torsion * (t - torsionT0); - return LCH_to_Lab([l, c, h + hueOffset]); -} - -export function curvePathFromPalette({ keyColor, darkCp, lightCp, hueTorsion }: Palette): CurvedHelixPath { - const blackPosition = [0, 0, 0]; - const whitePosition = [100, 0, 0]; - const keyColorPosition = LCH_to_Lab(keyColor); - const [l, a, b] = keyColorPosition; - - const darkControlPosition = [l * (1 - darkCp), a, b]; - const lightControlPosition = [l + (100 - l) * lightCp, a, b]; - - return { - curves: [ - { points: [blackPosition, darkControlPosition, keyColorPosition] }, - { points: [keyColorPosition, lightControlPosition, whitePosition] }, - ], - torsion: hueTorsion, - torsionT0: l, - } as CurvedHelixPath; -} - -export function hexColorsFromPalette( - keyColor: string, - palette: Palette, - nShades = 16, - linearity = defaultLinearity, - curveDepth = 24, -): string[] { - const curve = curvePathFromPalette(palette); - const shades = paletteShadesFromCurve(keyColor, curve, nShades, linearity, curveDepth); - return paletteShadesToHex(shades); -} - -export function RGBAToHexA(rgba: string, forceRemoveAlpha = false) { - return ( - '#' + - rgba - .replace(/^rgba?\(|\s+|\)$/g, '') // Gets rgba / rgb string values - .split(',') // splits them at "," - .filter((_string, index) => !forceRemoveAlpha || index !== 3) - .map((string) => parseFloat(string)) // Converts them to numbers - .map((number, index) => (index === 3 ? Math.round(number * 255) : number)) // Converts alpha to 255 number - .map((number) => number.toString(16)) // Converts numbers to hex - .map((string) => (string.length === 1 ? '0' + string : string)) // Adds 0 when length of one number is 1 - .join('') - ); // Puts the array together to a string -} diff --git a/src/webviews/theme/utils/types.ts b/src/webviews/theme/utils/types.ts deleted file mode 100644 index 3788c2b56..000000000 --- a/src/webviews/theme/utils/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type Vec3 = [number, number, number]; - -export type Curve = { - points: [Vec3, Vec3, Vec3]; - cacheArcLengths?: number[]; -}; - -export interface CurvePath { - curves: Curve[]; - cacheLengths?: number[]; -} - -export interface CurvedHelixPath extends CurvePath { - torsion?: number; - torsionT0?: number; -} - -export type Palette = { - keyColor: Vec3; - darkCp: number; - lightCp: number; - hueTorsion: number; -}; diff --git a/src/webviews/theme/vscodeThemeTokens.tsx b/src/webviews/theme/vscodeThemeTokens.tsx deleted file mode 100644 index f9eb2ce9e..000000000 --- a/src/webviews/theme/vscodeThemeTokens.tsx +++ /dev/null @@ -1,832 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * const workspaceColorsDocUri = vscode.Uri.parse('vscode://schemas/workbench-colors'); - * const doc = await vscode.workspace.openTextDocument(workspaceColorsDocUri); - * const workbenchColors = await JSON.parse(doc.getText()); - * - * https://code.visualstudio.com/api/references/theme-color#base-colors - */ - -export const vscodeThemeTokens = [ - 'actionBar.toggledBackground', - 'activityBar.activeBackground', - 'activityBar.activeBorder', - 'activityBar.activeFocusBorder', - 'activityBar.background', - 'activityBar.border', - 'activityBar.dropBorder', - 'activityBar.foreground', - 'activityBar.inactiveForeground', - 'activityBarBadge.background', - 'activityBarBadge.foreground', - 'activityBarTop.activeBackground', - 'activityBarTop.activeBorder', - 'activityBarTop.background', - 'activityBarTop.dropBorder', - 'activityBarTop.foreground', - 'activityBarTop.inactiveForeground', - 'badge.background', - 'badge.foreground', - 'banner.background', - 'banner.foreground', - 'banner.iconForeground', - 'breadcrumb.activeSelectionForeground', - 'breadcrumb.background', - 'breadcrumb.focusForeground', - 'breadcrumb.foreground', - 'breadcrumbPicker.background', - 'button.background', - 'button.border', - 'button.foreground', - 'button.hoverBackground', - 'button.secondaryBackground', - 'button.secondaryForeground', - 'button.secondaryHoverBackground', - 'button.separator', - 'charts.blue', - 'charts.foreground', - 'charts.green', - 'charts.lines', - 'charts.orange', - 'charts.purple', - 'charts.red', - 'charts.yellow', - 'chat.avatarBackground', - 'chat.avatarForeground', - 'chat.requestBackground', - 'chat.requestBorder', - 'chat.slashCommandBackground', - 'chat.slashCommandForeground', - 'checkbox.background', - 'checkbox.border', - 'checkbox.foreground', - 'checkbox.selectBackground', - 'checkbox.selectBorder', - 'commandCenter.activeBackground', - 'commandCenter.activeBorder', - 'commandCenter.activeForeground', - 'commandCenter.background', - 'commandCenter.border', - 'commandCenter.debuggingBackground', - 'commandCenter.foreground', - 'commandCenter.inactiveBorder', - 'commandCenter.inactiveForeground', - 'commentsView.resolvedIcon', - 'commentsView.unresolvedIcon', - 'contrastActiveBorder', - 'contrastBorder', - 'debugConsole.errorForeground', - 'debugConsole.infoForeground', - 'debugConsole.sourceForeground', - 'debugConsole.warningForeground', - 'debugConsoleInputIcon.foreground', - 'debugExceptionWidget.background', - 'debugExceptionWidget.border', - 'debugIcon.breakpointCurrentStackframeForeground', - 'debugIcon.breakpointDisabledForeground', - 'debugIcon.breakpointForeground', - 'debugIcon.breakpointStackframeForeground', - 'debugIcon.breakpointUnverifiedForeground', - 'debugIcon.continueForeground', - 'debugIcon.disconnectForeground', - 'debugIcon.pauseForeground', - 'debugIcon.restartForeground', - 'debugIcon.startForeground', - 'debugIcon.stepBackForeground', - 'debugIcon.stepIntoForeground', - 'debugIcon.stepOutForeground', - 'debugIcon.stepOverForeground', - 'debugIcon.stopForeground', - 'debugTokenExpression.boolean', - 'debugTokenExpression.error', - 'debugTokenExpression.name', - 'debugTokenExpression.number', - 'debugTokenExpression.string', - 'debugTokenExpression.type', - 'debugTokenExpression.value', - 'debugToolBar.background', - 'debugToolBar.border', - 'debugView.exceptionLabelBackground', - 'debugView.exceptionLabelForeground', - 'debugView.stateLabelBackground', - 'debugView.stateLabelForeground', - 'debugView.valueChangedHighlight', - 'descriptionForeground', - 'diffEditor.border', - 'diffEditor.diagonalFill', - 'diffEditor.insertedLineBackground', - 'diffEditor.insertedTextBackground', - 'diffEditor.insertedTextBorder', - 'diffEditor.move.border', - 'diffEditor.moveActive.border', - 'diffEditor.removedLineBackground', - 'diffEditor.removedTextBackground', - 'diffEditor.removedTextBorder', - 'diffEditor.unchangedCodeBackground', - 'diffEditor.unchangedRegionBackground', - 'diffEditor.unchangedRegionForeground', - 'diffEditor.unchangedRegionShadow', - 'diffEditorGutter.insertedLineBackground', - 'diffEditorGutter.removedLineBackground', - 'diffEditorOverview.insertedForeground', - 'diffEditorOverview.removedForeground', - 'disabledForeground', - 'dropdown.background', - 'dropdown.border', - 'dropdown.foreground', - 'dropdown.listBackground', - 'editor.background', - 'editor.findMatchBackground', - 'editor.findMatchBorder', - 'editor.findMatchForeground', - 'editor.findMatchHighlightBackground', - 'editor.findMatchHighlightBorder', - 'editor.findMatchHighlightForeground', - 'editor.findRangeHighlightBackground', - 'editor.findRangeHighlightBorder', - 'editor.focusedStackFrameHighlightBackground', - 'editor.foldBackground', - 'editor.foldPlaceholderForeground', - 'editor.foreground', - 'editor.hoverHighlightBackground', - 'editor.inactiveSelectionBackground', - 'editor.inlineValuesBackground', - 'editor.inlineValuesForeground', - 'editor.lineHighlightBackground', - 'editor.lineHighlightBorder', - 'editor.linkedEditingBackground', - 'editor.placeholder.foreground', - 'editor.rangeHighlightBackground', - 'editor.rangeHighlightBorder', - 'editor.selectionBackground', - 'editor.selectionForeground', - 'editor.selectionHighlightBackground', - 'editor.selectionHighlightBorder', - 'editor.snippetFinalTabstopHighlightBackground', - 'editor.snippetFinalTabstopHighlightBorder', - 'editor.snippetTabstopHighlightBackground', - 'editor.snippetTabstopHighlightBorder', - 'editor.stackFrameHighlightBackground', - 'editor.symbolHighlightBackground', - 'editor.symbolHighlightBorder', - 'editor.wordHighlightBackground', - 'editor.wordHighlightBorder', - 'editor.wordHighlightStrongBackground', - 'editor.wordHighlightStrongBorder', - 'editor.wordHighlightTextBackground', - 'editor.wordHighlightTextBorder', - 'editorActionList.background', - 'editorActionList.focusBackground', - 'editorActionList.focusForeground', - 'editorActionList.foreground', - 'editorActiveLineNumber.foreground', - 'editorBracketHighlight.foreground1', - 'editorBracketHighlight.foreground2', - 'editorBracketHighlight.foreground3', - 'editorBracketHighlight.foreground4', - 'editorBracketHighlight.foreground5', - 'editorBracketHighlight.foreground6', - 'editorBracketHighlight.unexpectedBracket.foreground', - 'editorBracketMatch.background', - 'editorBracketMatch.border', - 'editorBracketPairGuide.activeBackground1', - 'editorBracketPairGuide.activeBackground2', - 'editorBracketPairGuide.activeBackground3', - 'editorBracketPairGuide.activeBackground4', - 'editorBracketPairGuide.activeBackground5', - 'editorBracketPairGuide.activeBackground6', - 'editorBracketPairGuide.background1', - 'editorBracketPairGuide.background2', - 'editorBracketPairGuide.background3', - 'editorBracketPairGuide.background4', - 'editorBracketPairGuide.background5', - 'editorBracketPairGuide.background6', - 'editorCodeLens.foreground', - 'editorCommentsWidget.rangeActiveBackground', - 'editorCommentsWidget.rangeBackground', - 'editorCommentsWidget.replyInputBackground', - 'editorCommentsWidget.resolvedBorder', - 'editorCommentsWidget.unresolvedBorder', - 'editorCursor.background', - 'editorCursor.foreground', - 'editorError.background', - 'editorError.border', - 'editorError.foreground', - 'editorGhostText.background', - 'editorGhostText.border', - 'editorGhostText.foreground', - 'editorGroup.border', - 'editorGroup.dropBackground', - 'editorGroup.dropIntoPromptBackground', - 'editorGroup.dropIntoPromptBorder', - 'editorGroup.dropIntoPromptForeground', - 'editorGroup.emptyBackground', - 'editorGroup.focusedEmptyBorder', - 'editorGroupHeader.border', - 'editorGroupHeader.noTabsBackground', - 'editorGroupHeader.tabsBackground', - 'editorGroupHeader.tabsBorder', - 'editorGutter.addedBackground', - 'editorGutter.background', - 'editorGutter.commentGlyphForeground', - 'editorGutter.commentRangeForeground', - 'editorGutter.commentUnresolvedGlyphForeground', - 'editorGutter.deletedBackground', - 'editorGutter.foldingControlForeground', - 'editorGutter.modifiedBackground', - 'editorHint.border', - 'editorHint.foreground', - 'editorHoverWidget.background', - 'editorHoverWidget.border', - 'editorHoverWidget.foreground', - 'editorHoverWidget.highlightForeground', - 'editorHoverWidget.statusBarBackground', - 'editorIndentGuide.activeBackground', - 'editorIndentGuide.activeBackground1', - 'editorIndentGuide.activeBackground2', - 'editorIndentGuide.activeBackground3', - 'editorIndentGuide.activeBackground4', - 'editorIndentGuide.activeBackground5', - 'editorIndentGuide.activeBackground6', - 'editorIndentGuide.background', - 'editorIndentGuide.background1', - 'editorIndentGuide.background2', - 'editorIndentGuide.background3', - 'editorIndentGuide.background4', - 'editorIndentGuide.background5', - 'editorIndentGuide.background6', - 'editorInfo.background', - 'editorInfo.border', - 'editorInfo.foreground', - 'editorInlayHint.background', - 'editorInlayHint.foreground', - 'editorInlayHint.parameterBackground', - 'editorInlayHint.parameterForeground', - 'editorInlayHint.typeBackground', - 'editorInlayHint.typeForeground', - 'editorLightBulb.foreground', - 'editorLightBulbAi.foreground', - 'editorLightBulbAutoFix.foreground', - 'editorLineNumber.activeForeground', - 'editorLineNumber.dimmedForeground', - 'editorLineNumber.foreground', - 'editorLink.activeForeground', - 'editorMarkerNavigation.background', - 'editorMarkerNavigationError.background', - 'editorMarkerNavigationError.headerBackground', - 'editorMarkerNavigationInfo.background', - 'editorMarkerNavigationInfo.headerBackground', - 'editorMarkerNavigationWarning.background', - 'editorMarkerNavigationWarning.headerBackground', - 'editorMultiCursor.primary.background', - 'editorMultiCursor.primary.foreground', - 'editorMultiCursor.secondary.background', - 'editorMultiCursor.secondary.foreground', - 'editorOverviewRuler.addedForeground', - 'editorOverviewRuler.background', - 'editorOverviewRuler.border', - 'editorOverviewRuler.bracketMatchForeground', - 'editorOverviewRuler.commentForeground', - 'editorOverviewRuler.commentUnresolvedForeground', - 'editorOverviewRuler.commonContentForeground', - 'editorOverviewRuler.currentContentForeground', - 'editorOverviewRuler.deletedForeground', - 'editorOverviewRuler.errorForeground', - 'editorOverviewRuler.findMatchForeground', - 'editorOverviewRuler.incomingContentForeground', - 'editorOverviewRuler.infoForeground', - 'editorOverviewRuler.inlineChatInserted', - 'editorOverviewRuler.inlineChatRemoved', - 'editorOverviewRuler.modifiedForeground', - 'editorOverviewRuler.rangeHighlightForeground', - 'editorOverviewRuler.selectionHighlightForeground', - 'editorOverviewRuler.warningForeground', - 'editorOverviewRuler.wordHighlightForeground', - 'editorOverviewRuler.wordHighlightStrongForeground', - 'editorOverviewRuler.wordHighlightTextForeground', - 'editorPane.background', - 'editorRuler.foreground', - 'editorStickyScroll.background', - 'editorStickyScroll.border', - 'editorStickyScroll.shadow', - 'editorStickyScrollHover.background', - 'editorSuggestWidget.background', - 'editorSuggestWidget.border', - 'editorSuggestWidget.focusHighlightForeground', - 'editorSuggestWidget.foreground', - 'editorSuggestWidget.highlightForeground', - 'editorSuggestWidget.selectedBackground', - 'editorSuggestWidget.selectedForeground', - 'editorSuggestWidget.selectedIconForeground', - 'editorSuggestWidgetStatus.foreground', - 'editorUnicodeHighlight.background', - 'editorUnicodeHighlight.border', - 'editorUnnecessaryCode.border', - 'editorUnnecessaryCode.opacity', - 'editorWarning.background', - 'editorWarning.border', - 'editorWarning.foreground', - 'editorWatermark.foreground', - 'editorWhitespace.foreground', - 'editorWidget.background', - 'editorWidget.border', - 'editorWidget.foreground', - 'editorWidget.resizeBorder', - 'errorForeground', - 'extensionBadge.remoteBackground', - 'extensionBadge.remoteForeground', - 'extensionButton.background', - 'extensionButton.foreground', - 'extensionButton.hoverBackground', - 'extensionButton.prominentBackground', - 'extensionButton.prominentForeground', - 'extensionButton.prominentHoverBackground', - 'extensionButton.separator', - 'extensionIcon.preReleaseForeground', - 'extensionIcon.sponsorForeground', - 'extensionIcon.starForeground', - 'extensionIcon.verifiedForeground', - 'focusBorder', - 'foreground', - 'gitDecoration.addedResourceForeground', - 'gitDecoration.conflictingResourceForeground', - 'gitDecoration.deletedResourceForeground', - 'gitDecoration.ignoredResourceForeground', - 'gitDecoration.modifiedResourceForeground', - 'gitDecoration.renamedResourceForeground', - 'gitDecoration.stageDeletedResourceForeground', - 'gitDecoration.stageModifiedResourceForeground', - 'gitDecoration.submoduleResourceForeground', - 'gitDecoration.untrackedResourceForeground', - 'icon.foreground', - 'inlineChat.background', - 'inlineChat.border', - 'inlineChat.foreground', - 'inlineChat.shadow', - 'inlineChatDiff.inserted', - 'inlineChatDiff.removed', - 'inlineChatInput.background', - 'inlineChatInput.border', - 'inlineChatInput.focusBorder', - 'inlineChatInput.placeholderForeground', - 'input.background', - 'input.border', - 'input.foreground', - 'input.placeholderForeground', - 'inputOption.activeBackground', - 'inputOption.activeBorder', - 'inputOption.activeForeground', - 'inputOption.hoverBackground', - 'inputValidation.errorBackground', - 'inputValidation.errorBorder', - 'inputValidation.errorForeground', - 'inputValidation.infoBackground', - 'inputValidation.infoBorder', - 'inputValidation.infoForeground', - 'inputValidation.warningBackground', - 'inputValidation.warningBorder', - 'inputValidation.warningForeground', - 'interactive.activeCodeBorder', - 'interactive.inactiveCodeBorder', - 'keybindingLabel.background', - 'keybindingLabel.border', - 'keybindingLabel.bottomBorder', - 'keybindingLabel.foreground', - 'keybindingTable.headerBackground', - 'keybindingTable.rowsBackground', - 'list.activeSelectionBackground', - 'list.activeSelectionForeground', - 'list.activeSelectionIconForeground', - 'list.deemphasizedForeground', - 'list.dropBackground', - 'list.dropBetweenBackground', - 'list.errorForeground', - 'list.filterMatchBackground', - 'list.filterMatchBorder', - 'list.focusAndSelectionOutline', - 'list.focusBackground', - 'list.focusForeground', - 'list.focusHighlightForeground', - 'list.focusOutline', - 'list.highlightForeground', - 'list.hoverBackground', - 'list.hoverForeground', - 'list.inactiveFocusBackground', - 'list.inactiveFocusOutline', - 'list.inactiveSelectionBackground', - 'list.inactiveSelectionForeground', - 'list.inactiveSelectionIconForeground', - 'list.invalidItemForeground', - 'list.warningForeground', - 'listFilterWidget.background', - 'listFilterWidget.noMatchesOutline', - 'listFilterWidget.outline', - 'listFilterWidget.shadow', - 'menu.background', - 'menu.border', - 'menu.foreground', - 'menu.selectionBackground', - 'menu.selectionBorder', - 'menu.selectionForeground', - 'menu.separatorBackground', - 'menubar.selectionBackground', - 'menubar.selectionBorder', - 'menubar.selectionForeground', - 'merge.border', - 'merge.commonContentBackground', - 'merge.commonHeaderBackground', - 'merge.currentContentBackground', - 'merge.currentHeaderBackground', - 'merge.incomingContentBackground', - 'merge.incomingHeaderBackground', - 'mergeEditor.change.background', - 'mergeEditor.change.word.background', - 'mergeEditor.changeBase.background', - 'mergeEditor.changeBase.word.background', - 'mergeEditor.conflict.handled.minimapOverViewRuler', - 'mergeEditor.conflict.handledFocused.border', - 'mergeEditor.conflict.handledUnfocused.border', - 'mergeEditor.conflict.input1.background', - 'mergeEditor.conflict.input2.background', - 'mergeEditor.conflict.unhandled.minimapOverViewRuler', - 'mergeEditor.conflict.unhandledFocused.border', - 'mergeEditor.conflict.unhandledUnfocused.border', - 'mergeEditor.conflictingLines.background', - 'minimap.background', - 'minimap.errorHighlight', - 'minimap.findMatchHighlight', - 'minimap.foregroundOpacity', - 'minimap.infoHighlight', - 'minimap.selectionHighlight', - 'minimap.selectionOccurrenceHighlight', - 'minimap.warningHighlight', - 'minimapGutter.addedBackground', - 'minimapGutter.deletedBackground', - 'minimapGutter.modifiedBackground', - 'minimapSlider.activeBackground', - 'minimapSlider.background', - 'minimapSlider.hoverBackground', - 'multiDiffEditor.background', - 'multiDiffEditor.border', - 'multiDiffEditor.headerBackground', - 'notebook.cellBorderColor', - 'notebook.cellEditorBackground', - 'notebook.cellHoverBackground', - 'notebook.cellInsertionIndicator', - 'notebook.cellStatusBarItemHoverBackground', - 'notebook.cellToolbarSeparator', - 'notebook.editorBackground', - 'notebook.focusedCellBackground', - 'notebook.focusedCellBorder', - 'notebook.focusedEditorBorder', - 'notebook.inactiveFocusedCellBorder', - 'notebook.inactiveSelectedCellBorder', - 'notebook.outputContainerBackgroundColor', - 'notebook.outputContainerBorderColor', - 'notebook.selectedCellBackground', - 'notebook.selectedCellBorder', - 'notebook.symbolHighlightBackground', - 'notebookEditorOverviewRuler.runningCellForeground', - 'notebookScrollbarSlider.activeBackground', - 'notebookScrollbarSlider.background', - 'notebookScrollbarSlider.hoverBackground', - 'notebookStatusErrorIcon.foreground', - 'notebookStatusRunningIcon.foreground', - 'notebookStatusSuccessIcon.foreground', - 'notificationCenter.border', - 'notificationCenterHeader.background', - 'notificationCenterHeader.foreground', - 'notificationLink.foreground', - 'notifications.background', - 'notifications.border', - 'notifications.foreground', - 'notificationsErrorIcon.foreground', - 'notificationsInfoIcon.foreground', - 'notificationsWarningIcon.foreground', - 'notificationToast.border', - 'outputView.background', - 'outputViewStickyScroll.background', - 'panel.background', - 'panel.border', - 'panel.dropBorder', - 'panelInput.border', - 'panelSection.border', - 'panelSection.dropBackground', - 'panelSectionHeader.background', - 'panelSectionHeader.border', - 'panelSectionHeader.foreground', - 'panelStickyScroll.background', - 'panelStickyScroll.border', - 'panelStickyScroll.shadow', - 'panelTitle.activeBorder', - 'panelTitle.activeForeground', - 'panelTitle.inactiveForeground', - 'peekView.border', - 'peekViewEditor.background', - 'peekViewEditor.matchHighlightBackground', - 'peekViewEditor.matchHighlightBorder', - 'peekViewEditorGutter.background', - 'peekViewEditorStickyScroll.background', - 'peekViewResult.background', - 'peekViewResult.fileForeground', - 'peekViewResult.lineForeground', - 'peekViewResult.matchHighlightBackground', - 'peekViewResult.selectionBackground', - 'peekViewResult.selectionForeground', - 'peekViewTitle.background', - 'peekViewTitleDescription.foreground', - 'peekViewTitleLabel.foreground', - 'pickerGroup.border', - 'pickerGroup.foreground', - 'ports.iconRunningProcessForeground', - 'problemsErrorIcon.foreground', - 'problemsInfoIcon.foreground', - 'problemsWarningIcon.foreground', - 'profileBadge.background', - 'profileBadge.foreground', - 'profiles.sashBorder', - 'progressBar.background', - 'quickInput.background', - 'quickInput.foreground', - 'quickInput.list.focusBackground', - 'quickInputList.focusBackground', - 'quickInputList.focusForeground', - 'quickInputList.focusIconForeground', - 'quickInputTitle.background', - 'radio.activeBackground', - 'radio.activeBorder', - 'radio.activeForeground', - 'radio.inactiveBackground', - 'radio.inactiveBorder', - 'radio.inactiveForeground', - 'radio.inactiveHoverBackground', - 'sash.hoverBorder', - 'scm.historyItemAdditionsForeground', - 'scm.historyItemDeletionsForeground', - 'scm.historyItemSelectedStatisticsBorder', - 'scm.historyItemStatisticsBorder', - 'scmGraph.foreground1', - 'scmGraph.foreground2', - 'scmGraph.foreground3', - 'scmGraph.historyItemGroupBase', - 'scmGraph.historyItemGroupLocal', - 'scmGraph.historyItemGroupRemote', - 'scmGraph.historyItemHoverAdditionsForeground', - 'scmGraph.historyItemHoverDeletionsForeground', - 'scmGraph.historyItemHoverLabelForeground', - 'scrollbar.shadow', - 'scrollbarSlider.activeBackground', - 'scrollbarSlider.background', - 'scrollbarSlider.hoverBackground', - 'search.resultsInfoForeground', - 'searchEditor.findMatchBackground', - 'searchEditor.findMatchBorder', - 'searchEditor.textInputBorder', - 'selection.background', - 'settings.checkboxBackground', - 'settings.checkboxBorder', - 'settings.checkboxForeground', - 'settings.dropdownBackground', - 'settings.dropdownBorder', - 'settings.dropdownForeground', - 'settings.dropdownListBorder', - 'settings.focusedRowBackground', - 'settings.focusedRowBorder', - 'settings.headerBorder', - 'settings.headerForeground', - 'settings.modifiedItemIndicator', - 'settings.numberInputBackground', - 'settings.numberInputBorder', - 'settings.numberInputForeground', - 'settings.rowHoverBackground', - 'settings.sashBorder', - 'settings.settingsHeaderHoverForeground', - 'settings.textInputBackground', - 'settings.textInputBorder', - 'settings.textInputForeground', - 'sideBar.background', - 'sideBar.border', - 'sideBar.dropBackground', - 'sideBar.foreground', - 'sideBarActivityBarTop.border', - 'sideBarSectionHeader.background', - 'sideBarSectionHeader.border', - 'sideBarSectionHeader.foreground', - 'sideBarStickyScroll.background', - 'sideBarStickyScroll.border', - 'sideBarStickyScroll.shadow', - 'sideBarTitle.background', - 'sideBarTitle.foreground', - 'sideBySideEditor.horizontalBorder', - 'sideBySideEditor.verticalBorder', - 'simpleFindWidget.sashBorder', - 'statusBar.background', - 'statusBar.border', - 'statusBar.debuggingBackground', - 'statusBar.debuggingBorder', - 'statusBar.debuggingForeground', - 'statusBar.focusBorder', - 'statusBar.foreground', - 'statusBar.noFolderBackground', - 'statusBar.noFolderBorder', - 'statusBar.noFolderForeground', - 'statusBarItem.activeBackground', - 'statusBarItem.compactHoverBackground', - 'statusBarItem.errorBackground', - 'statusBarItem.errorForeground', - 'statusBarItem.errorHoverBackground', - 'statusBarItem.errorHoverForeground', - 'statusBarItem.focusBorder', - 'statusBarItem.hoverBackground', - 'statusBarItem.hoverForeground', - 'statusBarItem.offlineBackground', - 'statusBarItem.offlineForeground', - 'statusBarItem.offlineHoverBackground', - 'statusBarItem.offlineHoverForeground', - 'statusBarItem.prominentBackground', - 'statusBarItem.prominentForeground', - 'statusBarItem.prominentHoverBackground', - 'statusBarItem.prominentHoverForeground', - 'statusBarItem.remoteBackground', - 'statusBarItem.remoteForeground', - 'statusBarItem.remoteHoverBackground', - 'statusBarItem.remoteHoverForeground', - 'statusBarItem.warningBackground', - 'statusBarItem.warningForeground', - 'statusBarItem.warningHoverBackground', - 'statusBarItem.warningHoverForeground', - 'symbolIcon.arrayForeground', - 'symbolIcon.booleanForeground', - 'symbolIcon.classForeground', - 'symbolIcon.colorForeground', - 'symbolIcon.constantForeground', - 'symbolIcon.constructorForeground', - 'symbolIcon.enumeratorForeground', - 'symbolIcon.enumeratorMemberForeground', - 'symbolIcon.eventForeground', - 'symbolIcon.fieldForeground', - 'symbolIcon.fileForeground', - 'symbolIcon.folderForeground', - 'symbolIcon.functionForeground', - 'symbolIcon.interfaceForeground', - 'symbolIcon.keyForeground', - 'symbolIcon.keywordForeground', - 'symbolIcon.methodForeground', - 'symbolIcon.moduleForeground', - 'symbolIcon.namespaceForeground', - 'symbolIcon.nullForeground', - 'symbolIcon.numberForeground', - 'symbolIcon.objectForeground', - 'symbolIcon.operatorForeground', - 'symbolIcon.packageForeground', - 'symbolIcon.propertyForeground', - 'symbolIcon.referenceForeground', - 'symbolIcon.snippetForeground', - 'symbolIcon.stringForeground', - 'symbolIcon.structForeground', - 'symbolIcon.textForeground', - 'symbolIcon.typeParameterForeground', - 'symbolIcon.unitForeground', - 'symbolIcon.variableForeground', - 'tab.activeBackground', - 'tab.activeBorder', - 'tab.activeBorderTop', - 'tab.activeForeground', - 'tab.activeModifiedBorder', - 'tab.border', - 'tab.dragAndDropBorder', - 'tab.hoverBackground', - 'tab.hoverBorder', - 'tab.hoverForeground', - 'tab.inactiveBackground', - 'tab.inactiveForeground', - 'tab.inactiveModifiedBorder', - 'tab.lastPinnedBorder', - 'tab.selectedBackground', - 'tab.selectedBorderTop', - 'tab.selectedForeground', - 'tab.unfocusedActiveBackground', - 'tab.unfocusedActiveBorder', - 'tab.unfocusedActiveBorderTop', - 'tab.unfocusedActiveForeground', - 'tab.unfocusedActiveModifiedBorder', - 'tab.unfocusedHoverBackground', - 'tab.unfocusedHoverBorder', - 'tab.unfocusedHoverForeground', - 'tab.unfocusedInactiveBackground', - 'tab.unfocusedInactiveForeground', - 'tab.unfocusedInactiveModifiedBorder', - 'terminal.ansiBlack', - 'terminal.ansiBlue', - 'terminal.ansiBrightBlack', - 'terminal.ansiBrightBlue', - 'terminal.ansiBrightCyan', - 'terminal.ansiBrightGreen', - 'terminal.ansiBrightMagenta', - 'terminal.ansiBrightRed', - 'terminal.ansiBrightWhite', - 'terminal.ansiBrightYellow', - 'terminal.ansiCyan', - 'terminal.ansiGreen', - 'terminal.ansiMagenta', - 'terminal.ansiRed', - 'terminal.ansiWhite', - 'terminal.ansiYellow', - 'terminal.background', - 'terminal.border', - 'terminal.dropBackground', - 'terminal.findMatchBackground', - 'terminal.findMatchBorder', - 'terminal.findMatchHighlightBackground', - 'terminal.findMatchHighlightBorder', - 'terminal.foreground', - 'terminal.hoverHighlightBackground', - 'terminal.inactiveSelectionBackground', - 'terminal.initialHintForeground', - 'terminal.selectionBackground', - 'terminal.selectionForeground', - 'terminal.tab.activeBorder', - 'terminalCommandDecoration.defaultBackground', - 'terminalCommandDecoration.errorBackground', - 'terminalCommandDecoration.successBackground', - 'terminalCommandGuide.foreground', - 'terminalCursor.background', - 'terminalCursor.foreground', - 'terminalOverviewRuler.border', - 'terminalOverviewRuler.cursorForeground', - 'terminalOverviewRuler.findMatchForeground', - 'terminalStickyScroll.background', - 'terminalStickyScroll.border', - 'terminalStickyScrollHover.background', - 'testExplorer.errorDecorationBackground', - 'testing.coverCountBadgeBackground', - 'testing.coverCountBadgeForeground', - 'testing.coveredBackground', - 'testing.coveredBorder', - 'testing.coveredGutterBackground', - 'testing.iconErrored', - 'testing.iconErrored.retired', - 'testing.iconFailed', - 'testing.iconFailed.retired', - 'testing.iconPassed', - 'testing.iconPassed.retired', - 'testing.iconQueued', - 'testing.iconQueued.retired', - 'testing.iconSkipped', - 'testing.iconSkipped.retired', - 'testing.iconUnset', - 'testing.iconUnset.retired', - 'testing.message.error.decorationForeground', - 'testing.message.error.lineBackground', - 'testing.message.info.decorationForeground', - 'testing.message.info.lineBackground', - 'testing.messagePeekBorder', - 'testing.messagePeekHeaderBackground', - 'testing.peekBorder', - 'testing.peekHeaderBackground', - 'testing.runAction', - 'testing.uncoveredBackground', - 'testing.uncoveredBorder', - 'testing.uncoveredBranchBackground', - 'testing.uncoveredGutterBackground', - 'textBlockQuote.background', - 'textBlockQuote.border', - 'textCodeBlock.background', - 'textLink.activeForeground', - 'textLink.foreground', - 'textPreformat.background', - 'textPreformat.foreground', - 'textSeparator.foreground', - 'titleBar.activeBackground', - 'titleBar.activeForeground', - 'titleBar.border', - 'titleBar.inactiveBackground', - 'titleBar.inactiveForeground', - 'toolbar.activeBackground', - 'toolbar.hoverBackground', - 'toolbar.hoverOutline', - 'tree.inactiveIndentGuidesStroke', - 'tree.indentGuidesStroke', - 'tree.tableColumnsBorder', - 'tree.tableOddRowsBackground', - 'walkThrough.embeddedEditorBackground', - 'walkthrough.stepTitle.foreground', - 'welcomePage.background', - 'welcomePage.progress.background', - 'welcomePage.progress.foreground', - 'welcomePage.tileBackground', - 'welcomePage.tileBorder', - 'welcomePage.tileHoverBackground', - 'widget.border', - 'widget.shadow', - 'window.activeBorder', - 'window.inactiveBorder', -]; - -export const vscodeThemeTokenToCSSVar = (token: string) => { - return `--vscode-${token.replace(/\./g, '-')}`; -}; diff --git a/src/webviews/useConfiguration.ts b/src/webviews/useConfiguration.ts deleted file mode 100644 index ef166beed..000000000 --- a/src/webviews/useConfiguration.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { useState } from 'react'; - -declare global { - interface Window { - config?: { - _initialData?: string; - [key: string]: unknown; // Optional: Allows any other properties in config - }; - } -} - -/** - * Use this hook to access the configuration object that was passed to the webview at its creation. - * - * @returns The configuration object that was passed to the webview at its creation - */ -export function useConfiguration(): T { - const [configuration] = useState(() => { - const configString = decodeURIComponent(window.config?._initialData ?? '{}'); - return JSON.parse(configString) as T; - }); - - return configuration; -} diff --git a/src/webviews/webviewConstants.ts b/src/webviews/webviewConstants.ts deleted file mode 100644 index cfc882cea..000000000 --- a/src/webviews/webviewConstants.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -export const enum ConfirmationViewCommands { - Confirm = 'confirm', - Cancel = 'cancel', - Copilot = 'copilot', -} - -export const enum LoadingViewCommands { - AddProgressItem = 'addProgressItem', -}