diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 02ff8b3f52a..0b9aabae736 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -48,6 +48,7 @@ import { mark } from 'vs/base/common/performance'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { Promises } from 'vs/base/common/async'; +import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; export enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -150,6 +151,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private disposed: boolean | undefined; private titleBarPartView!: ISerializableView; + private bannerPartView!: ISerializableView; private activityBarPartView!: ISerializableView; private sideBarPartView!: ISerializableView; private panelPartView!: ISerializableView; @@ -263,6 +265,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.notificationService = accessor.get(INotificationService); this.activityBarService = accessor.get(IActivityBarService); this.statusBarService = accessor.get(IStatusbarService); + accessor.get(IBannerService); // Listeners this.registerLayoutListeners(); @@ -900,7 +903,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case Parts.STATUSBAR_PART: this.statusBarService.focus(); default: - // Title Bar simply pass focus to container + // Title Bar & Banner simply pass focus to container const container = this.getContainer(part); if (container) { container.focus(); @@ -912,6 +915,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi switch (part) { case Parts.TITLEBAR_PART: return this.getPart(Parts.TITLEBAR_PART).getContainer(); + case Parts.BANNER_PART: + return this.getPart(Parts.BANNER_PART).getContainer(); case Parts.ACTIVITYBAR_PART: return this.getPart(Parts.ACTIVITYBAR_PART).getContainer(); case Parts.SIDEBAR_PART: @@ -1159,6 +1164,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi protected createWorkbenchLayout(): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); + const bannerPart = this.getPart(Parts.BANNER_PART); const editorPart = this.getPart(Parts.EDITOR_PART); const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const panelPart = this.getPart(Parts.PANEL_PART); @@ -1167,6 +1173,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // View references for all parts this.titleBarPartView = titleBar; + this.bannerPartView = bannerPart; this.sideBarPartView = sideBar; this.activityBarPartView = activityBar; this.editorPartView = editorPart; @@ -1175,6 +1182,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const viewMap = { [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, + [Parts.BANNER_PART]: this.bannerPartView, [Parts.TITLEBAR_PART]: this.titleBarPartView, [Parts.EDITOR_PART]: this.editorPartView, [Parts.PANEL_PART]: this.panelPartView, @@ -1738,6 +1746,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, fallbackPanelSize) : fallbackPanelSize; const titleBarHeight = this.titleBarPartView.minimumHeight; + const bannerHeight = this.bannerPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; const activityBarWidth = this.activityBarPartView.minimumWidth; const middleSectionHeight = height - titleBarHeight - statusBarHeight; @@ -1790,6 +1799,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi size: titleBarHeight, visible: this.isVisible(Parts.TITLEBAR_PART) }, + { + type: 'leaf', + data: { type: Parts.BANNER_PART }, + size: bannerHeight + }, { type: 'branch', data: middleSection, diff --git a/src/vs/workbench/browser/parts/banner/bannerActions.ts b/src/vs/workbench/browser/parts/banner/bannerActions.ts new file mode 100644 index 00000000000..91c26ab3a90 --- /dev/null +++ b/src/vs/workbench/browser/parts/banner/bannerActions.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { CATEGORIES } from 'vs/workbench/common/actions'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; + + +class FocusBannerAction extends Action2 { + + static readonly ID = 'workbench.action.focusBanner'; + static readonly LABEL = localize('focusBanner', "Focus Banner"); + + constructor() { + super({ + id: FocusBannerAction.ID, + title: { value: FocusBannerAction.LABEL, original: 'Focus Banner' }, + category: CATEGORIES.View, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.focusPart(Parts.BANNER_PART); + } +} + +registerAction2(FocusBannerAction); diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts new file mode 100644 index 00000000000..857a897b9e5 --- /dev/null +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -0,0 +1,214 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/bannerpart'; +import { localize } from 'vs/nls'; +import { $, append, clearNode } from 'vs/base/browser/dom'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Codicon, registerCodicon } from 'vs/base/common/codicons'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { Part } from 'vs/workbench/browser/part'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { Action } from 'vs/base/common/actions'; +import { Link } from 'vs/platform/opener/browser/link'; +import { attachLinkStyler } from 'vs/platform/theme/common/styler'; +import { editorInfoForeground, listActiveSelectionBackground, listActiveSelectionForeground, registerColor, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Color } from 'vs/base/common/color'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { Emitter } from 'vs/base/common/event'; +import { IBannerItem, IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; +import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; + + +// Icons + +const bannerCloseIcon = registerCodicon('banner-close', Codicon.close); + + +// Theme colors + +const BANNER_BACKGROUND = registerColor('banner.background', { + dark: listActiveSelectionBackground, + light: listActiveSelectionBackground, + hc: listActiveSelectionBackground +}, localize('banner.background', "")); + +const BANNER_FOREGROUND = registerColor('banner.foreground', { + dark: listActiveSelectionForeground, + light: listActiveSelectionForeground, + hc: listActiveSelectionForeground +}, localize('banner.foreground', "")); + +const BANNER_ICON_FOREGROUND = registerColor('banner.iconForeground', { + dark: editorInfoForeground, + light: editorInfoForeground, + hc: editorInfoForeground +}, localize('banner.iconForeground', "")); + +const BANNER_TEXT_LINK_FOREGROUND = registerColor('banner.textLinkForeground', { + dark: textLinkForeground, + light: Color.white, + hc: Color.white +}, localize('banner.textLinkForeground', "")); + +registerThemingParticipant((theme, collector) => { + const backgroundColor = theme.getColor(BANNER_BACKGROUND); + const foregroundColor = theme.getColor(BANNER_FOREGROUND); + const iconForegroundColor = theme.getColor(BANNER_ICON_FOREGROUND); + + if (backgroundColor) { + collector.addRule(`.monaco-workbench .part.banner { background-color: ${backgroundColor}; }`); + } + + if (foregroundColor) { + collector.addRule(`.monaco-workbench .part.banner { color: ${foregroundColor}; }`); + collector.addRule(`.monaco-workbench .part.banner .action-container .codicon { color: ${foregroundColor}; }`); + } + + if (iconForegroundColor) { + collector.addRule(`.monaco-workbench .part.banner .icon-container .codicon { color: ${iconForegroundColor} }`); + } +}); + + +// Banner Part + +export class BannerPart extends Part implements IBannerService { + declare readonly _serviceBrand: undefined; + + // #region IView + + readonly height: number = 26; + readonly minimumWidth: number = 0; + readonly maximumWidth: number = Number.POSITIVE_INFINITY; + + get minimumHeight(): number { + return this.visible ? this.height : 0; + } + + get maximumHeight(): number { + return this.visible ? this.height : 0; + } + + private _onDidChangeSize = new Emitter<{ width: number; height: number; } | undefined>(); + + override get onDidChange() { return this._onDidChangeSize.event; } + + //#endregion + + private item: IBannerItem | undefined; + private readonly markdownRenderer: MarkdownRenderer; + private visible = false; + + constructor( + @IThemeService themeService: IThemeService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService private readonly storageService: IStorageService, + ) { + super(Parts.BANNER_PART, { hasTitle: false }, themeService, storageService, layoutService); + + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + } + + override createContentArea(parent: HTMLElement): HTMLElement { + this.element = parent; + return this.element; + } + + private close(item: IBannerItem): void { + // Hide banner + this.setVisibility(false); + + // Remove from document + clearNode(this.element); + + // Remember choice + if (item.scope) { + this.storageService.store(item.id, true, item.scope, StorageTarget.USER); + } + + this.item = undefined; + } + + private getBannerMessage(message: MarkdownString | string): HTMLElement { + if (typeof message === 'string') { + const element = $('span'); + element.innerText = message; + return element; + } + + return this.markdownRenderer.render(message).element; + } + + private setVisibility(visible: boolean): void { + if (visible !== this.visible) { + this.visible = visible; + + this._onDidChangeSize.fire(undefined); + } + } + + hide(id: string): void { + if (this.item?.id !== id) { + return; + } + + this.setVisibility(false); + } + + show(item: IBannerItem): void { + if (item.scope && this.storageService.getBoolean(item.id, item.scope, false)) { + return; + } + + if (item.id === this.item?.id) { + this.setVisibility(true); + return; + } + + // Clear previous item + clearNode(this.element); + + // Icon + const iconContainer = append(this.element, $('div.icon-container')); + iconContainer.appendChild($(`div${item.icon.cssSelector}`)); + + // Message + const messageContainer = append(this.element, $('div.message-container')); + messageContainer.appendChild(this.getBannerMessage(item.message)); + + // Message Actions + if (item.actions) { + const actionContainer = append(this.element, $('div.message-actions-container')); + + for (const action of item.actions) { + const actionLink = this._register(this.instantiationService.createInstance(Link, action)); + this._register(attachLinkStyler(actionLink, this.themeService, { textLinkForeground: BANNER_TEXT_LINK_FOREGROUND })); + actionContainer.appendChild(actionLink.el); + } + } + + // Action + const actionBarContainer = append(this.element, $('div.action-container')); + const actionBar = this._register(new ActionBar(actionBarContainer)); + const closeAction = this._register(new Action('banner.close', 'Close Banner', bannerCloseIcon.classNames, true, () => this.close(item))); + actionBar.push(closeAction, { icon: true, label: false }); + + this.setVisibility(true); + this.item = item; + } + + toJSON(): object { + return { + type: Parts.BANNER_PART + }; + } +} + +registerSingleton(IBannerService, BannerPart); diff --git a/src/vs/workbench/browser/parts/banner/media/bannerpart.css b/src/vs/workbench/browser/parts/banner/media/bannerpart.css new file mode 100644 index 00000000000..bc26a92404b --- /dev/null +++ b/src/vs/workbench/browser/parts/banner/media/bannerpart.css @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .part.banner { + box-sizing: border-box; + cursor: default; + width: 100%; + height: 26px; + font-size: 12px; + display: flex; + overflow: visible; +} + +.monaco-workbench .part.banner .icon-container { + display: flex; + align-items: center; + padding: 0 6px 0 10px; +} + +.monaco-workbench .part.banner .message-container { + line-height: 26px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.monaco-workbench .part.banner .message-container p { + margin-block-start: 0; + margin-block-end: 0; +} + +.monaco-workbench .part.banner .message-actions-container { + flex-grow: 1; + flex-shrink: 0; + line-height: 26px; +} + +.monaco-workbench .part.banner .message-actions-container a { + margin-left: 12px; + text-decoration: underline; +} + +.monaco-workbench .part.banner .message-actions-container a:hover { + text-decoration: none; +} + +.monaco-workbench .part.banner .action-container { + padding: 0 10px 0 6px; +} diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 452774df9ab..a83a1e516ba 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -328,6 +328,7 @@ export class Workbench extends Layout { // Create Parts [ { id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] }, + { id: Parts.BANNER_PART, role: 'alert', classes: ['banner'] }, { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892 { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index abbe1c328c0..18700c9b0d7 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -23,7 +23,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor'; -import { WorkspaceTrustEditor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustEditor'; +import { shieldIcon, WorkspaceTrustEditor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustEditor'; import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; import { isWorkspaceTrustEnabled, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_STARTUP_PROMPT } from 'vs/workbench/services/workspaces/common/workspaceTrust'; import { EditorInput, IEditorInputSerializer, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; @@ -42,29 +42,39 @@ import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGRO import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { splitName } from 'vs/base/common/labels'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IBannerItem, IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; +const BANNER_RESTRICTED_MODE = 'workbench.banner.restrictedMode'; const STARTUP_PROMPT_SHOWN_KEY = 'workspace.trust.startupPrompt.shown'; - /* * Trust Request UX Handler */ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkbenchContribution { + private readonly entryId = `status.workspaceTrust.${this.workspaceContextService.getWorkspace().id}`; + + private readonly statusbarEntryAccessor: MutableDisposable; + constructor( @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IStatusbarService private readonly statusbarService: IStatusbarService, @IStorageService private readonly storageService: IStorageService, @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, + @IBannerService private readonly bannerService: IBannerService, @IHostService private readonly hostService: IHostService, ) { super(); + this.statusbarEntryAccessor = this._register(new MutableDisposable()); + if (isWorkspaceTrustEnabled(configurationService)) { this.registerListeners(); + this.createStatusbarEntry(); if (this.hostService.hasFocus) { this.showModalOnStart(); @@ -127,6 +137,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben } break; case 1: + this.updateWorkbenchIndicators(false); this.workspaceTrustRequestService.cancelRequest(); break; } @@ -140,10 +151,12 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben } if (this.startupPromptSetting === 'never') { + this.updateWorkbenchIndicators(false); return; } if (this.startupPromptSetting === 'once' && this.storageService.getBoolean(STARTUP_PROMPT_SHOWN_KEY, StorageScope.WORKSPACE, false)) { + this.updateWorkbenchIndicators(false); return; } @@ -171,6 +184,64 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben ); } + private createStatusbarEntry(): void { + const entry = this.getStatusbarEntry(this.workspaceTrustManagementService.isWorkpaceTrusted()); + this.statusbarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, localize('status.WorkspaceTrust', "Workspace Trust"), StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); + this.statusbarService.updateEntryVisibility(this.entryId, false); + } + + private getBannerItem(): IBannerItem { + return { + id: BANNER_RESTRICTED_MODE, + icon: shieldIcon, + message: localize('restrictedModeBannerMessage', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features."), + actions: [ + { + label: localize('restrictedModeBannerManage', "Manage"), + href: 'command:workbench.trust.manage' + }, + { + label: localize('restrictedModeBannerLearnMore', "Learn More"), + href: 'https://aka.ms/vscode-workspace-trust' + } + ], + scope: StorageScope.WORKSPACE, + }; + } + + private getStatusbarEntry(trusted: boolean): IStatusbarEntry { + const text = workspaceTrustToString(trusted); + const backgroundColor = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND); + const color = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND); + + return { + text: trusted ? `$(shield)` : `$(shield) ${text}`, + ariaLabel: trusted ? localize('status.ariaTrusted', "This workspace is trusted.") : localize('status.ariaUntrusted', "Restricted Mode: Some features are disabled because this workspace is not trusted."), + tooltip: trusted ? localize('status.tooltipTrusted', "This workspace is trusted.") : localize('status.tooltipUntrusted', "Some features are disabled because this workspace is not trusted."), + command: 'workbench.trust.manage', + backgroundColor, + color + }; + } + + private updateStatusbarEntry(trusted: boolean): void { + this.statusbarEntryAccessor.value?.update(this.getStatusbarEntry(trusted)); + this.updateStatusbarEntryVisibility(trusted); + } + + private updateStatusbarEntryVisibility(trusted: boolean): void { + this.statusbarService.updateEntryVisibility(this.entryId, !trusted); + } + + private updateWorkbenchIndicators(trusted: boolean): void { + this.updateStatusbarEntry(trusted); + if (!trusted) { + this.bannerService.show(this.getBannerItem()); + } else { + this.bannerService.hide(BANNER_RESTRICTED_MODE); + } + } + private registerListeners(): void { this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => { // Message @@ -257,66 +328,14 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben resolve(); })); })); + + this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => { + this.updateWorkbenchIndicators(trusted); + })); } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustRequestHandler, LifecyclePhase.Ready); - -/* - * Status Bar Entry - */ -class WorkspaceTrustStatusbarItem extends Disposable implements IWorkbenchContribution { - private readonly entryId = `status.workspaceTrust.${this.workspaceService.getWorkspace().id}`; - private readonly statusBarEntryAccessor: MutableDisposable; - - constructor( - @IConfigurationService configurationService: IConfigurationService, - @IStatusbarService private readonly statusbarService: IStatusbarService, - @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, - @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService - ) { - super(); - - this.statusBarEntryAccessor = this._register(new MutableDisposable()); - - if (isWorkspaceTrustEnabled(configurationService)) { - const entry = this.getStatusbarEntry(this.workspaceTrustManagementService.isWorkpaceTrusted()); - this.statusBarEntryAccessor.value = this.statusbarService.addEntry(entry, this.entryId, localize('status.WorkspaceTrust', "Workspace Trust"), StatusbarAlignment.LEFT, 0.99 * Number.MAX_VALUE /* Right of remote indicator */); - this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => this.updateStatusbarEntry(trusted))); - - this.updateVisibility(this.workspaceTrustManagementService.isWorkpaceTrusted()); - } - } - - private getStatusbarEntry(trusted: boolean): IStatusbarEntry { - const text = workspaceTrustToString(trusted); - const backgroundColor = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND); - const color = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND); - - return { - text: trusted ? `$(shield)` : `$(shield) ${text}`, - ariaLabel: trusted ? localize('status.ariaTrusted', "This workspace is trusted.") : localize('status.ariaUntrusted', "Restricted Mode: Some features are disabled because this workspace is not trusted."), - tooltip: trusted ? localize('status.tooltipTrusted', "This workspace is trusted.") : localize('status.tooltipUntrusted', "Some features are disabled because this workspace is not trusted."), - command: 'workbench.trust.manage', - backgroundColor, - color - }; - } - - private updateVisibility(trusted: boolean): void { - this.statusbarService.updateEntryVisibility(this.entryId, !trusted); - } - - private updateStatusbarEntry(trusted: boolean): void { - this.statusBarEntryAccessor.value?.update(this.getStatusbarEntry(trusted)); - this.updateVisibility(trusted); - } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( - WorkspaceTrustStatusbarItem, - LifecyclePhase.Starting -); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceTrustRequestHandler, LifecyclePhase.Restored); /** * Trusted Workspace GUI Editor diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index ac4297c2620..0a5113a9239 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -47,7 +47,7 @@ import { filterSettingsRequireWorkspaceTrust, IWorkbenchConfigurationService } f import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; -const shieldIcon = registerCodicon('workspace-trust-icon', Codicon.shield); +export const shieldIcon = registerCodicon('workspace-trust-icon', Codicon.shield); const checkListIcon = registerCodicon('workspace-trusted-check-icon', Codicon.check); const xListIcon = registerCodicon('workspace-trusted-x-icon', Codicon.x); diff --git a/src/vs/workbench/services/banner/browser/bannerService.ts b/src/vs/workbench/services/banner/browser/bannerService.ts new file mode 100644 index 00000000000..2b0ea45dca4 --- /dev/null +++ b/src/vs/workbench/services/banner/browser/bannerService.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILinkDescriptor } from 'vs/platform/opener/browser/link'; +import { StorageScope } from 'vs/platform/storage/common/storage'; + + +export interface IBannerItem { + readonly id: string; + readonly icon: Codicon; + readonly message: string | MarkdownString; + readonly scope?: StorageScope; /* Used to remember that the banner has been closed. */ + readonly actions?: ILinkDescriptor[]; +} + +export const IBannerService = createDecorator('bannerService'); + +export interface IBannerService { + readonly _serviceBrand: undefined; + + hide(id: string): void; + show(item: IBannerItem): void; +} diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 1c385ecb034..25edebb2c71 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -15,6 +15,7 @@ export const IWorkbenchLayoutService = refineServiceDecorator