Skip to content

Commit dc2ce59

Browse files
committed
Implement dialog to input board user fields
1 parent af3b268 commit dc2ce59

File tree

5 files changed

+289
-38
lines changed

5 files changed

+289
-38
lines changed

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+7
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ import {
253253
UploadCertificateDialogProps,
254254
UploadCertificateDialogWidget,
255255
} from './dialogs/certificate-uploader/certificate-uploader-dialog';
256+
import { UserFieldsDialog, UserFieldsDialogProps, UserFieldsDialogWidget } from './dialogs/user-fields/user-fields-dialog';
256257
import { nls } from '@theia/core/lib/browser/nls';
257258

258259
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -736,4 +737,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
736737
bind(UploadCertificateDialogProps).toConstantValue({
737738
title: 'UploadCertificate',
738739
});
740+
741+
bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
742+
bind(UserFieldsDialog).toSelf().inSingletonScope();
743+
bind(UserFieldsDialogProps).toConstantValue({
744+
title: 'UserFields',
745+
});
739746
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import * as React from 'react';
2+
import { BoardUserField } from '../../../common/protocol';
3+
import { nls } from '@theia/core/lib/browser/nls';
4+
5+
export const UserFieldsComponent = ({
6+
initialBoardUserFields,
7+
updateUserFields,
8+
cancel,
9+
accept,
10+
}: {
11+
initialBoardUserFields: BoardUserField[];
12+
updateUserFields: (userFields: BoardUserField[]) => void;
13+
cancel: () => void;
14+
accept: () => Promise<void>;
15+
}): React.ReactElement => {
16+
const [boardUserFields, setBoardUserFields] = React.useState<
17+
BoardUserField[]
18+
>(initialBoardUserFields);
19+
20+
const [uploadButtonDisabled, setUploadButtonDisabled] =
21+
React.useState<boolean>(true);
22+
23+
React.useEffect(() => {
24+
setBoardUserFields(initialBoardUserFields);
25+
}, [initialBoardUserFields]);
26+
27+
const updateUserField =
28+
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
29+
let newBoardUserFields = [...boardUserFields];
30+
newBoardUserFields[index].value = e.target.value;
31+
setBoardUserFields(newBoardUserFields);
32+
};
33+
34+
const allFieldsHaveValues = (userFields: BoardUserField[]): boolean => {
35+
return userFields
36+
.map<boolean>((field: BoardUserField): boolean => {
37+
return field.value.length > 0;
38+
})
39+
.reduce((previous: boolean, current: boolean): boolean => {
40+
return previous && current;
41+
});
42+
};
43+
44+
React.useEffect(() => {
45+
updateUserFields(boardUserFields);
46+
setUploadButtonDisabled(!allFieldsHaveValues(boardUserFields));
47+
}, [boardUserFields]);
48+
49+
return (
50+
<div>
51+
{boardUserFields.map((field, index) => {
52+
return (
53+
<div className="dialogSection" key={index}>
54+
<div className="dialogRow">
55+
<label className="field-label">{field.label}</label>
56+
</div>
57+
<div className="dialogRow">
58+
<input
59+
type={field.secret ? 'password' : 'text'}
60+
value={field.value}
61+
className="theia-input"
62+
placeholder={'Enter ' + field.label}
63+
onChange={updateUserField(index)}
64+
/>
65+
</div>
66+
</div>
67+
);
68+
})}
69+
<div className="dialogSection">
70+
<div className="dialogRow">
71+
<button
72+
type="button"
73+
className="theia-button secondary install-cert-btn"
74+
onClick={cancel}
75+
>
76+
{nls.localize('arduino/userFields/cancel', 'Cancel')}
77+
</button>
78+
<button
79+
type="button"
80+
className="theia-button primary install-cert-btn"
81+
disabled={uploadButtonDisabled}
82+
onClick={accept}
83+
>
84+
{nls.localize('arduino/userFields/upload', 'Upload')}
85+
</button>
86+
</div>
87+
</div>
88+
</div>
89+
);
90+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as React from 'react';
2+
import { inject, injectable } from 'inversify';
3+
import {
4+
AbstractDialog,
5+
DialogProps,
6+
ReactWidget,
7+
} from '@theia/core/lib/browser';
8+
import { Widget } from '@phosphor/widgets';
9+
import { Message } from '@phosphor/messaging';
10+
import { UploadSketch } from '../../contributions/upload-sketch';
11+
import { UserFieldsComponent } from './user-fields-component';
12+
import { BoardUserField } from '../../../common/protocol';
13+
14+
@injectable()
15+
export class UserFieldsDialogWidget extends ReactWidget {
16+
protected _currentUserFields: BoardUserField[] = [];
17+
18+
constructor(private cancel: () => void, private accept: () => Promise<void>) {
19+
super();
20+
}
21+
22+
set currentUserFields(userFields: BoardUserField[]) {
23+
this.setUserFields(userFields);
24+
}
25+
26+
get currentUserFields(): BoardUserField[] {
27+
return this._currentUserFields;
28+
}
29+
30+
resetUserFieldsValue(): void {
31+
this._currentUserFields = this._currentUserFields.map((field) => {
32+
field.value = '';
33+
return field;
34+
});
35+
}
36+
37+
protected setUserFields(userFields: BoardUserField[]): void {
38+
this._currentUserFields = userFields;
39+
}
40+
41+
protected render(): React.ReactNode {
42+
return (
43+
<form>
44+
<UserFieldsComponent
45+
initialBoardUserFields={this._currentUserFields}
46+
updateUserFields={this.setUserFields.bind(this)}
47+
cancel={this.cancel}
48+
accept={this.accept}
49+
/>
50+
</form>
51+
);
52+
}
53+
}
54+
55+
@injectable()
56+
export class UserFieldsDialogProps extends DialogProps {}
57+
58+
@injectable()
59+
export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
60+
protected readonly widget: UserFieldsDialogWidget;
61+
62+
constructor(
63+
@inject(UserFieldsDialogProps)
64+
protected readonly props: UserFieldsDialogProps
65+
) {
66+
super({
67+
title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '',
68+
});
69+
this.titleNode.classList.add('user-fields-dialog-title');
70+
this.contentNode.classList.add('user-fields-dialog-content');
71+
this.acceptButton = undefined;
72+
this.widget = new UserFieldsDialogWidget(
73+
this.close.bind(this),
74+
this.accept.bind(this)
75+
);
76+
}
77+
78+
set value(userFields: BoardUserField[]) {
79+
this.widget.currentUserFields = userFields;
80+
}
81+
82+
get value(): BoardUserField[] {
83+
return this.widget.currentUserFields;
84+
}
85+
86+
protected onAfterAttach(msg: Message): void {
87+
if (this.widget.isAttached) {
88+
Widget.detach(this.widget);
89+
}
90+
Widget.attach(this.widget, this.contentNode);
91+
super.onAfterAttach(msg);
92+
this.update();
93+
}
94+
95+
protected onUpdateRequest(msg: Message): void {
96+
super.onUpdateRequest(msg);
97+
this.widget.update();
98+
}
99+
100+
protected onActivateRequest(msg: Message): void {
101+
super.onActivateRequest(msg);
102+
this.widget.activate();
103+
}
104+
105+
protected async accept(): Promise<void> {
106+
// If the user presses enter and at least
107+
// a field is empty don't accept the input
108+
for (const field of this.value) {
109+
if (field.value.length === 0) {
110+
return;
111+
}
112+
}
113+
return super.accept();
114+
}
115+
116+
close(): void {
117+
this.widget.resetUserFieldsValue();
118+
this.widget.close();
119+
super.close();
120+
}
121+
}

arduino-ide-extension/src/browser/style/index.css

+43-38
Original file line numberDiff line numberDiff line change
@@ -10,94 +10,99 @@
1010
@import './settings-dialog.css';
1111
@import './firmware-uploader-dialog.css';
1212
@import './certificate-uploader-dialog.css';
13+
@import './user-fields-dialog.css';
1314
@import './debug.css';
1415
@import './sketchbook.css';
1516
@import './cloud-sketchbook.css';
1617
@import './fonts.css';
1718
@import './custom-codicon.css';
1819

1920
.theia-input.warning:focus {
20-
outline-width: 1px;
21-
outline-style: solid;
22-
outline-offset: -1px;
23-
opacity: 1 !important;
24-
color: var(--theia-warningForeground);
25-
background-color: var(--theia-warningBackground);
21+
outline-width: 1px;
22+
outline-style: solid;
23+
outline-offset: -1px;
24+
opacity: 1 !important;
25+
color: var(--theia-warningForeground);
26+
background-color: var(--theia-warningBackground);
2627
}
2728

2829
.theia-input.warning {
29-
background-color: var(--theia-warningBackground);
30+
background-color: var(--theia-warningBackground);
3031
}
3132

32-
.theia-input.warning::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
33-
color: var(--theia-warningForeground);
34-
background-color: var(--theia-warningBackground);
35-
opacity: 1; /* Firefox */
33+
.theia-input.warning::placeholder {
34+
/* Chrome, Firefox, Opera, Safari 10.1+ */
35+
color: var(--theia-warningForeground);
36+
background-color: var(--theia-warningBackground);
37+
opacity: 1; /* Firefox */
3638
}
3739

38-
.theia-input.warning:-ms-input-placeholder { /* Internet Explorer 10-11 */
39-
color: var(--theia-warningForeground);
40-
background-color: var(--theia-warningBackground);
40+
.theia-input.warning:-ms-input-placeholder {
41+
/* Internet Explorer 10-11 */
42+
color: var(--theia-warningForeground);
43+
background-color: var(--theia-warningBackground);
4144
}
4245

43-
.theia-input.warning::-ms-input-placeholder { /* Microsoft Edge */
44-
color: var(--theia-warningForeground);
45-
background-color: var(--theia-warningBackground);
46+
.theia-input.warning::-ms-input-placeholder {
47+
/* Microsoft Edge */
48+
color: var(--theia-warningForeground);
49+
background-color: var(--theia-warningBackground);
4650
}
4751

48-
/* Makes the sidepanel a bit wider when opening the widget */
52+
/* Makes the sidepanel a bit wider when opening the widget */
4953
.p-DockPanel-widget {
50-
min-width: 200px;
51-
min-height: 200px;
54+
min-width: 200px;
55+
min-height: 200px;
5256
}
5357

5458
/* Overrule the default Theia CSS button styles. */
5559
button.theia-button,
5660
.theia-button {
57-
border: 1px solid var(--theia-dropdown-border);
61+
border: 1px solid var(--theia-dropdown-border);
5862
}
5963

6064
button.theia-button:hover,
6165
.theia-button:hover {
62-
border: 1px solid var(--theia-focusBorder);
66+
border: 1px solid var(--theia-focusBorder);
6367
}
6468

6569
button.theia-button {
66-
height: 31px;
70+
height: 31px;
6771
}
6872

6973
button.theia-button.secondary {
70-
background-color: var(--theia-secondaryButton-background);
71-
color: var(--theia-secondaryButton-foreground);
74+
background-color: var(--theia-secondaryButton-background);
75+
color: var(--theia-secondaryButton-foreground);
7276
}
7377

7478
button.theia-button.main {
75-
color: var(--theia-button-foreground);
79+
color: var(--theia-button-foreground);
7680
}
7781

7882
/* To make the progress-bar slightly thicker, and use the color from the status bar */
7983
.theia-progress-bar-container {
80-
width: 100%;
81-
height: 4px;
84+
width: 100%;
85+
height: 4px;
8286
}
8387

8488
.theia-progress-bar {
85-
height: 4px;
86-
width: 3%;
87-
animation: progress-animation 1.3s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
89+
height: 4px;
90+
width: 3%;
91+
animation: progress-animation 1.3s 0s infinite
92+
cubic-bezier(0.645, 0.045, 0.355, 1);
8893
}
8994

9095
.theia-notification-item-progressbar {
91-
height: 4px;
92-
width: 66%;
96+
height: 4px;
97+
width: 66%;
9398
}
9499

95100
.flex-line {
96-
display: flex;
97-
align-items: center;
98-
white-space: nowrap;
101+
display: flex;
102+
align-items: center;
103+
white-space: nowrap;
99104
}
100105

101106
.fa-reload {
102-
font-size: 14px;
103-
}
107+
font-size: 14px;
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.user-fields-dialog-title {
2+
font-family: Open Sans;
3+
font-size: 16px;
4+
font-style: normal;
5+
font-weight: 700;
6+
line-height: 27px;
7+
letter-spacing: 0.01em;
8+
text-align: left;
9+
}
10+
11+
.user-fields-dialog-content {
12+
width: 408px;
13+
}
14+
15+
.user-fields-dialog-content .field-label {
16+
color: #2c353a;
17+
font-family: Open Sans;
18+
font-size: 14px;
19+
font-style: normal;
20+
font-weight: 400;
21+
line-height: 21px;
22+
letter-spacing: 0.01em;
23+
text-align: left;
24+
}
25+
26+
.user-fields-dialog-content .theia-input {
27+
flex-grow: 1;
28+
}

0 commit comments

Comments
 (0)