@nordicsemiconductor/pc-nrfconnect-shared 102.0.0 → 104.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/Changelog.md +27 -0
  2. package/coverage/cobertura-coverage.xml +334 -395
  3. package/nrfutil/device/batch.ts +147 -53
  4. package/nrfutil/device/batchTypes.ts +2 -2
  5. package/nrfutil/device/common.ts +5 -10
  6. package/nrfutil/device/device.ts +3 -4
  7. package/nrfutil/device/list.ts +1 -2
  8. package/nrfutil/device/program.ts +1 -1
  9. package/nrfutil/sandbox.ts +20 -0
  10. package/nrfutil/sandboxTypes.ts +6 -4
  11. package/package.json +1 -1
  12. package/src/App/App.tsx +1 -1
  13. package/src/Button/Button.tsx +30 -31
  14. package/src/Dialog/Dialog.tsx +3 -1
  15. package/src/Dialog/dialog.scss +0 -8
  16. package/src/{Panes → Feedback}/FeedbackPane.tsx +5 -36
  17. package/src/Feedback/sendFeedback.ts +42 -0
  18. package/src/index.ts +2 -0
  19. package/typings/generated/nrfutil/device/batch.d.ts +5 -7
  20. package/typings/generated/nrfutil/device/batch.d.ts.map +1 -1
  21. package/typings/generated/nrfutil/device/batchTypes.d.ts +2 -2
  22. package/typings/generated/nrfutil/device/batchTypes.d.ts.map +1 -1
  23. package/typings/generated/nrfutil/device/common.d.ts.map +1 -1
  24. package/typings/generated/nrfutil/device/device.d.ts.map +1 -1
  25. package/typings/generated/nrfutil/device/list.d.ts.map +1 -1
  26. package/typings/generated/nrfutil/device/program.d.ts +1 -0
  27. package/typings/generated/nrfutil/device/program.d.ts.map +1 -1
  28. package/typings/generated/nrfutil/sandbox.d.ts +1 -0
  29. package/typings/generated/nrfutil/sandbox.d.ts.map +1 -1
  30. package/typings/generated/nrfutil/sandboxTypes.d.ts +5 -4
  31. package/typings/generated/nrfutil/sandboxTypes.d.ts.map +1 -1
  32. package/typings/generated/src/App/App.d.ts +1 -1
  33. package/typings/generated/src/App/App.d.ts.map +1 -1
  34. package/typings/generated/src/Button/Button.d.ts.map +1 -1
  35. package/typings/generated/src/Dialog/Dialog.d.ts.map +1 -1
  36. package/typings/generated/src/Feedback/FeedbackPane.d.ts.map +1 -0
  37. package/typings/generated/src/Feedback/sendFeedback.d.ts +3 -0
  38. package/typings/generated/src/Feedback/sendFeedback.d.ts.map +1 -0
  39. package/typings/generated/src/index.d.ts +1 -0
  40. package/typings/generated/src/index.d.ts.map +1 -1
  41. package/nrfutil/device/eraseBatch.ts +0 -28
  42. package/nrfutil/device/firmwareReadBatch.ts +0 -42
  43. package/nrfutil/device/getCoreInfoBatch.ts +0 -29
  44. package/nrfutil/device/getFwInfoBatch.ts +0 -29
  45. package/nrfutil/device/getProtectionStatusBatch.ts +0 -32
  46. package/nrfutil/device/programBatch.ts +0 -69
  47. package/nrfutil/device/recoverBatch.ts +0 -28
  48. package/nrfutil/device/resetBatch.ts +0 -30
  49. package/typings/generated/nrfutil/device/eraseBatch.d.ts +0 -7
  50. package/typings/generated/nrfutil/device/eraseBatch.d.ts.map +0 -1
  51. package/typings/generated/nrfutil/device/firmwareReadBatch.d.ts +0 -9
  52. package/typings/generated/nrfutil/device/firmwareReadBatch.d.ts.map +0 -1
  53. package/typings/generated/nrfutil/device/getCoreInfoBatch.d.ts +0 -8
  54. package/typings/generated/nrfutil/device/getCoreInfoBatch.d.ts.map +0 -1
  55. package/typings/generated/nrfutil/device/getFwInfoBatch.d.ts +0 -8
  56. package/typings/generated/nrfutil/device/getFwInfoBatch.d.ts.map +0 -1
  57. package/typings/generated/nrfutil/device/getProtectionStatusBatch.d.ts +0 -8
  58. package/typings/generated/nrfutil/device/getProtectionStatusBatch.d.ts.map +0 -1
  59. package/typings/generated/nrfutil/device/programBatch.d.ts +0 -9
  60. package/typings/generated/nrfutil/device/programBatch.d.ts.map +0 -1
  61. package/typings/generated/nrfutil/device/recoverBatch.d.ts +0 -7
  62. package/typings/generated/nrfutil/device/recoverBatch.d.ts.map +0 -1
  63. package/typings/generated/nrfutil/device/resetBatch.d.ts +0 -8
  64. package/typings/generated/nrfutil/device/resetBatch.d.ts.map +0 -1
  65. package/typings/generated/src/Panes/FeedbackPane.d.ts.map +0 -1
  66. /package/typings/generated/src/{Panes → Feedback}/FeedbackPane.d.ts +0 -0
@@ -4,31 +4,37 @@
4
4
  * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5
5
  */
6
6
 
7
+ import fs from 'fs';
8
+ import os from 'os';
9
+ import path from 'path';
10
+ import { v4 as uuid } from 'uuid';
11
+
7
12
  import { TaskEnd } from '../sandboxTypes';
8
13
  import { BatchOperationWrapper, Callbacks } from './batchTypes';
9
14
  import {
10
15
  DeviceCore,
16
+ DeviceTraits,
17
+ deviceTraitsToArgs,
11
18
  getDeviceSandbox,
12
19
  NrfutilDeviceWithSerialnumber,
13
20
  ResetKind,
14
21
  } from './common';
15
- import eraseBatch from './eraseBatch';
16
- import firmwareReadBatch from './firmwareReadBatch';
22
+ import { DeviceBuffer } from './firmwareRead';
17
23
  import { DeviceCoreInfo } from './getCoreInfo';
18
- import getCoreInfoBatch from './getCoreInfoBatch';
19
24
  import { FWInfo } from './getFwInfo';
20
- import getFwInfoBatch from './getFwInfoBatch';
21
25
  import { GetProtectionStatusResult } from './getProtectionStatus';
22
- import getProtectionStatusBatch from './getProtectionStatusBatch';
23
- import { FirmwareType, ProgrammingOptions } from './program';
24
- import programBatch from './programBatch';
25
- import recoverBatch from './recoverBatch';
26
- import resetBatch from './resetBatch';
26
+ import {
27
+ FirmwareType,
28
+ ProgrammingOptions,
29
+ programmingOptionsToArgs,
30
+ } from './program';
27
31
 
28
- type BatchOperationWrapperUnknown = BatchOperationWrapper<unknown, unknown>;
32
+ type BatchOperationWrapperUnknown = BatchOperationWrapper<unknown>;
33
+ type CallbacksUnknown = Callbacks<unknown>;
29
34
 
30
35
  export class Batch {
31
- private operations: BatchOperationWrapperUnknown[];
36
+ private operationBatchGeneration: Promise<BatchOperationWrapperUnknown>[] =
37
+ [];
32
38
 
33
39
  private collectOperations: {
34
40
  callback: (completedTasks: TaskEnd<unknown>[]) => void;
@@ -36,24 +42,63 @@ export class Batch {
36
42
  count: number;
37
43
  }[] = [];
38
44
 
39
- constructor(operations?: BatchOperationWrapperUnknown[]) {
40
- this.operations = operations ?? [];
45
+ private enqueueBatchOperationObject(
46
+ command: string,
47
+ core: DeviceCore,
48
+ callbacks?: Callbacks<unknown>,
49
+ args: string[] = []
50
+ ) {
51
+ const getPromise = async () => {
52
+ const box = await getDeviceSandbox();
53
+
54
+ const batchOperation =
55
+ await box.singleInfoOperationOptionalData<object>(
56
+ command,
57
+ undefined,
58
+ [
59
+ '--serial-number', // this is a workaround this param should now be needed with --generate
60
+ '123',
61
+ '--generate',
62
+ '--core',
63
+ core,
64
+ ].concat(args)
65
+ );
66
+
67
+ return {
68
+ operation: {
69
+ ...batchOperation,
70
+ },
71
+ ...callbacks,
72
+ };
73
+ };
74
+
75
+ this.operationBatchGeneration.push(getPromise());
41
76
  }
42
77
 
43
78
  public erase(core: DeviceCore, callbacks?: Callbacks) {
44
- this.operations.push(
45
- eraseBatch(core, { callbacks }) as BatchOperationWrapperUnknown
79
+ this.enqueueBatchOperationObject(
80
+ 'erase',
81
+ core,
82
+ callbacks as CallbacksUnknown
46
83
  );
47
84
 
48
85
  return this;
49
86
  }
50
87
 
51
88
  public firmwareRead(core: DeviceCore, callbacks?: Callbacks<Buffer>) {
52
- this.operations.push(
53
- firmwareReadBatch(core, {
54
- callbacks,
55
- }) as BatchOperationWrapperUnknown
56
- );
89
+ this.enqueueBatchOperationObject('fw-read', core, {
90
+ ...callbacks,
91
+ onTaskEnd: (taskEnd: TaskEnd<DeviceBuffer>) => {
92
+ if (taskEnd.result === 'success' && taskEnd.data)
93
+ callbacks?.onTaskEnd?.({
94
+ ...taskEnd,
95
+ data: Buffer.from(taskEnd.data.buffer, 'base64'),
96
+ });
97
+ else {
98
+ callbacks?.onException?.(new Error('Read failed'));
99
+ }
100
+ },
101
+ } as CallbacksUnknown);
57
102
 
58
103
  return this;
59
104
  }
@@ -62,20 +107,20 @@ export class Batch {
62
107
  core: DeviceCore,
63
108
  callbacks?: Callbacks<DeviceCoreInfo>
64
109
  ) {
65
- this.operations.push(
66
- getCoreInfoBatch(core, {
67
- callbacks,
68
- }) as BatchOperationWrapperUnknown
110
+ this.enqueueBatchOperationObject(
111
+ 'core-info',
112
+ core,
113
+ callbacks as CallbacksUnknown
69
114
  );
70
115
 
71
116
  return this;
72
117
  }
73
118
 
74
119
  public getFwInfo(core: DeviceCore, callbacks?: Callbacks<FWInfo>) {
75
- this.operations.push(
76
- getFwInfoBatch(core, {
77
- callbacks,
78
- }) as BatchOperationWrapperUnknown
120
+ this.enqueueBatchOperationObject(
121
+ 'fw-info',
122
+ core,
123
+ callbacks as CallbacksUnknown
79
124
  );
80
125
 
81
126
  return this;
@@ -85,10 +130,10 @@ export class Batch {
85
130
  core: DeviceCore,
86
131
  callbacks?: Callbacks<GetProtectionStatusResult>
87
132
  ) {
88
- this.operations.push(
89
- getProtectionStatusBatch(core, {
90
- callbacks,
91
- }) as BatchOperationWrapperUnknown
133
+ this.enqueueBatchOperationObject(
134
+ 'protection-get',
135
+ core,
136
+ callbacks as CallbacksUnknown
92
137
  );
93
138
 
94
139
  return this;
@@ -98,32 +143,69 @@ export class Batch {
98
143
  firmware: FirmwareType,
99
144
  core: DeviceCore,
100
145
  programmingOptions?: ProgrammingOptions,
146
+ deviceTraits?: DeviceTraits,
101
147
  callbacks?: Callbacks
102
148
  ) {
103
- this.operations.push(
104
- programBatch(firmware, core, {
105
- programmingOptions,
106
- callbacks,
107
- }) as BatchOperationWrapperUnknown
149
+ let args = [
150
+ ...(deviceTraits ? deviceTraitsToArgs(deviceTraits) : []),
151
+ ...programmingOptionsToArgs(programmingOptions),
152
+ ];
153
+ let newCallbacks = { ...callbacks };
154
+
155
+ if (typeof firmware === 'string') {
156
+ args = ['--firmware', firmware].concat(args);
157
+ } else {
158
+ const saveTemp = (): string => {
159
+ let tempFilePath;
160
+ do {
161
+ tempFilePath = path.join(
162
+ os.tmpdir(),
163
+ `${uuid()}.${firmware.type}`
164
+ );
165
+ } while (fs.existsSync(tempFilePath));
166
+
167
+ fs.writeFileSync(tempFilePath, firmware.buffer);
168
+
169
+ return tempFilePath;
170
+ };
171
+ const tempFilePath = saveTemp();
172
+ args = ['--firmware', tempFilePath].concat(args);
173
+
174
+ newCallbacks = {
175
+ ...callbacks,
176
+ onTaskEnd: (taskEnd: TaskEnd<void>) => {
177
+ fs.unlinkSync(tempFilePath);
178
+ callbacks?.onTaskEnd?.(taskEnd);
179
+ },
180
+ } as CallbacksUnknown;
181
+ }
182
+
183
+ this.enqueueBatchOperationObject(
184
+ 'program',
185
+ core,
186
+ newCallbacks as CallbacksUnknown,
187
+ args
108
188
  );
109
189
 
110
190
  return this;
111
191
  }
112
192
 
113
193
  public recover(core: DeviceCore, callbacks?: Callbacks) {
114
- this.operations.push(
115
- recoverBatch(core, { callbacks }) as BatchOperationWrapperUnknown
194
+ this.enqueueBatchOperationObject(
195
+ 'recover',
196
+ core,
197
+ callbacks as CallbacksUnknown
116
198
  );
117
199
 
118
200
  return this;
119
201
  }
120
202
 
121
203
  public reset(core: DeviceCore, reset?: ResetKind, callbacks?: Callbacks) {
122
- this.operations.push(
123
- resetBatch(core, {
124
- reset,
125
- callbacks,
126
- }) as BatchOperationWrapperUnknown
204
+ this.enqueueBatchOperationObject(
205
+ 'reset',
206
+ core,
207
+ callbacks as CallbacksUnknown,
208
+ reset ? ['--reset-kind', reset] : undefined
127
209
  );
128
210
 
129
211
  return this;
@@ -135,7 +217,7 @@ export class Batch {
135
217
  ) {
136
218
  this.collectOperations.push({
137
219
  callback,
138
- operationId: this.operations.length - 1,
220
+ operationId: this.operationBatchGeneration.length - 1,
139
221
  count,
140
222
  });
141
223
 
@@ -149,9 +231,21 @@ export class Batch {
149
231
  let beginId = 0;
150
232
  let endId = 0;
151
233
  const results: TaskEnd<unknown>[] = [];
234
+ const operations: BatchOperationWrapperUnknown[] = [];
235
+
236
+ const promiseResults =
237
+ await Promise.allSettled<BatchOperationWrapperUnknown>(
238
+ this.operationBatchGeneration
239
+ );
240
+ promiseResults.forEach(r => {
241
+ if (r.status === 'rejected') {
242
+ throw r.reason;
243
+ }
244
+ operations.push(r.value);
245
+ });
152
246
 
153
- const operations = {
154
- operations: this.operations.map((operation, index) => ({
247
+ const batchOperation = {
248
+ operations: operations.map((operation, index) => ({
155
249
  operationId: index.toString(),
156
250
  ...operation.operation,
157
251
  })),
@@ -165,21 +259,21 @@ export class Batch {
165
259
  '--serial-number',
166
260
  device.serialNumber,
167
261
  '--batch-json',
168
- JSON.stringify(operations),
262
+ JSON.stringify(batchOperation),
169
263
  ],
170
264
  (progress, task) => {
171
265
  if (task) {
172
- this.operations[endId].onProgress?.(progress, task);
266
+ operations[endId].onProgress?.(progress, task);
173
267
  }
174
268
  },
175
269
  onTaskBegin => {
176
270
  beginId += 1;
177
- this.operations[endId].onTaskBegin?.(onTaskBegin);
271
+ operations[endId].onTaskBegin?.(onTaskBegin);
178
272
  },
179
273
  taskEnd => {
180
274
  results.push(taskEnd);
181
275
 
182
- this.operations[endId].onTaskEnd?.(taskEnd);
276
+ operations[endId].onTaskEnd?.(taskEnd);
183
277
 
184
278
  this.collectOperations
185
279
  .filter(operation => operation.operationId === endId)
@@ -204,12 +298,12 @@ export class Batch {
204
298
  .map(e => `error: ${e.error}, message: ${e.message}`)
205
299
  .join('\n')}`
206
300
  );
207
- this.operations[endId].onException?.(error);
301
+ operations[endId].onException?.(error);
208
302
  throw error;
209
303
  }
210
304
  } catch (error) {
211
305
  if (beginId !== endId) {
212
- this.operations[beginId].onException?.(error as Error);
306
+ operations[beginId].onException?.(error as Error);
213
307
  }
214
308
  throw error;
215
309
  }
@@ -13,8 +13,8 @@ import {
13
13
  ProgrammingOptions,
14
14
  } from './program';
15
15
 
16
- export interface BatchOperationWrapper<OperationType, T = void> {
17
- operation: GenericOperation<OperationType>;
16
+ export interface BatchOperationWrapper<T = void> {
17
+ operation: object;
18
18
  onProgress?: (progress: Progress, task?: Task) => void;
19
19
  onTaskBegin?: TaskBeginCallback;
20
20
  onTaskEnd?: TaskEndCallback<T>;
@@ -205,12 +205,6 @@ export const getDeviceSandbox = async () => {
205
205
  deviceSandbox = await promiseDeviceSandbox;
206
206
 
207
207
  deviceSandbox.onLogging(evt => {
208
- if (
209
- process.env.NODE_ENV === 'production' &&
210
- deviceSandbox?.logLevel !== 'trace'
211
- )
212
- return;
213
-
214
208
  const deviceLogger = getNrfutilLogger();
215
209
  switch (evt.level) {
216
210
  case 'TRACE':
@@ -238,10 +232,11 @@ export const getDeviceSandbox = async () => {
238
232
  }
239
233
  });
240
234
 
241
- // Temporary until nrfutil fixes off log level NRFU-616
242
- // const fallbackLevel =
243
- // process.env.NODE_ENV === 'production' ? 'off' : 'error';
244
- deviceSandbox.setLogLevel(getIsLoggingVerbose() ? 'trace' : 'error');
235
+ const fallbackLevel =
236
+ process.env.NODE_ENV === 'production' ? 'off' : 'error';
237
+ deviceSandbox.setLogLevel(
238
+ getIsLoggingVerbose() ? 'trace' : fallbackLevel
239
+ );
245
240
  // Only the first reset after selecting "reset with verbose logging" is relevant
246
241
  persistIsLoggingVerbose(false);
247
242
  }
@@ -31,11 +31,10 @@ const setLogLevel = async (level: LogLevel) => {
31
31
 
32
32
  const setVerboseLogging = async (verbose: boolean) => {
33
33
  const sandbox = await getDeviceSandbox();
34
- // Temporary until nrfutil fixes off log level NRFU-616
35
- // const fallbackLevel =
36
- // process.env.NODE_ENV === 'production' ? 'off' : 'error';
34
+ const fallbackLevel =
35
+ process.env.NODE_ENV === 'production' ? 'off' : 'error';
37
36
 
38
- sandbox.setLogLevel(verbose ? 'trace' : 'error');
37
+ sandbox.setLogLevel(verbose ? 'trace' : fallbackLevel);
39
38
  };
40
39
  const getModuleVersion = async () => {
41
40
  const sandbox = await getDeviceSandbox();
@@ -39,8 +39,7 @@ export default async (
39
39
  },
40
40
  timeout = 3000
41
41
  ) => {
42
- const args: string[] = [];
43
- args.concat(deviceTraitsToArgs(traits));
42
+ const args: string[] = deviceTraitsToArgs(traits);
44
43
 
45
44
  if (onHotplugEvent) {
46
45
  args.push('--hotplug');
@@ -72,7 +72,7 @@ const deviceTraitsToArgs = (traits: DeviceTraits) => {
72
72
  return args;
73
73
  };
74
74
 
75
- const programmingOptionsToArgs = (options?: ProgrammingOptions) => {
75
+ export const programmingOptionsToArgs = (options?: ProgrammingOptions) => {
76
76
  if (!options) return [];
77
77
 
78
78
  const args: string[] = [];
@@ -449,6 +449,26 @@ export class NrfutilSandbox {
449
449
  throw new Error('Unexpected result');
450
450
  };
451
451
 
452
+ public singleInfoOperationOptionalData = async <T = void>(
453
+ command: string,
454
+ controller?: AbortController,
455
+ args: string[] = []
456
+ ) => {
457
+ const results = await this.execSubcommand<T>(
458
+ command,
459
+ args,
460
+ undefined,
461
+ undefined,
462
+ undefined,
463
+ controller
464
+ );
465
+
466
+ if (results.info.length === 1) {
467
+ return results.info[0];
468
+ }
469
+ throw new Error('Unexpected result');
470
+ };
471
+
452
472
  public onLogging = (handler: (logging: LogMessage) => void) => {
453
473
  this.onLoggingHandlers.push(handler);
454
474
 
@@ -82,6 +82,11 @@ type NrfutilJsonBegin = {
82
82
  data: TaskBegin;
83
83
  };
84
84
 
85
+ type NrfutilJsonInfo<T> = {
86
+ type: 'info';
87
+ data: T;
88
+ };
89
+
85
90
  type NrfutilJsonBatch<T = unknown> = {
86
91
  batch: {
87
92
  id: string;
@@ -98,10 +103,7 @@ export type NrfutilJsonBatchUpdate<T = unknown> = {
98
103
  };
99
104
 
100
105
  export type NrfutilJson<T = unknown> =
101
- | {
102
- type: 'info';
103
- data: T;
104
- }
106
+ | NrfutilJsonInfo<T>
105
107
  | NrfutilJsonBegin
106
108
  | NrfutilJsonEnd<T>
107
109
  | NrfutilJsonProgress
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nordicsemiconductor/pc-nrfconnect-shared",
3
- "version": "102.0.0",
3
+ "version": "104.0.0",
4
4
  "description": "Shared commodities for developing pc-nrfconnect-* packages",
5
5
  "repository": {
6
6
  "type": "git",
package/src/App/App.tsx CHANGED
@@ -24,11 +24,11 @@ import {
24
24
  } from '../Device/deviceSlice';
25
25
  import ErrorBoundary from '../ErrorBoundary/ErrorBoundary';
26
26
  import ErrorDialog from '../ErrorDialog/ErrorDialog';
27
+ import FeedbackPane, { FeedbackPaneProps } from '../Feedback/FeedbackPane';
27
28
  import FlashMessages from '../FlashMessage/FlashMessage';
28
29
  import LogViewer from '../Log/LogViewer';
29
30
  import logger from '../logging';
30
31
  import NavBar from '../NavBar/NavBar';
31
- import FeedbackPane, { FeedbackPaneProps } from '../Panes/FeedbackPane';
32
32
  import classNames from '../utils/classNames';
33
33
  import packageJson from '../utils/packageJson';
34
34
  import { getPersistedCurrentPane } from '../utils/persistentStore';
@@ -39,37 +39,36 @@ const Button: React.FC<ButtonProps> = ({
39
39
  title,
40
40
  size = 'sm',
41
41
  }) => (
42
- <div className={`tw-preflight ${className}`}>
43
- <button
44
- type="button"
45
- id={id}
46
- className={`${classNames(
47
- size === 'sm' && 'tw-h-6 tw-px-2 tw-text-xs',
48
- size === 'lg' && 'tw-h-8 tw-px-4 tw-text-sm',
49
- size === 'xl' && 'tw-h-8 tw-px-4 tw-text-base',
50
- variant === 'primary' &&
51
- 'tw-bg-nordicBlue tw-text-white active:enabled:tw-bg-nordicBlue-700',
52
- variant === 'secondary' &&
53
- 'tw-border tw-border-gray-700 tw-bg-white tw-text-gray-700 active:enabled:tw-bg-gray-50',
54
- variant === 'success' &&
55
- 'tw-bg-green tw-text-white active:enabled:tw-bg-green-700',
56
- variant === 'info' &&
57
- 'tw-bg-nordicBlue tw-text-white active:enabled:tw-bg-nordicBlue-700',
58
- variant === 'warning' &&
59
- 'tw-bg-orange tw-text-white active:enabled:tw-bg-orange-700',
60
- variant === 'danger' &&
61
- 'tw-bg-red tw-text-white active:enabled:tw-bg-red-700',
62
- variant === 'link-button' &&
63
- 'tw-border tw-border-nordicBlue tw-bg-white tw-text-nordicBlue active:enabled:tw-bg-gray-50',
64
- className
65
- )}`}
66
- disabled={disabled}
67
- onClick={onClick}
68
- title={title}
69
- >
70
- {children}
71
- </button>
72
- </div>
42
+ <button
43
+ type="button"
44
+ id={id}
45
+ className={`${classNames(
46
+ 'tw-preflight',
47
+ size === 'sm' && 'tw-h-6 tw-px-2 tw-text-xs',
48
+ size === 'lg' && 'tw-h-8 tw-px-4 tw-text-sm',
49
+ size === 'xl' && 'tw-h-8 tw-px-4 tw-text-base',
50
+ variant === 'primary' &&
51
+ 'tw-bg-nordicBlue tw-text-white active:enabled:tw-bg-nordicBlue-700',
52
+ variant === 'secondary' &&
53
+ 'tw-border tw-border-gray-700 tw-bg-white tw-text-gray-700 active:enabled:tw-bg-gray-50',
54
+ variant === 'success' &&
55
+ 'tw-bg-green tw-text-white active:enabled:tw-bg-green-700',
56
+ variant === 'info' &&
57
+ 'tw-bg-nordicBlue tw-text-white active:enabled:tw-bg-nordicBlue-700',
58
+ variant === 'warning' &&
59
+ 'tw-bg-orange tw-text-white active:enabled:tw-bg-orange-700',
60
+ variant === 'danger' &&
61
+ 'tw-bg-red tw-text-white active:enabled:tw-bg-red-700',
62
+ variant === 'link-button' &&
63
+ 'tw-border tw-border-nordicBlue tw-bg-white tw-text-nordicBlue active:enabled:tw-bg-gray-50',
64
+ className
65
+ )}`}
66
+ disabled={disabled}
67
+ onClick={onClick}
68
+ title={title}
69
+ >
70
+ {children}
71
+ </button>
73
72
  );
74
73
 
75
74
  export default Button;
@@ -82,7 +82,9 @@ Dialog.Body = ({ children }: { children: ReactNode }) => (
82
82
  );
83
83
 
84
84
  Dialog.Footer = ({ children }: { children: ReactNode }) => (
85
- <Modal.Footer>{children}</Modal.Footer>
85
+ <Modal.Footer className="tw-preflight tw-flex tw-flex-row-reverse tw-justify-start tw-gap-1 tw-border-none tw-p-4">
86
+ {children}
87
+ </Modal.Footer>
86
88
  );
87
89
 
88
90
  export interface DialogButtonProps {
@@ -40,12 +40,4 @@
40
40
  font-size: 14px;
41
41
  }
42
42
  }
43
-
44
- .modal-footer {
45
- padding: 16px;
46
- border: 0 transparent;
47
- flex-direction: row-reverse;
48
- justify-content: end;
49
- display: flex;
50
- }
51
43
  }
@@ -9,8 +9,8 @@ import React, { useMemo, useState } from 'react';
9
9
  import Button from '../Button/Button';
10
10
  import Dropdown, { DropdownItem } from '../Dropdown/Dropdown';
11
11
  import logger from '../logging';
12
- import { isDevelopment } from '../utils/environment';
13
- import packageJson from '../utils/packageJson';
12
+ import describeError from '../logging/describeError';
13
+ import sendFeedback from './sendFeedback';
14
14
 
15
15
  export interface FeedbackPaneProps {
16
16
  categories?: string[];
@@ -136,49 +136,18 @@ export default ({ categories }: FeedbackPaneProps) => {
136
136
  );
137
137
  };
138
138
 
139
- const formURL =
140
- isDevelopment === true
141
- ? 'https://formkeep.com/f/87deb409a565'
142
- : 'https://formkeep.com/f/36b394b92851';
143
-
144
139
  const handleFormData = async (
145
140
  feedback: string,
146
141
  setResponse: (response: boolean) => void,
147
142
  category?: string
148
143
  ) => {
149
- const data: Record<string, unknown> = {
150
- name: getAppName(),
151
- feedback,
152
- platform: process.platform,
153
- };
154
-
155
- if (category && category !== 'Select a category') {
156
- data.category = category;
157
- }
158
-
159
144
  try {
160
- const response = await fetch(formURL, {
161
- method: 'POST',
162
- body: JSON.stringify(data),
163
- headers: {
164
- 'Content-Type': 'application/json',
165
- enctype: 'multipart/form-data',
166
- },
167
- });
145
+ await sendFeedback(feedback, category);
168
146
 
169
- if (response.ok) {
170
- setResponse(true);
171
- return;
172
- }
173
-
174
- logger.error(
175
- `FeedbackForm: Server responded with status code ${response.status}`
176
- );
147
+ setResponse(true);
177
148
  } catch (error: unknown) {
178
149
  logger.error(
179
- `FeedbackForm: Could not send feedback. ${JSON.stringify(error)}`
150
+ `FeedbackForm: Could not send feedback. ${describeError(error)}`
180
151
  );
181
152
  }
182
153
  };
183
-
184
- const getAppName = () => packageJson().name;
@@ -0,0 +1,42 @@
1
+ /*
2
+ * Copyright (c) 2023 Nordic Semiconductor ASA
3
+ *
4
+ * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5
+ */
6
+
7
+ import { isDevelopment } from '../utils/environment';
8
+ import packageJson from '../utils/packageJson';
9
+
10
+ const formURL =
11
+ isDevelopment === true
12
+ ? 'https://formkeep.com/f/87deb409a565'
13
+ : 'https://formkeep.com/f/36b394b92851';
14
+
15
+ const getAppName = () => packageJson().name;
16
+
17
+ export default async (feedback: string, category?: string) => {
18
+ const data: Record<string, unknown> = {
19
+ name: getAppName(),
20
+ feedback,
21
+ platform: process.platform,
22
+ };
23
+
24
+ if (category && category !== 'Select a category') {
25
+ data.category = category;
26
+ }
27
+
28
+ const response = await fetch(formURL, {
29
+ method: 'POST',
30
+ body: JSON.stringify(data),
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ enctype: 'multipart/form-data',
34
+ },
35
+ });
36
+
37
+ if (response.ok) {
38
+ return;
39
+ }
40
+
41
+ throw new Error(`Server responded with status code ${response.status}`);
42
+ };