Skip to content

Commit 1518919

Browse files
committed
💄
1 parent 48bb437 commit 1518919

File tree

3 files changed

+120
-105
lines changed

3 files changed

+120
-105
lines changed

extensions/gitpod-web/src/extension.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { ThrottledDelayer } from './util/async';
2323
import { download } from './util/download';
2424
import { getManifest } from './util/extensionManagmentUtill';
2525
import { GitpodWorkspacePort, PortInfo, iconStatusMap } from './util/port';
26-
import { registerReleaseNotesView, RELEASE_NOTES_LAST_READ_KEY } from './releaseNote';
26+
import { ReleaseNotes } from './releaseNotes';
2727
import { registerWelcomeWalkthroughContribution, WELCOME_WALKTROUGH_KEY } from './welcomeWalktrough';
2828

2929
let gitpodContext: GitpodExtensionContext | undefined;
@@ -33,7 +33,7 @@ export async function activate(context: vscode.ExtensionContext) {
3333
return;
3434
}
3535

36-
context.globalState.setKeysForSync([WELCOME_WALKTROUGH_KEY, RELEASE_NOTES_LAST_READ_KEY]);
36+
context.globalState.setKeysForSync([WELCOME_WALKTROUGH_KEY, ReleaseNotes.RELEASE_NOTES_LAST_READ_KEY]);
3737

3838
registerDesktop();
3939
registerAuth(gitpodContext);
@@ -48,7 +48,7 @@ export async function activate(context: vscode.ExtensionContext) {
4848
registerIpcHookCli(gitpodContext);
4949
registerExtensionManagement(gitpodContext);
5050
registerWelcomeWalkthroughContribution(gitpodContext);
51-
registerReleaseNotesView(gitpodContext);
51+
context.subscriptions.push(new ReleaseNotes(context));
5252

5353
await gitpodContext.active;
5454
}

extensions/gitpod-web/src/releaseNote.ts extensions/gitpod-web/src/releaseNotes.ts

+75-102
Original file line numberDiff line numberDiff line change
@@ -7,74 +7,59 @@ import fetch, { Response } from 'node-fetch';
77
import * as vscode from 'vscode';
88
import { load } from 'js-yaml';
99
import { CacheHelper } from './util/cache';
10+
import { Disposable, disposeAll } from './util/dispose';
1011

11-
export const RELEASE_NOTES_LAST_READ_KEY = 'gitpod.lastReadReleaseNotesId';
12-
13-
export function registerReleaseNotesView(context: vscode.ExtensionContext) {
14-
const cacheHelper = new CacheHelper(context);
12+
export class ReleaseNotes extends Disposable {
13+
public static readonly viewType = 'gitpodReleaseNotes';
14+
public static readonly websiteHost = 'https://www.gitpod.io';
15+
public static readonly RELEASE_NOTES_LAST_READ_KEY = 'gitpod.lastReadReleaseNotesId';
1516

16-
async function shouldShowReleaseNotes(lastReadId: string | undefined) {
17-
const releaseId = await getLastPublish(cacheHelper);
18-
console.log(`gitpod release notes lastReadId: ${lastReadId}, latestReleaseId: ${releaseId}`);
19-
return releaseId !== lastReadId;
20-
}
17+
private panel: vscode.WebviewPanel | undefined;
18+
private panelDisposables: vscode.Disposable[] = [];
19+
private lastReadId: string | undefined;
20+
private cacheHelper = new CacheHelper(this.context);
2121

22-
context.subscriptions.push(
23-
vscode.commands.registerCommand('gitpod.showReleaseNotes', () => {
24-
ReleaseNotesPanel.createOrShow(context, cacheHelper);
25-
})
26-
);
22+
constructor(
23+
private readonly context: vscode.ExtensionContext,
24+
) {
25+
super();
2726

28-
// sync between machines
29-
context.globalState.setKeysForSync([RELEASE_NOTES_LAST_READ_KEY]);
27+
this.lastReadId = this.context.globalState.get<string>(ReleaseNotes.RELEASE_NOTES_LAST_READ_KEY);
3028

31-
const lastReadId = context.globalState.get<string>(RELEASE_NOTES_LAST_READ_KEY);
32-
shouldShowReleaseNotes(lastReadId).then(shouldShow => {
33-
if (shouldShow) {
34-
ReleaseNotesPanel.createOrShow(context, cacheHelper);
35-
}
36-
});
37-
}
29+
this._register(vscode.commands.registerCommand('gitpod.showReleaseNotes', () => this.createOrShow()));
3830

39-
function getResponseCacheTime(resp: Response) {
40-
const v = resp.headers.get('Cache-Control');
41-
if (!v) {
42-
return undefined;
31+
this.showIfNewRelease(this.lastReadId);
4332
}
44-
const t = /max-age=(\d+)/.exec(v);
45-
if (!t) {
46-
return undefined;
33+
34+
private async getLastPublish() {
35+
const url = `${ReleaseNotes.websiteHost}/changelog/latest`;
36+
return this.cacheHelper.getOrRefresh(url, async () => {
37+
const resp = await fetch(url);
38+
if (!resp.ok) {
39+
throw new Error(`Getting latest releaseId failed: ${resp.statusText}`);
40+
}
41+
const { releaseId } = JSON.parse(await resp.text());
42+
return {
43+
value: releaseId as string,
44+
ttl: this.getResponseCacheTime(resp),
45+
};
46+
});
4747
}
48-
return Number(t[1]);
49-
}
5048

51-
async function getLastPublish(cacheHelper: CacheHelper) {
52-
const url = `${websiteHost}/changelog/latest`;
53-
return cacheHelper.getOrRefresh(url, async () => {
54-
const resp = await fetch(url);
55-
if (!resp.ok) {
56-
throw new Error(`Getting latest releaseId failed: ${resp.statusText}`);
49+
private getResponseCacheTime(resp: Response) {
50+
const cacheControlHeader = resp.headers.get('Cache-Control');
51+
if (!cacheControlHeader) {
52+
return undefined;
5753
}
58-
const { releaseId } = JSON.parse(await resp.text());
59-
return {
60-
value: releaseId as string,
61-
ttl: getResponseCacheTime(resp),
62-
};
63-
});
64-
65-
}
66-
67-
const websiteHost = 'https://www.gitpod.io';
68-
69-
class ReleaseNotesPanel {
70-
public static currentPanel: ReleaseNotesPanel | undefined;
71-
public static readonly viewType = 'gitpodReleaseNotes';
72-
private readonly panel: vscode.WebviewPanel;
73-
private lastReadId: string | undefined;
74-
private _disposables: vscode.Disposable[] = [];
54+
const match = /max-age=(\d+)/.exec(cacheControlHeader);
55+
if (!match) {
56+
return undefined;
57+
}
58+
return parseInt(match[1], 10);
59+
}
7560

7661
private async loadChangelog(releaseId: string) {
77-
const url = `${websiteHost}/changelog/raw-markdown?releaseId=${releaseId}`;
62+
const url = `${ReleaseNotes.websiteHost}/changelog/raw-markdown?releaseId=${releaseId}`;
7863
const md = await this.cacheHelper.getOrRefresh(url, async () => {
7964
const resp = await fetch(url);
8065
if (!resp.ok) {
@@ -83,7 +68,7 @@ class ReleaseNotesPanel {
8368
const md = await resp.text();
8469
return {
8570
value: md,
86-
ttl: getResponseCacheTime(resp),
71+
ttl: this.getResponseCacheTime(resp),
8772
};
8873
});
8974

@@ -122,12 +107,14 @@ class ReleaseNotesPanel {
122107
].join('\n\n');
123108
}
124109

125-
public async updateHtml(releaseId?: string) {
126-
if (!releaseId) {
127-
releaseId = await getLastPublish(this.cacheHelper);
110+
public async updateHtml() {
111+
if (!this.panel?.visible) {
112+
return;
128113
}
114+
115+
const releaseId = await this.getLastPublish();
129116
const mdContent = await this.loadChangelog(releaseId);
130-
const html = await vscode.commands.executeCommand('markdown.api.render', mdContent) as string;
117+
const html = await vscode.commands.executeCommand<string>('markdown.api.render', mdContent);
131118
this.panel.webview.html = `<!DOCTYPE html>
132119
<html lang="en">
133120
<head>
@@ -143,63 +130,49 @@ class ReleaseNotesPanel {
143130
${html}
144131
</body>
145132
</html>`;
146-
if (!this.lastReadId || releaseId > this.lastReadId) {
147-
await this.context.globalState.update(RELEASE_NOTES_LAST_READ_KEY, releaseId);
133+
if (releaseId !== this.lastReadId) {
134+
await this.context.globalState.update(ReleaseNotes.RELEASE_NOTES_LAST_READ_KEY, releaseId);
148135
this.lastReadId = releaseId;
149136
}
150137
}
151138

152-
public static createOrShow(context: vscode.ExtensionContext, cacheHelper: CacheHelper) {
153-
if (ReleaseNotesPanel.currentPanel) {
154-
ReleaseNotesPanel.currentPanel.panel.reveal();
139+
private async showIfNewRelease(lastReadId: string | undefined) {
140+
const releaseId = await this.getLastPublish();
141+
console.log(`gitpod release notes lastReadId: ${lastReadId}, latestReleaseId: ${releaseId}`);
142+
if (releaseId !== lastReadId) {
143+
this.createOrShow();
144+
}
145+
}
146+
147+
public createOrShow() {
148+
if (this.panel) {
149+
this.panel.reveal();
155150
return;
156151
}
157152

158-
const panel = vscode.window.createWebviewPanel(
159-
ReleaseNotesPanel.viewType,
153+
this.panel = vscode.window.createWebviewPanel(
154+
ReleaseNotes.viewType,
160155
'Gitpod Release Notes',
161156
vscode.ViewColumn.Beside,
162157
{ enableScripts: true },
163158
);
164-
165-
ReleaseNotesPanel.currentPanel = new ReleaseNotesPanel(context, cacheHelper, panel);
166-
}
167-
168-
public static revive(context: vscode.ExtensionContext, cacheHelper: CacheHelper, panel: vscode.WebviewPanel) {
169-
ReleaseNotesPanel.currentPanel = new ReleaseNotesPanel(context, cacheHelper, panel);
170-
}
171-
172-
private constructor(
173-
private readonly context: vscode.ExtensionContext,
174-
private readonly cacheHelper: CacheHelper,
175-
panel: vscode.WebviewPanel
176-
) {
177-
this.lastReadId = this.context.globalState.get<string>(RELEASE_NOTES_LAST_READ_KEY);
178-
this.panel = panel;
179-
180-
this.updateHtml();
181-
182-
this.panel.onDidDispose(() => this.dispose(), null, this._disposables);
159+
this.panel.onDidDispose(() => {
160+
disposeAll(this.panelDisposables);
161+
this.panel = undefined;
162+
this.panelDisposables = [];
163+
}, null, this.panelDisposables);
183164
this.panel.onDidChangeViewState(
184-
() => {
185-
if (this.panel.visible) {
186-
this.updateHtml();
187-
}
188-
},
165+
() => this.updateHtml(),
189166
null,
190-
this._disposables
167+
this.panelDisposables
191168
);
169+
this.updateHtml();
192170
}
193171

194-
public dispose() {
195-
ReleaseNotesPanel.currentPanel = undefined;
196-
this.panel.dispose();
197-
while (this._disposables.length) {
198-
const x = this._disposables.pop();
199-
if (x) {
200-
x.dispose();
201-
}
202-
}
172+
override dispose() {
173+
super.dispose();
174+
disposeAll(this.panelDisposables);
175+
this.panel?.dispose();
203176
}
204177
}
205178

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Gitpod. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
8+
export function disposeAll(disposables: vscode.Disposable[]): void {
9+
while (disposables.length) {
10+
const item = disposables.pop();
11+
if (item) {
12+
item.dispose();
13+
}
14+
}
15+
}
16+
17+
export abstract class Disposable {
18+
private _isDisposed = false;
19+
20+
protected _disposables: vscode.Disposable[] = [];
21+
22+
public dispose(): any {
23+
if (this._isDisposed) {
24+
return;
25+
}
26+
this._isDisposed = true;
27+
disposeAll(this._disposables);
28+
}
29+
30+
protected _register<T extends vscode.Disposable>(value: T): T {
31+
if (this._isDisposed) {
32+
value.dispose();
33+
} else {
34+
this._disposables.push(value);
35+
}
36+
return value;
37+
}
38+
39+
protected get isDisposed(): boolean {
40+
return this._isDisposed;
41+
}
42+
}

0 commit comments

Comments
 (0)