Skip to content

Commit 3b8ed7f

Browse files
author
Akos Kitta
committed
fix: check for updates dialog
Signed-off-by: Akos Kitta <[email protected]>
1 parent 44cfd1a commit 3b8ed7f

File tree

5 files changed

+142
-103
lines changed

5 files changed

+142
-103
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

-2
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ import { IDEUpdaterClientImpl } from './ide-updater/ide-updater-client-impl';
262262
import {
263263
IDEUpdaterDialog,
264264
IDEUpdaterDialogProps,
265-
IDEUpdaterDialogWidget,
266265
} from './dialogs/ide-updater/ide-updater-dialog';
267266
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
268267
import { MonitorModel } from './monitor-model';
@@ -910,7 +909,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
910909
title: 'UploadCertificate',
911910
});
912911

913-
bind(IDEUpdaterDialogWidget).toSelf().inSingletonScope();
914912
bind(IDEUpdaterDialog).toSelf().inSingletonScope();
915913
bind(IDEUpdaterDialogProps).toConstantValue({
916914
title: 'IDEUpdater',

Diff for: arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx

+28-27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { nls } from '@theia/core/lib/common';
2-
import { shell } from 'electron';
2+
import { shell } from '@theia/core/electron-shared/@electron/remote';
33
import * as React from '@theia/core/shared/react';
4-
import { createRoot } from '@theia/core/shared/react-dom/client';
54
import ReactMarkdown from 'react-markdown';
65
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
76
import ProgressBar from '../../components/ProgressBar';
@@ -28,32 +27,19 @@ export const IDEUpdaterComponent = ({
2827
},
2928
}: IDEUpdaterComponentProps): React.ReactElement => {
3029
const { version, releaseNotes } = updateInfo;
31-
const changelogDivRef =
32-
React.useRef() as React.MutableRefObject<HTMLDivElement>;
33-
const changelogRoot = createRoot(changelogDivRef.current);
30+
const [changelog, setChangelog] = React.useState<string>('');
3431
React.useEffect(() => {
35-
if (!!releaseNotes && changelogDivRef.current) {
36-
let changelog: string;
37-
if (typeof releaseNotes === 'string') changelog = releaseNotes;
38-
else
39-
changelog = releaseNotes.reduce((acc, item) => {
40-
return item.note ? (acc += `${item.note}\n\n`) : acc;
41-
}, '');
42-
changelogRoot.render(
43-
<ReactMarkdown
44-
components={{
45-
a: ({ href, children, ...props }) => (
46-
<a onClick={() => href && shell.openExternal(href)} {...props}>
47-
{children}
48-
</a>
49-
),
50-
}}
51-
>
52-
{changelog}
53-
</ReactMarkdown>
32+
if (releaseNotes) {
33+
setChangelog(
34+
typeof releaseNotes === 'string'
35+
? releaseNotes
36+
: releaseNotes.reduce(
37+
(acc, item) => (item.note ? (acc += `${item.note}\n\n`) : acc),
38+
''
39+
)
5440
);
5541
}
56-
}, [updateInfo]);
42+
}, [releaseNotes, changelog]);
5743

5844
const DownloadCompleted: () => React.ReactElement = () => (
5945
<div className="ide-updater-dialog--downloaded">
@@ -106,9 +92,24 @@ export const IDEUpdaterComponent = ({
10692
version
10793
)}
10894
</div>
109-
{releaseNotes && (
95+
{changelog && (
11096
<div className="dialogRow changelog-container">
111-
<div className="changelog" ref={changelogDivRef} />
97+
<div className="changelog">
98+
<ReactMarkdown
99+
components={{
100+
a: ({ href, children, ...props }) => (
101+
<a
102+
onClick={() => href && shell.openExternal(href)}
103+
{...props}
104+
>
105+
{children}
106+
</a>
107+
),
108+
}}
109+
>
110+
{changelog}
111+
</ReactMarkdown>
112+
</div>
112113
</div>
113114
)}
114115
</div>

Diff for: arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx

+51-57
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import {
55
postConstruct,
66
} from '@theia/core/shared/inversify';
77
import { DialogProps } from '@theia/core/lib/browser/dialogs';
8-
import { AbstractDialog } from '../../theia/dialogs/dialogs';
9-
import { Widget } from '@theia/core/shared/@phosphor/widgets';
108
import { Message } from '@theia/core/shared/@phosphor/messaging';
11-
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
9+
import { ReactDialog } from '../../theia/dialogs/dialogs';
1210
import { nls } from '@theia/core';
1311
import { IDEUpdaterComponent, UpdateProgress } from './ide-updater-component';
1412
import {
@@ -22,47 +20,11 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
2220

2321
const DOWNLOAD_PAGE_URL = 'https://www.arduino.cc/en/software';
2422

25-
@injectable()
26-
export class IDEUpdaterDialogWidget extends ReactWidget {
27-
private _updateInfo: UpdateInfo;
28-
private _updateProgress: UpdateProgress = {};
29-
30-
setUpdateInfo(updateInfo: UpdateInfo): void {
31-
this._updateInfo = updateInfo;
32-
this.update();
33-
}
34-
35-
mergeUpdateProgress(updateProgress: UpdateProgress): void {
36-
this._updateProgress = { ...this._updateProgress, ...updateProgress };
37-
this.update();
38-
}
39-
40-
get updateInfo(): UpdateInfo {
41-
return this._updateInfo;
42-
}
43-
44-
get updateProgress(): UpdateProgress {
45-
return this._updateProgress;
46-
}
47-
48-
protected render(): React.ReactNode {
49-
return !!this._updateInfo ? (
50-
<IDEUpdaterComponent
51-
updateInfo={this._updateInfo}
52-
updateProgress={this._updateProgress}
53-
/>
54-
) : null;
55-
}
56-
}
57-
5823
@injectable()
5924
export class IDEUpdaterDialogProps extends DialogProps {}
6025

6126
@injectable()
62-
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
63-
@inject(IDEUpdaterDialogWidget)
64-
private readonly widget: IDEUpdaterDialogWidget;
65-
27+
export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
6628
@inject(IDEUpdater)
6729
private readonly updater: IDEUpdater;
6830

@@ -75,6 +37,9 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
7537
@inject(WindowService)
7638
private readonly windowService: WindowService;
7739

40+
private _updateInfo: UpdateInfo | undefined;
41+
private _updateProgress: UpdateProgress = {};
42+
7843
constructor(
7944
@inject(IDEUpdaterDialogProps)
8045
protected override readonly props: IDEUpdaterDialogProps
@@ -94,26 +59,34 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
9459
protected init(): void {
9560
this.updaterClient.onUpdaterDidFail((error) => {
9661
this.appendErrorButtons();
97-
this.widget.mergeUpdateProgress({ error });
62+
this.mergeUpdateProgress({ error });
9863
});
9964
this.updaterClient.onDownloadProgressDidChange((progressInfo) => {
100-
this.widget.mergeUpdateProgress({ progressInfo });
65+
this.mergeUpdateProgress({ progressInfo });
10166
});
10267
this.updaterClient.onDownloadDidFinish(() => {
10368
this.appendInstallButtons();
104-
this.widget.mergeUpdateProgress({ downloadFinished: true });
69+
this.mergeUpdateProgress({ downloadFinished: true });
10570
});
10671
}
10772

108-
get value(): UpdateInfo {
109-
return this.widget.updateInfo;
73+
protected render(): React.ReactNode {
74+
return (
75+
this.updateInfo && (
76+
<IDEUpdaterComponent
77+
updateInfo={this.updateInfo}
78+
updateProgress={this.updateProgress}
79+
/>
80+
)
81+
);
82+
}
83+
84+
get value(): UpdateInfo | undefined {
85+
return this.updateInfo;
11086
}
11187

11288
protected override onAfterAttach(msg: Message): void {
113-
if (this.widget.isAttached) {
114-
Widget.detach(this.widget);
115-
}
116-
Widget.attach(this.widget, this.contentNode);
89+
this.update();
11790
this.appendInitialButtons();
11891
super.onAfterAttach(msg);
11992
}
@@ -196,15 +169,19 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
196169
}
197170

198171
private skipVersion(): void {
172+
if (!this.updateInfo) {
173+
console.warn(`Nothing to skip. No update info is available`);
174+
return;
175+
}
199176
this.localStorageService.setData<string>(
200177
SKIP_IDE_VERSION,
201-
this.widget.updateInfo.version
178+
this.updateInfo.version
202179
);
203180
this.close();
204181
}
205182

206183
private startDownload(): void {
207-
this.widget.mergeUpdateProgress({
184+
this.mergeUpdateProgress({
208185
downloadStarted: true,
209186
});
210187
this.clearButtons();
@@ -216,31 +193,48 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
216193
this.close();
217194
}
218195

196+
private set updateInfo(updateInfo: UpdateInfo | undefined) {
197+
this._updateInfo = updateInfo;
198+
this.update();
199+
}
200+
201+
private get updateInfo(): UpdateInfo | undefined {
202+
return this._updateInfo;
203+
}
204+
205+
private get updateProgress(): UpdateProgress {
206+
return this._updateProgress;
207+
}
208+
209+
private mergeUpdateProgress(updateProgress: UpdateProgress): void {
210+
this._updateProgress = { ...this._updateProgress, ...updateProgress };
211+
this.update();
212+
}
213+
219214
override async open(
220215
data: UpdateInfo | undefined = undefined
221216
): Promise<UpdateInfo | undefined> {
222217
if (data && data.version) {
223-
this.widget.mergeUpdateProgress({
218+
this.mergeUpdateProgress({
224219
progressInfo: undefined,
225220
downloadStarted: false,
226221
downloadFinished: false,
227222
error: undefined,
228223
});
229-
this.widget.setUpdateInfo(data);
224+
this.updateInfo = data;
230225
return super.open();
231226
}
232227
}
233228

234229
protected override onActivateRequest(msg: Message): void {
235230
super.onActivateRequest(msg);
236-
this.widget.activate();
231+
this.update();
237232
}
238233

239234
override close(): void {
240-
this.widget.dispose();
241235
if (
242-
this.widget.updateProgress?.downloadStarted &&
243-
!this.widget.updateProgress?.downloadFinished
236+
this.updateProgress?.downloadStarted &&
237+
!this.updateProgress?.downloadFinished
244238
) {
245239
this.updater.stopDownload();
246240
}

Diff for: arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
AbstractDialog as TheiaAbstractDialog,
3+
DialogProps,
4+
} from '@theia/core/lib/browser/dialogs';
5+
import { ReactDialog as TheiaReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog';
6+
import { codiconArray, Message } from '@theia/core/lib/browser/widgets/widget';
7+
import {
8+
Disposable,
9+
DisposableCollection,
10+
} from '@theia/core/lib/common/disposable';
11+
import { inject, injectable } from '@theia/core/shared/inversify';
12+
import * as React from '@theia/core/shared/react';
13+
import { createRoot } from '@theia/core/shared/react-dom/client';
14+
15+
@injectable()
16+
export abstract class AbstractDialog<T> extends TheiaAbstractDialog<T> {
17+
constructor(
18+
@inject(DialogProps) protected override readonly props: DialogProps
19+
) {
20+
super(props);
21+
22+
this.closeCrossNode.classList.remove(...codiconArray('close'));
23+
this.closeCrossNode.classList.add('fa', 'fa-close');
24+
}
25+
}
26+
27+
@injectable()
28+
export abstract class ReactDialog<T> extends TheiaReactDialog<T> {
29+
protected override onUpdateRequest(msg: Message): void {
30+
// This is tricky to bypass the default Theia code.
31+
// Otherwise, there is a warning when opening the dialog for the second time.
32+
// You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.
33+
const disposables = new DisposableCollection();
34+
if (!this.isMounted) {
35+
// toggle the `isMounted` logic for the time being of the super call so that the `createRoot` does not run
36+
this.isMounted = true;
37+
disposables.push(Disposable.create(() => (this.isMounted = false)));
38+
}
39+
40+
// Always unset the `contentNodeRoot` so there is no double update when calling super.
41+
const restoreContentNodeRoot = this.contentNodeRoot;
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
(this.contentNodeRoot as any) = undefined;
44+
disposables.push(
45+
Disposable.create(() => (this.contentNodeRoot = restoreContentNodeRoot))
46+
);
47+
48+
try {
49+
super.onUpdateRequest(msg);
50+
} finally {
51+
disposables.dispose();
52+
}
53+
54+
// Use the patched rendering.
55+
if (!this.isMounted) {
56+
this.contentNodeRoot = createRoot(this.contentNode);
57+
// Resetting the prop is missing from the Theia code.
58+
// https://github.com/eclipse-theia/theia/blob/v1.31.1/packages/core/src/browser/dialogs/react-dialog.tsx#L41-L47
59+
this.isMounted = true;
60+
}
61+
this.contentNodeRoot?.render(<>{this.render()}</>);
62+
}
63+
}

0 commit comments

Comments
 (0)