Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/accountMenu/browser/updateHoverWidget.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 { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
7
import { localize } from '../../../../nls.js';
8
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
9
import { IProductService } from '../../../../platform/product/common/productService.js';
10
import { Downloading, IUpdate, IUpdateService, State, StateType, Updating } from '../../../../platform/update/common/update.js';
11
import './media/updateHoverWidget.css';
12
13
export class UpdateHoverWidget {
14
15
constructor(
16
private readonly updateService: IUpdateService,
17
private readonly productService: IProductService,
18
private readonly hoverService: IHoverService,
19
private readonly stateProvider?: () => State,
20
) { }
21
22
attachTo(target: HTMLElement) {
23
return this.hoverService.setupDelayedHover(
24
target,
25
() => ({
26
content: this.createHoverContent(),
27
position: { hoverPosition: HoverPosition.RIGHT },
28
appearance: { showPointer: true }
29
}),
30
{ groupId: 'sessions-account-update' }
31
);
32
}
33
34
createHoverContent(state: State = this.stateProvider?.() ?? this.updateService.state): HTMLElement {
35
const update = this.getUpdateFromState(state);
36
const currentVersion = this.productService.version ?? localize('unknownVersion', "Unknown");
37
const targetVersion = update?.productVersion ?? update?.version ?? localize('unknownVersion', "Unknown");
38
const currentCommit = this.productService.commit;
39
const targetCommit = update?.version;
40
const progressPercent = this.getUpdateProgressPercent(state);
41
42
const container = document.createElement('div');
43
container.classList.add('sessions-update-hover');
44
45
// Header: e.g. "Downloading VS Code Insiders"
46
const header = document.createElement('div');
47
header.classList.add('sessions-update-hover-header');
48
header.textContent = this.getUpdateHeaderLabel(state.type);
49
container.appendChild(header);
50
51
// Progress bar
52
if (progressPercent !== undefined) {
53
const progressTrack = document.createElement('div');
54
progressTrack.classList.add('sessions-update-hover-progress-track');
55
const progressFill = document.createElement('div');
56
progressFill.classList.add('sessions-update-hover-progress-fill');
57
progressFill.style.width = `${progressPercent}%`;
58
progressTrack.appendChild(progressFill);
59
container.appendChild(progressTrack);
60
}
61
62
// Version info grid
63
const detailsGrid = document.createElement('div');
64
detailsGrid.classList.add('sessions-update-hover-grid');
65
66
const currentDate = this.productService.date ? new Date(this.productService.date) : undefined;
67
const currentAge = currentDate ? this.formatCompactAge(currentDate.getTime()) : undefined;
68
const newAge = update?.timestamp ? this.formatCompactAge(update.timestamp) : undefined;
69
70
this.appendGridRow(detailsGrid, localize('updateHoverCurrentVersionLabel', "Current"), currentVersion, currentAge, currentCommit);
71
this.appendGridRow(detailsGrid, localize('updateHoverNewVersionLabel', "New"), targetVersion, newAge, targetCommit);
72
73
container.appendChild(detailsGrid);
74
75
return container;
76
}
77
78
private appendGridRow(grid: HTMLElement, label: string, version: string, age?: string, commit?: string): void {
79
const labelEl = document.createElement('span');
80
labelEl.classList.add('sessions-update-hover-label');
81
labelEl.textContent = label;
82
grid.appendChild(labelEl);
83
84
const versionEl = document.createElement('span');
85
versionEl.classList.add('sessions-update-hover-version');
86
versionEl.textContent = version;
87
grid.appendChild(versionEl);
88
89
const ageEl = document.createElement('span');
90
ageEl.classList.add('sessions-update-hover-age');
91
ageEl.textContent = age ?? '';
92
grid.appendChild(ageEl);
93
94
const commitEl = document.createElement('span');
95
commitEl.classList.add('sessions-update-hover-commit');
96
commitEl.textContent = commit ? commit.substring(0, 7) : '';
97
grid.appendChild(commitEl);
98
}
99
100
private formatCompactAge(timestamp: number): string {
101
const seconds = Math.round((Date.now() - timestamp) / 1000);
102
if (seconds < 60) {
103
return localize('compactAgeNow', "now");
104
}
105
const minutes = Math.round(seconds / 60);
106
if (minutes < 60) {
107
return localize('compactAgeMinutes', "{0}m ago", minutes);
108
}
109
const hours = Math.round(seconds / 3600);
110
if (hours < 24) {
111
return localize('compactAgeHours', "{0}h ago", hours);
112
}
113
const days = Math.round(seconds / 86400);
114
if (days < 7) {
115
return localize('compactAgeDays', "{0}d ago", days);
116
}
117
const weeks = Math.round(days / 7);
118
if (weeks < 5) {
119
return localize('compactAgeWeeks', "{0}w ago", weeks);
120
}
121
const months = Math.round(days / 30);
122
return localize('compactAgeMonths', "{0}mo ago", months);
123
}
124
125
private getUpdateFromState(state: State): IUpdate | undefined {
126
switch (state.type) {
127
case StateType.AvailableForDownload:
128
case StateType.Downloaded:
129
case StateType.Ready:
130
case StateType.Overwriting:
131
case StateType.Updating:
132
return state.update;
133
case StateType.Downloading:
134
return state.update;
135
default:
136
return undefined;
137
}
138
}
139
140
/**
141
* Returns progress as a percentage (0-100), or undefined if progress is not applicable.
142
*/
143
private getUpdateProgressPercent(state: State): number | undefined {
144
switch (state.type) {
145
case StateType.Downloading: {
146
const downloadingState = state as Downloading;
147
if (downloadingState.downloadedBytes !== undefined && downloadingState.totalBytes && downloadingState.totalBytes > 0) {
148
return Math.min(100, Math.round((downloadingState.downloadedBytes / downloadingState.totalBytes) * 100));
149
}
150
return 0;
151
}
152
case StateType.Updating: {
153
const updatingState = state as Updating;
154
if (updatingState.currentProgress !== undefined && updatingState.maxProgress && updatingState.maxProgress > 0) {
155
return Math.min(100, Math.round((updatingState.currentProgress / updatingState.maxProgress) * 100));
156
}
157
return 0;
158
}
159
case StateType.Downloaded:
160
case StateType.Ready:
161
return 100;
162
case StateType.AvailableForDownload:
163
case StateType.Overwriting:
164
return 0;
165
default:
166
return undefined;
167
}
168
}
169
170
private getUpdateHeaderLabel(type: StateType): string {
171
const productName = this.productService.nameShort;
172
switch (type) {
173
case StateType.Ready:
174
return localize('updateReady', "{0} Update Ready", productName);
175
case StateType.AvailableForDownload:
176
return localize('downloadAvailable', "{0} Update Available", productName);
177
case StateType.Downloading:
178
case StateType.Overwriting:
179
return localize('downloadingUpdate', "Downloading {0}", productName);
180
case StateType.Downloaded:
181
return localize('installingUpdate', "Installing {0}", productName);
182
case StateType.Updating:
183
return localize('updatingApp', "Updating {0}", productName);
184
default:
185
return localize('updating', "Updating {0}", productName);
186
}
187
}
188
}
189
190