Skip to content

Commit 04b5f49

Browse files
authoredAug 20, 2020
v5.0.0 (#684)
1 parent 57d615d commit 04b5f49

13 files changed

+329
-67
lines changed
 

‎CHANGELOG.md

+45-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
# v5.0.0
2+
3+
This version brings with it several new major features to react-console-emulator.
4+
5+
### Breaking changes
6+
7+
The recommended method for terminal input text styling has been moved. Terminal input text should now be styled using the `inputTextStyle` and `inputTextClassName` props. **Note:** If using the newly-introduced styling persistence options for command echoes, leaving text styling in `inputStyle` or `inputClassName` may cause unexpected styling bugs.
8+
9+
The `styleEchoBack` prop no longer accepts boolean input. Instead, one should either omit it (For default behaviour) or define one of the string types detailed in the [options](docs/CONFIG.md#options).
10+
11+
### Main changes
12+
13+
Async command executor support is here! You can now use async functions as command handlers and their outputs will function identically to synchronous functions.
14+
15+
Added the ability to hide the prompt area entirely when the terminal is disabled either manually through the `disabled` prop or on process if `disableOnProcess` is enabled. ([#639](https://github.com/linuswillner/react-console-emulator/issues/639))
16+
17+
Added the ability to set the terminal to read-only mode with the `readOnly` prop, which disables input entirely and hides the prompt area. ([#639](https://github.com/linuswillner/react-console-emulator/issues/639))
18+
19+
Added the ability to lock terminal output to updating the last line only. This could be useful for, among other things, creating progress bars. ([#638](https://github.com/linuswillner/react-console-emulator/issues/638))
20+
21+
### Other changes
22+
23+
Fixed a bug where newline parsing did not recognise newline literals properly. ([#632](https://github.com/linuswillner/react-console-emulator/issues/632))
24+
25+
Full run-down of prop changes:
26+
```diff
27+
+ inputTextStyle
28+
+ inputTextClassName
29+
+ hidePromptWhenDisabled
30+
+ readOnly
31+
+ locked
32+
```
33+
134
# v4.0.1
235

336
Correct eslint-config-standard-react getting installed as a production dependency as opposed to a development one.
@@ -16,14 +49,14 @@ Renamed `noAutomaticStdout` prop to `noEchoBack` for added clarity.
1649

1750
### Main changes
1851

19-
Terminal message styling is here! You can now re-style the messages output by the terminal (Including echoes, optionally with the `styleEchoBack` prop) using the `messageStyle` and `messageClassName` props ([#518](https://github.com/linuswillner/react-console-emulator/issues/518)).
52+
Terminal message styling is here! You can now re-style the messages output by the terminal (Including echoes, optionally with the `styleEchoBack` prop) using the `messageStyle` and `messageClassName` props. ([#518](https://github.com/linuswillner/react-console-emulator/issues/518))
2053

21-
JSX prompt labels! Prompt labels now support elements instead of just plain old strings ([#210](https://github.com/linuswillner/react-console-emulator/issues/210)).
54+
JSX prompt labels! Prompt labels now support elements instead of just plain old strings. ([#210](https://github.com/linuswillner/react-console-emulator/issues/210))
2255

23-
Newline parsing is now possible! The terminal can now parse newline characters in terminal messages - anything with a \n character in it will be rendered as a separate line in the response message. This does of course not apply to command back-echoes. This behaviour can also be disabled, if desired, using the `noNewlineParsing` prop ([#519](https://github.com/linuswillner/react-console-emulator/issues/519)).
56+
Newline parsing is now possible! The terminal can now parse newline characters in terminal messages - anything with a \n character in it will be rendered as a separate line in the response message. This does of course not apply to command back-echoes. This behaviour can also be disabled, if desired, using the `noNewlineParsing` prop. ([#519](https://github.com/linuswillner/react-console-emulator/issues/519))
2457

2558

26-
Case-insensitive command matching! You can now supply the `ignoreCommandCase` prop to allow matching commands even when their casing is not correct. Do note that for security reasons, enabling case-insensitive command matching restricts command names to letters, numbers and dashes/underscores ([#415](https://github.com/linuswillner/react-console-emulator/issues/415)).
59+
Case-insensitive command matching! You can now supply the `ignoreCommandCase` prop to allow matching commands even when their casing is not correct. Do note that for security reasons, enabling case-insensitive command matching restricts command names to letters, numbers and dashes/underscores. ([#415](https://github.com/linuswillner/react-console-emulator/issues/415))
2760

2861
### Other changes
2962

@@ -47,19 +80,19 @@ A full run-down of the prop changes is as follows:
4780

4881
# v3.0.4
4982

50-
Fixed a bug preventing users from selecting text in the terminal output ([#414](https://github.com/linuswillner/react-console-emulator/issues/414)).
83+
Fixed a bug preventing users from selecting text in the terminal output. ([#414](https://github.com/linuswillner/react-console-emulator/issues/414))
5184

5285
# v3.0.3
5386

54-
Removed redundant `stringify-object` dependency to properly enable command re-validation based on raw objects alone. This was supposed to have been fixed in 3.0.2, but due to a mishap the old validation was left dangling. This has now been fixed ([#411](https://github.com/linuswillner/react-console-emulator/issues/411)).
87+
Removed redundant `stringify-object` dependency to properly enable command re-validation based on raw objects alone. This was supposed to have been fixed in 3.0.2, but due to a mishap the old validation was left dangling. This has now been fixed. ([#411](https://github.com/linuswillner/react-console-emulator/issues/411))
5588

5689
# v3.0.2
5790

58-
Fixed command re-validation reliability issues relating to source-identical commands ([#35](https://github.com/linuswillner/react-console-emulator/issues/35)).
91+
Fixed command re-validation reliability issues relating to source-identical commands. ([#35](https://github.com/linuswillner/react-console-emulator/issues/35))
5992

6093
# v3.0.1
6194

62-
Fixed input outline showing on Safari ([#258](https://github.com/linuswillner/react-console-emulator/pull/258)) ([Herve07h22](https://github.com/Herve07h22)).
95+
Fixed input outline showing on Safari. ([Herve07h22](https://github.com/Herve07h22)) ([#258](https://github.com/linuswillner/react-console-emulator/pull/258))
6396

6497
# v3.0.0
6598

@@ -119,19 +152,19 @@ Enabled module transpilation to widen the support amongst Node versions for dist
119152

120153
# v1.7.2
121154

122-
Re-added Babel into build flow in a different format to improve compatibility ([#39, comment](https://github.com/linuswillner/react-console-emulator/issues/39#issuecomment-440973765)).
155+
Re-added Babel into build flow in a different format to improve compatibility. ([#39, comment](https://github.com/linuswillner/react-console-emulator/issues/39#issuecomment-440973765))
123156

124157
# v1.7.1
125158

126-
Removed Babel from the build flow in order to allow the inclusion of the helper files ([#39](https://github.com/linuswillner/react-console-emulator/issues/39)).
159+
Removed Babel from the build flow in order to allow the inclusion of the helper files. ([#39](https://github.com/linuswillner/react-console-emulator/issues/39))
127160

128161
# v1.7.0
129162

130163
Internal refactoring for better maintainability.
131164

132-
Added default-enabled automatic scrolling to the bottom of the terminal when a command is run ([#36](https://github.com/linuswillner/react-console-emulator/issues/36)).
165+
Added default-enabled automatic scrolling to the bottom of the terminal when a command is run. ([#36](https://github.com/linuswillner/react-console-emulator/issues/36))
133166

134-
Added command callback support to run a function each time a command is executed ([#36](https://github.com/linuswillner/react-console-emulator/issues/36)).
167+
Added command callback support to run a function each time a command is executed. ([#36](https://github.com/linuswillner/react-console-emulator/issues/36))
135168

136169
Added `noAutoScroll` and `commandCallback` props.
137170

‎demo/App.jsx

+92-11
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ import Terminal from '../src/Terminal' // In your app, import from 'react-consol
1414
export default class App extends Component {
1515
constructor (props) {
1616
super(props)
17+
this.state = {
18+
locked: false,
19+
increment: 0,
20+
isProgressing: false,
21+
progress: 0
22+
}
1723
this.terminal = React.createRef()
24+
this.progressTerminal = React.createRef()
1825
}
1926

2027
generateTerminalDemos = (terminals) => {
@@ -50,7 +57,7 @@ export default class App extends Component {
5057
const terminals = [
5158
{
5259
title: 'Default terminal (With autoFocus enabled)',
53-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L55-L57',
60+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L61-L65',
5461
component: <Terminal
5562
style={globalStyles}
5663
commands={commands}
@@ -59,7 +66,7 @@ export default class App extends Component {
5966
},
6067
{
6168
title: 'Default welcome message (With danger mode enabled)',
62-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L64-L67',
69+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L70-L75',
6370
component: <Terminal
6471
style={globalStyles}
6572
commands={commands}
@@ -69,7 +76,7 @@ export default class App extends Component {
6976
},
7077
{
7178
title: 'Custom welcome message as an array, overriding of default commands enabled, custom error message and command callback',
72-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L74-L87',
79+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L80-L95',
7380
component: <Terminal
7481
style={globalStyles}
7582
commands={{
@@ -89,7 +96,7 @@ export default class App extends Component {
8996
},
9097
{
9198
title: 'Custom styles on the terminal elements (Incl. restyling the background) and JSX as prompt label',
92-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L94-L105',
99+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#100-L114',
93100
component: <Terminal
94101
commands={commands}
95102
welcomeMessage={[
@@ -101,13 +108,14 @@ export default class App extends Component {
101108
style={{ backgroundColor: null, background: 'url(\'https://storage.needpix.com/rsynced_images/abstract-wallpaper-1442844111BON.jpg\')' }} // Terminal background
102109
contentStyle={{ color: '#FF8E00' }} // Text colour
103110
promptLabelStyle={{ color: '#FFFFFF' }} // Prompt label colour
104-
inputStyle={{ color: 'red' }} // Prompt text colour
111+
inputTextStyle={{ color: 'red' }} // Prompt text colour
105112
promptLabel={<b>root@React:~$</b>}
113+
styleEchoBack='fullInherit' // Inherit echo styling from prompt
106114
/>
107115
},
108116
{
109117
title: 'Manual pushing with no echo back (Due to manual pushing) and custom terminal message colours',
110-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L112-L129',
118+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L119-L138',
111119
component: <Terminal
112120
style={globalStyles}
113121
ref={this.terminal}
@@ -131,7 +139,7 @@ export default class App extends Component {
131139
},
132140
{
133141
title: 'History demo',
134-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L136-L138',
142+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L143-L147',
135143
component: <Terminal
136144
style={globalStyles}
137145
commands={commands}
@@ -140,7 +148,7 @@ export default class App extends Component {
140148
},
141149
{
142150
title: 'EOL parsing enabled',
143-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L145-L151',
151+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L152-L160',
144152
component: <Terminal
145153
style={globalStyles}
146154
commands={commands}
@@ -153,7 +161,7 @@ export default class App extends Component {
153161
},
154162
{
155163
title: 'EOL parsing disabled',
156-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L158-L164',
164+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L165-L173',
157165
component: <Terminal
158166
style={globalStyles}
159167
commands={commands}
@@ -166,7 +174,7 @@ export default class App extends Component {
166174
},
167175
{
168176
title: 'Case sensitive command validation',
169-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L171-L176',
177+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L178-L185',
170178
component: <Terminal
171179
style={globalStyles}
172180
commands={casingCommands}
@@ -178,7 +186,7 @@ export default class App extends Component {
178186
},
179187
{
180188
title: 'Case insensitive command validation',
181-
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L183-L189',
189+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L190-L198',
182190
component: <Terminal
183191
style={globalStyles}
184192
commands={casingCommands}
@@ -188,6 +196,79 @@ export default class App extends Component {
188196
]}
189197
ignoreCommandCase
190198
/>
199+
},
200+
{
201+
title: 'Read-only terminal',
202+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L203-L208',
203+
component: <Terminal
204+
style={globalStyles}
205+
commands={commands}
206+
welcomeMessage='This terminal is read-only, and does not take any input.'
207+
readOnly
208+
/>
209+
},
210+
{
211+
title: 'Terminal that disables input on process',
212+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L213-L219',
213+
component: <Terminal
214+
style={globalStyles}
215+
commands={commands}
216+
welcomeMessage='This terminal hides the input when the terminal is disabled on command process. Try running the "delay" command and see what happens!'
217+
hidePromptWhenDisabled
218+
disableOnProcess
219+
/>
220+
},
221+
{
222+
title: 'Terminal with conditionally locked output',
223+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L224-L239',
224+
component: <Terminal
225+
style={globalStyles}
226+
commands={{
227+
increment: {
228+
description: 'Increments a number by one.',
229+
fn: () => {
230+
this.setState({ locked: true }) // This is just here so the welcome message or anything run before 'increment' doesn't go away
231+
const newIncrement = this.state.increment + 1
232+
this.setState({ increment: newIncrement })
233+
return newIncrement
234+
}
235+
}
236+
}}
237+
welcomeMessage='This terminal updates the output of the "increment" command when run multiple times in succession.'
238+
locked={this.state.locked}
239+
/>
240+
},
241+
{
242+
title: 'Progress demo',
243+
link: 'https://github.com/linuswillner/react-console-emulator/blob/master/demo/App.jsx#L244-L271',
244+
component: <Terminal
245+
style={globalStyles}
246+
ref={this.progressTerminal}
247+
commands={{
248+
progress: {
249+
description: 'Displays a progress counter.',
250+
fn: () => {
251+
this.setState({ isProgressing: true }, () => {
252+
const terminal = this.progressTerminal.current
253+
254+
const interval = setInterval(() => {
255+
if (this.state.progress === 100) { // Stop at 100%
256+
clearInterval(interval)
257+
this.setState({ isProgressing: false, progress: 0 })
258+
} else {
259+
this.setState({ progress: this.state.progress + 1 }, () => terminal.pushToStdout(`Progress: ${this.state.progress}%`))
260+
}
261+
}, 15)
262+
})
263+
264+
return ''
265+
}
266+
}
267+
}}
268+
welcomeMessage='This terminal displays a progress counter when you run the "progress" command.'
269+
disabled={this.state.isProgressing}
270+
locked={this.state.isProgressing}
271+
/>
191272
}
192273
]
193274

‎demo/extra/config.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,31 @@ export default {
44
},
55
commands: {
66
echo: {
7-
description: 'Echo a passed string.',
7+
description: 'Echoes a passed string.',
88
usage: 'echo <string>',
99
fn: function () {
1010
return `${Array.from(arguments).join(' ')}`
1111
}
1212
},
1313
danger: {
14-
description: 'This command returns HTML. It will only work with terminals that have dangerous mode.',
14+
description: 'This command returns HTML. It will only work with terminals that have danger mode enabled.',
1515
fn: () => 'I can<br/>use HTML in this<br/>and it will be parsed'
16+
},
17+
async: {
18+
description: 'This command runs an asynchronous task.',
19+
fn: async () => {
20+
const asyncTask = async () => 'Hello from a promise!'
21+
const result = await asyncTask()
22+
return result
23+
}
24+
},
25+
delay: {
26+
description: 'Delays return of value by 1000 ms.',
27+
fn: () => {
28+
return new Promise(resolve => {
29+
setTimeout(() => resolve('Finished!'), 1000)
30+
})
31+
}
1632
}
1733
},
1834
casingCommands: {

‎docs/API.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,33 @@ const commands = {
1717
const lowerCaseArg1 = arg1.toLowerCase()
1818

1919
// What you return in this function will be output to the terminal
20+
// If you want to render non-string items, stringify them before outputting them here
2021
return `test ${lowerCaseArg1}`
2122
},
2223
explicitExec: true, // If you need to execute your function again after the output has been emitted, enable
2324
}
2425
}
2526
```
2627

28+
### Sync vs async command functions
29+
30+
Command functions (The `fn` property) can be sync or async. Asynchronous functions are awaited and their return values are displayed as those of a regular function would.
31+
32+
This is particularly useful if you have to make relatively low-latency operations like network requests and display their outputs. However, if your tasks are predicted to take longer than is feasible to wait for with a promise, see the [Async output](#async-output) section below.
33+
34+
### Updating terminal output
35+
36+
Akin to native terminals, the terminal output can at will be locked (Using the `locked` prop) to redirect all output to only replace the latest line, as opposed to pushing new lines. This can be utilised along with [Async output](#async-output) to, for example, create a continually incrementing progress bar.
37+
38+
**Note:** It might be worth setting the `locked` prop conditionally only when a command is run, if you do not want your welcome message to disappear, or get stripped down to only the last one if you're using a multi-message welcome, considering welcome messages behave exactly like ordinary user-triggered outputs.
39+
2740
### Async output
2841

29-
If you terminal deals with HTTP requests or cross-component functionality, you may need to wait for a result before pushing to the output without relying on the function return time.
42+
If your terminal commands need to perform tasks with significant delays (Wait for events, etc.) that cause promise resolution times to be prohibitively long, you may need to return a temporary value and then wait for a result before pushing to the output.
3043

3144
**Note:** Doing output this way is a workaround, and ideally your output should be returned by the command function. This method will expose functions to you that you do not normally have access to due to React component encapsulation. Proceed with caution.
3245

33-
To do this, you can use the [React refs API](https://reactjs.org/docs/refs-and-the-dom.html). Below is an example component that uses async pushing.
46+
To do this, you can use the [React refs API](https://reactjs.org/docs/refs-and-the-dom.html). Below is an example component that uses asynchronous output in this manner.
3447

3548
```jsx
3649
import React, { Component } from 'react'

‎docs/CONFIG.md

+18-16
Original file line numberDiff line numberDiff line change
@@ -27,48 +27,50 @@ commandCallback={result => console.log(result)}
2727
*/
2828
```
2929

30-
31-
### Changing labels
32-
33-
The different labels used by the terminal can be easily changed via the following props.
34-
35-
| Prop | Description | Type | Default |
36-
| ---- | ----------- | ---- | ------- |
37-
| welcomeMessage | The terminal welcome message. Set to `false` to disable, `true` to show the default, or supply a string (Or an array of them) to set a custom one. | Boolean/String/Array<String\> | `false` |
38-
| promptLabel | The prefix to use for the input field. Can be either string or element. | Node | `$` |
39-
| errorText | The text to display when a command does not exist. Use the `[command]` placeholder for input substitution. | String | `Command '[command]' not found!` |
40-
4130
### Options
4231

43-
The terminal has several options you can use to change the behaviour of it.
32+
The terminal has several options you can use to change the behaviour of it. All props in this section are optional.
4433

4534
| Prop | Description | Type | Default |
4635
| ---- | ----------- | ---- | ------- |
4736
| autoFocus | Focus the terminal on page load. | Boolean | `false` |
4837
| dangerMode | Enable parsing of HTML in terminal messages. | Boolean | `false` |
4938
| disabled | Whether to enable terminal input or not. | Boolean | `false` |
5039
| disableOnProcess | Disable input to the terminal during command execution. | Boolean | `false` |
51-
| styleEchoBack | Style command echoes (Terminal outputs of any commands entered) as regular terminal messages. | Boolean | `false` |
40+
| errorText | The text to display when a command does not exist. Use the `[command]` placeholder for input substitution. | String | `'Command \'[command]\' not found!'` |
41+
| hidePromptWhenDisabled | Hide entire prompt when input is manually disabled (Via the `disabled` prop) or when `disableOnProcess` is enabled and the terminal is processing. | Boolean | `false` |
42+
| ignoreCommandCase | Disable case-sensitive matching of command inputs. **Note:** Enabling this feature results in a restriction of command names to alphanumeric characters, dashes and underscores, for security reasons. | Boolean | `false` |
43+
| locked | Lock output to the current line. When this prop is set to `true`, all output to the terminal will only replace the latest line. This could be useful if you want to make something akin to a progress bar and update the latest line. | Boolean | `false` |
44+
| noAutoScroll | Disable automatic scrolling to the bottom of the terminal when a command is executed (*nix-like). | Boolean | `false` |
5245
| noDefaults | Do not register the default commands (`help` and `clear`). Useful if you want to override the functionality of either. | Boolean | `false` |
5346
| noEchoBack | Disable command echoes (Terminal outputs of any commands entered). | Boolean | `false` |
5447
| noHistory | Disable the storing and scrolling of history of the commands entered in the terminal. | Boolean | `false` |
55-
| noAutoScroll | Disable automatic scrolling to the bottom of the terminal when a command is executed (*nix-like). | Boolean | `false` |
5648
| noNewlineParsing | Disable the parsing line breaks (\n) in command outputs as separate message, leave them unchanged. | Boolean | `false` |
57-
| ignoreCommandCase | Disable case-sensitive matching of command inputs. **Note:** Enabling this feature results in a restriction of command names to alphanumeric characters, dashes and underscores, for security reasons. | Boolean | `false` |
49+
| promptLabel | The prefix to use for the input field. Can be either string or element. | Node | `$` |
50+
| readOnly | Hides the entire prompt, thus setting the terminal to read-only mode. | Boolean | `false` |
51+
| styleEchoBack | Inherit style for command echoes (Terminal outputs of any commands entered) from prompt (Fully or partially, i.e. label or text only), or style them as regular messages. Omitting this prop enables default behaviour. | String<'labelOnly'/'textOnly'/'fullInherit'/'messageInherit'\> | `undefined` |
52+
| welcomeMessage | The terminal welcome message. Set to `false` to disable, `true` to show the default, or supply a string (Or an array of them) to set a custom one. | Boolean/String/Array<String\> | `false` |
5853

5954
### Re-styling
6055

6156
To re-style the terminal, you have two options: [Inline styles](https://reactjs.org/docs/dom-elements.html#style) or supplying a class name. The former is recommended due to it automatically overriding the previous property without having to faff about with `!important` and similar hacks.
6257

6358
The default styles for the terminal can be found in [src/defs/styles/Terminal.js](../src/defs/styles/Terminal.js). The definitions contained within can be adjusted by submitting style overrides or class names to the following props.
6459

60+
All props in this section are optional.
61+
6562
| Prop | Target |
6663
| ---- | ------ |
6764
| style / className | Terminal root container. |
6865
| contentStyle / contentClassName | Terminal content container. |
6966
| inputAreaStyle / inputAreaClassName | Input area element (Container for prompt label and input field). |
7067
| promptLabelStyle / promptLabelClassName | Prompt label (The prefix for the input). |
71-
| inputStyle / inputClassName | Text input field. |
68+
| inputStyle / inputClassName | Input field. **Note:** Applying styles for the text here may cause unexpected results, see below. |
69+
| inputTextStyle / inputTextClassName | Input field text. |
7270
| messageStyle / messageClassName | Terminal messages (Incl. command echoes if enabled via the `styleEchoBack` prop). |
7371

72+
**Note about input text styling:**
73+
74+
As of v5.0.0, input text styling has been moved to `inputTextStyle` and `inputTextClassName`. The reason for this is the introduction of prompt styling persistence for echoes in the same version. Despite the input text being part of the input element strictly speaking, **do not apply text styles in `inputStyle` or `inputClassName`, or you may see unexpected styling errors.**
75+
7476
Examples on how to override the terminal styles can be found in [src/App.jsx](../src/App.jsx).

‎jest.coverage.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ module.exports = {
1010
// These are covered by interactivity tests or other tests
1111
'cleanArray.js',
1212
'scrollHistory.js',
13-
'sendCursorToEnd.js'
13+
'sendCursorToEnd.js',
14+
'constructEcho.js'
1415
],
1516
coverageDirectory: './coverage/'
1617
}

‎src/Terminal.jsx

+13-14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import types from './defs/types/Terminal'
1616

1717
// Utils
1818
import commandExists from './utils/commandExists'
19+
import constructEcho from './utils/constructEcho'
20+
import shouldPromptBeVisible from './utils/shouldPromptBeVisible'
1921

2022
export default class Terminal extends Component {
2123
constructor (props) {
@@ -26,7 +28,6 @@ export default class Terminal extends Component {
2628
history: [],
2729
historyPosition: null,
2830
previousHistoryPosition: null,
29-
// TODO: Add prop-controlled enable/disable on the input
3031
processing: false
3132
}
3233

@@ -40,6 +41,7 @@ export default class Terminal extends Component {
4041
focusTerminal = () => {
4142
// Only focus the terminal if text isn't being copied
4243
const isTextSelected = window.getSelection().type === 'Range'
44+
// Only focus if input is there (Goes away for read-only terminals)
4345
if (!isTextSelected) this.terminalInput.current.focus()
4446
}
4547

@@ -86,6 +88,9 @@ export default class Terminal extends Component {
8688
*/
8789
pushToStdout = (message, options) => {
8890
const { stdout } = this.state
91+
92+
if (this.props.locked) stdout.pop()
93+
8994
stdout.push({ message, isEcho: options?.isEcho || false })
9095

9196
/* istanbul ignore next: Covered by interactivity tests */
@@ -131,7 +136,7 @@ export default class Terminal extends Component {
131136

132137
/* istanbul ignore next: Covered by interactivity tests */
133138
processCommand = () => {
134-
this.setState({ processing: true }, () => {
139+
this.setState({ processing: true }, async () => {
135140
// Initialise command result object
136141
const commandResult = { command: null, args: [], rawInput: null, result: null }
137142
const rawInput = this.terminalInput.current.value
@@ -140,10 +145,7 @@ export default class Terminal extends Component {
140145

141146
if (!this.props.noEchoBack) {
142147
// Mimic native terminal by echoing command back
143-
// Also exempt it from message since it should not really be a message despite behaving like one
144-
// Containing it in a span to allow JSX values in the prompt label
145-
const echo = <span>{this.props.promptLabel || '$'} {rawInput}</span>
146-
this.pushToStdout(echo, { isEcho: true })
148+
this.pushToStdout(constructEcho(this.props.promptLabel || '$', rawInput, this.props), { isEcho: true })
147149
}
148150

149151
if (rawInput) {
@@ -164,11 +166,11 @@ export default class Terminal extends Component {
164166
)
165167
} else {
166168
const cmd = this.state.commands[command]
167-
const res = cmd.fn(...args)
169+
const res = await cmd.fn(...args)
168170

169171
this.pushToStdout(res)
170172
commandResult.result = res
171-
if (cmd.explicitExec) cmd.fn(...args)
173+
if (cmd.explicitExec) await cmd.fn(...args)
172174
}
173175
}
174176

@@ -222,7 +224,7 @@ export default class Terminal extends Component {
222224
content: defaults(this.props.contentStyle, sourceStyles.content),
223225
inputArea: defaults(this.props.inputAreaStyle, sourceStyles.inputArea),
224226
promptLabel: defaults(this.props.promptLabelStyle, sourceStyles.promptLabel),
225-
input: defaults(this.props.inputStyle, sourceStyles.input)
227+
input: defaults({ ...this.props.inputStyle, ...this.props.inputTextStyle }, { ...sourceStyles.input, ...sourceStyles.inputText })
226228
}
227229

228230
return (
@@ -245,7 +247,7 @@ export default class Terminal extends Component {
245247
<div
246248
name='react-console-emulator__inputArea'
247249
className={this.props.inputAreaClassName}
248-
style={styles.inputArea}
250+
style={shouldPromptBeVisible(this.state, this.props) ? styles.inputArea : { display: 'none' }}
249251
>
250252
{/* Prompt label */}
251253
<span
@@ -264,10 +266,7 @@ export default class Terminal extends Component {
264266
onKeyDown={this.handleInput}
265267
type='text'
266268
autoComplete='off'
267-
disabled={
268-
this.props.disabled ||
269-
(this.props.disableOnProcess && /* istanbul ignore next: Covered by interactivity tests */ this.state.processing)
270-
}
269+
disabled={this.props.disabled || (this.props.disableOnProcess && /* istanbul ignore next: Covered by interactivity tests */ this.state.processing)}
271270
/>
272271
</div>
273272
</div>

‎src/defs/styles/Terminal.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export default {
2525
paddingTop: '3px',
2626
color: '#EE9C34'
2727
},
28+
inputText: {
29+
fontSize: '15px',
30+
color: '#F0BF81',
31+
fontFamily: 'monospace'
32+
},
2833
input: {
2934
border: '0',
3035
padding: '0 0 0 7px',
@@ -33,9 +38,6 @@ export default {
3338
width: '100%',
3439
height: '22px',
3540
background: 'transparent',
36-
fontSize: '15px',
37-
color: '#F0BF81',
38-
fontFamily: 'monospace',
3941
outline: 'none' // Fix for outline showing up on some browsers
4042
}
4143
}

‎src/defs/types/Terminal.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,35 @@ const styleTypes = {
55
contentStyle: PropTypes.object,
66
inputAreaStyle: PropTypes.object,
77
promptLabelStyle: PropTypes.object,
8-
inputStyle: PropTypes.object
8+
inputStyle: PropTypes.object,
9+
inputTextStyle: PropTypes.object
910
}
1011

1112
const classNameTypes = {
1213
className: PropTypes.string,
1314
contentClassName: PropTypes.string,
1415
inputAreaClassName: PropTypes.string,
1516
promptLabelClassName: PropTypes.string,
16-
inputClassName: PropTypes.string
17+
inputClassName: PropTypes.string,
18+
inputTextClassName: PropTypes.string
1719
}
1820

1921
const optionTypes = {
2022
autoFocus: PropTypes.bool,
2123
dangerMode: PropTypes.bool,
24+
styleEchoBack: PropTypes.oneOf([
25+
'labelOnly', // Only persist label style
26+
'textOnly', // Only persist text style
27+
'fullInherit', // Inherit entire prompt style
28+
'messageInherit' // Inherit message style
29+
// (undefined signifies default behaviour)
30+
// Not offering individual options for message styling as messages only have one uniform style for the entire element per the spec
31+
]),
32+
locked: PropTypes.bool,
33+
readOnly: PropTypes.bool,
2234
disabled: PropTypes.bool,
2335
disableOnProcess: PropTypes.bool,
36+
hidePromptWhenDisabled: PropTypes.bool,
2437
ignoreCommandCase: PropTypes.bool,
2538
noDefaults: PropTypes.bool,
2639
noEchoBack: PropTypes.bool,

‎src/handlers/parseEOL.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default stdout => {
1010
const messageText = innerText(message)
1111

1212
// Do not parse echoes (Raw inputs)
13-
const parsed = !isEcho && /\\n/g.test(messageText) ? messageText.split(/\\n/g) : [messageText]
13+
const parsed = !isEcho && /\n|\\n/g.test(messageText) ? messageText.split(/\n|\\n/g) : [message]
1414

1515
for (const line of parsed) {
1616
parsedStdout.push({ message: line, isEcho: currentLine.isEcho })

‎src/utils/constructEcho.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from 'react'
2+
import defaults from 'defaults'
3+
import terminalSourceStyles from '../defs/styles/Terminal'
4+
import messageSourceStyles from '../defs/styles/TerminalMessage'
5+
6+
/**
7+
* Constructs command echo based on user's styling options
8+
* @param promptLabel - Prompt label element
9+
* @param rawInput - Raw command input
10+
* @param stylingProps - {
11+
* styleEchoBack: string
12+
* promptLabelClassName: string,
13+
* promptLabelStyle: object,
14+
* inputTextClassName: string,
15+
* inputTextStyle: object
16+
* }
17+
*/
18+
export default (promptLabel, rawInput, stylingProps) => {
19+
const sources = {
20+
echo: {
21+
label: {
22+
className: stylingProps.promptLabelClassName,
23+
style: defaults(stylingProps.promptLabelStyle, terminalSourceStyles.promptLabel)
24+
},
25+
text: {
26+
className: stylingProps.inputTextClassName,
27+
style: defaults(stylingProps.inputTextStyle, terminalSourceStyles.inputText)
28+
}
29+
},
30+
// Note: Not offering individual options for message styling as messages only have one uniform style for the entire element per the spec
31+
message: {
32+
label: {
33+
className: stylingProps.messageClassName,
34+
style: defaults(stylingProps.messageStyle, messageSourceStyles)
35+
},
36+
text: {
37+
className: stylingProps.messageClassName,
38+
style: defaults(stylingProps.messageStyle, messageSourceStyles)
39+
}
40+
}
41+
}
42+
43+
// Getting these via an IIFE so I can use returns
44+
// This is because variable reassignment in switch statements gets really hairy really quick
45+
const styles = (() => {
46+
switch (stylingProps.styleEchoBack) {
47+
case 'fullInherit': return sources.echo
48+
case 'messageInherit': return sources.message
49+
case 'labelOnly': return {
50+
label: sources.echo.label,
51+
text: {}
52+
}
53+
case 'textOnly': return {
54+
label: {},
55+
text: sources.echo.text
56+
}
57+
default: return {
58+
label: {},
59+
text: {}
60+
}
61+
}
62+
})()
63+
64+
return (
65+
<div>
66+
<span {...styles.label}>{promptLabel} </span>
67+
<span {...styles.text}>{rawInput}</span>
68+
</div>
69+
)
70+
}

‎src/utils/shouldPromptBeVisible.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export default (state, props) => {
2+
const isNotReadOnly = !props.readOnly
3+
4+
const shouldHideWhenDisabled = props.hidePromptWhenDisabled
5+
const shouldDisableOnProcess = props.disableOnProcess
6+
const isDisabled = props.disabled
7+
const isProcessing = state.processing
8+
9+
// If prompt should be hidden when disabled...
10+
/* istanbul ignore if: Covered by interactivity tests */
11+
if (shouldHideWhenDisabled) {
12+
if (isDisabled) return false // ...hide on explicit prop-controlled disable...
13+
else if (shouldDisableOnProcess && isProcessing) return false // ...or when disabling on process is enabled and terminal is processing.
14+
}
15+
16+
// If no above conditions were met, the read-only state controls whether the prompt should be visible or not
17+
return isNotReadOnly
18+
}

‎test/Terminal.test.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ describe('Terminal HTML structure', () => {
7676
expect(wrapper.find('[name="react-console-emulator__promptLabel"]')).toHaveLength(1)
7777
expect(wrapper.find('[name="react-console-emulator__input"]')).toHaveLength(1)
7878
})
79+
80+
it('Hides the prompt in read-only mode', () => {
81+
const wrapper = shallow(<Terminal commands={commands} readOnly/>)
82+
expect(wrapper.find('[name="react-console-emulator__inputArea"]').prop('style')).toEqual({ display: 'none' })
83+
})
7984
})
8085

8186
describe('Terminal welcome messages', () => {
@@ -139,8 +144,8 @@ describe('Terminal functionality', () => {
139144
})
140145

141146
it('Parses newlines (But not when disabled)', () => {
142-
const wrapperEnabled = mount(<Terminal commands={commands} welcomeMessage={'split1\\nsplit2'}/>)
143-
const wrapperDisabled = mount(<Terminal commands={commands} welcomeMessage={'split1\\nsplit2'} noNewlineParsing/>)
147+
const wrapperEnabled = mount(<Terminal commands={commands} welcomeMessage={'split1\nsplit2'}/>)
148+
const wrapperDisabled = mount(<Terminal commands={commands} welcomeMessage={'split1\nsplit2'} noNewlineParsing/>)
144149

145150
const enabledContent = wrapperEnabled.find('[name="react-console-emulator__content"]')
146151
const disabledContent = wrapperDisabled.find('[name="react-console-emulator__content"]')
@@ -150,11 +155,20 @@ describe('Terminal functionality', () => {
150155
expect(enabledContent.childAt(1).text()).toBe('split2')
151156

152157
// ...and doesn't with parsing disabled
153-
expect(disabledContent.childAt(0).text()).toBe('split1\\nsplit2')
158+
expect(disabledContent.childAt(0).text()).toBe('split1\nsplit2')
154159

155160
wrapperEnabled.unmount()
156161
wrapperDisabled.unmount()
157162
})
163+
164+
it('Only updates the last line when locked', () => {
165+
const wrapper = mount(<Terminal commands={commands} welcomeMessage={['this is the first message', 'this is the second message']} locked/>)
166+
const content = wrapper.find('[name="react-console-emulator__content"]')
167+
168+
expect(content.childAt(0).text()).toBe('this is the second message')
169+
170+
wrapper.unmount()
171+
})
158172
})
159173

160174
describe('Terminal command validator', () => {

0 commit comments

Comments
 (0)
Please sign in to comment.