Web: remote indicator API (#105069)
* remote - refactor indicator a bit for better readability
* remote indicator - more refactorings and cleanup
* web api - shuffle some things around
* remote indicator - add remote transition indicator
* update remote indicator API
* 💄
This commit is contained in:
parent
0e0a0657cd
commit
a9624db8e1
5 changed files with 266 additions and 111 deletions
|
@ -3,7 +3,10 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IWorkbenchConstructionOptions, create, URI, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace } from 'vs/workbench/workbench.web.api';
|
import { IWorkbenchConstructionOptions, create, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IRemoteIndicator, ICommand, IHomeIndicator } from 'vs/workbench/workbench.web.api';
|
||||||
|
import product from 'vs/platform/product/common/product';
|
||||||
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||||
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { streamToBuffer } from 'vs/base/common/buffer';
|
import { streamToBuffer } from 'vs/base/common/buffer';
|
||||||
|
@ -276,6 +279,51 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RemoteIndicator implements IRemoteIndicator {
|
||||||
|
|
||||||
|
readonly onDidChange = Event.None;
|
||||||
|
|
||||||
|
readonly label: string;
|
||||||
|
readonly tooltip: string;
|
||||||
|
readonly command: string | undefined;
|
||||||
|
|
||||||
|
readonly commandImpl: ICommand | undefined = undefined;
|
||||||
|
|
||||||
|
constructor(workspace: IWorkspace) {
|
||||||
|
let repositoryOwner: string | undefined = undefined;
|
||||||
|
let repositoryName: string | undefined = undefined;
|
||||||
|
|
||||||
|
if (workspace) {
|
||||||
|
let uri: URI | undefined = undefined;
|
||||||
|
if (isFolderToOpen(workspace)) {
|
||||||
|
uri = workspace.folderUri;
|
||||||
|
} else if (isWorkspaceToOpen(workspace)) {
|
||||||
|
uri = workspace.workspaceUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri?.scheme === 'github' || uri?.scheme === 'codespace') {
|
||||||
|
[repositoryOwner, repositoryName] = uri.authority.split('+');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repositoryName && repositoryOwner) {
|
||||||
|
this.label = localize('openInDesktopLabel', "$(remote) Open in Desktop");
|
||||||
|
this.tooltip = localize('openInDesktopTooltip', "Open in Desktop");
|
||||||
|
this.command = '_web.openInDesktop';
|
||||||
|
this.commandImpl = {
|
||||||
|
id: this.command,
|
||||||
|
handler: () => {
|
||||||
|
const protocol = product.quality === 'stable' ? 'vscode' : 'vscode-insiders';
|
||||||
|
window.open(`${protocol}://vscode.git/clone?url=${encodeURIComponent(`https://github.com/${repositoryOwner}/${repositoryName}.git`)}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.label = localize('playgroundLabel', "Web Playground");
|
||||||
|
this.tooltip = this.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
|
|
||||||
// Find config by checking for DOM
|
// Find config by checking for DOM
|
||||||
|
@ -343,14 +391,28 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Home Indicator
|
||||||
|
const homeIndicator: IHomeIndicator = {
|
||||||
|
href: 'https://github.com/Microsoft/vscode',
|
||||||
|
icon: 'code',
|
||||||
|
title: localize('home', "Home")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
const commands: ICommand[] = [];
|
||||||
|
|
||||||
|
// Remote indicator
|
||||||
|
const remoteIndicator = new RemoteIndicator(workspace);
|
||||||
|
if (remoteIndicator.commandImpl) {
|
||||||
|
commands.push(remoteIndicator.commandImpl);
|
||||||
|
}
|
||||||
|
|
||||||
// Finally create workbench
|
// Finally create workbench
|
||||||
create(document.body, {
|
create(document.body, {
|
||||||
...config,
|
...config,
|
||||||
homeIndicator: {
|
homeIndicator,
|
||||||
href: 'https://github.com/Microsoft/vscode',
|
commands,
|
||||||
icon: 'code',
|
remoteIndicator,
|
||||||
title: localize('home', "Home")
|
|
||||||
},
|
|
||||||
workspaceProvider: new WorkspaceProvider(workspace, payload),
|
workspaceProvider: new WorkspaceProvider(workspace, payload),
|
||||||
urlCallbackProvider: new PollingURLCallbackProvider(),
|
urlCallbackProvider: new PollingURLCallbackProvider(),
|
||||||
credentialsProvider: new LocalStorageCredentialsProvider()
|
credentialsProvider: new LocalStorageCredentialsProvider()
|
||||||
|
|
|
@ -21,8 +21,6 @@ import { PanelPositionContext } from 'vs/workbench/common/panel';
|
||||||
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
|
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
|
||||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||||
|
|
||||||
export const Deprecated_RemoteAuthorityContext = new RawContextKey<string>('remoteAuthority', '');
|
|
||||||
|
|
||||||
export const RemoteNameContext = new RawContextKey<string>('remoteName', '');
|
export const RemoteNameContext = new RawContextKey<string>('remoteName', '');
|
||||||
export const RemoteConnectionState = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', '');
|
export const RemoteConnectionState = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', '');
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
import { RemoteWindowActiveIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator';
|
import { RemoteStatusIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator';
|
||||||
import { inQuickPickContextKeyValue } from 'vs/workbench/browser/quickaccess';
|
import { inQuickPickContextKeyValue } from 'vs/workbench/browser/quickaccess';
|
||||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||||
|
@ -838,4 +838,4 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
|
||||||
|
|
||||||
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
|
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
|
||||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
|
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteStatusIndicator, LifecyclePhase.Starting);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||||
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
|
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
|
||||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
|
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||||
|
@ -21,88 +21,112 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { RemoteConnectionState, Deprecated_RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys';
|
import { RemoteConnectionState } from 'vs/workbench/browser/contextkeys';
|
||||||
import { isWeb } from 'vs/base/common/platform';
|
import { isWeb } from 'vs/base/common/platform';
|
||||||
import { once } from 'vs/base/common/functional';
|
import { once } from 'vs/base/common/functional';
|
||||||
|
|
||||||
const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu';
|
export class RemoteStatusIndicator extends Disposable implements IWorkbenchContribution {
|
||||||
const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close';
|
|
||||||
const SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command
|
|
||||||
|
|
||||||
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
|
private static REMOTE_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu';
|
||||||
|
private static CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close';
|
||||||
|
private static SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command
|
||||||
|
|
||||||
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
|
private remoteStatusEntry: IStatusbarEntryAccessor | undefined;
|
||||||
private windowCommandMenu: IMenu;
|
|
||||||
private hasWindowActions: boolean = false;
|
private remoteMenu = this._register(this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService));
|
||||||
private remoteAuthority: string | undefined;
|
private hasRemoteActions = false;
|
||||||
|
|
||||||
|
private remoteAuthority = this.environmentService.configuration.remoteAuthority;
|
||||||
private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined;
|
private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined;
|
||||||
|
private connectionStateContextKey = RemoteConnectionState.bindTo(this.contextKeyService);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
||||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||||
@ILabelService private readonly labelService: ILabelService,
|
@ILabelService private readonly labelService: ILabelService,
|
||||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||||
@IMenuService private menuService: IMenuService,
|
@IMenuService private menuService: IMenuService,
|
||||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||||
@ICommandService private readonly commandService: ICommandService,
|
@ICommandService private readonly commandService: ICommandService,
|
||||||
@IExtensionService extensionService: IExtensionService,
|
@IExtensionService private readonly extensionService: IExtensionService,
|
||||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||||
@IHostService hostService: IHostService
|
@IHostService private readonly hostService: IHostService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
|
// Set initial connection state
|
||||||
this._register(this.windowCommandMenu);
|
if (this.remoteAuthority) {
|
||||||
|
this.connectionState = 'initializing';
|
||||||
|
this.connectionStateContextKey.set(this.connectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registerActions();
|
||||||
|
this.registerListeners();
|
||||||
|
|
||||||
|
this.updateWhenInstalledExtensionsRegistered();
|
||||||
|
this.updateRemoteStatusIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerActions(): void {
|
||||||
const category = { value: nls.localize('remote.category', "Remote"), original: 'Remote' };
|
const category = { value: nls.localize('remote.category', "Remote"), original: 'Remote' };
|
||||||
|
|
||||||
|
// Show Remote Menu
|
||||||
const that = this;
|
const that = this;
|
||||||
registerAction2(class extends Action2 {
|
registerAction2(class extends Action2 {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
id: WINDOW_ACTIONS_COMMAND_ID,
|
id: RemoteStatusIndicator.REMOTE_ACTIONS_COMMAND_ID,
|
||||||
category,
|
category,
|
||||||
title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' },
|
title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' },
|
||||||
f1: true,
|
f1: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
run = () => that.showIndicatorActions(that.windowCommandMenu);
|
run = () => that.showRemoteMenu(that.remoteMenu);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.remoteAuthority = environmentService.configuration.remoteAuthority;
|
// Close Remote Connection
|
||||||
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || '');
|
if (RemoteStatusIndicator.SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) {
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID,
|
||||||
|
category,
|
||||||
|
title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' },
|
||||||
|
f1: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
run = () => that.remoteAuthority && that.hostService.openWindow({ forceReuseWindow: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
||||||
|
group: '6_close',
|
||||||
|
command: {
|
||||||
|
id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID,
|
||||||
|
title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection")
|
||||||
|
},
|
||||||
|
order: 3.5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerListeners(): void {
|
||||||
|
|
||||||
|
// Menu changes
|
||||||
|
this._register(this.remoteMenu.onDidChange(() => this.updateRemoteActions()));
|
||||||
|
|
||||||
|
// Update indicator when formatter changes as it may have an impact on the remote label
|
||||||
|
this._register(this.labelService.onDidChangeFormatters(() => this.updateRemoteStatusIndicator()));
|
||||||
|
|
||||||
|
// Update based on remote indicator changes if any
|
||||||
|
const remoteIndicator = this.environmentService.options?.remoteIndicator;
|
||||||
|
if (remoteIndicator) {
|
||||||
|
this._register(remoteIndicator.onDidChange(() => this.updateRemoteStatusIndicator()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen to changes of the connection
|
||||||
if (this.remoteAuthority) {
|
if (this.remoteAuthority) {
|
||||||
|
const connection = this.remoteAgentService.getConnection();
|
||||||
if (SHOW_CLOSE_REMOTE_COMMAND_ID) {
|
|
||||||
registerAction2(class extends Action2 {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
id: CLOSE_REMOTE_COMMAND_ID,
|
|
||||||
category,
|
|
||||||
title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' },
|
|
||||||
f1: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
run = () => that.remoteAuthority && hostService.openWindow({ forceReuseWindow: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
|
||||||
group: '6_close',
|
|
||||||
command: {
|
|
||||||
id: CLOSE_REMOTE_COMMAND_ID,
|
|
||||||
title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection")
|
|
||||||
},
|
|
||||||
order: 3.5
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pending entry until extensions are ready
|
|
||||||
this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID);
|
|
||||||
this.connectionState = 'initializing';
|
|
||||||
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
|
|
||||||
|
|
||||||
const connection = remoteAgentService.getConnection();
|
|
||||||
if (connection) {
|
if (connection) {
|
||||||
this._register(connection.onDidStateChange((e) => {
|
this._register(connection.onDidStateChange((e) => {
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
|
@ -119,72 +143,106 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extensionService.whenInstalledExtensionsRegistered().then(_ => {
|
private async updateWhenInstalledExtensionsRegistered(): Promise<void> {
|
||||||
if (this.remoteAuthority) {
|
await this.extensionService.whenInstalledExtensionsRegistered();
|
||||||
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
|
|
||||||
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
|
const remoteAuthority = this.remoteAuthority;
|
||||||
}
|
if (remoteAuthority) {
|
||||||
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
|
|
||||||
this.updateWindowIndicator();
|
// Try to resolve the authority to figure out connection state
|
||||||
});
|
(async () => {
|
||||||
|
try {
|
||||||
|
await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
|
||||||
|
|
||||||
|
this.setDisconnected(false);
|
||||||
|
} catch (error) {
|
||||||
|
this.setDisconnected(true);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateRemoteStatusIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDisconnected(isDisconnected: boolean): void {
|
private setDisconnected(isDisconnected: boolean): void {
|
||||||
const newState = isDisconnected ? 'disconnected' : 'connected';
|
const newState = isDisconnected ? 'disconnected' : 'connected';
|
||||||
if (this.connectionState !== newState) {
|
if (this.connectionState !== newState) {
|
||||||
this.connectionState = newState;
|
this.connectionState = newState;
|
||||||
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
|
this.connectionStateContextKey.set(this.connectionState);
|
||||||
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? `disconnected/${this.remoteAuthority!}` : this.remoteAuthority!);
|
|
||||||
this.updateWindowIndicator();
|
this.updateRemoteStatusIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWindowIndicator(): void {
|
private updateRemoteActions() {
|
||||||
const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined;
|
const newHasWindowActions = this.remoteMenu.getActions().length > 0;
|
||||||
if (this.remoteAuthority) {
|
if (newHasWindowActions !== this.hasRemoteActions) {
|
||||||
|
this.hasRemoteActions = newHasWindowActions;
|
||||||
|
|
||||||
|
this.updateRemoteStatusIndicator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateRemoteStatusIndicator(): void {
|
||||||
|
|
||||||
|
// Remote indicator: show if provided via options
|
||||||
|
const remoteIndicator = this.environmentService.options?.remoteIndicator;
|
||||||
|
if (remoteIndicator) {
|
||||||
|
this.renderRemoteStatusIndicator(remoteIndicator.label, remoteIndicator.tooltip, remoteIndicator.command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote Authority: show connection state
|
||||||
|
else if (this.remoteAuthority) {
|
||||||
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
|
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
|
||||||
if (this.connectionState !== 'disconnected') {
|
switch (this.connectionState) {
|
||||||
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
|
case 'initializing':
|
||||||
} else {
|
this.renderRemoteStatusIndicator(`$(sync~spin) ${nls.localize('host.open', "Opening Remote...")}`, nls.localize('host.open', "Opening Remote..."));
|
||||||
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand);
|
break;
|
||||||
}
|
case 'disconnected':
|
||||||
} else {
|
this.renderRemoteStatusIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from {0}", hostLabel)}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel));
|
||||||
if (windowActionCommand) {
|
break;
|
||||||
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
|
default:
|
||||||
} else if (this.windowIndicatorEntry) {
|
this.renderRemoteStatusIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel));
|
||||||
this.windowIndicatorEntry.dispose();
|
|
||||||
this.windowIndicatorEntry = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remote Extensions Installed: offer the indicator to show actions
|
||||||
|
else if (this.remoteMenu.getActions().length > 0) {
|
||||||
|
this.renderRemoteStatusIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a Remote Window"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Remote Extensions: hide status indicator
|
||||||
|
else {
|
||||||
|
dispose(this.remoteStatusEntry);
|
||||||
|
this.remoteStatusEntry = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWindowActions() {
|
private renderRemoteStatusIndicator(text: string, tooltip?: string, command?: string): void {
|
||||||
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
|
const name = nls.localize('remoteHost', "Remote Host");
|
||||||
if (newHasWindowActions !== this.hasWindowActions) {
|
if (typeof command !== 'string' && this.remoteMenu.getActions().length > 0) {
|
||||||
this.hasWindowActions = newHasWindowActions;
|
command = RemoteStatusIndicator.REMOTE_ACTIONS_COMMAND_ID;
|
||||||
this.updateWindowIndicator();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
|
|
||||||
const properties: IStatusbarEntry = {
|
const properties: IStatusbarEntry = {
|
||||||
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND),
|
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND),
|
||||||
color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND),
|
color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND),
|
||||||
ariaLabel: nls.localize('remote', "Remote"),
|
ariaLabel: name,
|
||||||
text,
|
text,
|
||||||
tooltip,
|
tooltip,
|
||||||
command
|
command
|
||||||
};
|
};
|
||||||
if (this.windowIndicatorEntry) {
|
|
||||||
this.windowIndicatorEntry.update(properties);
|
if (this.remoteStatusEntry) {
|
||||||
|
this.remoteStatusEntry.update(properties);
|
||||||
} else {
|
} else {
|
||||||
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
|
this.remoteStatusEntry = this.statusbarService.addEntry(properties, 'status.host', name, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showIndicatorActions(menu: IMenu) {
|
private showRemoteMenu(menu: IMenu) {
|
||||||
|
|
||||||
const actions = menu.getActions();
|
const actions = menu.getActions();
|
||||||
|
|
||||||
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
|
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
|
||||||
|
@ -192,6 +250,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
items.push({ type: 'separator' });
|
items.push({ type: 'separator' });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let action of actionGroup[1]) {
|
for (let action of actionGroup[1]) {
|
||||||
if (action instanceof MenuItemAction) {
|
if (action instanceof MenuItemAction) {
|
||||||
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
|
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
|
||||||
|
@ -199,6 +258,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
|
||||||
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
|
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
|
||||||
label = nls.localize('cat.title', "{0}: {1}", category, label);
|
label = nls.localize('cat.title', "{0}: {1}", category, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
type: 'item',
|
type: 'item',
|
||||||
id: action.item.id,
|
id: action.item.id,
|
||||||
|
@ -208,13 +268,14 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) {
|
if (RemoteStatusIndicator.SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) {
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
items.push({ type: 'separator' });
|
items.push({ type: 'separator' });
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
type: 'item',
|
type: 'item',
|
||||||
id: CLOSE_REMOTE_COMMAND_ID,
|
id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID,
|
||||||
label: nls.localize('closeRemote.title', 'Close Remote Connection')
|
label: nls.localize('closeRemote.title', 'Close Remote Connection')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -227,8 +288,10 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc
|
||||||
if (selectedItems.length === 1) {
|
if (selectedItems.length === 1) {
|
||||||
this.commandService.executeCommand(selectedItems[0].id!);
|
this.commandService.executeCommand(selectedItems[0].id!);
|
||||||
}
|
}
|
||||||
|
|
||||||
quickPick.hide();
|
quickPick.hide();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
quickPick.show();
|
quickPick.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,32 @@ interface IHomeIndicator {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IRemoteIndicator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggering this event will cause the remote indicator to update.
|
||||||
|
*/
|
||||||
|
onDidChange: Event<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label of the remote indicator may include octicons
|
||||||
|
* e.g. `$(remote) label`
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tooltip of the remote indicator should not include
|
||||||
|
* octicons and be descriptive.
|
||||||
|
*/
|
||||||
|
tooltip: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If provided, overrides the default command that
|
||||||
|
* is executed when clicking on the remote indicator.
|
||||||
|
*/
|
||||||
|
command?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface IDefaultSideBarLayout {
|
interface IDefaultSideBarLayout {
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
containers?: ({
|
containers?: ({
|
||||||
|
@ -204,6 +230,11 @@ interface IWorkbenchConstructionOptions {
|
||||||
*/
|
*/
|
||||||
readonly connectionToken?: string;
|
readonly connectionToken?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session id of the current authenticated user
|
||||||
|
*/
|
||||||
|
readonly authenticationSessionId?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An endpoint to serve iframe content ("webview") from. This is required
|
* An endpoint to serve iframe content ("webview") from. This is required
|
||||||
* to provide full security isolation from the workbench host.
|
* to provide full security isolation from the workbench host.
|
||||||
|
@ -231,6 +262,11 @@ interface IWorkbenchConstructionOptions {
|
||||||
*/
|
*/
|
||||||
readonly tunnelProvider?: ITunnelProvider;
|
readonly tunnelProvider?: ITunnelProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoints to be used for proxying authentication code exchange calls in the browser.
|
||||||
|
*/
|
||||||
|
readonly codeExchangeProxyEndpoints?: { [providerId: string]: string }
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
|
@ -247,11 +283,6 @@ interface IWorkbenchConstructionOptions {
|
||||||
*/
|
*/
|
||||||
userDataProvider?: IFileSystemProvider;
|
userDataProvider?: IFileSystemProvider;
|
||||||
|
|
||||||
/**
|
|
||||||
* Session id of the current authenticated user
|
|
||||||
*/
|
|
||||||
readonly authenticationSessionId?: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables user data sync by default and syncs into the current authenticated user account using the provided [authenticationSessionId}(#authenticationSessionId).
|
* Enables user data sync by default and syncs into the current authenticated user account using the provided [authenticationSessionId}(#authenticationSessionId).
|
||||||
*/
|
*/
|
||||||
|
@ -345,6 +376,11 @@ interface IWorkbenchConstructionOptions {
|
||||||
*/
|
*/
|
||||||
readonly productConfiguration?: Partial<IProductConfiguration>;
|
readonly productConfiguration?: Partial<IProductConfiguration>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional override for properties of the remote window indicator in the status bar.
|
||||||
|
*/
|
||||||
|
readonly remoteIndicator?: IRemoteIndicator;
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
|
@ -360,11 +396,6 @@ interface IWorkbenchConstructionOptions {
|
||||||
*/
|
*/
|
||||||
readonly driver?: boolean;
|
readonly driver?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Endpoints to be used for proxying authentication code exchange calls in the browser.
|
|
||||||
*/
|
|
||||||
readonly codeExchangeProxyEndpoints?: { [providerId: string]: string }
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,6 +535,7 @@ export {
|
||||||
// Branding
|
// Branding
|
||||||
IHomeIndicator,
|
IHomeIndicator,
|
||||||
IProductConfiguration,
|
IProductConfiguration,
|
||||||
|
IRemoteIndicator,
|
||||||
|
|
||||||
// Default layout
|
// Default layout
|
||||||
IDefaultView,
|
IDefaultView,
|
||||||
|
|
Loading…
Reference in a new issue