Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/tunnelHost/electron-browser/toggleRemoteConnectionsActionViewItem.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/tunnelHost.css';
7
import * as dom from '../../../../base/browser/dom.js';
8
import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
9
import { BaseActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
10
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
11
import { IAction } from '../../../../base/common/actions.js';
12
import { Codicon } from '../../../../base/common/codicons.js';
13
import { MarkdownString } from '../../../../base/common/htmlContent.js';
14
import { disposableTimeout } from '../../../../base/common/async.js';
15
import { localize } from '../../../../nls.js';
16
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
17
import { ITunnelHostService } from '../common/tunnelHost.js';
18
import { SHOW_TUNNEL_HOST_OUTPUT_ID } from './tunnelHostService.js';
19
import { IManagedHover, IManagedHoverContent } from '../../../../base/browser/ui/hover/hover.js';
20
21
/**
22
* Custom action view item for the toggle remote connections button.
23
* Provides pulse animation while connecting, hover with tunnel status,
24
* and a brief toast message after enabling.
25
*/
26
export class ToggleRemoteConnectionsActionViewItem extends BaseActionViewItem {
27
28
private _iconElement: HTMLElement | undefined;
29
private _toastElement: HTMLElement | undefined;
30
private _hover: IManagedHover | undefined;
31
private _wasSharing = false;
32
33
constructor(
34
action: IAction,
35
@ITunnelHostService private readonly _tunnelHostService: ITunnelHostService,
36
@IHoverService private readonly _hoverService: IHoverService,
37
) {
38
super(undefined, action);
39
40
this._wasSharing = this._tunnelHostService.isSharing;
41
42
this._register(this._tunnelHostService.onDidChangeStatus(() => {
43
this._updateState();
44
}));
45
}
46
47
override render(container: HTMLElement): void {
48
super.render(container);
49
50
if (!this.element) {
51
return;
52
}
53
54
this.element.classList.add('tunnel-host-toggle');
55
this.element.tabIndex = 0;
56
this.element.role = 'button';
57
58
// Icon
59
this._iconElement = dom.append(this.element, dom.$('span.tunnel-host-icon'));
60
this._iconElement.append(...renderLabelWithIcons(`$(${Codicon.radioTower.id})`));
61
62
// Toast text (initially hidden)
63
this._toastElement = dom.append(this.element, dom.$('span.tunnel-host-toast'));
64
65
// Hover
66
const hoverDelegate = getDefaultHoverDelegate('element');
67
this._hover = this._register(this._hoverService.setupManagedHover(
68
hoverDelegate, this.element, this._getHoverContent()
69
));
70
71
this._updateState();
72
}
73
74
private _updateState(): void {
75
if (!this.element) {
76
return;
77
}
78
79
const isSharing = this._tunnelHostService.isSharing;
80
const isConnecting = this._tunnelHostService.isConnecting;
81
82
// Toggle CSS classes for visual state
83
this.element.classList.toggle('sharing', isSharing);
84
this.element.classList.toggle('connecting', isConnecting);
85
86
// Update hover content
87
this._hover?.update(this._getHoverContent());
88
89
// Update ARIA
90
this.element.setAttribute('aria-label', this._getAriaLabel());
91
this.element.setAttribute('aria-pressed', String(isSharing));
92
93
// Show toast when transitioning to sharing
94
if (isSharing && !this._wasSharing && !isConnecting) {
95
this._showToast();
96
} else if (!isSharing && this._wasSharing) {
97
this._hideToast();
98
}
99
100
this._wasSharing = isSharing;
101
}
102
103
private _showToast(): void {
104
if (!this._toastElement) {
105
return;
106
}
107
108
this._toastElement.textContent = localize('tunnelHost.toast', "Remote session access is now enabled");
109
this._toastElement.classList.add('visible');
110
111
disposableTimeout(() => {
112
this._hideToast();
113
}, 3000, this._store);
114
}
115
116
private _hideToast(): void {
117
this._toastElement?.classList.remove('visible');
118
}
119
120
private _getHoverContent(): IManagedHoverContent {
121
const lines: string[] = [];
122
123
if (this._tunnelHostService.isConnecting) {
124
lines.push(localize('tunnelHost.hover.connecting', "Establishing tunnel connection..."));
125
} else if (this._tunnelHostService.isSharing) {
126
const info = this._tunnelHostService.sharingInfo;
127
if (info) {
128
lines.push(localize('tunnelHost.hover.sharing', "Remote session access enabled via tunnel '{0}'", info.tunnelName));
129
} else {
130
lines.push(localize('tunnelHost.hover.enabled', "Remote session access is enabled"));
131
}
132
} else {
133
lines.push(localize('tunnelHost.hover.idle', "Allow remote session access"));
134
}
135
136
lines.push(`[${localize('tunnelHost.hover.showOutput', "Show Output")}](command:${SHOW_TUNNEL_HOST_OUTPUT_ID})`);
137
138
const md = new MarkdownString(lines.join('\n\n'), { isTrusted: { enabledCommands: [SHOW_TUNNEL_HOST_OUTPUT_ID] } });
139
return { markdown: md, markdownNotSupportedFallback: lines[0] };
140
}
141
142
private _getAriaLabel(): string {
143
if (this._tunnelHostService.isConnecting) {
144
return localize('tunnelHost.hover.connecting', "Establishing tunnel connection...");
145
}
146
if (this._tunnelHostService.isSharing) {
147
const info = this._tunnelHostService.sharingInfo;
148
if (info) {
149
return localize('tunnelHost.hover.sharing', "Remote session access enabled via tunnel '{0}'", info.tunnelName);
150
}
151
return localize('tunnelHost.hover.enabled', "Remote session access is enabled");
152
}
153
return localize('tunnelHost.hover.idle', "Allow remote session access");
154
}
155
156
override dispose(): void {
157
super.dispose();
158
}
159
}
160
161