From 6614dcbc7f959fba2d40407602b2f6bc49149e12 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 5 Feb 2026 12:38:13 -0800 Subject: [PATCH 1/2] Render update action button in release notes webview --- .../update/browser/releaseNotesEditor.ts | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 94603fb67e6871..4d57a7704589d6 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -36,6 +36,8 @@ import { Schemas } from '../../../../base/common/network.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { dirname } from '../../../../base/common/resources.js'; import { asWebviewUri } from '../../webview/common/webview.js'; +import { IUpdateService, StateType } from '../../../../platform/update/common/update.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; export class ReleaseNotesManager extends Disposable { private readonly _simpleSettingRenderer: SimpleSettingRenderer; @@ -58,6 +60,8 @@ export class ReleaseNotesManager extends Disposable { @IExtensionService private readonly _extensionService: IExtensionService, @IProductService private readonly _productService: IProductService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IUpdateService private readonly _updateService: IUpdateService, + @ICommandService private readonly _commandService: ICommandService, ) { super(); @@ -67,6 +71,7 @@ export class ReleaseNotesManager extends Disposable { this._register(_configurationService.onDidChangeConfiguration((e) => this.onDidChangeConfiguration(e))); this._register(_webviewWorkbenchService.onDidChangeActiveWebviewEditor((e) => this.onDidChangeActiveWebviewEditor(e))); + this._register(this._updateService.onStateChange(() => this.postUpdateAction())); this._simpleSettingRenderer = this._instantiationService.createInstance(SimpleSettingRenderer); } @@ -129,6 +134,10 @@ export class ReleaseNotesManager extends Disposable { disposables.add(this._currentReleaseNotes.webview.onMessage(e => { if (e.message.type === 'showReleaseNotes') { this._configurationService.updateValue('update.showReleaseNotes', e.message.value); + } else if (e.message.type === 'updateAction') { + if (e.message.commandId) { + this._commandService.executeCommand(e.message.commandId); + } } else if (e.message.type === 'clickSetting') { const x = this._currentReleaseNotes?.webview.container.offsetLeft + e.message.value.x; const y = this._currentReleaseNotes?.webview.container.offsetTop + e.message.value.y; @@ -271,6 +280,7 @@ export class ReleaseNotesManager extends Disposable { const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; const showReleaseNotes = Boolean(this._configurationService.getValue('update.showReleaseNotes')); + const updateAction = this.getUpdateAction(); return ` @@ -531,6 +541,80 @@ export class ReleaseNotesManager extends Disposable { margin-left: 0; } } + + /* Update action button */ + #update-action-btn { + position: fixed; + right: 25px; + top: 25px; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: 1px solid var(--vscode-button-border, transparent); + border-radius: 50%; + width: 40px; + height: 40px; + padding: 0; + cursor: pointer; + font-size: var(--vscode-font-size); + font-family: var(--vscode-font-family); + white-space: nowrap; + box-shadow: 1px 1px 1px rgba(0,0,0,.25); + z-index: 100; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + transition: border-radius 0.25s ease, padding 0.25s ease, width 0.25s ease; + } + + #update-action-btn .icon { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + } + + #update-action-btn .icon svg { + width: 16px; + height: 16px; + display: block; + } + + #update-action-btn .label { + overflow: hidden; + max-width: 0; + opacity: 0; + margin-left: 0; + transition: max-width 0.25s ease, opacity 0.2s ease, margin-left 0.25s ease; + } + + #update-action-btn:hover, + #update-action-btn.expanded { + background-color: var(--vscode-button-hoverBackground); + box-shadow: 2px 2px 2px rgba(0,0,0,.25); + width: auto; + border-radius: 20px; + padding: 0 14px; + } + + #update-action-btn:hover .label, + #update-action-btn.expanded .label { + max-width: 200px; + opacity: 1; + margin-left: 6px; + } + + #update-action-btn.expanded { + background-color: var(--vscode-button-background); + } + + body.vscode-high-contrast #update-action-btn { + border-width: 2px; + border-style: solid; + box-shadow: none; + } @@ -562,6 +646,14 @@ export class ReleaseNotesManager extends Disposable { window.addEventListener('message', event => { if (event.data.type === 'showReleaseNotes') { input.checked = event.data.value; + } else if (event.data.type === 'updateAction') { + if (event.data.label) { + updateActionBtn.querySelector('.label').textContent = event.data.label; + updateActionBtn.dataset.commandId = event.data.commandId; + updateActionBtn.style.display = 'flex'; + } else { + updateActionBtn.style.display = 'none'; + } } }); @@ -584,11 +676,78 @@ export class ReleaseNotesManager extends Disposable { input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); + + // Update action button + const updateActionBtn = document.createElement('button'); + updateActionBtn.id = 'update-action-btn'; + + // Arrow-circle-down SVG icon + const iconSpan = document.createElement('span'); + iconSpan.className = 'icon'; + iconSpan.innerHTML = ''; + updateActionBtn.appendChild(iconSpan); + + const labelSpan = document.createElement('span'); + labelSpan.className = 'label'; + updateActionBtn.appendChild(labelSpan); + + const initialAction = ${JSON.stringify(updateAction ?? null)}; + if (initialAction) { + labelSpan.textContent = initialAction.label; + updateActionBtn.dataset.commandId = initialAction.commandId; + updateActionBtn.style.display = 'flex'; + } else { + updateActionBtn.style.display = 'none'; + } + + document.body.appendChild(updateActionBtn); + + updateActionBtn.addEventListener('click', () => { + if (updateActionBtn.dataset.commandId) { + vscode.postMessage({ type: 'updateAction', commandId: updateActionBtn.dataset.commandId }); + } + }); + + // Expand button when at top of page + function updateExpandedState() { + if (window.scrollY <= 100) { + updateActionBtn.classList.add('expanded'); + } else { + updateActionBtn.classList.remove('expanded'); + } + } + updateExpandedState(); + window.addEventListener('scroll', updateExpandedState); `; } + private getUpdateAction(): { label: string; commandId: string } | undefined { + const state = this._updateService.state; + switch (state.type) { + case StateType.AvailableForDownload: + return { label: nls.localize('releaseNotes.downloadUpdate', "Download Update"), commandId: 'update.downloadNow' }; + case StateType.Downloaded: + return { label: nls.localize('releaseNotes.installUpdate', "Install Update"), commandId: 'update.install' }; + case StateType.Ready: + return { label: nls.localize('releaseNotes.restartToUpdate', "Restart to Update"), commandId: 'update.restart' }; + default: + return undefined; + } + } + + private postUpdateAction(): void { + if (this._currentReleaseNotes) { + const action = this.getUpdateAction(); + this._currentReleaseNotes.webview.postMessage({ + type: 'updateAction', + label: action?.label ?? '', + commandId: action?.commandId ?? '' + }); + } + } + private onDidChangeConfiguration(e: IConfigurationChangeEvent): void { if (e.affectsConfiguration('update.showReleaseNotes')) { this.updateCheckboxWebview(); From c05aa2b0d3295d8265083ab535b2e8bd605f1053 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Thu, 5 Feb 2026 13:59:39 -0800 Subject: [PATCH 2/2] PR feedback --- src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 4d57a7704589d6..7d207e48c414d2 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -650,6 +650,7 @@ export class ReleaseNotesManager extends Disposable { if (event.data.label) { updateActionBtn.querySelector('.label').textContent = event.data.label; updateActionBtn.dataset.commandId = event.data.commandId; + updateActionBtn.setAttribute('aria-label', event.data.label); updateActionBtn.style.display = 'flex'; } else { updateActionBtn.style.display = 'none'; @@ -695,6 +696,7 @@ export class ReleaseNotesManager extends Disposable { if (initialAction) { labelSpan.textContent = initialAction.label; updateActionBtn.dataset.commandId = initialAction.commandId; + updateActionBtn.setAttribute('aria-label', initialAction.label); updateActionBtn.style.display = 'flex'; } else { updateActionBtn.style.display = 'none';