Improve window.withProgress in status bar to use a static spin icon (fix #108657)
This commit is contained in:
parent
74ef0a92fe
commit
8d7ad831e5
|
@ -18,7 +18,7 @@ export function renderCodicons(text: string): Array<HTMLSpanElement | string> {
|
|||
textStart = (match.index || 0) + match[0].length;
|
||||
|
||||
const [, escaped, codicon, name, animation] = match;
|
||||
elements.push(escaped ? `$(${codicon})` : dom.$(`span.codicon.codicon-${name}${animation ? `.codicon-animation-${animation}` : ''}`));
|
||||
elements.push(escaped ? `$(${codicon})` : renderCodicon(name, animation));
|
||||
}
|
||||
|
||||
if (textStart < text.length) {
|
||||
|
@ -26,3 +26,7 @@ export function renderCodicons(text: string): Array<HTMLSpanElement | string> {
|
|||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
export function renderCodicon(name: string, animation: string): HTMLSpanElement {
|
||||
return dom.$(`span.codicon.codicon-${name}${animation ? `.codicon-animation-${animation}` : ''}`);
|
||||
}
|
||||
|
|
|
@ -998,8 +998,15 @@ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
|
|||
/**
|
||||
* Removes all children from `parent` and appends `children`
|
||||
*/
|
||||
export function reset(parent: HTMLElement, ...children: Array<Node | string>) {
|
||||
export function reset(parent: HTMLElement, ...children: Array<Node | string>): void {
|
||||
parent.innerText = '';
|
||||
appendChildren(parent, ...children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends `children` to `parent`
|
||||
*/
|
||||
export function appendChildren(parent: HTMLElement, ...children: Array<Node | string>): void {
|
||||
for (const child of children) {
|
||||
if (child instanceof Node) {
|
||||
parent.appendChild(child);
|
||||
|
|
|
@ -21,7 +21,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
|
|||
import { contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor } from 'vs/base/browser/dom';
|
||||
import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, appendChildren } from 'vs/base/browser/dom';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
@ -39,6 +39,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
|
|||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ColorScheme } from 'vs/platform/theme/common/theme';
|
||||
import { renderCodicon, renderCodicons } from 'vs/base/browser/codicons';
|
||||
|
||||
interface IPendingStatusbarEntry {
|
||||
id: string;
|
||||
|
@ -64,17 +65,18 @@ class StatusbarViewModel extends Disposable {
|
|||
|
||||
static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
|
||||
|
||||
private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>());
|
||||
readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event;
|
||||
|
||||
private readonly _entries: IStatusbarViewModelEntry[] = [];
|
||||
get entries(): IStatusbarViewModelEntry[] { return this._entries; }
|
||||
|
||||
private hidden!: Set<string>;
|
||||
private _lastFocusedEntry: IStatusbarViewModelEntry | undefined;
|
||||
get lastFocusedEntry(): IStatusbarViewModelEntry | undefined {
|
||||
return this._lastFocusedEntry && !this.isHidden(this._lastFocusedEntry.id) ? this._lastFocusedEntry : undefined;
|
||||
}
|
||||
private _lastFocusedEntry: IStatusbarViewModelEntry | undefined;
|
||||
|
||||
private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>());
|
||||
readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event;
|
||||
private hidden!: Set<string>;
|
||||
|
||||
constructor(private readonly storageService: IStorageService) {
|
||||
super();
|
||||
|
@ -706,12 +708,67 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
|||
}
|
||||
}
|
||||
|
||||
class StatusBarCodiconLabel extends CodiconLabel {
|
||||
|
||||
private readonly progressCodicon = renderCodicon('sync', 'spin');
|
||||
|
||||
private currentText = '';
|
||||
private currentShowProgress = false;
|
||||
|
||||
constructor(
|
||||
private readonly container: HTMLElement
|
||||
) {
|
||||
super(container);
|
||||
}
|
||||
|
||||
set showProgress(showProgress: boolean) {
|
||||
if (this.currentShowProgress !== showProgress) {
|
||||
this.currentShowProgress = showProgress;
|
||||
this.text = this.currentText;
|
||||
}
|
||||
}
|
||||
|
||||
set text(text: string) {
|
||||
|
||||
// Progress: insert progress codicon as first element as needed
|
||||
// but keep it stable so that the animation does not reset
|
||||
if (this.currentShowProgress) {
|
||||
|
||||
// Append as needed
|
||||
if (this.container.firstChild !== this.progressCodicon) {
|
||||
this.container.appendChild(this.progressCodicon);
|
||||
}
|
||||
|
||||
// Remove others
|
||||
for (const node of Array.from(this.container.childNodes)) {
|
||||
if (node !== this.progressCodicon) {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// If we have text to show, add a space to separate from progress
|
||||
let textContent = text ?? '';
|
||||
if (textContent) {
|
||||
textContent = ` ${textContent}`;
|
||||
}
|
||||
|
||||
// Append new elements
|
||||
appendChildren(this.container, ...renderCodicons(textContent));
|
||||
}
|
||||
|
||||
// No Progress: no special handling
|
||||
else {
|
||||
super.text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StatusbarEntryItem extends Disposable {
|
||||
|
||||
private entry!: IStatusbarEntry;
|
||||
readonly labelContainer: HTMLElement;
|
||||
private readonly label: StatusBarCodiconLabel;
|
||||
|
||||
labelContainer!: HTMLElement;
|
||||
private label!: CodiconLabel;
|
||||
private entry: IStatusbarEntry | undefined = undefined;
|
||||
|
||||
private readonly foregroundListener = this._register(new MutableDisposable());
|
||||
private readonly backgroundListener = this._register(new MutableDisposable());
|
||||
|
@ -729,26 +786,25 @@ class StatusbarEntryItem extends Disposable {
|
|||
) {
|
||||
super();
|
||||
|
||||
this.create();
|
||||
this.update(entry);
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
|
||||
// Label Container
|
||||
this.labelContainer = document.createElement('a');
|
||||
this.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.
|
||||
this.labelContainer.setAttribute('role', 'button');
|
||||
|
||||
// Label
|
||||
this.label = new CodiconLabel(this.labelContainer);
|
||||
// Label (with support for progress)
|
||||
this.label = new StatusBarCodiconLabel(this.labelContainer);
|
||||
|
||||
// Add to parent
|
||||
this.container.appendChild(this.labelContainer);
|
||||
|
||||
this.update(entry);
|
||||
}
|
||||
|
||||
update(entry: IStatusbarEntry): void {
|
||||
|
||||
// Update: Progress
|
||||
this.label.showProgress = !!entry.showProgress;
|
||||
|
||||
// Update: Text
|
||||
if (!this.entry || entry.text !== this.entry.text) {
|
||||
this.label.text = entry.text;
|
||||
|
@ -760,8 +816,9 @@ class StatusbarEntryItem extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
// Set the aria label on both elements so screen readers would read
|
||||
// the correct thing without duplication #96210
|
||||
if (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) {
|
||||
// Set the aria label on both elements so screen readers would read the correct thing without duplication #96210
|
||||
this.container.setAttribute('aria-label', entry.ariaLabel);
|
||||
this.labelContainer.setAttribute('aria-label', entry.ariaLabel);
|
||||
}
|
||||
|
|
|
@ -82,7 +82,8 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
|
|||
|
||||
if (visible) {
|
||||
const indicator: IStatusbarEntry = {
|
||||
text: '$(sync~spin) ' + nls.localize('profilingExtensionHost', "Profiling Extension Host"),
|
||||
text: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
|
||||
showProgress: true,
|
||||
ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
|
||||
tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),
|
||||
command: 'workbench.action.extensionHostProfilder.stop'
|
||||
|
@ -91,7 +92,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
|
|||
const timeStarted = Date.now();
|
||||
const handle = setInterval(() => {
|
||||
if (this.profilingStatusBarIndicator) {
|
||||
this.profilingStatusBarIndicator.update({ ...indicator, text: '$(sync~spin) ' + nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });
|
||||
this.profilingStatusBarIndicator.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });
|
||||
}
|
||||
}, 1000);
|
||||
this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle));
|
||||
|
|
|
@ -197,7 +197,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.remoteAuthority) || this.remoteAuthority;
|
||||
switch (this.connectionState) {
|
||||
case 'initializing':
|
||||
this.renderRemoteStatusIndicator(`$(sync~spin) ${nls.localize('host.open', "Opening Remote...")}`, nls.localize('host.open', "Opening Remote..."));
|
||||
this.renderRemoteStatusIndicator(nls.localize('host.open', "Opening Remote..."), nls.localize('host.open', "Opening Remote..."), undefined, true /* progress */);
|
||||
break;
|
||||
case 'disconnected':
|
||||
this.renderRemoteStatusIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from {0}", hostLabel)}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel));
|
||||
|
@ -219,7 +219,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
}
|
||||
}
|
||||
|
||||
private renderRemoteStatusIndicator(text: string, tooltip?: string, command?: string): void {
|
||||
private renderRemoteStatusIndicator(text: string, tooltip?: string, command?: string, showProgress?: boolean): void {
|
||||
const name = nls.localize('remoteHost', "Remote Host");
|
||||
if (typeof command !== 'string' && this.remoteMenu.getActions().length > 0) {
|
||||
command = RemoteStatusIndicator.REMOTE_ACTIONS_COMMAND_ID;
|
||||
|
@ -230,6 +230,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND),
|
||||
ariaLabel: name,
|
||||
text,
|
||||
showProgress,
|
||||
tooltip,
|
||||
command
|
||||
};
|
||||
|
|
|
@ -151,7 +151,8 @@ export class ProgressService extends Disposable implements IProgressService {
|
|||
}
|
||||
|
||||
const statusEntryProperties: IStatusbarEntry = {
|
||||
text: `$(sync~spin) ${text}`,
|
||||
text,
|
||||
showProgress: true,
|
||||
ariaLabel: text,
|
||||
tooltip: title,
|
||||
command: progressCommand
|
||||
|
|
|
@ -63,6 +63,11 @@ export interface IStatusbarEntry {
|
|||
* Whether to show a beak above the status bar entry.
|
||||
*/
|
||||
readonly showBeak?: boolean;
|
||||
|
||||
/**
|
||||
* Will enable a spinning icon in front of the text to indicate progress.
|
||||
*/
|
||||
readonly showProgress?: boolean;
|
||||
}
|
||||
|
||||
export interface IStatusbarService {
|
||||
|
|
Loading…
Reference in a new issue