mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-13 20:29:27 +00:00
Add dialog to insert user fields for board that require them to upload (#550)
* Rebuild gRPC protocol interfaces * Implement methods to get user fields for board/port combination * Implement dialog to input board user fields * Add configure and upload step when uploading to board requiring user fields * Disable Sketch > Configure and Upload menu if board doesn't support user fields * Fix serial upload not working with all boards * Update i18n source file * fix user fields UI * regenerate cli protocol * fix localisation * check if user fields are empty Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import { BoardUserField } from '../../../common/protocol';
|
||||
import { nls } from '@theia/core/lib/browser/nls';
|
||||
|
||||
export const UserFieldsComponent = ({
|
||||
initialBoardUserFields,
|
||||
updateUserFields,
|
||||
cancel,
|
||||
accept,
|
||||
}: {
|
||||
initialBoardUserFields: BoardUserField[];
|
||||
updateUserFields: (userFields: BoardUserField[]) => void;
|
||||
cancel: () => void;
|
||||
accept: () => Promise<void>;
|
||||
}): React.ReactElement => {
|
||||
const [boardUserFields, setBoardUserFields] = React.useState<
|
||||
BoardUserField[]
|
||||
>(initialBoardUserFields);
|
||||
|
||||
const [uploadButtonDisabled, setUploadButtonDisabled] =
|
||||
React.useState<boolean>(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
setBoardUserFields(initialBoardUserFields);
|
||||
}, [initialBoardUserFields]);
|
||||
|
||||
const updateUserField =
|
||||
(index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newBoardUserFields = [...boardUserFields];
|
||||
newBoardUserFields[index].value = e.target.value;
|
||||
setBoardUserFields(newBoardUserFields);
|
||||
};
|
||||
|
||||
const allFieldsHaveValues = (userFields: BoardUserField[]): boolean => {
|
||||
return (
|
||||
userFields &&
|
||||
userFields.length > 0 &&
|
||||
userFields
|
||||
.map<boolean>((field: BoardUserField): boolean => {
|
||||
return field.value.length > 0;
|
||||
})
|
||||
.reduce((previous: boolean, current: boolean): boolean => {
|
||||
return previous && current;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
updateUserFields(boardUserFields);
|
||||
setUploadButtonDisabled(!allFieldsHaveValues(boardUserFields));
|
||||
}, [boardUserFields]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="user-fields-container">
|
||||
<div className="user-fields-list">
|
||||
{boardUserFields.map((field, index) => {
|
||||
return (
|
||||
<div className="dialogSection" key={index}>
|
||||
<div className="dialogRow">
|
||||
<label className="field-label">{field.label}</label>
|
||||
</div>
|
||||
<div className="dialogRow">
|
||||
<input
|
||||
type={field.secret ? 'password' : 'text'}
|
||||
value={field.value}
|
||||
className="theia-input"
|
||||
placeholder={'Enter ' + field.label}
|
||||
onChange={updateUserField(index)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="dialogSection">
|
||||
<div className="dialogRow button-container">
|
||||
<button
|
||||
type="button"
|
||||
className="theia-button secondary install-cert-btn"
|
||||
onClick={cancel}
|
||||
>
|
||||
{nls.localize('arduino/userFields/cancel', 'Cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="theia-button primary install-cert-btn"
|
||||
disabled={uploadButtonDisabled}
|
||||
onClick={accept}
|
||||
>
|
||||
{nls.localize('arduino/userFields/upload', 'Upload')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
import * as React from 'react';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
AbstractDialog,
|
||||
DialogProps,
|
||||
ReactWidget,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { Widget } from '@phosphor/widgets';
|
||||
import { Message } from '@phosphor/messaging';
|
||||
import { UploadSketch } from '../../contributions/upload-sketch';
|
||||
import { UserFieldsComponent } from './user-fields-component';
|
||||
import { BoardUserField } from '../../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class UserFieldsDialogWidget extends ReactWidget {
|
||||
protected _currentUserFields: BoardUserField[] = [];
|
||||
|
||||
constructor(private cancel: () => void, private accept: () => Promise<void>) {
|
||||
super();
|
||||
}
|
||||
|
||||
set currentUserFields(userFields: BoardUserField[]) {
|
||||
this.setUserFields(userFields);
|
||||
}
|
||||
|
||||
get currentUserFields(): BoardUserField[] {
|
||||
return this._currentUserFields;
|
||||
}
|
||||
|
||||
resetUserFieldsValue(): void {
|
||||
this._currentUserFields = this._currentUserFields.map((field) => {
|
||||
field.value = '';
|
||||
return field;
|
||||
});
|
||||
}
|
||||
|
||||
protected setUserFields(userFields: BoardUserField[]): void {
|
||||
this._currentUserFields = userFields;
|
||||
}
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return (
|
||||
<form>
|
||||
<UserFieldsComponent
|
||||
initialBoardUserFields={this._currentUserFields}
|
||||
updateUserFields={this.setUserFields.bind(this)}
|
||||
cancel={this.cancel}
|
||||
accept={this.accept}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class UserFieldsDialogProps extends DialogProps {}
|
||||
|
||||
@injectable()
|
||||
export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
|
||||
protected readonly widget: UserFieldsDialogWidget;
|
||||
|
||||
constructor(
|
||||
@inject(UserFieldsDialogProps)
|
||||
protected readonly props: UserFieldsDialogProps
|
||||
) {
|
||||
super({
|
||||
title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '',
|
||||
});
|
||||
this.titleNode.classList.add('user-fields-dialog-title');
|
||||
this.contentNode.classList.add('user-fields-dialog-content');
|
||||
this.acceptButton = undefined;
|
||||
this.widget = new UserFieldsDialogWidget(
|
||||
this.close.bind(this),
|
||||
this.accept.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
set value(userFields: BoardUserField[]) {
|
||||
this.widget.currentUserFields = userFields;
|
||||
}
|
||||
|
||||
get value(): BoardUserField[] {
|
||||
return this.widget.currentUserFields;
|
||||
}
|
||||
|
||||
protected onAfterAttach(msg: Message): void {
|
||||
if (this.widget.isAttached) {
|
||||
Widget.detach(this.widget);
|
||||
}
|
||||
Widget.attach(this.widget, this.contentNode);
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected onUpdateRequest(msg: Message): void {
|
||||
super.onUpdateRequest(msg);
|
||||
this.widget.update();
|
||||
}
|
||||
|
||||
protected onActivateRequest(msg: Message): void {
|
||||
super.onActivateRequest(msg);
|
||||
this.widget.activate();
|
||||
}
|
||||
|
||||
protected async accept(): Promise<void> {
|
||||
// If the user presses enter and at least
|
||||
// a field is empty don't accept the input
|
||||
for (const field of this.value) {
|
||||
if (field.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return super.accept();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.widget.resetUserFieldsValue();
|
||||
this.widget.close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user