From 10e6e876822fa2da15fb68e8a6f1fb9aa8d94ce2 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 20 Jun 2022 17:09:19 -0700 Subject: [PATCH] Change modified in to use custom hover (#152401) Ref #151787 --- .../browser/ui/iconLabel/iconHoverDelegate.ts | 3 +- .../browser/ui/iconLabel/iconLabelHover.ts | 34 ++++++--- .../browser/media/settingsEditor2.css | 3 +- .../settingsEditorSettingIndicators.ts | 72 ++++++++++++++----- 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 79f13f8dad1..1dd1e111bd9 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IUpdatableHoverOptions } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -12,7 +13,7 @@ export interface IHoverDelegateTarget extends IDisposable { x?: number; } -export interface IHoverDelegateOptions { +export interface IHoverDelegateOptions extends IUpdatableHoverOptions { content: IMarkdownString | string | HTMLElement; target: IHoverDelegateTarget | HTMLElement; hoverPosition?: HoverPosition; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 3034ea52d93..41e4c168286 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -33,6 +33,21 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo export type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined; type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined; +/** + * Copied from src\vs\workbench\services\hover\browser\hover.ts + * @deprecated Use IHoverService + */ +export interface IHoverAction { + label: string; + commandId: string; + iconClass?: string; + run(target: HTMLElement): void; +} + +export interface IUpdatableHoverOptions { + actions?: IHoverAction[]; + linkHandler?(url: string): void; +} export interface ICustomHover extends IDisposable { @@ -49,7 +64,7 @@ export interface ICustomHover extends IDisposable { /** * Updates the contents of the hover. */ - update(tooltip: IHoverContent): void; + update(tooltip: IHoverContent, options?: IUpdatableHoverOptions): void; } @@ -61,7 +76,7 @@ class UpdatableHoverWidget implements IDisposable { constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) { } - async update(content: IHoverContent, focus?: boolean): Promise { + async update(content: IHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): Promise { if (this._cancellationTokenSource) { // there's an computation ongoing, cancel it this._cancellationTokenSource.dispose(true); @@ -99,10 +114,10 @@ class UpdatableHoverWidget implements IDisposable { } } - this.show(resolvedContent, focus); + this.show(resolvedContent, focus, options); } - private show(content: IResolvedHoverContent, focus?: boolean): void { + private show(content: IResolvedHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): void { const oldHoverWidget = this._hoverWidget; if (this.hasContent(content)) { @@ -111,7 +126,8 @@ class UpdatableHoverWidget implements IDisposable { target: this.target, showPointer: this.hoverDelegate.placement === 'element', hoverPosition: HoverPosition.BELOW, - skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget // do not fade in if the hover is already showing + skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing + ...options }; this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus); @@ -142,7 +158,7 @@ class UpdatableHoverWidget implements IDisposable { } } -export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent): ICustomHover { +export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent, options?: IUpdatableHoverOptions): ICustomHover { let hoverPreparation: IDisposable | undefined; let hoverWidget: UpdatableHoverWidget | undefined; @@ -163,7 +179,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM return new TimeoutTimer(async () => { if (!hoverWidget || hoverWidget.isDisposed) { hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0); - await hoverWidget.update(content, focus); + await hoverWidget.update(content, focus, options); } }, delay); }; @@ -208,9 +224,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM hide: () => { hideHover(true, true); }, - update: async newContent => { + update: async (newContent, hoverOptions) => { content = newContent; - await hoverWidget?.update(content); + await hoverWidget?.update(content, undefined, hoverOptions); }, dispose: () => { mouseOverDomEmitter.dispose(); diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index c32f22f0870..ff06cba0a11 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -351,8 +351,7 @@ font-style: italic; } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored .codicon, -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .setting-item-default-overridden .codicon { +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .codicon { vertical-align: middle; padding-left: 1px; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 9bfac0d0356..b30d6b3333e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ITooltipMarkdownString, IUpdatableHoverOptions, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -30,6 +30,7 @@ export interface ISettingOverrideClickEvent { export class SettingsTreeIndicatorsLabel { private indicatorsContainerElement: HTMLElement; private scopeOverridesElement: HTMLElement; + private scopeOverridesLabel: SimpleIconLabel; private syncIgnoredElement: HTMLElement; private defaultOverrideIndicatorElement: HTMLElement; private hoverDelegate: IHoverDelegate; @@ -42,7 +43,9 @@ export class SettingsTreeIndicatorsLabel { this.indicatorsContainerElement = DOM.append(container, $('.misc-label')); this.indicatorsContainerElement.style.display = 'inline'; - this.scopeOverridesElement = this.createScopeOverridesElement(); + const scopeOverridesIndicator = this.createScopeOverridesIndicator(); + this.scopeOverridesElement = scopeOverridesIndicator.element; + this.scopeOverridesLabel = scopeOverridesIndicator.label; this.syncIgnoredElement = this.createSyncIgnoredElement(); this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator(); @@ -55,16 +58,17 @@ export class SettingsTreeIndicatorsLabel { }; } - private createScopeOverridesElement(): HTMLElement { + private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } { const otherOverridesElement = $('span.setting-item-overrides'); - return otherOverridesElement; + const otherOverridesLabel = new SimpleIconLabel(otherOverridesElement); + return { element: otherOverridesElement, label: otherOverridesLabel }; } private createSyncIgnoredElement(): HTMLElement { const syncIgnoredElement = $('span.setting-item-ignored'); const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); syncIgnoredLabel.text = '$(info) ' + localize('extensionSyncIgnoredLabel', 'Not synced'); - const syncIgnoredHoverContent = localize('syncIgnoredTitle', "Settings sync does not sync this setting"); + const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync"); setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent); return syncIgnoredElement; } @@ -106,28 +110,58 @@ export class SettingsTreeIndicatorsLabel { this.scopeOverridesElement.style.display = 'none'; if (element.overriddenScopeList.length) { this.scopeOverridesElement.style.display = 'inline'; - const otherOverridesLabel = element.isConfigured ? - localize('alsoConfiguredIn', "Also modified in") : - localize('configuredIn', "Modified in"); - - DOM.append(this.scopeOverridesElement, $('span', undefined, `${otherOverridesLabel}: `)); - - for (let i = 0; i < element.overriddenScopeList.length; i++) { - const view = DOM.append(this.scopeOverridesElement, $('a.modified-scope', undefined, element.overriddenScopeList[i])); - - if (i !== element.overriddenScopeList.length - 1) { - DOM.append(this.scopeOverridesElement, $('span', undefined, ', ')); - } + if (element.overriddenScopeList.length === 1) { + // Just show all the text in the label. + const prefaceText = element.isConfigured ? + localize('alsoConfiguredIn', "Also modified in") : + localize('configuredIn', "Modified in"); + this.scopeOverridesLabel.text = `${prefaceText}: `; + const firstScope = element.overriddenScopeList[0]; + const view = DOM.append(this.scopeOverridesElement, $('a.modified-scope', undefined, firstScope)); elementDisposables.add( DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { onDidClickOverrideElement.fire({ targetKey: element.setting.key, - scope: element.overriddenScopeList[i] + scope: firstScope }); e.preventDefault(); e.stopPropagation(); })); + } else { + // Show most of the text in a custom hover. + let scopeOverridesLabelText = '$(info) '; + scopeOverridesLabelText += element.isConfigured ? + localize('alsoConfiguredElsewhere', "Also modified elsewhere") : + localize('configuredElsewhere', "Modified elsewhere"); + this.scopeOverridesLabel.text = scopeOverridesLabelText; + + const prefaceText = element.isConfigured ? + localize('alsoModifiedInScopes', "The setting has also been modified in the following scopes:") : + localize('modifiedInScopes', "The setting has been modified in the following scopes:"); + let contentMarkdownString = prefaceText; + let contentFallback = prefaceText; + for (const scope of element.overriddenScopeList) { + contentMarkdownString += `\n- [${scope}](${scope})`; + contentFallback += `\n• ${scope}`; + } + const content: ITooltipMarkdownString = { + markdown: { + value: contentMarkdownString, + isTrusted: false, + supportHtml: false + }, + markdownNotSupportedFallback: contentFallback + }; + const options: IUpdatableHoverOptions = { + linkHandler: (scope: string) => { + onDidClickOverrideElement.fire({ + targetKey: element.setting.key, + scope + }); + } + }; + setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content, options); } } this.render(); @@ -173,7 +207,7 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, // Add sync ignored text const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), configurationService); if (ignoredSettings.includes(element.setting.key)) { - ariaLabelSections.push(localize('syncIgnoredTitle', "Settings sync does not sync this setting")); + ariaLabelSections.push(localize('syncIgnoredTitle', "This setting is ignored during sync")); } // Add default override indicator text