@nordicsemiconductor/pc-nrfconnect-shared 120.0.0 → 122.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 (123) hide show
  1. package/Changelog.md +48 -0
  2. package/coverage/cobertura-coverage.xml +1358 -1215
  3. package/nrfutil/device/__mocks__/device.ts +2 -0
  4. package/nrfutil/device/batch.ts +18 -2
  5. package/nrfutil/device/common.ts +17 -38
  6. package/nrfutil/device/device.ts +2 -0
  7. package/nrfutil/device/deviceInfo.ts +70 -0
  8. package/nrfutil/device/erase.ts +2 -2
  9. package/nrfutil/device/firmwareRead.ts +2 -2
  10. package/nrfutil/device/getCoreInfo.ts +2 -2
  11. package/nrfutil/device/getFwInfo.ts +2 -2
  12. package/nrfutil/device/getProtectionStatus.ts +2 -2
  13. package/nrfutil/device/list.ts +4 -30
  14. package/nrfutil/device/program.ts +4 -4
  15. package/nrfutil/device/recover.ts +2 -2
  16. package/nrfutil/device/reset.ts +2 -2
  17. package/nrfutil/device/setMcuState.ts +2 -5
  18. package/nrfutil/device/setProtectionStatus.ts +2 -2
  19. package/nrfutil/index.ts +1 -1
  20. package/nrfutil/moduleVersion.ts +20 -0
  21. package/nrfutil/sandboxTypes.ts +14 -0
  22. package/package.json +1 -1
  23. package/src/About/DeviceCard.tsx +6 -5
  24. package/src/About/SupportCard.tsx +2 -2
  25. package/src/App/shared.scss +0 -1
  26. package/src/Device/DeviceSelector/BasicDeviceInfo.tsx +12 -6
  27. package/src/Device/DeviceSelector/DeviceList/AnimatedList.tsx +1 -1
  28. package/src/Device/DeviceSelector/DeviceList/Device.tsx +7 -5
  29. package/src/Device/DeviceSelector/DeviceList/DeviceList.tsx +23 -5
  30. package/src/Device/DeviceSelector/DeviceList/EditDeviceButtons.tsx +7 -6
  31. package/src/Device/DeviceSelector/DeviceList/MoreDeviceInfo.tsx +26 -30
  32. package/src/Device/DeviceSelector/DeviceList/edit-device-buttons.scss +1 -2
  33. package/src/Device/DeviceSelector/DeviceSelector.test.tsx +26 -46
  34. package/src/Device/DeviceSelector/DeviceSelector.tsx +40 -15
  35. package/src/Device/DeviceSelector/Favorite.tsx +10 -10
  36. package/src/Device/DeviceSelector/basic-device-info.scss +0 -12
  37. package/src/Device/deviceInfo/deviceInfo.ts +2 -2
  38. package/src/Device/deviceLister.ts +59 -50
  39. package/src/Device/deviceSetup.ts +28 -10
  40. package/src/Device/deviceSlice.ts +148 -84
  41. package/src/Device/jprogOperations.ts +56 -27
  42. package/src/Device/sdfuOperations.ts +25 -31
  43. package/src/Dropdown/Dropdown.tsx +2 -2
  44. package/src/InlineInput/InlineInput.tsx +25 -3
  45. package/src/InlineInput/NumberInlineInput.tsx +24 -8
  46. package/src/InlineInput/NumberInputWithDropdown.tsx +131 -0
  47. package/src/index.ts +3 -0
  48. package/src/logging/sendInitialLogMessages.ts +13 -9
  49. package/src/utils/logLibVersions.ts +1 -1
  50. package/src/utils/systemReport.ts +3 -3
  51. package/typings/generated/nrfutil/device/__mocks__/device.d.ts +1 -0
  52. package/typings/generated/nrfutil/device/__mocks__/device.d.ts.map +1 -1
  53. package/typings/generated/nrfutil/device/batch.d.ts +3 -2
  54. package/typings/generated/nrfutil/device/batch.d.ts.map +1 -1
  55. package/typings/generated/nrfutil/device/common.d.ts +6 -33
  56. package/typings/generated/nrfutil/device/common.d.ts.map +1 -1
  57. package/typings/generated/nrfutil/device/device.d.ts +13 -12
  58. package/typings/generated/nrfutil/device/device.d.ts.map +1 -1
  59. package/typings/generated/nrfutil/device/deviceInfo.d.ts +40 -0
  60. package/typings/generated/nrfutil/device/deviceInfo.d.ts.map +1 -0
  61. package/typings/generated/nrfutil/device/erase.d.ts +2 -2
  62. package/typings/generated/nrfutil/device/erase.d.ts.map +1 -1
  63. package/typings/generated/nrfutil/device/firmwareRead.d.ts +2 -2
  64. package/typings/generated/nrfutil/device/firmwareRead.d.ts.map +1 -1
  65. package/typings/generated/nrfutil/device/getCoreInfo.d.ts +2 -2
  66. package/typings/generated/nrfutil/device/getCoreInfo.d.ts.map +1 -1
  67. package/typings/generated/nrfutil/device/getFwInfo.d.ts +2 -2
  68. package/typings/generated/nrfutil/device/getFwInfo.d.ts.map +1 -1
  69. package/typings/generated/nrfutil/device/getProtectionStatus.d.ts +2 -2
  70. package/typings/generated/nrfutil/device/getProtectionStatus.d.ts.map +1 -1
  71. package/typings/generated/nrfutil/device/list.d.ts +3 -3
  72. package/typings/generated/nrfutil/device/list.d.ts.map +1 -1
  73. package/typings/generated/nrfutil/device/program.d.ts +2 -2
  74. package/typings/generated/nrfutil/device/program.d.ts.map +1 -1
  75. package/typings/generated/nrfutil/device/recover.d.ts +2 -2
  76. package/typings/generated/nrfutil/device/recover.d.ts.map +1 -1
  77. package/typings/generated/nrfutil/device/reset.d.ts +2 -2
  78. package/typings/generated/nrfutil/device/reset.d.ts.map +1 -1
  79. package/typings/generated/nrfutil/device/setMcuState.d.ts +2 -2
  80. package/typings/generated/nrfutil/device/setMcuState.d.ts.map +1 -1
  81. package/typings/generated/nrfutil/device/setProtectionStatus.d.ts +2 -2
  82. package/typings/generated/nrfutil/device/setProtectionStatus.d.ts.map +1 -1
  83. package/typings/generated/nrfutil/index.d.ts +2 -1
  84. package/typings/generated/nrfutil/index.d.ts.map +1 -1
  85. package/typings/generated/nrfutil/moduleVersion.d.ts +4 -0
  86. package/typings/generated/nrfutil/moduleVersion.d.ts.map +1 -1
  87. package/typings/generated/nrfutil/sandboxTypes.d.ts +9 -0
  88. package/typings/generated/nrfutil/sandboxTypes.d.ts.map +1 -1
  89. package/typings/generated/src/About/DeviceCard.d.ts.map +1 -1
  90. package/typings/generated/src/Device/DeviceSelector/BasicDeviceInfo.d.ts.map +1 -1
  91. package/typings/generated/src/Device/DeviceSelector/DeviceList/Device.d.ts +0 -1
  92. package/typings/generated/src/Device/DeviceSelector/DeviceList/Device.d.ts.map +1 -1
  93. package/typings/generated/src/Device/DeviceSelector/DeviceList/DeviceList.d.ts.map +1 -1
  94. package/typings/generated/src/Device/DeviceSelector/DeviceList/EditDeviceButtons.d.ts.map +1 -1
  95. package/typings/generated/src/Device/DeviceSelector/DeviceList/MoreDeviceInfo.d.ts +0 -1
  96. package/typings/generated/src/Device/DeviceSelector/DeviceList/MoreDeviceInfo.d.ts.map +1 -1
  97. package/typings/generated/src/Device/DeviceSelector/DeviceSelector.d.ts.map +1 -1
  98. package/typings/generated/src/Device/DeviceSelector/Favorite.d.ts +0 -1
  99. package/typings/generated/src/Device/DeviceSelector/Favorite.d.ts.map +1 -1
  100. package/typings/generated/src/Device/deviceLister.d.ts +1 -15
  101. package/typings/generated/src/Device/deviceLister.d.ts.map +1 -1
  102. package/typings/generated/src/Device/deviceSetup.d.ts +6 -5
  103. package/typings/generated/src/Device/deviceSetup.d.ts.map +1 -1
  104. package/typings/generated/src/Device/deviceSlice.d.ts +12 -15
  105. package/typings/generated/src/Device/deviceSlice.d.ts.map +1 -1
  106. package/typings/generated/src/Device/jprogOperations.d.ts.map +1 -1
  107. package/typings/generated/src/Device/sdfuOperations.d.ts.map +1 -1
  108. package/typings/generated/src/Dropdown/Dropdown.d.ts +2 -2
  109. package/typings/generated/src/Dropdown/Dropdown.d.ts.map +1 -1
  110. package/typings/generated/src/InlineInput/InlineInput.d.ts +3 -0
  111. package/typings/generated/src/InlineInput/InlineInput.d.ts.map +1 -1
  112. package/typings/generated/src/InlineInput/NumberInlineInput.d.ts +8 -4
  113. package/typings/generated/src/InlineInput/NumberInlineInput.d.ts.map +1 -1
  114. package/typings/generated/src/InlineInput/NumberInputWithDropdown.d.ts +19 -0
  115. package/typings/generated/src/InlineInput/NumberInputWithDropdown.d.ts.map +1 -0
  116. package/typings/generated/src/index.d.ts +3 -1
  117. package/typings/generated/src/index.d.ts.map +1 -1
  118. package/typings/generated/src/logging/sendInitialLogMessages.d.ts.map +1 -1
  119. package/typings/generated/src/utils/systemReport.d.ts +1 -1
  120. package/typings/generated/src/utils/systemReport.d.ts.map +1 -1
  121. package/src/Device/DeviceSelector/DeviceList/device.scss +0 -28
  122. package/src/Device/DeviceSelector/DeviceList/more-device-info.scss +0 -33
  123. package/src/Device/DeviceSelector/favorite.scss +0 -17
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import NrfutilDeviceLib from '../../nrfutil/device/device';
8
+ import { DeviceInfo } from '../../nrfutil/device/deviceInfo';
8
9
  import { FWInfo } from '../../nrfutil/device/getFwInfo';
9
10
  import logger from '../logging';
10
11
  import type { AppThunk, RootState } from '../store';
@@ -18,14 +19,15 @@ const getDeviceReadProtection = async (
18
19
  readbackProtection: 'unknown' | 'protected' | 'unprotected';
19
20
  }> => {
20
21
  try {
22
+ logger.info('Checking readback protection on device');
21
23
  const info = await NrfutilDeviceLib.getFwInfo(device);
22
24
  return {
23
25
  fwInfo: info,
24
26
  readbackProtection: 'unprotected',
25
27
  };
26
- } catch (error) {
27
- // @ts-expect-error Wrongly typed in device lib at the moment
28
- if (error.error_code === 24) {
28
+ } catch (e) {
29
+ const error = e as Error;
30
+ if (error.message.includes('NotAvailableBecauseProtection')) {
29
31
  logger.warn(
30
32
  'Readback protection on device enabled. Unable to verify that the firmware version is correct.'
31
33
  );
@@ -49,29 +51,50 @@ const programDeviceWithFw =
49
51
  ): AppThunk<RootState, Promise<Device>> =>
50
52
  async (dispatch, getState) => {
51
53
  try {
52
- if (getState().device.readbackProtection === 'protected') {
53
- logger.info('Recovering device');
54
- onProgress(0, 'Recovering device');
55
- await NrfutilDeviceLib.recover(device, 'Application');
54
+ const batch = NrfutilDeviceLib.batch();
55
+ if (getState().device.readbackProtection !== 'unprotected') {
56
+ batch.recover('Application', {
57
+ onTaskBegin: () =>
58
+ logger.info(`Device protected, recovering device`),
59
+ onTaskEnd: () => logger.info(`Finished recovering device.`),
60
+ onException: () =>
61
+ logger.error(`Failed to recover device.`),
62
+ onProgress: progress => {
63
+ onProgress(
64
+ progress.totalProgressPercentage,
65
+ 'Recovering device'
66
+ );
67
+ },
68
+ });
56
69
  }
57
70
 
58
- logger.debug(
59
- `Programming ${device.serialNumber} with ${selectedFw.fw}`
60
- );
61
- await NrfutilDeviceLib.program(
62
- device,
63
- selectedFw.fw,
64
- progress => {
71
+ batch.program(selectedFw.fw, 'Application', undefined, undefined, {
72
+ onTaskBegin: () => logger.info(`Programming device`),
73
+ onTaskEnd: () => logger.info(`Finished programming device.`),
74
+ onException: () => logger.error(`Failed to program device.`),
75
+ onProgress: progress => {
65
76
  onProgress(
66
77
  progress.totalProgressPercentage,
67
78
  progress.message ?? 'programming'
68
79
  );
69
80
  },
70
- 'Application'
71
- );
72
- logger.debug(`Resetting ${device.serialNumber}`);
73
- onProgress(100, 'Resetting device');
81
+ });
82
+
83
+ batch.reset('Application', undefined, {
84
+ onTaskBegin: () => logger.info(`Resting device`),
85
+ onTaskEnd: () => logger.info(`Finished resting device.`),
86
+ onException: () => logger.error(`Failed to reset device.`),
87
+ onProgress: progress => {
88
+ onProgress(
89
+ progress.totalProgressPercentage,
90
+ 'Resetting device'
91
+ );
92
+ },
93
+ });
94
+
95
+ await batch.run(device);
74
96
  await NrfutilDeviceLib.reset(device);
97
+ onProgress(0, 'Updating readback protection');
75
98
  const { readbackProtection } = await getDeviceReadProtection(
76
99
  device
77
100
  );
@@ -88,12 +111,18 @@ const programDeviceWithFw =
88
111
  return device;
89
112
  };
90
113
 
91
- const firmwareOptions = (device: Device, firmware: JprogEntry[]) =>
114
+ const firmwareOptions = (
115
+ device: Device,
116
+ firmware: JprogEntry[],
117
+ deviceInfo?: DeviceInfo
118
+ ) =>
92
119
  firmware.filter(fw => {
93
- const family = (device.jlink?.deviceFamily || '').toLowerCase();
94
- const deviceType = (device.jlink?.deviceVersion || '').toLowerCase();
120
+ const family = (device.devkit?.deviceFamily || '').toLowerCase();
121
+ const deviceType = (
122
+ deviceInfo?.jlink?.deviceVersion || ''
123
+ ).toLowerCase();
95
124
  const shortDeviceType = deviceType.split('_').shift();
96
- const boardVersion = (device.jlink?.boardVersion || '').toLowerCase();
125
+ const boardVersion = (device.devkit?.boardVersion || '').toLowerCase();
97
126
 
98
127
  const key = fw.key.toLowerCase();
99
128
  return (
@@ -108,11 +137,11 @@ export const jprogDeviceSetup = (
108
137
  firmware: JprogEntry[],
109
138
  needSerialport = false
110
139
  ): DeviceSetup => ({
111
- supportsProgrammingMode: (device: Device) =>
140
+ supportsProgrammingMode: device =>
112
141
  (needSerialport === !!device.traits.serialPorts || !needSerialport) &&
113
142
  !!device.traits.jlink,
114
- getFirmwareOptions: device =>
115
- firmwareOptions(device, firmware).map(firmwareOption => ({
143
+ getFirmwareOptions: (device, deviceInfo) =>
144
+ firmwareOptions(device, firmware, deviceInfo).map(firmwareOption => ({
116
145
  key: firmwareOption.key,
117
146
  description: firmwareOption.description,
118
147
  programDevice: onProgress => dispatch =>
@@ -120,8 +149,8 @@ export const jprogDeviceSetup = (
120
149
  programDeviceWithFw(device, firmwareOption, onProgress)
121
150
  ),
122
151
  })),
123
- isExpectedFirmware: (device: Device) => dispatch => {
124
- const fwVersions = firmwareOptions(device, firmware);
152
+ isExpectedFirmware: (device, deviceInfo) => dispatch => {
153
+ const fwVersions = firmwareOptions(device, firmware, deviceInfo);
125
154
  if (fwVersions.length === 0) {
126
155
  return Promise.resolve({
127
156
  device,
@@ -46,8 +46,9 @@ export const isDeviceInDFUBootloader = (device: Device) => {
46
46
  d.idProduct === NORDIC_DFU_PRODUCT_ID
47
47
  );
48
48
  }
49
- if (device.serialport) {
50
- const { vendorId, productId } = device.serialport;
49
+
50
+ if (device.serialPorts && device.serialPorts[0]) {
51
+ const { vendorId, productId } = device.serialPorts[0];
51
52
  return vendorId === '1915' && productId?.toUpperCase() === '521F';
52
53
  }
53
54
  return false;
@@ -410,9 +411,7 @@ const programInDFUBootloader =
410
411
  onFail: (reason?: unknown) => void
411
412
  ): AppThunk<RootState, Promise<void>> =>
412
413
  async dispatch => {
413
- logger.debug(
414
- `${device.serialNumber} on ${device.serialport?.comName} is now in DFU-Bootloader...`
415
- );
414
+ logger.debug(`${device.serialNumber} on is now in DFU-Bootloader...`);
416
415
  const { application, softdevice } = dfu;
417
416
  const params: Partial<InitPacket> = dfu.params || {};
418
417
 
@@ -545,9 +544,9 @@ export const sdfuDeviceSetup = (
545
544
  dfuFirmware: DfuEntry[],
546
545
  needSerialport = false
547
546
  ): DeviceSetup => ({
548
- supportsProgrammingMode: (device: Device) =>
549
- ((!!device.dfuTriggerVersion &&
550
- device.dfuTriggerVersion.semVer.length > 0) ||
547
+ supportsProgrammingMode: (device, deviceInfo) =>
548
+ ((!!deviceInfo?.dfuTriggerVersion &&
549
+ deviceInfo.dfuTriggerVersion.semVer.length > 0) ||
551
550
  isDeviceInDFUBootloader(device)) &&
552
551
  (needSerialport === device.traits.serialPorts || !needSerialport),
553
552
  getFirmwareOptions: device =>
@@ -559,31 +558,26 @@ export const sdfuDeviceSetup = (
559
558
  programDeviceWithFw(device, firmwareOption, onProgress)
560
559
  ),
561
560
  })),
562
- isExpectedFirmware: (device: Device) => () =>
563
- new Promise<{
564
- device: Device;
565
- validFirmware: boolean;
566
- }>(resolve => {
567
- if (device.dfuTriggerVersion) {
568
- logger.debug(
569
- 'Device has DFU trigger interface, the device is in Application mode'
570
- );
561
+ isExpectedFirmware: (device, deviceInfo) => () => {
562
+ if (deviceInfo?.dfuTriggerVersion) {
563
+ logger.debug(
564
+ 'Device has DFU trigger interface, the device is in Application mode'
565
+ );
571
566
 
572
- const { semVer } = device.dfuTriggerVersion;
567
+ const { semVer } = deviceInfo.dfuTriggerVersion;
573
568
 
574
- resolve({
575
- device,
576
- validFirmware:
577
- dfuFirmware.filter(fw => fw.semver === semVer).length >
578
- 0,
579
- });
580
- } else {
581
- resolve({
582
- device,
583
- validFirmware: false,
584
- });
585
- }
586
- }),
569
+ return Promise.resolve({
570
+ device,
571
+ validFirmware:
572
+ dfuFirmware.filter(fw => fw.semver === semVer).length > 0,
573
+ });
574
+ }
575
+
576
+ return Promise.resolve({
577
+ device,
578
+ validFirmware: false,
579
+ });
580
+ },
587
581
  tryToSwitchToApplicationMode: device => dispatch =>
588
582
  new Promise<Device>((resolve, reject) => {
589
583
  dispatch(switchToApplicationMode(device, resolve, reject));
@@ -11,9 +11,9 @@ import classNames from '../utils/classNames';
11
11
 
12
12
  import styles from './Dropdown.module.scss';
13
13
 
14
- export interface DropdownItem {
14
+ export interface DropdownItem<T = string> {
15
15
  label: React.ReactNode;
16
- value: string;
16
+ value: T;
17
17
  }
18
18
 
19
19
  export interface DropdownProps {
@@ -57,6 +57,9 @@ interface Props {
57
57
  onKeyboardIncrementAction?: () => string;
58
58
  onKeyboardDecrementAction?: () => string;
59
59
  className?: string;
60
+ textAlignLeft?: boolean;
61
+ onValidityChanged?: (validity: boolean) => void;
62
+ preventDefaultInvalidStyle?: boolean;
60
63
  }
61
64
 
62
65
  const InlineInput = React.forwardRef<HTMLInputElement, Props>(
@@ -70,19 +73,28 @@ const InlineInput = React.forwardRef<HTMLInputElement, Props>(
70
73
  onKeyboardIncrementAction = () => externalValue,
71
74
  onKeyboardDecrementAction = () => externalValue,
72
75
  className = '',
76
+ textAlignLeft = false,
77
+ onValidityChanged,
78
+ preventDefaultInvalidStyle,
73
79
  },
74
80
  ref
75
81
  ) => {
76
82
  const [internalValue, setInternalValue] = useState(externalValue);
77
83
  const [initialValue, setInitialValue] = useState(externalValue);
78
84
  useSynchronisationIfChangedFromOutside(externalValue, setInternalValue);
85
+
79
86
  const onChangeIfValid = (newValue: string) => {
80
87
  if (disabled) {
81
88
  return;
82
89
  }
83
90
 
91
+ const validity = isValid(newValue);
92
+ if (onValidityChanged != null) {
93
+ onValidityChanged(validity);
94
+ }
95
+
84
96
  setInternalValue(newValue);
85
- if (isValid(newValue)) {
97
+ if (validity) {
86
98
  if (externalValue !== newValue) {
87
99
  onChange(newValue);
88
100
  }
@@ -94,7 +106,9 @@ const InlineInput = React.forwardRef<HTMLInputElement, Props>(
94
106
  return;
95
107
  }
96
108
 
97
- if (isValid(internalValue)) {
109
+ const validity = isValid(internalValue);
110
+
111
+ if (validity) {
98
112
  if (initialValue !== internalValue) {
99
113
  setInitialValue(internalValue);
100
114
  onChangeComplete(internalValue);
@@ -102,6 +116,11 @@ const InlineInput = React.forwardRef<HTMLInputElement, Props>(
102
116
  } else {
103
117
  setInternalValue(externalValue);
104
118
  }
119
+
120
+ // Should always end up valid in this case
121
+ if (onValidityChanged != null) {
122
+ onValidityChanged(true);
123
+ }
105
124
  };
106
125
 
107
126
  const onChangeCompleteIfValid = (event: React.KeyboardEvent) => {
@@ -144,8 +163,11 @@ const InlineInput = React.forwardRef<HTMLInputElement, Props>(
144
163
  type="text"
145
164
  className={classNames(
146
165
  'inline-input',
147
- isValid(internalValue) || 'invalid',
166
+ preventDefaultInvalidStyle
167
+ ? null
168
+ : isValid(internalValue) || 'invalid',
148
169
  disabled && 'disabled',
170
+ textAlignLeft && 'tw-pl-2 tw-text-left',
149
171
  className
150
172
  )}
151
173
  size={
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5
5
  */
6
6
 
7
- import React, { FC } from 'react';
7
+ import React from 'react';
8
8
 
9
9
  import { isFactor } from '../Slider/factor';
10
10
  import {
@@ -19,12 +19,16 @@ import InlineInput from './InlineInput';
19
19
 
20
20
  import './number-inline-input.scss';
21
21
 
22
- export interface Props {
22
+ interface NumberInlineInput {
23
23
  disabled?: boolean;
24
24
  value: number;
25
25
  range: RangeOrValues;
26
+ className?: string;
26
27
  onChange: (value: number) => void;
27
28
  onChangeComplete?: (value: number) => void;
29
+ textAlignLeft?: boolean;
30
+ onValidityChanged?: (validity: boolean) => void;
31
+ preventDefaultInvalidStyle?: boolean;
28
32
  }
29
33
 
30
34
  const isInValues = (value: number, values: Values) => values.includes(value);
@@ -77,21 +81,24 @@ const changeValueStepwise = (
77
81
  return nextValue != null ? nextValue : current;
78
82
  };
79
83
 
80
- const NumberInlineInput: FC<Props> = ({
84
+ export default ({
81
85
  disabled,
82
86
  value,
83
87
  range,
88
+ className,
84
89
  onChange,
85
90
  onChangeComplete = () => {},
86
- }) => {
91
+ textAlignLeft,
92
+ onValidityChanged,
93
+ preventDefaultInvalidStyle,
94
+ }: NumberInlineInput) => {
87
95
  useValidatedRangeOrValues(range);
88
96
 
89
97
  return (
90
98
  <InlineInput
91
- className="number-inline-input"
99
+ className={`${className} number-inline-input`}
92
100
  disabled={disabled}
93
101
  value={String(value)}
94
- isValid={newValue => isValid(Number(newValue), range)}
95
102
  onChange={newValue => onChange(Number(newValue))}
96
103
  onChangeComplete={newValue => onChangeComplete(Number(newValue))}
97
104
  onKeyboardIncrementAction={() =>
@@ -100,8 +107,17 @@ const NumberInlineInput: FC<Props> = ({
100
107
  onKeyboardDecrementAction={() =>
101
108
  changeValueStepwise(value, range, -1).toString()
102
109
  }
110
+ textAlignLeft={textAlignLeft}
111
+ isValid={newValue => {
112
+ const validity = isValid(Number(newValue), range);
113
+ if (onValidityChanged != null) {
114
+ // Then propagate the validity back to parent
115
+ onValidityChanged(validity);
116
+ }
117
+ return validity;
118
+ }}
119
+ onValidityChanged={onValidityChanged}
120
+ preventDefaultInvalidStyle={preventDefaultInvalidStyle}
103
121
  />
104
122
  );
105
123
  };
106
-
107
- export default NumberInlineInput;
@@ -0,0 +1,131 @@
1
+ /*
2
+ * Copyright (c) 2023 Nordic Semiconductor ASA
3
+ *
4
+ * SPDX-License-Identifier: LicenseRef-Nordic-4-Clause
5
+ */
6
+
7
+ import React, { useState } from 'react';
8
+
9
+ import { DropdownItem } from '../Dropdown/Dropdown';
10
+ import { RangeOrValues } from '../Slider/range';
11
+ import classNames from '../utils/classNames';
12
+ import NumberInlineInput from './NumberInlineInput';
13
+
14
+ import styles from '../Dropdown/Dropdown.module.scss';
15
+
16
+ export type NumberDropdownItem = DropdownItem<number>;
17
+
18
+ interface DropdownProps {
19
+ id?: string;
20
+ label?: React.ReactNode;
21
+ items: NumberDropdownItem[];
22
+ value: number;
23
+ onChange: (value: number) => void;
24
+ range: RangeOrValues;
25
+ numItemsBeforeScroll?: number;
26
+ className?: string;
27
+ disabled?: boolean;
28
+ title?: string;
29
+ }
30
+
31
+ export default ({
32
+ id,
33
+ label,
34
+ items,
35
+ value,
36
+ onChange,
37
+ range,
38
+ numItemsBeforeScroll = 0,
39
+ className = '',
40
+ disabled = false,
41
+ title,
42
+ }: DropdownProps) => {
43
+ const [isActive, setIsActive] = useState(false);
44
+ const [inlineValue, setInlineValue] = useState(value);
45
+ const [isValid, setIsValid] = useState(true);
46
+
47
+ const setNewValue = (newValue: number) => {
48
+ setInlineValue(newValue);
49
+ onChange(newValue);
50
+ setIsActive(false);
51
+ };
52
+ const onClickItem = (item: NumberDropdownItem) => {
53
+ if (item.value != null && typeof item.value === 'number') {
54
+ setNewValue(item.value);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <div
60
+ className={`tw-preflight tw-relative tw-w-full ${className}`}
61
+ onBlur={event => {
62
+ if (!event.currentTarget.contains(event.relatedTarget)) {
63
+ setIsActive(false);
64
+ }
65
+ }}
66
+ title={title}
67
+ >
68
+ <div className="tw-mb-1 tw-text-xs">{label}</div>
69
+ <div className="tw-flex tw-h-8 tw-w-full">
70
+ <NumberInlineInput
71
+ value={inlineValue}
72
+ range={range}
73
+ onChange={val => setInlineValue(val)}
74
+ onChangeComplete={val => setNewValue(val)}
75
+ className={`tw-x-2 tw-h-full tw-w-full tw-bg-gray-700 tw-text-white tw-underline tw-underline-offset-2 ${
76
+ isValid ? 'tw-decoration-white' : 'tw-decoration-red'
77
+ }`}
78
+ textAlignLeft
79
+ onValidityChanged={setIsValid}
80
+ preventDefaultInvalidStyle
81
+ />
82
+ <button
83
+ id={id}
84
+ type="button"
85
+ className="tw-overflow-hidden tw-border-b tw-border-solid tw-border-b-gray-200 tw-bg-gray-700 tw-px-2 tw-text-white"
86
+ onClick={() => setIsActive(!isActive)}
87
+ disabled={disabled}
88
+ >
89
+ <span
90
+ className={`mdi mdi-chevron-down tw-text-lg ${classNames(
91
+ isActive && 'tw-rotate-180'
92
+ )}`}
93
+ />
94
+ </button>
95
+ </div>
96
+ {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- We need an interactive handler as described below */}
97
+ <div
98
+ onMouseDown={ev => {
99
+ // To prevent the dropdown from closing when users click on the scrollbar of the items
100
+ ev.preventDefault();
101
+ }}
102
+ style={
103
+ numItemsBeforeScroll > 0
104
+ ? {
105
+ maxHeight: `${numItemsBeforeScroll * 24}px`,
106
+ }
107
+ : {}
108
+ }
109
+ data-height={
110
+ numItemsBeforeScroll > 0 &&
111
+ items.length > numItemsBeforeScroll
112
+ }
113
+ className={`tw-text-while tw-absolute tw-right-0 tw-z-10 tw-w-full tw-bg-gray-700 tw-p-0 ${classNames(
114
+ styles.content,
115
+ !isActive && 'tw-hidden'
116
+ )}`}
117
+ >
118
+ {items.map(item => (
119
+ <button
120
+ type="button"
121
+ className="tw-bg-transparent tw-clear-both tw-block tw-h-6 tw-w-full tw-whitespace-nowrap tw-border-0 tw-px-2 tw-py-1 tw-text-left tw-font-normal tw-text-white hover:tw-bg-gray-600 focus:tw-bg-gray-600"
122
+ key={item.value}
123
+ onClick={() => onClickItem(item)}
124
+ >
125
+ {item.label}
126
+ </button>
127
+ ))}
128
+ </div>
129
+ </div>
130
+ );
131
+ };
package/src/index.ts CHANGED
@@ -48,6 +48,8 @@ export { Group, CollapsibleGroup } from './SidePanel/Group';
48
48
  export { default as InlineInput } from './InlineInput/InlineInput';
49
49
  export { default as NumberInlineInput } from './InlineInput/NumberInlineInput';
50
50
  export { default as NumberInputSliderWithUnit } from './NumberInputWithSlider/NumberInputSliderWithUnit';
51
+ export { default as NumberInputWithDropdown } from './InlineInput/NumberInputWithDropdown';
52
+ export type { NumberDropdownItem } from './InlineInput/NumberInputWithDropdown';
51
53
 
52
54
  export { default as Spinner } from './Spinner/Spinner';
53
55
 
@@ -101,6 +103,7 @@ export { sdfuDeviceSetup } from './Device/sdfuOperations';
101
103
 
102
104
  export {
103
105
  selectedDevice,
106
+ selectedDeviceInfo,
104
107
  getReadbackProtection,
105
108
  persistSerialPortOptions,
106
109
  type Device,
@@ -7,7 +7,7 @@
7
7
  import { inMain as appDetails } from '../../ipc/appDetails';
8
8
  import NrfutilDeviceLib from '../../nrfutil/device/device';
9
9
  import {
10
- describeVersion,
10
+ getExpectedVersion,
11
11
  resolveModuleVersion,
12
12
  } from '../../nrfutil/moduleVersion';
13
13
  import { getAppDataDir } from '../utils/appDirs';
@@ -34,7 +34,6 @@ export default async () => {
34
34
  installed,
35
35
  homeDir,
36
36
  tmpDir,
37
- bundledJlink,
38
37
  source,
39
38
  } = details;
40
39
 
@@ -47,16 +46,21 @@ export default async () => {
47
46
  logger.debug(`HomeDir: ${homeDir}`);
48
47
  logger.debug(`TmpDir: ${tmpDir}`);
49
48
 
50
- if (bundledJlink) {
51
- const dependencies = (await NrfutilDeviceLib.getModuleVersion())
52
- .dependencies;
53
- const jlinkVersion = resolveModuleVersion('JlinkARM', dependencies);
49
+ const dependencies = (await NrfutilDeviceLib.getModuleVersion())
50
+ .dependencies;
51
+ const jlinkVersion = resolveModuleVersion('JlinkARM', dependencies);
54
52
 
55
- if (!describeVersion(jlinkVersion).includes(bundledJlink)) {
56
- logger.info(
57
- `Installed JLink version does not match the provided version (${bundledJlink})`
53
+ if (jlinkVersion) {
54
+ const result = getExpectedVersion(jlinkVersion);
55
+ if (!result.isExpectedVersion) {
56
+ logger.warn(
57
+ `Installed JLink version does not match the expected version (${result.expectedVersion})`
58
58
  );
59
59
  }
60
+ } else {
61
+ logger.warn(
62
+ `JLink is not installed. Please install JLink from: https://www.segger.com/downloads/jlink`
63
+ );
60
64
  }
61
65
 
62
66
  if (!udevInstalled()) {
@@ -91,7 +91,7 @@ export default async () => {
91
91
  `It looks like you have installed JLink using ${JLinkInstallerVersion}, but currently we only support their Universal Installer for your system.`
92
92
  );
93
93
  logger.warn(
94
- `Please install JLink: https://www.segger.com/downloads/jlink/JLink_MacOSX_V780c_universal.pkg`
94
+ `Please install JLink: https://www.segger.com/downloads/jlink/JLink_MacOSX_V788j_universal.pkg`
95
95
  );
96
96
  }
97
97
  }
@@ -89,7 +89,7 @@ const allDevicesReport = (allDevices: Device[] = []) => [
89
89
  ...allDevices.map(
90
90
  d =>
91
91
  ` - ${d.serialNumber} ${
92
- d.jlink?.boardVersion || ''
92
+ d.devkit?.boardVersion || ''
93
93
  }: ${d.serialPorts?.map(s => s.comName).join(', ')}`
94
94
  ),
95
95
  '',
@@ -129,14 +129,14 @@ export const generateSystemReport = async (
129
129
  export default async (
130
130
  allDevices: Device[],
131
131
  currentSerialNumber: string,
132
- currentDevice: Device | null
132
+ currentDevice?: Device
133
133
  ) => {
134
134
  logger.info('Generating system report...');
135
135
  const timestamp = new Date().toISOString().replace(/:/g, '-');
136
136
  const report = await generateSystemReport(
137
137
  timestamp,
138
138
  allDevices,
139
- currentDevice ?? undefined,
139
+ currentDevice,
140
140
  currentSerialNumber
141
141
  );
142
142
 
@@ -2,6 +2,7 @@
2
2
  declare const _default: {
3
3
  program: jest.Mock<any, any, any>;
4
4
  programBuffer: jest.Mock<any, any, any>;
5
+ deviceInfo: jest.Mock<Promise<{}>, [], any>;
5
6
  erase: jest.Mock<any, any, any>;
6
7
  recover: jest.Mock<any, any, any>;
7
8
  reset: jest.Mock<any, any, any>;
@@ -1 +1 @@
1
- {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../../../../nrfutil/device/__mocks__/device.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAyBA,wBAiBE"}
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../../../../nrfutil/device/__mocks__/device.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA0BA,wBAkBE"}
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { TaskEnd } from '../sandboxTypes';
3
3
  import { Callbacks } from './batchTypes';
4
- import { DeviceCore, DeviceTraits, NrfutilDeviceWithSerialnumber, ResetKind } from './common';
4
+ import { DeviceCore, DeviceTraits, NrfutilDevice, ResetKind } from './common';
5
5
  import { DeviceCoreInfo } from './getCoreInfo';
6
6
  import { FWInfo } from './getFwInfo';
7
7
  import { GetProtectionStatusResult } from './getProtectionStatus';
@@ -10,6 +10,7 @@ export declare class Batch {
10
10
  private operationBatchGeneration;
11
11
  private collectOperations;
12
12
  private enqueueBatchOperationObject;
13
+ getDeviceInfo(core: DeviceCore, callbacks?: Callbacks<FWInfo>): this;
13
14
  erase(core: DeviceCore, callbacks?: Callbacks): this;
14
15
  firmwareRead(core: DeviceCore, callbacks?: Callbacks<Buffer>): this;
15
16
  getCoreInfo(core: DeviceCore, callbacks?: Callbacks<DeviceCoreInfo>): this;
@@ -19,6 +20,6 @@ export declare class Batch {
19
20
  recover(core: DeviceCore, callbacks?: Callbacks): this;
20
21
  reset(core: DeviceCore, reset?: ResetKind, callbacks?: Callbacks): this;
21
22
  collect(count: number, callback: (completedTasks: TaskEnd<unknown>[]) => void): this;
22
- run(device: NrfutilDeviceWithSerialnumber, controller?: AbortController | undefined): Promise<unknown[]>;
23
+ run(device: NrfutilDevice, controller?: AbortController | undefined): Promise<unknown[]>;
23
24
  }
24
25
  //# sourceMappingURL=batch.d.ts.map