Skip to content

Commit 7b3be27

Browse files
committed
Implement dialog to input board user fields
1 parent 4805297 commit 7b3be27

File tree

5 files changed

+288
-38
lines changed

5 files changed

+288
-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

257258
const ElementQueries = require('css-element-queries/src/ElementQueries');
258259

@@ -735,4 +736,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
735736
bind(UploadCertificateDialogProps).toConstantValue({
736737
title: 'UploadCertificate',
737738
});
739+
740+
bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
741+
bind(UserFieldsDialog).toSelf().inSingletonScope();
742+
bind(UserFieldsDialogProps).toConstantValue({
743+
title: 'UserFields',
744+
});
738745
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as React from 'react';
2+
import { BoardUserField } from '../../../common/protocol';
3+
4+
export const UserFieldsComponent = ({
5+
initialBoardUserFields,
6+
updateUserFields,
7+
cancel,
8+
accept,
9+
}: {
10+
initialBoardUserFields: BoardUserField[];
11+
updateUserFields: (userFields: BoardUserField[]) => void;
12+
cancel: () => void;
13+
accept: () => Promise<void>;
14+
}): React.ReactElement => {
15+
const [boardUserFields, setBoardUserFields] = React.useState<
16+
BoardUserField[]
17+
>(initialBoardUserFields);
18+
19+
const [uploadButtonDisabled, setUploadButtonDisabled] =
20+
React.useState<boolean>(true);
21+
22+
React.useEffect(() => {
23+
setBoardUserFields(initialBoardUserFields);
24+
}, [initialBoardUserFields]);
25+
26+
const updateUserField =
27+
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
28+
let newBoardUserFields = [...boardUserFields];
29+
newBoardUserFields[index].value = e.target.value;
30+
setBoardUserFields(newBoardUserFields);
31+
};
32+
33+
const allFieldsHaveValues = (userFields: BoardUserField[]): boolean => {
34+
return userFields
35+
.map<boolean>((field: BoardUserField): boolean => {
36+
return field.value.length > 0;
37+
})
38+
.reduce((previous: boolean, current: boolean): boolean => {
39+
return previous && current;
40+
});
41+
};
42+
43+
React.useEffect(() => {
44+
updateUserFields(boardUserFields);
45+
setUploadButtonDisabled(!allFieldsHaveValues(boardUserFields));
46+
}, [boardUserFields]);
47+
48+
return (
49+
<div>
50+
{boardUserFields.map((field, index) => {
51+
return (
52+
<div className="dialogSection" key={index}>
53+
<div className="dialogRow">
54+
<label className="field-label">{field.label}</label>
55+
</div>
56+
<div className="dialogRow">
57+
<input
58+
type={field.secret ? 'password' : 'text'}
59+
value={field.value}
60+
className="theia-input"
61+
placeholder={'Enter ' + field.label}
62+
onChange={updateUserField(index)}
63+
/>
64+
</div>
65+
</div>
66+
);
67+
})}
68+
<div className="dialogSection">
69+
<div className="dialogRow">
70+
<button
71+
type="button"
72+
className="theia-button secondary install-cert-btn"
73+
onClick={cancel}
74+
>
75+
Cancel
76+
</button>
77+
<button
78+
type="button"
79+
className="theia-button primary install-cert-btn"
80+
disabled={uploadButtonDisabled}
81+
onClick={accept}
82+
>
83+
Upload
84+
</button>
85+
</div>
86+
</div>
87+
</div>
88+
);
89+
};
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)