From bc2ce86f48345acdc752cbdc4d224021aa86ca89 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 8 Sep 2022 16:06:50 +0200 Subject: [PATCH 1/3] Show user fields dialog again if upload fails --- .../browser/contributions/upload-sketch.ts | 84 ++++++++++++------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 5d6135cec..32222e726 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -25,6 +25,7 @@ export class UploadSketch extends CoreServiceContribution { private readonly userFieldsDialog: UserFieldsDialog; private boardRequiresUserFields = false; + private userFieldsSet = false; private readonly cachedUserFields: Map = new Map(); private readonly menuActionsDisposables = new DisposableCollection(); @@ -61,20 +62,22 @@ export class UploadSketch extends CoreServiceContribution { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { execute: async () => { const key = this.selectedFqbnAddress(); + /* + If the board requires to be configured with user fields, we want + to show the user fields dialog, but if they weren't already + filled in or if they were filled in, but the previous upload failed. + */ if ( this.boardRequiresUserFields && key && - !this.cachedUserFields.has(key) + (!this.cachedUserFields.has(key) || !this.userFieldsSet) ) { - // Deep clone the array of board fields to avoid editing the cached ones - this.userFieldsDialog.value = ( - await this.boardsServiceProvider.selectedBoardUserFields() - ).map((f) => ({ ...f })); - const result = await this.userFieldsDialog.open(); - if (!result) { + const userFieldsFilledIn = Boolean( + await this.showUserFieldsDialog(key) + ); + if (!userFieldsFilledIn) { return; } - this.cachedUserFields.set(key, result); } this.uploadSketch(); }, @@ -86,18 +89,12 @@ export class UploadSketch extends CoreServiceContribution { 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.boardsServiceProvider.selectedBoardUserFields()) - ).map((f) => ({ ...f })); - - const result = await this.userFieldsDialog.open(); - if (!result) { + const userFieldsFilledIn = Boolean( + await this.showUserFieldsDialog(key) + ); + if (!userFieldsFilledIn) { return; } - this.cachedUserFields.set(key, result); this.uploadSketch(); }, isEnabled: () => !this.uploadInProgress && this.boardRequiresUserFields, @@ -215,19 +212,20 @@ export class UploadSketch extends CoreServiceContribution { return; } - // TODO: This does not belong here. - // IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message. - if ( - uploadOptions.userFields.length === 0 && - this.boardRequiresUserFields - ) { - this.messageService.error( - nls.localize( - 'arduino/sketch/userFieldsNotFoundError', - "Can't find user fields for connected board" - ) - ); - return; + if (this.boardRequiresUserFields) { + // TODO: This does not belong here. + // IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message. + if (uploadOptions.userFields.length === 0) { + this.messageService.error( + nls.localize( + 'arduino/sketch/userFieldsNotFoundError', + "Can't find user fields for connected board" + ) + ); + this.userFieldsSet = false; + return; + } + this.userFieldsSet = true; } await this.doWithProgress({ @@ -242,6 +240,13 @@ export class UploadSketch extends CoreServiceContribution { { timeout: 3000 } ); } catch (e) { + if ( + this.boardRequiresUserFields && + typeof e.message === 'string' && + e.message.startsWith('Upload error:') + ) { + this.userFieldsSet = false; + } this.handleError(e); } finally { this.uploadInProgress = false; @@ -317,6 +322,23 @@ export class UploadSketch extends CoreServiceContribution { const [vendor, arch, id] = fqbn.split(':'); return `${vendor}:${arch}:${id}`; } + + private async showUserFieldsDialog( + key: string + ): Promise { + 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.boardsServiceProvider.selectedBoardUserFields()) + ).map((f) => ({ ...f })); + const result = await this.userFieldsDialog.open(); + if (!result) { + return; + } + this.userFieldsSet = true; + this.cachedUserFields.set(key, result); + return result; + } } export namespace UploadSketch { From a34f6ff5e99c1f2c575e330a6c39e21078162f92 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 9 Sep 2022 17:47:01 +0200 Subject: [PATCH 2/3] move user fields logic into own contribution --- .../browser/arduino-ide-frontend-module.ts | 2 + .../browser/contributions/upload-sketch.ts | 179 +++--------------- .../src/browser/contributions/user-fields.ts | 149 +++++++++++++++ 3 files changed, 180 insertions(+), 150 deletions(-) create mode 100644 arduino-ide-extension/src/browser/contributions/user-fields.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 000e9d0f9..d6e9571f7 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -337,6 +337,7 @@ import { CheckForUpdates } from './contributions/check-for-updates'; import { OutputEditorFactory } from './theia/output/output-editor-factory'; import { StartupTaskProvider } from '../electron-common/startup-task'; import { DeleteSketch } from './contributions/delete-sketch'; +import { UserFields } from './contributions/user-fields'; const registerArduinoThemes = () => { const themes: MonacoThemeJson[] = [ @@ -761,6 +762,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, OpenBoardsConfig); Contribution.configure(bind, SketchFilesTracker); Contribution.configure(bind, CheckForUpdates); + Contribution.configure(bind, UserFields); Contribution.configure(bind, DeleteSketch); bindContributionProvider(bind, StartupTaskProvider); diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 32222e726..8190f1c92 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -1,7 +1,7 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; -import { BoardUserField, CoreService, Port } from '../../common/protocol'; -import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; +import { CoreService, Port } from '../../common/protocol'; +import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { Command, @@ -11,93 +11,36 @@ import { TabBarToolbarRegistry, CoreServiceContribution, } from './contribution'; -import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog'; -import { deepClone, DisposableCollection, nls } from '@theia/core/lib/common'; +import { deepClone, nls } from '@theia/core/lib/common'; import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; import type { VerifySketchParams } from './verify-sketch'; +import { UserFields } from './user-fields'; @injectable() export class UploadSketch extends CoreServiceContribution { - @inject(MenuModelRegistry) - private readonly menuRegistry: MenuModelRegistry; - - @inject(UserFieldsDialog) - private readonly userFieldsDialog: UserFieldsDialog; - - private boardRequiresUserFields = false; - private userFieldsSet = false; - private readonly cachedUserFields: Map = new Map(); - private readonly menuActionsDisposables = new DisposableCollection(); - private readonly onDidChangeEmitter = new Emitter(); private readonly onDidChange = this.onDidChangeEmitter.event; private uploadInProgress = false; - protected override init(): void { - super.init(); - this.boardsServiceProvider.onBoardsConfigChanged(async () => { - const userFields = - await this.boardsServiceProvider.selectedBoardUserFields(); - this.boardRequiresUserFields = userFields.length > 0; - this.registerMenus(this.menuRegistry); - }); - } - - private selectedFqbnAddress(): string { - const { boardsConfig } = this.boardsServiceProvider; - const fqbn = boardsConfig.selectedBoard?.fqbn; - if (!fqbn) { - return ''; - } - const address = - boardsConfig.selectedBoard?.port?.address || - boardsConfig.selectedPort?.address; - if (!address) { - return ''; - } - return fqbn + '|' + address; - } + @inject(UserFields) + private readonly userFields: UserFields; override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { execute: async () => { - const key = this.selectedFqbnAddress(); - /* - If the board requires to be configured with user fields, we want - to show the user fields dialog, but if they weren't already - filled in or if they were filled in, but the previous upload failed. - */ - if ( - this.boardRequiresUserFields && - key && - (!this.cachedUserFields.has(key) || !this.userFieldsSet) - ) { - const userFieldsFilledIn = Boolean( - await this.showUserFieldsDialog(key) - ); - if (!userFieldsFilledIn) { - return; - } + if (await this.userFields.checkUserFieldsDialog(false)) { + this.uploadSketch(); } - this.uploadSketch(); }, isEnabled: () => !this.uploadInProgress, }); registry.registerCommand(UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION, { execute: async () => { - const key = this.selectedFqbnAddress(); - if (!key) { - return; - } - const userFieldsFilledIn = Boolean( - await this.showUserFieldsDialog(key) - ); - if (!userFieldsFilledIn) { - return; + if (await this.userFields.checkUserFieldsDialog(true)) { + this.uploadSketch(); } - this.uploadSketch(); }, - isEnabled: () => !this.uploadInProgress && this.boardRequiresUserFields, + isEnabled: () => !this.uploadInProgress && this.userFields.isRequired(), }); registry.registerCommand( UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER, @@ -117,45 +60,20 @@ export class UploadSketch extends CoreServiceContribution { } override registerMenus(registry: MenuModelRegistry): void { - 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', - }) - ); + 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: '3', + }); } override registerKeybindings(registry: KeybindingRegistry): void { @@ -212,20 +130,8 @@ export class UploadSketch extends CoreServiceContribution { return; } - if (this.boardRequiresUserFields) { - // TODO: This does not belong here. - // IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message. - if (uploadOptions.userFields.length === 0) { - this.messageService.error( - nls.localize( - 'arduino/sketch/userFieldsNotFoundError', - "Can't find user fields for connected board" - ) - ); - this.userFieldsSet = false; - return; - } - this.userFieldsSet = true; + if (!this.userFields.checkUserFieldsForUpload()) { + return; } await this.doWithProgress({ @@ -240,13 +146,7 @@ export class UploadSketch extends CoreServiceContribution { { timeout: 3000 } ); } catch (e) { - if ( - this.boardRequiresUserFields && - typeof e.message === 'string' && - e.message.startsWith('Upload error:') - ) { - this.userFieldsSet = false; - } + this.userFields.notifyFailedWithError(e); this.handleError(e); } finally { this.uploadInProgress = false; @@ -263,7 +163,7 @@ export class UploadSketch extends CoreServiceContribution { if (!CurrentSketch.isValid(sketch)) { return undefined; } - const userFields = this.userFields(); + const userFields = this.userFields.getUserFields(); const { boardsConfig } = this.boardsServiceProvider; const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([ @@ -306,10 +206,6 @@ export class UploadSketch extends CoreServiceContribution { return port; } - private userFields(): BoardUserField[] { - return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? []; - } - /** * Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to * `VENDOR:ARCHITECTURE:BOARD_ID` format. @@ -322,23 +218,6 @@ export class UploadSketch extends CoreServiceContribution { const [vendor, arch, id] = fqbn.split(':'); return `${vendor}:${arch}:${id}`; } - - private async showUserFieldsDialog( - key: string - ): Promise { - 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.boardsServiceProvider.selectedBoardUserFields()) - ).map((f) => ({ ...f })); - const result = await this.userFieldsDialog.open(); - if (!result) { - return; - } - this.userFieldsSet = true; - this.cachedUserFields.set(key, result); - return result; - } } export namespace UploadSketch { diff --git a/arduino-ide-extension/src/browser/contributions/user-fields.ts b/arduino-ide-extension/src/browser/contributions/user-fields.ts new file mode 100644 index 000000000..634de36be --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/user-fields.ts @@ -0,0 +1,149 @@ +import { inject, injectable } from '@theia/core/shared/inversify'; +import { DisposableCollection, nls } from '@theia/core/lib/common'; +import { BoardUserField } from '../../common/protocol'; +import { BoardsServiceProvider } from '../boards/boards-service-provider'; +import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog'; +import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; +import { MenuModelRegistry, Contribution } from './contribution'; +import { UploadSketch } from './upload-sketch'; + +@injectable() +export class UserFields extends Contribution { + private boardRequiresUserFields = false; + private userFieldsSet = false; + private readonly cachedUserFields: Map = new Map(); + private readonly menuActionsDisposables = new DisposableCollection(); + + @inject(UserFieldsDialog) + private readonly userFieldsDialog: UserFieldsDialog; + + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider; + + @inject(MenuModelRegistry) + private readonly menuRegistry: MenuModelRegistry; + + protected override init(): void { + super.init(); + this.boardsServiceProvider.onBoardsConfigChanged(async () => { + const userFields = + await this.boardsServiceProvider.selectedBoardUserFields(); + this.boardRequiresUserFields = userFields.length > 0; + this.registerMenus(this.menuRegistry); + }); + } + + override registerMenus(registry: MenuModelRegistry): void { + this.menuActionsDisposables.dispose(); + 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' } + ) + ) + ); + } + } + + private selectedFqbnAddress(): string { + const { boardsConfig } = this.boardsServiceProvider; + const fqbn = boardsConfig.selectedBoard?.fqbn; + if (!fqbn) { + return ''; + } + const address = + boardsConfig.selectedBoard?.port?.address || + boardsConfig.selectedPort?.address; + if (!address) { + return ''; + } + return fqbn + '|' + address; + } + + private async showUserFieldsDialog( + key: string + ): Promise { + 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.boardsServiceProvider.selectedBoardUserFields()) + ).map((f) => ({ ...f })); + const result = await this.userFieldsDialog.open(); + if (!result) { + return; + } + + this.userFieldsSet = true; + this.cachedUserFields.set(key, result); + return result; + } + + async checkUserFieldsDialog(forceOpen: boolean): Promise { + const key = this.selectedFqbnAddress(); + if (!key) { + return false; + } + /* + If the board requires to be configured with user fields, we want + to show the user fields dialog, but only if they weren't already + filled in or if they were filled in, but the previous upload failed. + */ + if ( + !forceOpen && + (!this.boardRequiresUserFields || + (this.cachedUserFields.has(key) && this.userFieldsSet)) + ) { + return true; + } + const userFieldsFilledIn = Boolean(await this.showUserFieldsDialog(key)); + return userFieldsFilledIn; + } + + checkUserFieldsForUpload(): boolean { + // TODO: This does not belong here. + // IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message. + if (!this.boardRequiresUserFields || this.getUserFields().length > 0) { + this.userFieldsSet = true; + return true; + } + this.messageService.error( + nls.localize( + 'arduino/sketch/userFieldsNotFoundError', + "Can't find user fields for connected board" + ) + ); + this.userFieldsSet = false; + return false; + } + + getUserFields(): BoardUserField[] { + return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? []; + } + + isRequired(): boolean { + return this.boardRequiresUserFields; + } + + notifyFailedWithError(e: Error): void { + if ( + this.boardRequiresUserFields && + typeof e.message === 'string' && + e.message.startsWith('Upload error:') + ) { + this.userFieldsSet = false; + } + } +} From 94ca5f3f5fdb93bfc57a12e6113b984fa27f6ea1 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 15 Sep 2022 16:01:11 +0200 Subject: [PATCH 3/3] apply suggestions --- .../browser/contributions/upload-sketch.ts | 2 +- .../src/browser/contributions/user-fields.ts | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 8190f1c92..f337fb1d7 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -28,7 +28,7 @@ export class UploadSketch extends CoreServiceContribution { override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { execute: async () => { - if (await this.userFields.checkUserFieldsDialog(false)) { + if (await this.userFields.checkUserFieldsDialog()) { this.uploadSketch(); } }, diff --git a/arduino-ide-extension/src/browser/contributions/user-fields.ts b/arduino-ide-extension/src/browser/contributions/user-fields.ts index 634de36be..c73ead9e6 100644 --- a/arduino-ide-extension/src/browser/contributions/user-fields.ts +++ b/arduino-ide-extension/src/browser/contributions/user-fields.ts @@ -1,6 +1,6 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { DisposableCollection, nls } from '@theia/core/lib/common'; -import { BoardUserField } from '../../common/protocol'; +import { BoardUserField, CoreError } from '../../common/protocol'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; @@ -18,7 +18,7 @@ export class UserFields extends Contribution { private readonly userFieldsDialog: UserFieldsDialog; @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider; + private readonly boardsServiceProvider: BoardsServiceProvider; @inject(MenuModelRegistry) private readonly menuRegistry: MenuModelRegistry; @@ -58,17 +58,17 @@ export class UserFields extends Contribution { } } - private selectedFqbnAddress(): string { + private selectedFqbnAddress(): string | undefined { const { boardsConfig } = this.boardsServiceProvider; const fqbn = boardsConfig.selectedBoard?.fqbn; if (!fqbn) { - return ''; + return undefined; } const address = boardsConfig.selectedBoard?.port?.address || boardsConfig.selectedPort?.address; if (!address) { - return ''; + return undefined; } return fqbn + '|' + address; } @@ -78,9 +78,7 @@ export class UserFields extends Contribution { ): Promise { 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.boardsServiceProvider.selectedBoardUserFields()) - ).map((f) => ({ ...f })); + this.userFieldsDialog.value = cached ? cached.slice() : await this.boardsServiceProvider.selectedBoardUserFields(); const result = await this.userFieldsDialog.open(); if (!result) { return; @@ -91,7 +89,7 @@ export class UserFields extends Contribution { return result; } - async checkUserFieldsDialog(forceOpen: boolean): Promise { + async checkUserFieldsDialog(forceOpen = false): Promise { const key = this.selectedFqbnAddress(); if (!key) { return false; @@ -130,7 +128,11 @@ export class UserFields extends Contribution { } getUserFields(): BoardUserField[] { - return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? []; + const fqbnAddress = this.selectedFqbnAddress(); + if (!fqbnAddress) { + return []; + } + return this.cachedUserFields.get(fqbnAddress) ?? []; } isRequired(): boolean { @@ -140,8 +142,7 @@ export class UserFields extends Contribution { notifyFailedWithError(e: Error): void { if ( this.boardRequiresUserFields && - typeof e.message === 'string' && - e.message.startsWith('Upload error:') + CoreError.UploadFailed.is(e) ) { this.userFieldsSet = false; }