Skip to content

Commit 95cbce3

Browse files
author
Akos Kitta
committed
feat: new UX for the boards/library manager widget
Closes #19 Signed-off-by: Akos Kitta <[email protected]>
1 parent 7721350 commit 95cbce3

File tree

15 files changed

+451
-341
lines changed

15 files changed

+451
-341
lines changed

Diff for: arduino-ide-extension/package.json

-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
"@types/p-queue": "^2.3.1",
5959
"@types/ps-tree": "^1.1.0",
6060
"@types/react-tabs": "^2.3.2",
61-
"@types/react-virtualized": "^9.21.21",
6261
"@types/temp": "^0.8.34",
6362
"@types/which": "^1.3.1",
6463
"@vscode/debugprotocol": "^1.51.0",
@@ -95,7 +94,6 @@
9594
"react-perfect-scrollbar": "^1.5.8",
9695
"react-select": "^5.6.0",
9796
"react-tabs": "^3.1.2",
98-
"react-virtualized": "^9.22.3",
9997
"react-window": "^1.8.6",
10098
"semver": "^7.3.2",
10199
"string-natural-compare": "^2.0.3",

Diff for: arduino-ide-extension/src/browser/boards/boards-auto-installer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
174174
// CLI returns the packages already sorted with the deprecated ones at the end of the list
175175
// in order to ensure the new ones are preferred
176176
const candidates = packagesForBoard.filter(
177-
({ installable, installedVersion }) => installable && !installedVersion
177+
({ installedVersion }) => !installedVersion
178178
);
179179

180180
return candidates[0];

Diff for: arduino-ide-extension/src/browser/style/index.css

-4
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,6 @@ button.theia-button.message-box-dialog-button {
154154
font-size: 14px;
155155
}
156156

157-
.uppercase {
158-
text-transform: uppercase;
159-
}
160-
161157
/* High Contrast Theme rules */
162158
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
163159
.hc-black.hc-theia.theia-hc button.theia-button:hover,

Diff for: arduino-ide-extension/src/browser/style/list-widget.css

+15-11
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,8 @@
117117
.component-list-item .header .installed:hover:before {
118118
background-color: var(--theia-button-foreground);
119119
color: var(--theia-button-background);
120-
content: attr(uninstall);
121-
}
122-
123-
.component-list-item[min-width~="170px"] .footer {
124-
padding: 5px 5px 0px 0px;
125-
min-height: 35px;
126-
display: flex;
127-
flex-direction: row-reverse;
120+
content: attr(remove);
121+
text-transform: uppercase;
128122
}
129123

130124
.component-list-item .footer {
@@ -133,13 +127,11 @@
133127

134128
.component-list-item .footer > * {
135129
display: inline-block;
136-
margin: 5px 0px 0px 10px;
137130
}
138131

139132
.component-list-item:hover .footer > label {
140133
display: inline-block;
141134
align-self: center;
142-
margin: 5px 0px 0px 10px;
143135
}
144136

145137
.component-list-item .info a {
@@ -151,11 +143,23 @@
151143
text-decoration: underline;
152144
}
153145

146+
.component-list-item .theia-button.secondary.no-border {
147+
border: 2px solid var(--theia-button-foreground)
148+
}
149+
150+
.component-list-item .theia-button.secondary.no-border:hover {
151+
border: 2px solid var(--theia-secondaryButton-foreground)
152+
}
153+
154+
.component-list-item .theia-button {
155+
margin-left: 5px;
156+
}
157+
154158
/* High Contrast Theme rules */
155159
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
156160
.hc-black.hc-theia.theia-hc .component-list-item .header .installed:hover:before {
157161
background-color: transparent;
158-
outline: 1px dashed var(--theia-focusBorder);
162+
outline: 1px dashed var(--theia-focusBorder);
159163
}
160164

161165
.hc-black.hc-theia.theia-hc .component-list-item .header .installed:before {
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import * as React from '@theia/core/shared/react';
2+
import type { ArduinoComponent } from '../../../common/protocol/arduino-component';
23
import { Installable } from '../../../common/protocol/installable';
3-
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
4-
import { ListItemRenderer } from './list-item-renderer';
4+
import type { ListItemRenderer } from './list-item-renderer';
55

66
export class ComponentListItem<
77
T extends ArduinoComponent
88
> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
99
constructor(props: ComponentListItem.Props<T>) {
1010
super(props);
11-
if (props.item.installable) {
12-
const version = props.item.availableVersions.filter(
13-
(version) => version !== props.item.installedVersion
14-
)[0];
15-
this.state = {
16-
selectedVersion: version,
17-
};
18-
}
11+
this.state = {
12+
selectedVersion: Installable.latest(props.item.availableVersions),
13+
};
1914
}
2015

2116
override render(): React.ReactNode {
@@ -33,24 +28,25 @@ export class ComponentListItem<
3328
}
3429

3530
private async install(item: T): Promise<void> {
36-
const toInstall = this.state.selectedVersion;
37-
const version = this.props.item.availableVersions.filter(
38-
(version) => version !== this.state.selectedVersion
39-
)[0];
40-
this.setState({
41-
selectedVersion: version,
42-
});
43-
try {
44-
await this.props.install(item, toInstall);
45-
} catch {
46-
this.setState({
47-
selectedVersion: toInstall,
48-
});
49-
}
31+
await this.withState('installing', () =>
32+
this.props.install(item, this.state.selectedVersion)
33+
);
5034
}
5135

5236
private async uninstall(item: T): Promise<void> {
53-
await this.props.uninstall(item);
37+
await this.withState('uninstalling', () => this.props.uninstall(item));
38+
}
39+
40+
private async withState(
41+
state: 'installing' | 'uninstalling',
42+
task: () => Promise<unknown>
43+
): Promise<void> {
44+
this.setState({ state });
45+
try {
46+
await task();
47+
} finally {
48+
this.setState({ state: undefined });
49+
}
5450
}
5551

5652
private onVersionChange(version: Installable.Version): void {
@@ -68,5 +64,6 @@ export namespace ComponentListItem {
6864

6965
export interface State {
7066
selectedVersion?: Installable.Version;
67+
state?: 'installing' | 'uninstalling' | undefined;
7168
}
7269
}

Diff for: arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx

+13-131
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,30 @@
1-
import 'react-virtualized/styles.css';
21
import * as React from '@theia/core/shared/react';
3-
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
4-
import {
5-
CellMeasurer,
6-
CellMeasurerCache,
7-
} from 'react-virtualized/dist/commonjs/CellMeasurer';
8-
import type {
9-
ListRowProps,
10-
ListRowRenderer,
11-
} from 'react-virtualized/dist/commonjs/List';
12-
import List from 'react-virtualized/dist/commonjs/List';
2+
import { Virtuoso } from '@theia/core/shared/react-virtuoso';
133
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
144
import { Installable } from '../../../common/protocol/installable';
155
import { ComponentListItem } from './component-list-item';
166
import { ListItemRenderer } from './list-item-renderer';
177

18-
function sameAs<T>(
19-
left: T[],
20-
right: T[],
21-
...compareProps: (keyof T)[]
22-
): boolean {
23-
if (left === right) {
24-
return true;
25-
}
26-
const leftLength = left.length;
27-
if (leftLength !== right.length) {
28-
return false;
29-
}
30-
for (let i = 0; i < leftLength; i++) {
31-
for (const prop of compareProps) {
32-
const leftValue = left[i][prop];
33-
const rightValue = right[i][prop];
34-
if (leftValue !== rightValue) {
35-
return false;
36-
}
37-
}
38-
}
39-
return true;
40-
}
41-
428
export class ComponentList<T extends ArduinoComponent> extends React.Component<
439
ComponentList.Props<T>
4410
> {
45-
private readonly cache: CellMeasurerCache;
46-
private resizeAllFlag: boolean;
47-
private list: List | undefined;
48-
private mostRecentWidth: number | undefined;
49-
50-
constructor(props: ComponentList.Props<T>) {
51-
super(props);
52-
this.cache = new CellMeasurerCache({
53-
defaultHeight: 140,
54-
fixedWidth: true,
55-
});
56-
}
57-
5811
override render(): React.ReactNode {
5912
return (
60-
<AutoSizer>
61-
{({ width, height }) => {
62-
if (this.mostRecentWidth && this.mostRecentWidth !== width) {
63-
this.resizeAllFlag = true;
64-
setTimeout(() => this.clearAll(), 0);
65-
}
66-
this.mostRecentWidth = width;
67-
return (
68-
<List
69-
className={'items-container'}
70-
rowRenderer={this.createItem}
71-
height={height}
72-
width={width}
73-
rowCount={this.props.items.length}
74-
rowHeight={this.cache.rowHeight}
75-
deferredMeasurementCache={this.cache}
76-
ref={this.setListRef}
77-
estimatedRowSize={140}
78-
// If default value, then `react-virtualized` will optimize and list item will not receive a `:hover` event.
79-
// Hence, install and version `<select>` won't be visible even if the mouse cursor is over the `<div>`.
80-
// See https://github.com/bvaughn/react-virtualized/blob/005be24a608add0344284053dae7633be86053b2/source/Grid/Grid.js#L38-L42
81-
scrollingResetTimeInterval={0}
82-
/>
83-
);
84-
}}
85-
</AutoSizer>
86-
);
87-
}
88-
89-
override componentDidUpdate(prevProps: ComponentList.Props<T>): void {
90-
if (
91-
this.resizeAllFlag ||
92-
!sameAs(this.props.items, prevProps.items, 'name', 'installedVersion')
93-
) {
94-
this.clearAll(true);
95-
}
96-
}
97-
98-
private readonly setListRef = (ref: List | null): void => {
99-
this.list = ref || undefined;
100-
};
101-
102-
private clearAll(scrollToTop = false): void {
103-
this.resizeAllFlag = false;
104-
this.cache.clearAll();
105-
if (this.list) {
106-
this.list.recomputeRowHeights();
107-
if (scrollToTop) {
108-
this.list.scrollToPosition(0);
109-
}
110-
}
111-
}
112-
113-
private readonly createItem: ListRowRenderer = ({
114-
index,
115-
parent,
116-
key,
117-
style,
118-
}: ListRowProps): React.ReactNode => {
119-
const item = this.props.items[index];
120-
return (
121-
<CellMeasurer
122-
cache={this.cache}
123-
columnIndex={0}
124-
key={key}
125-
rowIndex={index}
126-
parent={parent}
127-
>
128-
{({ registerChild }) => (
129-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
130-
// @ts-ignore
131-
<div ref={registerChild} style={style}>
132-
<ComponentListItem<T>
133-
key={this.props.itemLabel(item)}
134-
item={item}
135-
itemRenderer={this.props.itemRenderer}
136-
install={this.props.install}
137-
uninstall={this.props.uninstall}
138-
/>
139-
</div>
13+
<Virtuoso
14+
data={this.props.items}
15+
itemContent={(_: number, item: T) => (
16+
<ComponentListItem<T>
17+
key={this.props.itemLabel(item)}
18+
item={item}
19+
itemRenderer={this.props.itemRenderer}
20+
install={this.props.install}
21+
uninstall={this.props.uninstall}
22+
/>
14023
)}
141-
</CellMeasurer>
24+
/>
14225
);
143-
};
26+
}
14427
}
145-
14628
export namespace ComponentList {
14729
export interface Props<T extends ArduinoComponent> {
14830
readonly items: T[];

0 commit comments

Comments
 (0)