react-native-web-serial-api 0.0.3 → 0.1.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 (119) hide show
  1. package/README.md +23 -0
  2. package/TESTING.md +301 -0
  3. package/android/build.gradle +2 -2
  4. package/lib/commonjs/UsbSerial.js +58 -26
  5. package/lib/commonjs/UsbSerial.js.map +1 -1
  6. package/lib/commonjs/WebSerial.js +169 -57
  7. package/lib/commonjs/WebSerial.js.map +1 -1
  8. package/lib/commonjs/index.js +13 -1
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/lib/dom-exception.js +176 -0
  11. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  12. package/lib/commonjs/lib/event-target.js +138 -0
  13. package/lib/commonjs/lib/event-target.js.map +1 -0
  14. package/lib/commonjs/lib/promise.js +23 -0
  15. package/lib/commonjs/lib/promise.js.map +1 -0
  16. package/lib/commonjs/testing/index.js +70 -0
  17. package/lib/commonjs/testing/index.js.map +1 -0
  18. package/lib/commonjs/testing/install.js +54 -0
  19. package/lib/commonjs/testing/install.js.map +1 -0
  20. package/lib/commonjs/testing/serial-device.js +164 -0
  21. package/lib/commonjs/testing/serial-device.js.map +1 -0
  22. package/lib/commonjs/testing/virtual-serial.js +615 -0
  23. package/lib/commonjs/testing/virtual-serial.js.map +1 -0
  24. package/lib/commonjs/transport.js +61 -0
  25. package/lib/commonjs/transport.js.map +1 -0
  26. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  27. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  28. package/lib/typescript/src/WebSerial.d.ts +11 -2
  29. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +2 -0
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  33. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  34. package/lib/typescript/src/lib/event-target.d.ts +53 -0
  35. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  36. package/lib/typescript/src/lib/promise.d.ts +11 -0
  37. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  38. package/lib/typescript/src/testing/index.d.ts +23 -0
  39. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  40. package/lib/typescript/src/testing/install.d.ts +25 -0
  41. package/lib/typescript/src/testing/install.d.ts.map +1 -0
  42. package/lib/typescript/src/testing/serial-device.d.ts +127 -0
  43. package/lib/typescript/src/testing/serial-device.d.ts.map +1 -0
  44. package/lib/typescript/src/testing/virtual-serial.d.ts +205 -0
  45. package/lib/typescript/src/testing/virtual-serial.d.ts.map +1 -0
  46. package/lib/typescript/src/transport.d.ts +131 -0
  47. package/lib/typescript/src/transport.d.ts.map +1 -0
  48. package/package.json +38 -2
  49. package/src/UsbSerial.ts +65 -90
  50. package/src/WebSerial.ts +227 -88
  51. package/src/index.ts +2 -7
  52. package/src/lib/dom-exception.ts +129 -60
  53. package/src/lib/event-target.ts +46 -21
  54. package/src/lib/promise.ts +7 -7
  55. package/src/testing/index.ts +42 -0
  56. package/src/testing/install.ts +65 -0
  57. package/src/testing/serial-device.ts +193 -0
  58. package/src/testing/virtual-serial.ts +801 -0
  59. package/src/transport.ts +200 -0
  60. package/babel.config.js +0 -3
  61. package/biome.json +0 -35
  62. package/example/.watchmanconfig +0 -1
  63. package/example/App.tsx +0 -71
  64. package/example/__tests__/App.test.tsx +0 -16
  65. package/example/__tests__/connectEvents.test.tsx +0 -81
  66. package/example/__tests__/getPorts.test.tsx +0 -140
  67. package/example/android/app/build.gradle +0 -120
  68. package/example/android/app/debug.keystore +0 -0
  69. package/example/android/app/proguard-rules.pro +0 -10
  70. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  71. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  72. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  73. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  74. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  75. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  76. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  77. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  78. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  79. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  80. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  81. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  85. package/example/android/app/src/main/res/values/strings.xml +0 -3
  86. package/example/android/app/src/main/res/values/styles.xml +0 -9
  87. package/example/android/build.gradle +0 -22
  88. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  89. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  90. package/example/android/gradle.properties +0 -47
  91. package/example/android/gradlew +0 -252
  92. package/example/android/gradlew.bat +0 -94
  93. package/example/android/settings.gradle +0 -6
  94. package/example/app.json +0 -4
  95. package/example/babel.config.js +0 -21
  96. package/example/biome.json +0 -47
  97. package/example/deploy.sh +0 -11
  98. package/example/index.html +0 -26
  99. package/example/index.js +0 -9
  100. package/example/index.web.js +0 -8
  101. package/example/jest.config.js +0 -12
  102. package/example/metro.config.js +0 -58
  103. package/example/package-lock.json +0 -14510
  104. package/example/package.json +0 -48
  105. package/example/react-native.config.js +0 -17
  106. package/example/src/components/AppBar.tsx +0 -73
  107. package/example/src/components/Menu.tsx +0 -90
  108. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  109. package/example/src/screens/ConnectScreen.tsx +0 -195
  110. package/example/src/screens/DevicesScreen.tsx +0 -252
  111. package/example/src/screens/TerminalScreen.tsx +0 -572
  112. package/example/src/settings.ts +0 -43
  113. package/example/src/theme.ts +0 -19
  114. package/example/src/util/TextUtil.ts +0 -129
  115. package/example/tsconfig.json +0 -10
  116. package/example/vite.config.mjs +0 -55
  117. package/scripts/deploy-release.sh +0 -127
  118. package/tsconfig.build.json +0 -7
  119. package/tsconfig.json +0 -20
@@ -1,572 +0,0 @@
1
- import React from 'react';
2
- import {
3
- ScrollView,
4
- StyleSheet,
5
- Text,
6
- TextInput,
7
- TouchableOpacity,
8
- View,
9
- } from 'react-native';
10
- import type {SerialPort} from 'react-native-web-serial-api';
11
- import {AppBar} from '../components/AppBar';
12
- import {SingleChoiceDialog} from '../components/SingleChoiceDialog';
13
- import {type ConnectionSettings, toSerialOptions} from '../settings';
14
- import {colors} from '../theme';
15
- import * as TextUtil from '../util/TextUtil';
16
-
17
- type Span = {text: string; color: string; caret: boolean};
18
- type Connected = 'False' | 'Pending' | 'True';
19
- type SendBtnState = 'Idle' | 'Disabled';
20
-
21
- const NEWLINES = [
22
- {label: 'CR+LF', value: TextUtil.NEWLINE_CRLF},
23
- {label: 'LF', value: TextUtil.NEWLINE_LF},
24
- {label: '<none>', value: ''},
25
- ];
26
- const POLL_MS = 200;
27
- const MAX_SPANS = 2000;
28
-
29
- type Props = {
30
- port: SerialPort;
31
- settings: ConnectionSettings;
32
- onBack: () => void;
33
- };
34
-
35
- export function TerminalScreen({port, settings, onBack}: Props) {
36
- const [log, setLog] = React.useState<Span[]>([]);
37
- const [input, setInput] = React.useState('');
38
- const [connected, setConnected] = React.useState<Connected>('False');
39
- // HEX mode is on by default (per product requirement).
40
- const [hexEnabled, setHexEnabled] = React.useState(true);
41
- const [newline, setNewline] = React.useState<string>(TextUtil.NEWLINE_CRLF);
42
- const [showControlLines, setShowControlLines] = React.useState(false);
43
- const [sendBtn, setSendBtn] = React.useState<SendBtnState>('Idle');
44
- const [lines, setLines] = React.useState({
45
- rts: false,
46
- cts: false,
47
- dtr: false,
48
- dsr: false,
49
- cd: false,
50
- ri: false,
51
- });
52
- const [newlineDialog, setNewlineDialog] = React.useState(false);
53
-
54
- // refs read by long-lived async loops / callbacks
55
- const connectedRef = React.useRef<Connected>('False');
56
- const hexRef = React.useRef(hexEnabled);
57
- const newlineRef = React.useRef(newline);
58
- const showCLRef = React.useRef(showControlLines);
59
- const settingsRef = React.useRef(settings);
60
- const pendingNewlineRef = React.useRef(false);
61
- const rtsRef = React.useRef(false);
62
- const dtrRef = React.useRef(false);
63
- const writerRef =
64
- React.useRef<WritableStreamDefaultWriter<Uint8Array> | null>(null);
65
- const readerRef =
66
- React.useRef<ReadableStreamDefaultReader<Uint8Array> | null>(null);
67
- const pollRef = React.useRef<ReturnType<typeof setInterval> | null>(null);
68
- const scrollRef = React.useRef<ScrollView>(null);
69
- // Stable refs to the latest connect/teardown so the USB attach/detach
70
- // listeners (registered once) never call stale closures.
71
- const connectRef = React.useRef<() => Promise<void>>(async () => {});
72
- const teardownRef = React.useRef<(closePort: boolean) => Promise<void>>(
73
- async () => {},
74
- );
75
-
76
- React.useEffect(() => {
77
- hexRef.current = hexEnabled;
78
- }, [hexEnabled]);
79
- React.useEffect(() => {
80
- newlineRef.current = newline;
81
- }, [newline]);
82
- React.useEffect(() => {
83
- showCLRef.current = showControlLines;
84
- }, [showControlLines]);
85
- React.useEffect(() => {
86
- settingsRef.current = settings;
87
- }, [settings]);
88
-
89
- const append = React.useCallback((spans: Span[]) => {
90
- setLog(prev => {
91
- const next = prev.concat(spans);
92
- return next.length > MAX_SPANS
93
- ? next.slice(next.length - MAX_SPANS)
94
- : next;
95
- });
96
- }, []);
97
-
98
- const status = React.useCallback(
99
- (s: string) =>
100
- append([{text: `${s}\n`, color: colors.statusText, caret: false}]),
101
- [append],
102
- );
103
-
104
- // ported from TerminalFragment.receive()
105
- const receive = React.useCallback((data: Uint8Array) => {
106
- if (hexRef.current) {
107
- setLog(prev => {
108
- const next = prev.concat([
109
- {
110
- text: `${TextUtil.toHexString(data)}\n`,
111
- color: colors.receiveText,
112
- caret: false,
113
- },
114
- ]);
115
- return next.length > MAX_SPANS
116
- ? next.slice(next.length - MAX_SPANS)
117
- : next;
118
- });
119
- return;
120
- }
121
- let msg = TextUtil.bytesToString(data);
122
- const nl = newlineRef.current;
123
- let dropCaret = false;
124
- if (nl === TextUtil.NEWLINE_CRLF && msg.length > 0) {
125
- // don't show CR as ^M directly before LF
126
- msg = msg.split('\r\n').join('\n');
127
- if (pendingNewlineRef.current && msg[0] === '\n') {
128
- dropCaret = true; // CR/LF arrived in separate chunks -> drop the "^M"
129
- }
130
- pendingNewlineRef.current = msg[msg.length - 1] === '\r';
131
- }
132
- const runs = TextUtil.toCaretRuns(msg, nl.length !== 0).map(r => ({
133
- text: r.text,
134
- color: colors.receiveText,
135
- caret: r.caret,
136
- }));
137
- setLog(prev => {
138
- let next = prev;
139
- if (dropCaret) {
140
- const last = prev[prev.length - 1];
141
- if (last?.caret && last.text === '^M') {
142
- next = prev.slice(0, prev.length - 1);
143
- }
144
- }
145
- next = next.concat(runs);
146
- return next.length > MAX_SPANS
147
- ? next.slice(next.length - MAX_SPANS)
148
- : next;
149
- });
150
- }, []);
151
-
152
- const stopPoll = React.useCallback(() => {
153
- if (pollRef.current) {
154
- clearInterval(pollRef.current);
155
- pollRef.current = null;
156
- }
157
- }, []);
158
-
159
- const startPoll = React.useCallback(() => {
160
- stopPoll();
161
- if (!(showCLRef.current || settingsRef.current.flowControl !== 'none')) {
162
- return;
163
- }
164
- pollRef.current = setInterval(async () => {
165
- if (connectedRef.current !== 'True') {
166
- return;
167
- }
168
- try {
169
- const sig = await port.getSignals();
170
- if (showCLRef.current) {
171
- setLines({
172
- rts: rtsRef.current,
173
- dtr: dtrRef.current,
174
- cts: sig.clearToSend,
175
- dsr: sig.dataSetReady,
176
- cd: sig.dataCarrierDetect,
177
- ri: sig.ringIndicator,
178
- });
179
- }
180
- if (settingsRef.current.flowControl === 'hardware') {
181
- setSendBtn(sig.clearToSend ? 'Idle' : 'Disabled');
182
- }
183
- } catch {
184
- // ignore transient signal read errors (e.g. mid-detach)
185
- }
186
- }, POLL_MS);
187
- }, [port, stopPoll]);
188
-
189
- const readLoop = React.useCallback(async () => {
190
- try {
191
- // Non-null: readLoop only runs after open() resolves, so readable is set.
192
- const reader = port.readable!.getReader();
193
- readerRef.current = reader;
194
- while (true) {
195
- const {value, done} = await reader.read();
196
- if (done) {
197
- break;
198
- }
199
- if (value?.length) {
200
- receive(value);
201
- }
202
- }
203
- } catch {
204
- // Read errors are handled by the disconnect listener / teardown.
205
- } finally {
206
- try {
207
- readerRef.current?.releaseLock();
208
- } catch {}
209
- readerRef.current = null;
210
- }
211
- }, [port, receive]);
212
-
213
- /**
214
- * Release this screen's reader/writer + poll loop. Idempotent. When
215
- * `closePort` is true (intentional leave) we also close the port; on a
216
- * physical detach the library has already reset the port, so we must NOT
217
- * touch the device (closePort=false) — that's what previously produced the
218
- * repeating "USB get_status request failed".
219
- */
220
- const teardown = React.useCallback(
221
- async (closePort: boolean) => {
222
- connectedRef.current = 'False';
223
- setConnected('False');
224
- stopPoll(); // stop control-line polling FIRST (no more control transfers)
225
- setSendBtn('Idle');
226
- try {
227
- if (readerRef.current) {
228
- await readerRef.current.cancel().catch(() => {});
229
- }
230
- } catch {}
231
- try {
232
- if (writerRef.current) {
233
- await writerRef.current.close().catch(() => {});
234
- writerRef.current.releaseLock();
235
- }
236
- } catch {}
237
- writerRef.current = null;
238
- if (closePort) {
239
- try {
240
- await port.close();
241
- } catch {}
242
- }
243
- },
244
- [port, stopPoll],
245
- );
246
-
247
- React.useEffect(() => {
248
- teardownRef.current = teardown;
249
- }, [teardown]);
250
-
251
- const connect = React.useCallback(async () => {
252
- // Idempotent: ignore overlapping triggers. On re-attach the device may emit
253
- // several "connect" events (USB attach + permission-grant), and port.open()
254
- // itself awaits the permission dialog — so guard against re-entrancy that
255
- // would otherwise cause a double-open ("port already open") and leave us
256
- // stuck in the failed/closed state.
257
- if (connectedRef.current !== 'False') {
258
- return;
259
- }
260
- connectedRef.current = 'Pending';
261
- setConnected('Pending');
262
- try {
263
- await port.open(toSerialOptions(settingsRef.current));
264
- connectedRef.current = 'True';
265
- setConnected('True');
266
- status('connected');
267
- // Non-null: writable is guaranteed set once open() has resolved.
268
- writerRef.current = port.writable!.getWriter();
269
- readLoop();
270
- } catch (e: any) {
271
- status(`connection failed: ${e?.message ?? e}`);
272
- connectedRef.current = 'False';
273
- setConnected('False');
274
- }
275
- }, [port, status, readLoop]);
276
-
277
- React.useEffect(() => {
278
- connectRef.current = connect;
279
- }, [connect]);
280
-
281
- // Connect on mount; close the port on unmount (intentional leave).
282
- React.useEffect(() => {
283
- connectRef.current();
284
- return () => {
285
- teardownRef.current(true);
286
- };
287
- }, []);
288
-
289
- // Physical detach / re-attach. The library fires these on the SAME SerialPort
290
- // instance (it re-associates the new Android deviceId on re-attach). We keep
291
- // the log across reconnects.
292
- React.useEffect(() => {
293
- const onDisconnect = () => {
294
- if (connectedRef.current === 'False') {
295
- return;
296
- }
297
- status('device disconnected');
298
- teardownRef.current(false);
299
- };
300
- const onReconnect = () => {
301
- status('reconnected');
302
- connectRef.current();
303
- };
304
- port.addEventListener('disconnect', onDisconnect);
305
- port.addEventListener('connect', onReconnect);
306
- return () => {
307
- port.removeEventListener('disconnect', onDisconnect);
308
- port.removeEventListener('connect', onReconnect);
309
- };
310
- }, [port, status]);
311
-
312
- // start/stop the control-line poll loop
313
- React.useEffect(() => {
314
- if (
315
- connected === 'True' &&
316
- (showControlLines || settings.flowControl !== 'none')
317
- ) {
318
- startPoll();
319
- } else {
320
- stopPoll();
321
- }
322
- if (settings.flowControl === 'none') {
323
- setSendBtn('Idle');
324
- }
325
- }, [connected, showControlLines, settings.flowControl, startPoll, stopPoll]);
326
-
327
- const onChangeInput = (t: string) =>
328
- setInput(hexRef.current ? TextUtil.formatHexInput(t) : t);
329
-
330
- const doSend = async () => {
331
- if (connectedRef.current !== 'True') {
332
- status('not connected');
333
- return;
334
- }
335
- let data: Uint8Array;
336
- let echo: string;
337
- if (hexRef.current) {
338
- const body = TextUtil.fromHexString(input);
339
- const nl = TextUtil.stringToBytes(newlineRef.current);
340
- data = new Uint8Array(body.length + nl.length);
341
- data.set(body, 0);
342
- data.set(nl, body.length);
343
- echo = TextUtil.toHexString(data);
344
- } else {
345
- echo = input;
346
- data = TextUtil.stringToBytes(input + newlineRef.current);
347
- }
348
- append([{text: `${echo}\n`, color: colors.sendText, caret: false}]);
349
- try {
350
- await writerRef.current?.write(data);
351
- } catch (e: any) {
352
- status(`write failed: ${e?.message ?? e}`);
353
- }
354
- };
355
-
356
- const toggleRts = async () => {
357
- if (connectedRef.current !== 'True') {
358
- status('not connected');
359
- return;
360
- }
361
- const v = !rtsRef.current;
362
- rtsRef.current = v;
363
- setLines(l => ({...l, rts: v}));
364
- try {
365
- await port.setSignals({requestToSend: v});
366
- } catch (e: any) {
367
- status(`setRTS failed: ${e?.message ?? e}`);
368
- }
369
- };
370
-
371
- const toggleDtr = async () => {
372
- if (connectedRef.current !== 'True') {
373
- status('not connected');
374
- return;
375
- }
376
- const v = !dtrRef.current;
377
- dtrRef.current = v;
378
- setLines(l => ({...l, dtr: v}));
379
- try {
380
- await port.setSignals({dataTerminalReady: v});
381
- } catch (e: any) {
382
- status(`setDTR failed: ${e?.message ?? e}`);
383
- }
384
- };
385
-
386
- const sendBreak = async () => {
387
- if (connectedRef.current !== 'True') {
388
- status('not connected');
389
- return;
390
- }
391
- try {
392
- await port.setSignals({break: true});
393
- await new Promise<void>(r => setTimeout(r, 100));
394
- status('send BREAK');
395
- await port.setSignals({break: false});
396
- } catch (e: any) {
397
- status(`send BREAK failed: ${e?.message ?? e}`);
398
- }
399
- };
400
-
401
- const toggleHex = () => {
402
- setHexEnabled(h => !h);
403
- setInput('');
404
- };
405
-
406
- const menu = [
407
- {key: 'clear', title: 'Clear', onPress: () => setLog([])},
408
- {key: 'newline', title: 'Newline', onPress: () => setNewlineDialog(true)},
409
- {
410
- key: 'hex',
411
- title: 'HEX Mode',
412
- checkable: true,
413
- checked: hexEnabled,
414
- onPress: toggleHex,
415
- },
416
- {
417
- key: 'controlLines',
418
- title: 'Control Lines',
419
- checkable: true,
420
- checked: showControlLines,
421
- onPress: () => setShowControlLines(s => !s),
422
- },
423
- {key: 'sendBreak', title: 'Send BREAK', onPress: sendBreak},
424
- ];
425
-
426
- const sendDisabled = connected !== 'True' || sendBtn === 'Disabled';
427
-
428
- return (
429
- <View style={styles.container}>
430
- <AppBar title="Simple USB Terminal" onBack={onBack} menu={menu} />
431
-
432
- {showControlLines ? (
433
- <View style={styles.controlLines}>
434
- <CtrlButton label="RTS" on={lines.rts} onPress={toggleRts} />
435
- <CtrlButton label="CTS" on={lines.cts} readOnly />
436
- <View style={styles.ctrlGap} />
437
- <CtrlButton label="DTR" on={lines.dtr} onPress={toggleDtr} />
438
- <CtrlButton label="DSR" on={lines.dsr} readOnly />
439
- <View style={styles.ctrlGap} />
440
- <CtrlButton label="CD" on={lines.cd} readOnly />
441
- <CtrlButton label="RI" on={lines.ri} readOnly />
442
- </View>
443
- ) : null}
444
-
445
- <View style={styles.divider} />
446
-
447
- <ScrollView
448
- ref={scrollRef}
449
- style={styles.receive}
450
- contentContainerStyle={styles.receiveContent}
451
- onContentSizeChange={() =>
452
- scrollRef.current?.scrollToEnd({animated: false})
453
- }>
454
- <Text style={styles.mono}>
455
- {log.map((s, i) => (
456
- <Text
457
- key={i}
458
- style={[{color: s.color}, s.caret ? styles.caret : null]}>
459
- {s.text}
460
- </Text>
461
- ))}
462
- </Text>
463
- </ScrollView>
464
-
465
- <View style={styles.divider} />
466
-
467
- <View style={styles.sendRow}>
468
- <TextInput
469
- style={styles.input}
470
- value={input}
471
- onChangeText={onChangeInput}
472
- placeholder={hexEnabled ? 'HEX mode' : ''}
473
- placeholderTextColor={colors.textSecondary}
474
- autoCapitalize="none"
475
- autoCorrect={false}
476
- blurOnSubmit={false}
477
- onSubmitEditing={doSend}
478
- />
479
- <TouchableOpacity
480
- style={[styles.sendBtn, sendDisabled && styles.sendBtnDisabled]}
481
- onPress={doSend}
482
- disabled={sendDisabled}>
483
- <Text style={styles.sendIcon}>
484
- {sendBtn === 'Disabled' ? '⊘' : '➤'}
485
- </Text>
486
- </TouchableOpacity>
487
- </View>
488
-
489
- <SingleChoiceDialog
490
- visible={newlineDialog}
491
- title="Newline"
492
- options={NEWLINES}
493
- selected={newline}
494
- onSelect={setNewline}
495
- onClose={() => setNewlineDialog(false)}
496
- />
497
- </View>
498
- );
499
- }
500
-
501
- function CtrlButton({
502
- label,
503
- on,
504
- onPress,
505
- readOnly,
506
- }: {
507
- label: string;
508
- on: boolean;
509
- onPress?: () => void;
510
- readOnly?: boolean;
511
- }) {
512
- return (
513
- <TouchableOpacity
514
- style={[styles.ctrlBtn, on && styles.ctrlBtnOn]}
515
- onPress={onPress}
516
- disabled={readOnly}
517
- activeOpacity={readOnly ? 1 : 0.6}>
518
- <Text style={[styles.ctrlBtnText, readOnly && styles.ctrlBtnTextRo]}>
519
- {label}
520
- </Text>
521
- </TouchableOpacity>
522
- );
523
- }
524
-
525
- const styles = StyleSheet.create({
526
- container: {flex: 1, backgroundColor: colors.background},
527
- controlLines: {
528
- flexDirection: 'row',
529
- alignItems: 'center',
530
- padding: 6,
531
- flexWrap: 'wrap',
532
- },
533
- ctrlGap: {width: 8},
534
- ctrlBtn: {
535
- minWidth: 48,
536
- paddingVertical: 8,
537
- paddingHorizontal: 6,
538
- marginHorizontal: 2,
539
- borderWidth: 1,
540
- borderColor: colors.divider,
541
- borderRadius: 4,
542
- alignItems: 'center',
543
- },
544
- ctrlBtnOn: {backgroundColor: colors.accent, borderColor: colors.accent},
545
- ctrlBtnText: {fontSize: 13, fontWeight: '600', color: colors.text},
546
- ctrlBtnTextRo: {color: colors.textSecondary},
547
- divider: {height: 2, backgroundColor: colors.divider},
548
- receive: {flex: 1, backgroundColor: colors.terminalBackground},
549
- receiveContent: {padding: 8, flexGrow: 1, justifyContent: 'flex-end'},
550
- mono: {
551
- fontFamily: 'monospace',
552
- fontSize: 13,
553
- color: colors.receiveText,
554
- },
555
- caret: {backgroundColor: colors.caretBackground},
556
- sendRow: {flexDirection: 'row', alignItems: 'center', padding: 4},
557
- input: {
558
- flex: 1,
559
- fontSize: 15,
560
- color: colors.text,
561
- paddingHorizontal: 8,
562
- paddingVertical: 8,
563
- },
564
- sendBtn: {
565
- width: 44,
566
- height: 44,
567
- alignItems: 'center',
568
- justifyContent: 'center',
569
- },
570
- sendBtnDisabled: {opacity: 0.35},
571
- sendIcon: {fontSize: 22, color: colors.primary},
572
- });
@@ -1,43 +0,0 @@
1
- import type {SerialOptions} from 'react-native-web-serial-api';
2
-
3
- // Connection settings chosen on the form, passed straight into port.open().
4
- export type ConnectionSettings = {
5
- baudRate: number;
6
- dataBits: 7 | 8;
7
- stopBits: 1 | 2;
8
- parity: 'none' | 'even' | 'odd';
9
- flowControl: 'none' | 'hardware';
10
- };
11
-
12
- export const DEFAULT_SETTINGS: ConnectionSettings = {
13
- baudRate: 115200,
14
- dataBits: 8,
15
- stopBits: 1,
16
- parity: 'none',
17
- flowControl: 'none',
18
- };
19
-
20
- export const BAUD_RATES = [2400, 9600, 19200, 57600, 115200] as const;
21
- export const DATA_BITS = [7, 8] as const;
22
- export const STOP_BITS = [1, 2] as const;
23
- export const PARITIES = ['none', 'even', 'odd'] as const;
24
- export const FLOW_CONTROLS = ['none', 'hardware'] as const;
25
-
26
- export const FLOW_CONTROL_LABELS: Record<
27
- ConnectionSettings['flowControl'],
28
- string
29
- > = {
30
- none: '<none>',
31
- hardware: 'RTS/CTS',
32
- };
33
-
34
- /** Map the form settings onto the Web Serial open() options. */
35
- export function toSerialOptions(s: ConnectionSettings): SerialOptions {
36
- return {
37
- baudRate: s.baudRate,
38
- dataBits: s.dataBits,
39
- stopBits: s.stopBits,
40
- parity: s.parity,
41
- flowControl: s.flowControl,
42
- };
43
- }
@@ -1,19 +0,0 @@
1
- // Colors ported from SimpleUsbTerminal res/values/colors.xml
2
- export const colors = {
3
- primary: '#d84315',
4
- primaryDark: '#bf360c',
5
- accent: '#ff6e40',
6
-
7
- receiveText: '#00FF00',
8
- sendText: '#82CAFF',
9
- statusText: '#FFDB58',
10
- caretBackground: '#666666',
11
-
12
- // surrounding chrome
13
- background: '#ffffff',
14
- text: '#202020',
15
- textSecondary: '#707070',
16
- divider: '#d0d0d0',
17
- terminalBackground: '#000000',
18
- onPrimary: '#ffffff',
19
- };