mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-11 11:19:26 +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:
@@ -445,7 +445,7 @@ export class ArduinoFrontendContribution
|
||||
'arduino/debug/optimizeForDebugging',
|
||||
'Optimize for Debugging'
|
||||
),
|
||||
order: '4',
|
||||
order: '5',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,11 @@ import {
|
||||
UploadCertificateDialogWidget,
|
||||
} from './dialogs/certificate-uploader/certificate-uploader-dialog';
|
||||
import { PlotterFrontendContribution } from './serial/plotter/plotter-frontend-contribution';
|
||||
import {
|
||||
UserFieldsDialog,
|
||||
UserFieldsDialogProps,
|
||||
UserFieldsDialogWidget,
|
||||
} from './dialogs/user-fields/user-fields-dialog';
|
||||
import { nls } from '@theia/core/lib/browser/nls';
|
||||
|
||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||
@@ -739,4 +744,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(UploadCertificateDialogProps).toConstantValue({
|
||||
title: 'UploadCertificate',
|
||||
});
|
||||
|
||||
bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
|
||||
bind(UserFieldsDialog).toSelf().inSingletonScope();
|
||||
bind(UserFieldsDialogProps).toConstantValue({
|
||||
title: 'UserFields',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
BoardsPackage,
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardWithPackage,
|
||||
BoardUserField,
|
||||
} from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { naturalCompare } from '../../common/utils';
|
||||
@@ -68,7 +69,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
* This event is also emitted when the board package for the currently selected board was uninstalled.
|
||||
*/
|
||||
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
||||
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
|
||||
readonly onAvailableBoardsChanged =
|
||||
this.onAvailableBoardsChangedEmitter.event;
|
||||
readonly onAvailablePortsChanged = this.onAvailablePortsChangedEmitter.event;
|
||||
|
||||
onStart(): void {
|
||||
@@ -183,8 +185,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const selectedAvailableBoard = AvailableBoard.is(selectedBoard)
|
||||
? selectedBoard
|
||||
: this._availableBoards.find((availableBoard) =>
|
||||
Board.sameAs(availableBoard, selectedBoard)
|
||||
);
|
||||
Board.sameAs(availableBoard, selectedBoard)
|
||||
);
|
||||
if (
|
||||
selectedAvailableBoard &&
|
||||
selectedAvailableBoard.selected &&
|
||||
@@ -274,6 +276,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
return boards;
|
||||
}
|
||||
|
||||
async selectedBoardUserFields(): Promise<BoardUserField[]> {
|
||||
if (!this._boardsConfig.selectedBoard || !this._boardsConfig.selectedPort) {
|
||||
return [];
|
||||
}
|
||||
const fqbn = this._boardsConfig.selectedBoard.fqbn;
|
||||
if (!fqbn) {
|
||||
return [];
|
||||
}
|
||||
const protocol = this._boardsConfig.selectedPort.protocol;
|
||||
return await this.boardsService.getBoardUserFields({ fqbn, protocol });
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
|
||||
*/
|
||||
@@ -361,14 +375,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const timeoutTask =
|
||||
!!timeout && timeout > 0
|
||||
? new Promise<void>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error(`Timeout after ${timeout} ms.`)),
|
||||
timeout
|
||||
setTimeout(
|
||||
() => reject(new Error(`Timeout after ${timeout} ms.`)),
|
||||
timeout
|
||||
)
|
||||
)
|
||||
)
|
||||
: new Promise<void>(() => {
|
||||
/* never */
|
||||
});
|
||||
/* never */
|
||||
});
|
||||
const waitUntilTask = new Promise<void>((resolve) => {
|
||||
let candidate = find(what, this.availableBoards);
|
||||
if (candidate) {
|
||||
@@ -406,7 +420,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const availableBoards: AvailableBoard[] = [];
|
||||
const attachedBoards = this._attachedBoards.filter(({ port }) => !!port);
|
||||
const availableBoardPorts = availablePorts.filter((port) => {
|
||||
if (port.protocol === "serial") {
|
||||
if (port.protocol === 'serial') {
|
||||
// We always show all serial ports, even if there
|
||||
// is no recognized board connected to it
|
||||
return true;
|
||||
@@ -424,8 +438,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
});
|
||||
|
||||
for (const boardPort of availableBoardPorts) {
|
||||
let board = attachedBoards.find(({ port }) => Port.sameAs(boardPort, port));
|
||||
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(boardPort);
|
||||
const board = attachedBoards.find(({ port }) =>
|
||||
Port.sameAs(boardPort, port)
|
||||
);
|
||||
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
|
||||
boardPort
|
||||
);
|
||||
|
||||
let availableBoard = {} as AvailableBoard;
|
||||
if (board) {
|
||||
@@ -454,11 +472,16 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
availableBoards.push(availableBoard);
|
||||
}
|
||||
|
||||
if (boardsConfig.selectedBoard && !availableBoards.some(({ selected }) => selected)) {
|
||||
if (
|
||||
boardsConfig.selectedBoard &&
|
||||
!availableBoards.some(({ selected }) => selected)
|
||||
) {
|
||||
// If the selected board has the same port of an unknown board
|
||||
// that is already in availableBoards we might get a duplicate port.
|
||||
// So we remove the one already in the array and add the selected one.
|
||||
const found = availableBoards.findIndex(board => board.port?.address === boardsConfig.selectedPort?.address);
|
||||
const found = availableBoards.findIndex(
|
||||
(board) => board.port?.address === boardsConfig.selectedPort?.address
|
||||
);
|
||||
if (found >= 0) {
|
||||
availableBoards.splice(found, 1);
|
||||
}
|
||||
@@ -475,7 +498,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
let hasChanged = availableBoards.length !== currentAvailableBoards.length;
|
||||
for (let i = 0; !hasChanged && i < availableBoards.length; i++) {
|
||||
const [left, right] = [availableBoards[i], currentAvailableBoards[i]];
|
||||
hasChanged = !!AvailableBoard.compare(left, right) || left.selected !== right.selected;
|
||||
hasChanged =
|
||||
!!AvailableBoard.compare(left, right) ||
|
||||
left.selected !== right.selected;
|
||||
}
|
||||
if (hasChanged) {
|
||||
this._availableBoards = availableBoards;
|
||||
@@ -483,7 +508,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
}
|
||||
|
||||
protected async getLastSelectedBoardOnPort(port: Port): Promise<Board | undefined> {
|
||||
protected async getLastSelectedBoardOnPort(
|
||||
port: Port
|
||||
): Promise<Board | undefined> {
|
||||
const key = this.getLastSelectedBoardOnPortKey(port);
|
||||
return this.getData<Board>(key);
|
||||
}
|
||||
@@ -504,8 +531,11 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
]);
|
||||
}
|
||||
|
||||
protected getLastSelectedBoardOnPortKey(port: Port): string {
|
||||
return `last-selected-board-on-port:${Port.toString(port)}`;
|
||||
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
|
||||
// TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`.
|
||||
return `last-selected-board-on-port:${
|
||||
typeof port === 'string' ? port : Port.toString(port)
|
||||
}`;
|
||||
}
|
||||
|
||||
protected async loadState(): Promise<void> {
|
||||
@@ -596,13 +626,22 @@ export namespace AvailableBoard {
|
||||
// 4. Network with recognized boards
|
||||
// 5. Other protocols with recognized boards
|
||||
export const compare = (left: AvailableBoard, right: AvailableBoard) => {
|
||||
if (left.port?.protocol === "serial" && right.port?.protocol !== "serial") {
|
||||
if (left.port?.protocol === 'serial' && right.port?.protocol !== 'serial') {
|
||||
return -1;
|
||||
} else if (left.port?.protocol !== "serial" && right.port?.protocol === "serial") {
|
||||
} else if (
|
||||
left.port?.protocol !== 'serial' &&
|
||||
right.port?.protocol === 'serial'
|
||||
) {
|
||||
return 1;
|
||||
} else if (left.port?.protocol === "network" && right.port?.protocol !== "network") {
|
||||
} else if (
|
||||
left.port?.protocol === 'network' &&
|
||||
right.port?.protocol !== 'network'
|
||||
) {
|
||||
return -1;
|
||||
} else if (left.port?.protocol !== "network" && right.port?.protocol === "network") {
|
||||
} else if (
|
||||
left.port?.protocol !== 'network' &&
|
||||
right.port?.protocol === 'network'
|
||||
) {
|
||||
return 1;
|
||||
} else if (left.port?.protocol === right.port?.protocol) {
|
||||
// We show all ports, including those that have guessed
|
||||
@@ -614,5 +653,5 @@ export namespace AvailableBoard {
|
||||
}
|
||||
}
|
||||
return naturalCompare(left.port?.address!, right.port?.address!);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { CoreService } from '../../common/protocol';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { BoardUserField, CoreService } from '../../common/protocol';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { SerialConnectionManager } from '../serial/serial-connection-manager';
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
||||
import { nls } from '@theia/core/lib/browser/nls';
|
||||
import { DisposableCollection } from '@theia/core';
|
||||
|
||||
@injectable()
|
||||
export class UploadSketch extends SketchContribution {
|
||||
@@ -24,22 +26,96 @@ export class UploadSketch extends SketchContribution {
|
||||
@inject(SerialConnectionManager)
|
||||
protected readonly serialConnection: SerialConnectionManager;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(BoardsDataStore)
|
||||
protected readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
||||
|
||||
@inject(UserFieldsDialog)
|
||||
protected readonly userFieldsDialog: UserFieldsDialog;
|
||||
|
||||
protected cachedUserFields: Map<string, BoardUserField[]> = new Map();
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<void>>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
protected uploadInProgress = false;
|
||||
protected boardRequiresUserFields = false;
|
||||
|
||||
protected readonly menuActionsDisposables = new DisposableCollection();
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(async () => {
|
||||
const userFields =
|
||||
await this.boardsServiceClientImpl.selectedBoardUserFields();
|
||||
this.boardRequiresUserFields = userFields.length > 0;
|
||||
this.registerMenus(this.menuRegistry);
|
||||
});
|
||||
}
|
||||
|
||||
private selectedFqbnAddress(): string {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
||||
if (!fqbn) {
|
||||
return '';
|
||||
}
|
||||
const address = boardsConfig.selectedBoard?.port?.address;
|
||||
if (!address) {
|
||||
return '';
|
||||
}
|
||||
return fqbn + '|' + address;
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
|
||||
execute: () => this.uploadSketch(),
|
||||
execute: async () => {
|
||||
const key = this.selectedFqbnAddress();
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (this.boardRequiresUserFields && !this.cachedUserFields.has(key)) {
|
||||
// Deep clone the array of board fields to avoid editing the cached ones
|
||||
this.userFieldsDialog.value = (
|
||||
await this.boardsServiceClientImpl.selectedBoardUserFields()
|
||||
).map((f) => ({ ...f }));
|
||||
const result = await this.userFieldsDialog.open();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
this.cachedUserFields.set(key, result);
|
||||
}
|
||||
this.uploadSketch();
|
||||
},
|
||||
isEnabled: () => !this.uploadInProgress,
|
||||
});
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION, {
|
||||
execute: async () => {
|
||||
const key = this.selectedFqbnAddress();
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = this.cachedUserFields.get(key);
|
||||
// Deep clone the array of board fields to avoid editing the cached ones
|
||||
this.userFieldsDialog.value = (
|
||||
cached ??
|
||||
(await this.boardsServiceClientImpl.selectedBoardUserFields())
|
||||
).map((f) => ({ ...f }));
|
||||
|
||||
const result = await this.userFieldsDialog.open();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
this.cachedUserFields.set(key, result);
|
||||
this.uploadSketch();
|
||||
},
|
||||
isEnabled: () => !this.uploadInProgress && this.boardRequiresUserFields,
|
||||
});
|
||||
registry.registerCommand(
|
||||
UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER,
|
||||
{
|
||||
@@ -58,19 +134,46 @@ export class UploadSketch extends SketchContribution {
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
label: nls.localize('arduino/sketch/upload', 'Upload'),
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
label: nls.localize(
|
||||
'arduino/sketch/uploadUsingProgrammer',
|
||||
'Upload Using Programmer'
|
||||
),
|
||||
order: '2',
|
||||
});
|
||||
this.menuActionsDisposables.dispose();
|
||||
|
||||
this.menuActionsDisposables.push(
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
label: nls.localize('arduino/sketch/upload', 'Upload'),
|
||||
order: '1',
|
||||
})
|
||||
);
|
||||
if (this.boardRequiresUserFields) {
|
||||
this.menuActionsDisposables.push(
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
|
||||
label: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
|
||||
order: '2',
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.menuActionsDisposables.push(
|
||||
registry.registerMenuNode(
|
||||
ArduinoMenus.SKETCH__MAIN_GROUP,
|
||||
new PlaceholderMenuNode(
|
||||
ArduinoMenus.SKETCH__MAIN_GROUP,
|
||||
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
|
||||
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label!,
|
||||
{ order: '2' }
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
this.menuActionsDisposables.push(
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
label: nls.localize(
|
||||
'arduino/sketch/uploadUsingProgrammer',
|
||||
'Upload Using Programmer'
|
||||
),
|
||||
order: '3',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
@@ -127,6 +230,17 @@ export class UploadSketch extends SketchContribution {
|
||||
const optimizeForDebug = this.editorMode.compileForDebug;
|
||||
const { selectedPort } = boardsConfig;
|
||||
const port = selectedPort;
|
||||
const userFields =
|
||||
this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
|
||||
if (userFields.length === 0 && this.boardRequiresUserFields) {
|
||||
this.messageService.error(
|
||||
nls.localize(
|
||||
'arduino/sketch/userFieldsNotFoundError',
|
||||
"Can't find user fields for connected board"
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (usingProgrammer) {
|
||||
const programmer = selectedProgrammer;
|
||||
@@ -139,6 +253,7 @@ export class UploadSketch extends SketchContribution {
|
||||
verbose,
|
||||
verify,
|
||||
sourceOverride,
|
||||
userFields,
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
@@ -149,6 +264,7 @@ export class UploadSketch extends SketchContribution {
|
||||
verbose,
|
||||
verify,
|
||||
sourceOverride,
|
||||
userFields,
|
||||
};
|
||||
}
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
@@ -197,6 +313,14 @@ export namespace UploadSketch {
|
||||
export const UPLOAD_SKETCH: Command = {
|
||||
id: 'arduino-upload-sketch',
|
||||
};
|
||||
export const UPLOAD_WITH_CONFIGURATION: Command = {
|
||||
id: 'arduino-upload-with-configuration-sketch',
|
||||
label: nls.localize(
|
||||
'arduino/sketch/configureAndUpload',
|
||||
'Configure And Upload'
|
||||
),
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const UPLOAD_SKETCH_USING_PROGRAMMER: Command = {
|
||||
id: 'arduino-upload-sketch-using-programmer',
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ export class VerifySketch extends SketchContribution {
|
||||
'arduino/sketch/exportBinary',
|
||||
'Export Compiled Binary'
|
||||
),
|
||||
order: '3',
|
||||
order: '4',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
@import './settings-dialog.css';
|
||||
@import './firmware-uploader-dialog.css';
|
||||
@import './certificate-uploader-dialog.css';
|
||||
@import './user-fields-dialog.css';
|
||||
@import './debug.css';
|
||||
@import './sketchbook.css';
|
||||
@import './cloud-sketchbook.css';
|
||||
@@ -17,87 +18,91 @@
|
||||
@import './custom-codicon.css';
|
||||
|
||||
.theia-input.warning:focus {
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
outline-offset: -1px;
|
||||
opacity: 1 !important;
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
outline-offset: -1px;
|
||||
opacity: 1 !important;
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
.theia-input.warning {
|
||||
background-color: var(--theia-warningBackground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
.theia-input.warning::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
opacity: 1; /* Firefox */
|
||||
.theia-input.warning::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
opacity: 1; /* Firefox */
|
||||
}
|
||||
|
||||
.theia-input.warning:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
.theia-input.warning:-ms-input-placeholder {
|
||||
/* Internet Explorer 10-11 */
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
.theia-input.warning::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
.theia-input.warning::-ms-input-placeholder {
|
||||
/* Microsoft Edge */
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
/* Makes the sidepanel a bit wider when opening the widget */
|
||||
/* Makes the sidepanel a bit wider when opening the widget */
|
||||
.p-DockPanel-widget {
|
||||
min-width: 200px;
|
||||
min-height: 200px;
|
||||
min-width: 200px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* Overrule the default Theia CSS button styles. */
|
||||
button.theia-button,
|
||||
.theia-button {
|
||||
border: 1px solid var(--theia-dropdown-border);
|
||||
border: 1px solid var(--theia-dropdown-border);
|
||||
}
|
||||
|
||||
button.theia-button:hover,
|
||||
.theia-button:hover {
|
||||
border: 1px solid var(--theia-focusBorder);
|
||||
border: 1px solid var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
button.theia-button {
|
||||
height: 31px;
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
button.theia-button.secondary {
|
||||
background-color: var(--theia-secondaryButton-background);
|
||||
color: var(--theia-secondaryButton-foreground);
|
||||
background-color: var(--theia-secondaryButton-background);
|
||||
color: var(--theia-secondaryButton-foreground);
|
||||
}
|
||||
|
||||
button.theia-button.main {
|
||||
color: var(--theia-button-foreground);
|
||||
color: var(--theia-button-foreground);
|
||||
}
|
||||
|
||||
/* To make the progress-bar slightly thicker, and use the color from the status bar */
|
||||
.theia-progress-bar-container {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.theia-progress-bar {
|
||||
height: 4px;
|
||||
width: 3%;
|
||||
animation: progress-animation 1.3s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
height: 4px;
|
||||
width: 3%;
|
||||
animation: progress-animation 1.3s 0s infinite
|
||||
cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.theia-notification-item-progressbar {
|
||||
height: 4px;
|
||||
width: 66%;
|
||||
height: 4px;
|
||||
width: 66%;
|
||||
}
|
||||
|
||||
.flex-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fa-reload {
|
||||
font-size: 14px;
|
||||
}
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.user-fields-container {
|
||||
max-height: 332px;
|
||||
overflow: auto;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.user-fields-list {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content {
|
||||
width: 408px;
|
||||
max-height: 491px;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content .field-label {
|
||||
color: #2c353a;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content .theia-input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content .button-container {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
Reference in New Issue
Block a user