support workspace trust keyboard shortcut (#171131)

* support workspace trust keyboard shortcut

* macos test/fix

* Add support for short labels to the Button

* Remove code that is not needed

Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com>
This commit is contained in:
SteVen Batten 2023-01-13 09:42:14 -08:00 committed by GitHub
parent 8f9abdacc2
commit 9194231bb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 172 additions and 121 deletions

View file

@ -173,13 +173,13 @@ export class ActionButtonCommand {
return {
command: {
command: 'git.sync',
title: `${icon}${behind}${ahead}`,
title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead),
tooltip: this.state.isSyncInProgress ?
l10n.t('Synchronizing Changes...')
: this.repository.syncTooltip,
arguments: [this.repository.sourceControl],
},
description: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead),
description: `${icon}${behind}${ahead}`,
enabled: !this.state.isSyncInProgress
};
}

View file

@ -31,11 +31,39 @@
cursor: default;
}
.monaco-text-button > .codicon {
.monaco-text-button .codicon {
margin: 0 0.2em;
color: inherit !important;
}
.monaco-text-button.monaco-text-button-with-short-label {
flex-direction: row;
flex-wrap: wrap;
padding: 0 4px;
overflow: hidden;
height: 28px;
}
.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label {
flex-basis: 100%;
}
.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label-short {
flex-grow: 1;
width: 0;
overflow: hidden;
}
.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label,
.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label-short {
display: flex;
justify-content: center;
align-items: center;
font-weight: normal;
font-style: inherit;
padding: 4px 0;
}
.monaco-button-dropdown {
display: flex;
cursor: pointer;
@ -79,28 +107,27 @@
}
.monaco-description-button {
display: flex;
flex-direction: column;
}
.monaco-description-button .monaco-button-label {
font-weight: 500;
align-items: center;
margin: 4px 5px; /* allows button focus outline to be visible */
}
.monaco-description-button .monaco-button-description {
font-style: italic;
font-size: 11px;
padding: 0px 20px;
}
.monaco-description-button .monaco-button-label,
.monaco-description-button .monaco-button-description
{
.monaco-description-button .monaco-button-description {
display: flex;
justify-content: center;
align-items: center;
}
.monaco-description-button .monaco-button-label > .codicon,
.monaco-description-button .monaco-button-description > .codicon
{
.monaco-description-button .monaco-button-description > .codicon {
margin: 0 0.2em;
color: inherit !important;
}

View file

@ -20,10 +20,10 @@ import 'vs/css!./button';
export interface IButtonOptions extends IButtonStyles {
readonly title?: boolean | string;
readonly supportIcons?: boolean;
readonly supportShortLabel?: boolean;
readonly secondary?: boolean;
}
export interface IButtonStyles {
readonly buttonBackground: string | undefined;
readonly buttonHoverBackground: string | undefined;
@ -62,8 +62,10 @@ export interface IButtonWithDescription extends IButton {
export class Button extends Disposable implements IButton {
protected _element: HTMLElement;
protected options: IButtonOptions;
protected _element: HTMLElement;
protected _labelElement: HTMLElement | undefined;
protected _labelShortElement: HTMLElement | undefined;
private _onDidClick = this._register(new Emitter<Event>());
get onDidClick(): BaseEvent<Event> { return this._onDidClick.event; }
@ -86,6 +88,17 @@ export class Button extends Disposable implements IButton {
this._element.style.color = foreground || '';
this._element.style.backgroundColor = background || '';
if (options.supportShortLabel) {
this._labelShortElement = document.createElement('div');
this._labelShortElement.classList.add('monaco-button-label-short');
this._element.appendChild(this._labelShortElement);
this._labelElement = document.createElement('div');
this._labelElement.classList.add('monaco-button-label');
this._element.appendChild(this._labelElement);
this._element.classList.add('monaco-text-button-with-short-label');
}
container.appendChild(this._element);
@ -134,6 +147,29 @@ export class Button extends Disposable implements IButton {
this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.updateBackground(false); } }));
}
private getContentElements(content: string): HTMLElement[] {
const elements: HTMLSpanElement[] = [];
for (let segment of renderLabelWithIcons(content)) {
if (typeof (segment) === 'string') {
segment = segment.trim();
// Ignore empty segment
if (segment === '') {
continue;
}
// Convert string segments to <span> nodes
const node = document.createElement('span');
node.textContent = segment;
elements.push(node);
} else {
elements.push(segment);
}
}
return elements;
}
private updateBackground(hover: boolean): void {
let background;
if (this.options.secondary) {
@ -152,30 +188,14 @@ export class Button extends Disposable implements IButton {
set label(value: string) {
this._element.classList.add('monaco-text-button');
const labelElement = this.options.supportShortLabel ? this._labelElement! : this._element;
if (this.options.supportIcons) {
const content: HTMLSpanElement[] = [];
for (let segment of renderLabelWithIcons(value)) {
if (typeof (segment) === 'string') {
segment = segment.trim();
// Ignore empty segment
if (segment === '') {
continue;
}
// Convert string segments to <span> nodes
const node = document.createElement('span');
node.textContent = segment;
content.push(node);
} else {
content.push(segment);
}
}
reset(this._element, ...content);
reset(labelElement, ...this.getContentElements(value));
} else {
this._element.textContent = value;
labelElement.textContent = value;
}
if (typeof this.options.title === 'string') {
this._element.title = this.options.title;
} else if (this.options.title) {
@ -183,6 +203,18 @@ export class Button extends Disposable implements IButton {
}
}
set labelShort(value: string) {
if (!this.options.supportShortLabel || !this._labelShortElement) {
return;
}
if (this.options.supportIcons) {
reset(this._labelShortElement, ...this.getContentElements(value));
} else {
this._labelShortElement.textContent = value;
}
}
set icon(icon: CSSIcon) {
this._element.classList.add(...CSSIcon.asClassNameArray(icon));
}
@ -305,37 +337,55 @@ export class ButtonWithDropdown extends Disposable implements IButton {
}
}
export class ButtonWithDescription extends Button implements IButtonWithDescription {
private _labelElement: HTMLElement;
export class ButtonWithDescription implements IButtonWithDescription {
private _button: Button;
private _element: HTMLElement;
private _descriptionElement: HTMLElement;
constructor(container: HTMLElement, options: IButtonOptions) {
super(container, options);
constructor(container: HTMLElement, private readonly options: IButtonOptions) {
this._element = document.createElement('div');
this._element.classList.add('monaco-description-button');
this._labelElement = document.createElement('div');
this._labelElement.classList.add('monaco-button-label');
this._element.appendChild(this._labelElement);
this._button = new Button(this._element, options);
this._descriptionElement = document.createElement('div');
this._descriptionElement.classList.add('monaco-button-description');
this._element.appendChild(this._descriptionElement);
container.appendChild(this._element);
}
override set label(value: string) {
this._element.classList.add('monaco-text-button');
if (this.options.supportIcons) {
reset(this._labelElement, ...renderLabelWithIcons(value));
} else {
this._labelElement.textContent = value;
}
if (typeof this.options.title === 'string') {
this._element.title = this.options.title;
} else if (this.options.title) {
this._element.title = value;
}
get onDidClick(): BaseEvent<Event | undefined> {
return this._button.onDidClick;
}
get element(): HTMLElement {
return this._element;
}
set label(value: string) {
this._button.label = value;
}
set icon(icon: CSSIcon) {
this._button.icon = icon;
}
get enabled(): boolean {
return this._button.enabled;
}
set enabled(enabled: boolean) {
this._button.enabled = enabled;
}
focus(): void {
this._button.focus();
}
hasFocus(): boolean {
return this._button.hasFocus();
}
dispose(): void {
this._button.dispose();
}
set description(value: string) {

View file

@ -224,35 +224,6 @@
align-items: center;
}
.scm-view .button-container .monaco-description-button {
flex-direction: row;
flex-wrap: wrap;
padding: 0 4px;
overflow: hidden;
height: 28px;
}
.scm-view .button-container .monaco-description-button:hover {
border-color: var(--vscode-button-hoverBackground);
}
.scm-view .button-container .monaco-description-button > .monaco-button-label {
flex-grow: 1;
width: 0;
overflow: hidden;
}
.scm-view .button-container .monaco-description-button > .monaco-button-description {
flex-basis: 100%;
}
.scm-view .button-container .monaco-description-button > .monaco-button-label,
.scm-view .button-container .monaco-description-button > .monaco-button-description {
font-weight: normal;
font-style: inherit;
padding: 4px 0;
}
.scm-view .button-container .codicon.codicon-cloud-upload,
.scm-view .button-container .codicon.codicon-sync {
margin: 0 4px 0 0;

View file

@ -2550,17 +2550,16 @@ export class SCMActionButton implements IDisposable {
supportIcons: true,
...defaultButtonStyles
});
} else if (button.description) {
// ButtonWithDescription
this.button = new ButtonWithDescription(this.container, { supportIcons: true, title: button.command.tooltip, ...defaultButtonStyles });
(this.button as ButtonWithDescription).description = button.description;
} else {
// Button
this.button = new Button(this.container, { supportIcons: true, title: button.command.tooltip, ...defaultButtonStyles });
this.button = new Button(this.container, { supportIcons: true, supportShortLabel: !!button.description, title: button.command.tooltip, ...defaultButtonStyles });
}
this.button.enabled = button.enabled;
this.button.label = button.command.title;
if (this.button instanceof Button && button.description) {
this.button.labelShort = button.description;
}
this.button.onDidClick(async () => await this.executeCommand(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value);
this.disposables.value!.add(this.button);

View file

@ -181,7 +181,6 @@
margin-top: 5px;
}
.workspace-trust-editor .workspace-trust-features .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button,
.workspace-trust-editor .workspace-trust-settings .trusted-uris-button-bar .monaco-button {
width: fit-content;
padding: 5px 10px;

View file

@ -14,7 +14,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { debounce } from 'vs/base/common/decorators';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { splitName } from 'vs/base/common/labels';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { parseLinkedText } from 'vs/base/common/linkedText';
@ -23,7 +23,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
@ -40,7 +40,6 @@ import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { ChoiceAction } from 'vs/workbench/common/notifications';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
import { IExtensionsWorkbenchService, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
@ -56,6 +55,9 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IProductService } from 'vs/platform/product/common/productService';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { isMacintosh } from 'vs/base/common/platform';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
export const shieldIcon = registerIcon('workspace-trust-banner', Codicon.shield, localize('shieldIcon', 'Icon for workspace trust ion the banner.'));
@ -678,11 +680,11 @@ export class WorkspaceTrustEditor extends EditorPane {
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
@IProductService private readonly productService: IProductService
@IProductService private readonly productService: IProductService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); }
protected createEditor(parent: HTMLElement): void {
@ -733,6 +735,14 @@ export class WorkspaceTrustEditor extends EditorPane {
navOrder[newIndex].focus();
} else if (event.equals(KeyCode.Escape)) {
this.rootElement.focus();
} else if (event.equals(KeyMod.CtrlCmd | KeyCode.Enter)) {
if (this.workspaceTrustManagementService.canSetWorkspaceTrust()) {
this.workspaceTrustManagementService.setWorkspaceTrust(!this.workspaceTrustManagementService.isWorkspaceTrusted());
}
} else if (event.equals(KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter)) {
if (this.workspaceTrustManagementService.canSetParentFolderTrust()) {
this.workspaceTrustManagementService.setParentFolderTrust(true);
}
}
}));
}
@ -1003,28 +1013,18 @@ export class WorkspaceTrustEditor extends EditorPane {
}
}
private createButtonRow(parent: HTMLElement, actions: Action | Action[], enabled?: boolean): void {
private createButtonRow(parent: HTMLElement, buttonInfo: { action: Action; keybinding: ResolvedKeybinding }[], enabled?: boolean): void {
const buttonRow = append(parent, $('.workspace-trust-buttons-row'));
const buttonContainer = append(buttonRow, $('.workspace-trust-buttons'));
const buttonBar = this.rerenderDisposables.add(new ButtonBar(buttonContainer));
if (actions instanceof Action) {
actions = [actions];
}
for (const action of actions) {
const button =
action instanceof ChoiceAction && action.menu?.length ?
buttonBar.addButtonWithDropdown({
title: true,
actions: action.menu ?? [],
contextMenuProvider: this.contextMenuService,
...defaultButtonStyles
}) :
buttonBar.addButton(defaultButtonStyles);
for (const { action, keybinding } of buttonInfo) {
const button = buttonBar.addButtonWithDescription(defaultButtonStyles);
button.label = action.label;
button.enabled = enabled !== undefined ? enabled : action.enabled;
button.description = keybinding.getLabel()!;
button.element.ariaLabel = action.label + ', ' + localize('keyboardShortcut', "Keyboard Shortcut: {0}", keybinding.getAriaLabel()!);
this.rerenderDisposables.add(button.onDidClick(e => {
if (e) {
@ -1037,11 +1037,11 @@ export class WorkspaceTrustEditor extends EditorPane {
}
private addTrustButtonToElement(parent: HTMLElement): void {
const trustActions = [
new Action('workspace.trust.button.action.grant', localize('trustButton', "Trust"), undefined, true, async () => {
await this.workspaceTrustManagementService.setWorkspaceTrust(true);
})
];
const trustAction = new Action('workspace.trust.button.action.grant', localize('trustButton', "Trust"), undefined, true, async () => {
await this.workspaceTrustManagementService.setWorkspaceTrust(true);
});
const trustActions = [{ action: trustAction, keybinding: this.keybindingService.resolveUserBinding(isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter')[0] }];
if (this.workspaceTrustManagementService.canSetParentFolderTrust()) {
const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace()) as ISingleFolderWorkspaceIdentifier;
@ -1050,18 +1050,23 @@ export class WorkspaceTrustEditor extends EditorPane {
const trustMessageElement = append(parent, $('.trust-message-box'));
trustMessageElement.innerText = localize('trustMessage', "Trust the authors of all files in the current folder or its parent '{0}'.", name);
trustActions.push(new Action('workspace.trust.button.action.grantParent', localize('trustParentButton', "Trust Parent"), undefined, true, async () => {
const trustParentAction = new Action('workspace.trust.button.action.grantParent', localize('trustParentButton', "Trust Parent"), undefined, true, async () => {
await this.workspaceTrustManagementService.setParentFolderTrust(true);
}));
});
trustActions.push({ action: trustParentAction, keybinding: this.keybindingService.resolveUserBinding(isMacintosh ? 'Cmd+Shift+Enter' : 'Ctrl+Shift+Enter')[0] });
}
this.createButtonRow(parent, trustActions);
}
private addDontTrustButtonToElement(parent: HTMLElement): void {
this.createButtonRow(parent, new Action('workspace.trust.button.action.deny', localize('dontTrustButton', "Don't Trust"), undefined, true, async () => {
await this.workspaceTrustManagementService.setWorkspaceTrust(false);
}));
this.createButtonRow(parent, [{
action: new Action('workspace.trust.button.action.deny', localize('dontTrustButton', "Don't Trust"), undefined, true, async () => {
await this.workspaceTrustManagementService.setWorkspaceTrust(false);
}),
keybinding: this.keybindingService.resolveUserBinding(isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter')[0]
}]);
}
private addTrustedTextToElement(parent: HTMLElement): void {