@webscopeio/react-console
Advanced tools
/** | ||
* @class ReactConsole | ||
*/ | ||
import * as React from 'react'; | ||
export declare type Props = { | ||
import React, { CSSProperties } from 'react'; | ||
declare type CommandsProp = { | ||
[command: string]: { | ||
description?: string; | ||
fn: (...args: any[]) => Promise<any>; | ||
}; | ||
}; | ||
export declare type ReactConsoleProps = { | ||
commands: CommandsProp; | ||
noCommandFound: (...str: string[]) => Promise<string>; | ||
autoFocus: boolean; | ||
prompt: string; | ||
commands: any; | ||
welcomeMessage?: string; | ||
autoFocus: boolean; | ||
noCommandFound: (...str: string[]) => Promise<string>; | ||
welcomeMessage?: string | undefined; | ||
wrapperClassName?: string; | ||
promptWrapperClassName?: string; | ||
promptClassName?: string; | ||
wrapperClassName?: string; | ||
lineClassName?: string; | ||
inputClassName?: string; | ||
wrapperStyle?: object; | ||
promptStyle?: object; | ||
inputStyle?: object; | ||
wrapperStyle: CSSProperties; | ||
promptWrapperStyle: CSSProperties; | ||
promptStyle: CSSProperties; | ||
lineStyle: CSSProperties; | ||
inputStyle: CSSProperties; | ||
history?: string[]; | ||
onAddHistoryItem?: (entry: string) => void; | ||
}; | ||
declare type State = { | ||
output: Array<string>; | ||
declare type ReactConsoleState = { | ||
output: string[]; | ||
commandInProgress: boolean; | ||
input: string; | ||
historyPosition: number; | ||
reverseSearchString?: string; | ||
reverseSearchPosition: number; | ||
}; | ||
export default class ReactConsole extends React.Component<Props, State> { | ||
export default class ReactConsole extends React.Component<ReactConsoleProps, ReactConsoleState> { | ||
inputRef: any; | ||
wrapperRef: any; | ||
reverseStringRef: any; | ||
static defaultProps: { | ||
@@ -31,3 +47,5 @@ prompt: string; | ||
wrapperStyle: {}; | ||
promptWrapperStyle: {}; | ||
promptStyle: {}; | ||
lineStyle: {}; | ||
inputStyle: {}; | ||
@@ -39,2 +57,5 @@ }; | ||
commandInProgress: boolean; | ||
reverseSearchString: undefined; | ||
historyPosition: number; | ||
reverseSearchPosition: number; | ||
}; | ||
@@ -44,7 +65,78 @@ componentDidMount(): void; | ||
scrollToBottom: () => void; | ||
onSubmit: (e: any) => Promise<void>; | ||
/** | ||
* Get filtered history entries based on reverse search string | ||
*/ | ||
private getReverseHistory; | ||
/** | ||
* Takes current text of a main input and generates a string that will be outputted as a log. | ||
*/ | ||
private getCurrentTextSnapshot; | ||
private onSubmit; | ||
render(): JSX.Element; | ||
onInputChange: (e: any) => void; | ||
/** | ||
* Reverse search input handler | ||
* @param event | ||
*/ | ||
private onReverseStringInputChange; | ||
/** | ||
* Invoked when pressed ctrl+r and already in a reverse search mode. | ||
*/ | ||
private nextReverseSearch; | ||
/** | ||
* Helper function that sets the history preview based on a requested history index. | ||
* @param historyIndex | ||
*/ | ||
private executeNextReverseSearch; | ||
/** | ||
* This function sets a given history entry as a main input element content. | ||
* It's called when changing the preview of a 'current' history entry e.g. by ctrl+r call, or | ||
* when pressing up/down arrow. | ||
* @param historyPosition | ||
*/ | ||
private setPreviewPosition; | ||
/** | ||
* Enables reverse search. | ||
* The side effect is that we focus o reverse search input. | ||
*/ | ||
private onReverseSearch; | ||
/** | ||
* When reverse search is confirmed, we disable reverse search mode and keep the result. | ||
* @param e | ||
*/ | ||
private onReverseSearchSubmit; | ||
/** | ||
* Main input change handler. | ||
* @param event | ||
*/ | ||
private onInputChange; | ||
/** | ||
* Helper function to determine whether reverse search is active or not. | ||
*/ | ||
private isReverseSearchOn; | ||
/** | ||
* Disables reverse search mode. | ||
* @param keepPreviewString - determines whether the result of a reverse search should be kept or not | ||
*/ | ||
private disableReverseSearch; | ||
/** | ||
* onKeyDown implementation of a reverse search input. | ||
* @param event | ||
*/ | ||
private onReverseKeyDown; | ||
/** | ||
* onKeyDown implementation of a main input. | ||
* @param event | ||
*/ | ||
private onKeyDown; | ||
/** | ||
* Focuses console input. | ||
* Whenever an user clicks on a terminal, we want to focus an actual input where he/she can type. | ||
*/ | ||
focusConsole: () => void; | ||
/** | ||
* Calls onAddHistoryItem property and sets historyPosition to a default value. | ||
* @param inputString | ||
*/ | ||
private addHistoryEntry; | ||
} | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import { createElement, Component } from 'react'; | ||
import React from 'react'; | ||
@@ -81,2 +81,10 @@ /*! ***************************************************************************** | ||
function __spreadArrays() { | ||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; | ||
for (var r = Array(s), k = 0, i = 0; i < il; i++) | ||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) | ||
r[k] = a[j]; | ||
return r; | ||
} | ||
function createCommonjsModule(fn, module) { | ||
@@ -177,2 +185,3 @@ return module = { exports: {} }, fn(module, module.exports), module.exports; | ||
_this.wrapperRef = null; | ||
_this.reverseStringRef = null; | ||
_this.state = { | ||
@@ -182,2 +191,5 @@ input: '', | ||
commandInProgress: false, | ||
reverseSearchString: undefined, | ||
historyPosition: Infinity, | ||
reverseSearchPosition: Infinity, | ||
}; | ||
@@ -192,8 +204,27 @@ _this.clear = function () { | ||
}; | ||
/** | ||
* Get filtered history entries based on reverse search string | ||
*/ | ||
_this.getReverseHistory = function () { | ||
var reverseSearchString = _this.state.reverseSearchString; | ||
return _this.props.history === undefined ? | ||
[] | ||
: _this.props.history.map(function (entry) { return (reverseSearchString === undefined || reverseSearchString === '') ? | ||
// @ts-ignore | ||
false : entry.indexOf(reverseSearchString) !== -1; }); | ||
}; | ||
/** | ||
* Takes current text of a main input and generates a string that will be outputted as a log. | ||
*/ | ||
_this.getCurrentTextSnapshot = function () { | ||
var prompt = _this.props.prompt; | ||
var inputString = _this.state.input; | ||
return prompt + "\u00A0" + inputString; | ||
}; | ||
_this.onSubmit = function (e) { return __awaiter(_this, void 0, void 0, function () { | ||
var _a, prompt, inputString, log, _b, cmd, args, command, ret, cmdNotFound; | ||
var inputString, log, _a, cmd, args, command, ret, cmdNotFound; | ||
var _b; | ||
return __generator(this, function (_c) { | ||
switch (_c.label) { | ||
case 0: | ||
prompt = this.props.prompt; | ||
e.preventDefault(); | ||
@@ -204,6 +235,6 @@ inputString = this.state.input; | ||
} | ||
log = prompt + "\u00A0" + inputString; | ||
log = this.getCurrentTextSnapshot(); | ||
if (inputString === '') { | ||
this.setState({ | ||
output: this.state.output.concat([log]), | ||
output: __spreadArrays(this.state.output, [log]), | ||
input: '', | ||
@@ -214,3 +245,4 @@ }); | ||
} | ||
_b = inputString.split(" "), cmd = _b[0], args = _b.slice(1); | ||
this.addHistoryEntry(inputString); | ||
_a = inputString.split(" "), cmd = _a[0], args = _a.slice(1); | ||
if (cmd === 'clear') { | ||
@@ -227,10 +259,10 @@ this.clear(); | ||
this.setState({ | ||
output: this.state.output.concat([log, ret]) | ||
output: __spreadArrays(this.state.output, [log, ret]) | ||
}); | ||
return [3 /*break*/, 4]; | ||
case 2: return [4 /*yield*/, (_a = this.props).noCommandFound.apply(_a, [cmd].concat(args))]; | ||
case 2: return [4 /*yield*/, (_b = this.props).noCommandFound.apply(_b, __spreadArrays([cmd], args))]; | ||
case 3: | ||
cmdNotFound = _c.sent(); | ||
this.setState({ | ||
output: this.state.output.concat([log, cmdNotFound]) | ||
output: __spreadArrays(this.state.output, [log, cmdNotFound]) | ||
}); | ||
@@ -246,7 +278,165 @@ _c.label = 4; | ||
}); }; | ||
_this.onInputChange = function (e) { | ||
/** | ||
* Reverse search input handler | ||
* @param event | ||
*/ | ||
_this.onReverseStringInputChange = function (event) { | ||
_this.setState({ | ||
input: e.target.value, | ||
reverseSearchString: event.target.value, | ||
}, function () { | ||
var history = _this.getReverseHistory(); | ||
var historyIndex = history.lastIndexOf(true); | ||
_this.executeNextReverseSearch(historyIndex); | ||
}); | ||
}; | ||
/** | ||
* Invoked when pressed ctrl+r and already in a reverse search mode. | ||
*/ | ||
_this.nextReverseSearch = function () { | ||
var history = _this.getReverseHistory(); | ||
var endOffset = Math.max(0, _this.state.reverseSearchPosition - 1); // so that we don't go from the end again | ||
var historyIndex = history.lastIndexOf(true, endOffset); | ||
_this.executeNextReverseSearch(historyIndex); | ||
}; | ||
/** | ||
* Helper function that sets the history preview based on a requested history index. | ||
* @param historyIndex | ||
*/ | ||
_this.executeNextReverseSearch = function (historyIndex) { | ||
_this.setState({ | ||
reverseSearchPosition: historyIndex, | ||
}); | ||
if (historyIndex !== -1) { | ||
_this.setPreviewPosition(historyIndex); | ||
} | ||
if (_this.state.reverseSearchString === '') { | ||
_this.setPreviewPosition(Infinity); | ||
} | ||
}; | ||
/** | ||
* This function sets a given history entry as a main input element content. | ||
* It's called when changing the preview of a 'current' history entry e.g. by ctrl+r call, or | ||
* when pressing up/down arrow. | ||
* @param historyPosition | ||
*/ | ||
_this.setPreviewPosition = function (historyPosition) { | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
_this.setState({ | ||
historyPosition: historyPosition, | ||
input: _this.props.history[historyPosition] || '', | ||
}); | ||
}; | ||
/** | ||
* Enables reverse search. | ||
* The side effect is that we focus o reverse search input. | ||
*/ | ||
_this.onReverseSearch = function () { | ||
// we enabled reverse search | ||
_this.setState({ | ||
reverseSearchString: '', | ||
}, function () { | ||
_this.reverseStringRef.focus(); | ||
}); | ||
}; | ||
/** | ||
* When reverse search is confirmed, we disable reverse search mode and keep the result. | ||
* @param e | ||
*/ | ||
_this.onReverseSearchSubmit = function (event) { | ||
event.preventDefault(); | ||
_this.disableReverseSearch(); | ||
}; | ||
/** | ||
* Main input change handler. | ||
* @param event | ||
*/ | ||
_this.onInputChange = function (event) { | ||
_this.setState({ | ||
input: event.target.value, | ||
}); | ||
}; | ||
/** | ||
* Helper function to determine whether reverse search is active or not. | ||
*/ | ||
_this.isReverseSearchOn = function () { return _this.state.reverseSearchString !== undefined; }; | ||
/** | ||
* Disables reverse search mode. | ||
* @param keepPreviewString - determines whether the result of a reverse search should be kept or not | ||
*/ | ||
_this.disableReverseSearch = function (keepPreviewString) { | ||
if (keepPreviewString === void 0) { keepPreviewString = true; } | ||
_this.setState({ | ||
reverseSearchString: undefined, | ||
}); | ||
if (!keepPreviewString) { | ||
_this.setState({ | ||
input: '', | ||
}); | ||
} | ||
setTimeout(function () { | ||
_this.inputRef.focus(); | ||
}); | ||
}; | ||
/** | ||
* onKeyDown implementation of a reverse search input. | ||
* @param event | ||
*/ | ||
_this.onReverseKeyDown = function (event) { | ||
if (event.which === 38 || event.which === 40) { // up or down | ||
_this.disableReverseSearch(); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 67 && event.ctrlKey) { // ctrl + c | ||
_this.disableReverseSearch(false); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 82 && event.ctrlKey) { // ctrl + r | ||
_this.nextReverseSearch(); | ||
event.preventDefault(); | ||
} | ||
}; | ||
/** | ||
* onKeyDown implementation of a main input. | ||
* @param event | ||
*/ | ||
_this.onKeyDown = function (event) { | ||
if (event.which === 38) { // key up | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
var currentPos = Math.min(_this.state.historyPosition, _this.props.history.length); | ||
var historyPosition = Math.max(0, currentPos - 1); | ||
_this.setPreviewPosition(historyPosition); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 40) { | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
var historyPosition = Math.min(_this.props.history.length, _this.state.historyPosition + 1); | ||
_this.setPreviewPosition(historyPosition); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 82 && event.ctrlKey) { // ctrl + r | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
_this.onReverseSearch(); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 67 && event.ctrlKey) { // ctrl + c | ||
_this.setState({ | ||
output: __spreadArrays(_this.state.output, [_this.getCurrentTextSnapshot()]), | ||
input: '', | ||
}); | ||
_this.scrollToBottom(); | ||
event.preventDefault(); | ||
} | ||
}; | ||
/** | ||
* Focuses console input. | ||
* Whenever an user clicks on a terminal, we want to focus an actual input where he/she can type. | ||
*/ | ||
_this.focusConsole = function () { | ||
@@ -259,2 +449,15 @@ if (_this.inputRef) { | ||
}; | ||
/** | ||
* Calls onAddHistoryItem property and sets historyPosition to a default value. | ||
* @param inputString | ||
*/ | ||
_this.addHistoryEntry = function (inputString) { | ||
var onAddHistoryItem = _this.props.onAddHistoryItem; | ||
if (typeof onAddHistoryItem === 'function') { | ||
onAddHistoryItem(inputString); | ||
} | ||
_this.setState({ | ||
historyPosition: Infinity, | ||
}); | ||
}; | ||
return _this; | ||
@@ -269,19 +472,24 @@ } | ||
} | ||
if (this.props.history !== undefined) { | ||
this.setState({ | ||
historyPosition: this.props.history.length, | ||
}); | ||
} | ||
}; | ||
ReactConsole.prototype.render = function () { | ||
var _this = this; | ||
var _a = this.props, wrapperClassName = _a.wrapperClassName, promptClassName = _a.promptClassName, inputClassName = _a.inputClassName, wrapperStyle = _a.wrapperStyle, promptStyle = _a.promptStyle, inputStyle = _a.inputStyle, prompt = _a.prompt, autoFocus = _a.autoFocus; | ||
var promptClass = promptClassName | ||
? styles.prompt + " " + promptClassName | ||
: styles.prompt; | ||
return (createElement("div", { className: classnames([styles.wrapper, wrapperClassName]), style: __assign({ overflowY: isIE11 ? "scroll" : "auto" }, wrapperStyle), onClick: this.focusConsole, ref: function (ref) { return _this.wrapperRef = ref; } }, | ||
createElement("div", null, this.state.output.map(function (line, key) { | ||
return createElement("pre", { key: key, className: styles.line, dangerouslySetInnerHTML: { __html: line } }); | ||
var _a = this.props, wrapperClassName = _a.wrapperClassName, promptWrapperClassName = _a.promptWrapperClassName, promptClassName = _a.promptClassName, lineClassName = _a.lineClassName, inputClassName = _a.inputClassName, wrapperStyle = _a.wrapperStyle, promptWrapperStyle = _a.promptWrapperStyle, promptStyle = _a.promptStyle, lineStyle = _a.lineStyle, inputStyle = _a.inputStyle, prompt = _a.prompt, autoFocus = _a.autoFocus; | ||
return (React.createElement("div", { className: classnames(styles.wrapper, wrapperClassName), style: __assign({ overflowY: isIE11 ? "scroll" : "auto" }, wrapperStyle), onClick: this.focusConsole, ref: function (ref) { return _this.wrapperRef = ref; } }, | ||
React.createElement("div", null, this.state.output.map(function (line, key) { | ||
return React.createElement("pre", { key: key, className: classnames(styles.line, lineClassName), style: lineStyle, dangerouslySetInnerHTML: { __html: line } }); | ||
})), | ||
createElement("form", { onSubmit: this.onSubmit }, | ||
createElement("div", { className: classnames([styles.promptWrapper, promptClassName]), style: promptStyle }, | ||
createElement("span", { className: promptClass }, | ||
React.createElement("form", { onSubmit: this.onSubmit }, | ||
React.createElement("div", { className: classnames(styles.promptWrapper, promptWrapperClassName), style: promptWrapperStyle }, | ||
React.createElement("span", { className: classnames(styles.prompt, promptClassName), style: promptStyle }, | ||
prompt, | ||
"\u00A0"), | ||
createElement("input", { disabled: this.state.commandInProgress, ref: function (ref) { return _this.inputRef = ref; }, autoFocus: autoFocus, value: this.state.input, onChange: this.onInputChange, autoComplete: 'off', spellCheck: false, autoCapitalize: 'false', name: "input", className: classnames([styles.input, inputClassName]), style: inputStyle }))))); | ||
React.createElement("input", { disabled: this.state.commandInProgress || this.isReverseSearchOn(), ref: function (ref) { return _this.inputRef = ref; }, autoFocus: autoFocus, value: this.state.input, onChange: this.onInputChange, onKeyDown: this.onKeyDown, autoComplete: 'off', spellCheck: false, autoCapitalize: 'false', name: "input", className: classnames([styles.input, inputClassName]), style: inputStyle }))), | ||
this.isReverseSearchOn() && React.createElement("form", { onSubmit: this.onReverseSearchSubmit }, | ||
"bck-i-search: ", | ||
React.createElement("input", { value: this.state.reverseSearchString, ref: function (ref) { return _this.reverseStringRef = ref; }, onKeyDown: this.onReverseKeyDown, className: classnames([styles.input, inputClassName]), onChange: this.onReverseStringInputChange })))); | ||
}; | ||
@@ -293,9 +501,11 @@ ReactConsole.defaultProps = { | ||
wrapperStyle: {}, | ||
promptWrapperStyle: {}, | ||
promptStyle: {}, | ||
lineStyle: {}, | ||
inputStyle: {}, | ||
}; | ||
return ReactConsole; | ||
}(Component)); | ||
}(React.Component)); | ||
export default ReactConsole; | ||
//# sourceMappingURL=index.es.js.map |
@@ -5,4 +5,6 @@ 'use strict'; | ||
var React = require('react'); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var React = _interopDefault(require('react')); | ||
/*! ***************************************************************************** | ||
@@ -86,2 +88,10 @@ Copyright (c) Microsoft Corporation. | ||
function __spreadArrays() { | ||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; | ||
for (var r = Array(s), k = 0, i = 0; i < il; i++) | ||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) | ||
r[k] = a[j]; | ||
return r; | ||
} | ||
function createCommonjsModule(fn, module) { | ||
@@ -182,2 +192,3 @@ return module = { exports: {} }, fn(module, module.exports), module.exports; | ||
_this.wrapperRef = null; | ||
_this.reverseStringRef = null; | ||
_this.state = { | ||
@@ -187,2 +198,5 @@ input: '', | ||
commandInProgress: false, | ||
reverseSearchString: undefined, | ||
historyPosition: Infinity, | ||
reverseSearchPosition: Infinity, | ||
}; | ||
@@ -197,8 +211,27 @@ _this.clear = function () { | ||
}; | ||
/** | ||
* Get filtered history entries based on reverse search string | ||
*/ | ||
_this.getReverseHistory = function () { | ||
var reverseSearchString = _this.state.reverseSearchString; | ||
return _this.props.history === undefined ? | ||
[] | ||
: _this.props.history.map(function (entry) { return (reverseSearchString === undefined || reverseSearchString === '') ? | ||
// @ts-ignore | ||
false : entry.indexOf(reverseSearchString) !== -1; }); | ||
}; | ||
/** | ||
* Takes current text of a main input and generates a string that will be outputted as a log. | ||
*/ | ||
_this.getCurrentTextSnapshot = function () { | ||
var prompt = _this.props.prompt; | ||
var inputString = _this.state.input; | ||
return prompt + "\u00A0" + inputString; | ||
}; | ||
_this.onSubmit = function (e) { return __awaiter(_this, void 0, void 0, function () { | ||
var _a, prompt, inputString, log, _b, cmd, args, command, ret, cmdNotFound; | ||
var inputString, log, _a, cmd, args, command, ret, cmdNotFound; | ||
var _b; | ||
return __generator(this, function (_c) { | ||
switch (_c.label) { | ||
case 0: | ||
prompt = this.props.prompt; | ||
e.preventDefault(); | ||
@@ -209,6 +242,6 @@ inputString = this.state.input; | ||
} | ||
log = prompt + "\u00A0" + inputString; | ||
log = this.getCurrentTextSnapshot(); | ||
if (inputString === '') { | ||
this.setState({ | ||
output: this.state.output.concat([log]), | ||
output: __spreadArrays(this.state.output, [log]), | ||
input: '', | ||
@@ -219,3 +252,4 @@ }); | ||
} | ||
_b = inputString.split(" "), cmd = _b[0], args = _b.slice(1); | ||
this.addHistoryEntry(inputString); | ||
_a = inputString.split(" "), cmd = _a[0], args = _a.slice(1); | ||
if (cmd === 'clear') { | ||
@@ -232,10 +266,10 @@ this.clear(); | ||
this.setState({ | ||
output: this.state.output.concat([log, ret]) | ||
output: __spreadArrays(this.state.output, [log, ret]) | ||
}); | ||
return [3 /*break*/, 4]; | ||
case 2: return [4 /*yield*/, (_a = this.props).noCommandFound.apply(_a, [cmd].concat(args))]; | ||
case 2: return [4 /*yield*/, (_b = this.props).noCommandFound.apply(_b, __spreadArrays([cmd], args))]; | ||
case 3: | ||
cmdNotFound = _c.sent(); | ||
this.setState({ | ||
output: this.state.output.concat([log, cmdNotFound]) | ||
output: __spreadArrays(this.state.output, [log, cmdNotFound]) | ||
}); | ||
@@ -251,7 +285,165 @@ _c.label = 4; | ||
}); }; | ||
_this.onInputChange = function (e) { | ||
/** | ||
* Reverse search input handler | ||
* @param event | ||
*/ | ||
_this.onReverseStringInputChange = function (event) { | ||
_this.setState({ | ||
input: e.target.value, | ||
reverseSearchString: event.target.value, | ||
}, function () { | ||
var history = _this.getReverseHistory(); | ||
var historyIndex = history.lastIndexOf(true); | ||
_this.executeNextReverseSearch(historyIndex); | ||
}); | ||
}; | ||
/** | ||
* Invoked when pressed ctrl+r and already in a reverse search mode. | ||
*/ | ||
_this.nextReverseSearch = function () { | ||
var history = _this.getReverseHistory(); | ||
var endOffset = Math.max(0, _this.state.reverseSearchPosition - 1); // so that we don't go from the end again | ||
var historyIndex = history.lastIndexOf(true, endOffset); | ||
_this.executeNextReverseSearch(historyIndex); | ||
}; | ||
/** | ||
* Helper function that sets the history preview based on a requested history index. | ||
* @param historyIndex | ||
*/ | ||
_this.executeNextReverseSearch = function (historyIndex) { | ||
_this.setState({ | ||
reverseSearchPosition: historyIndex, | ||
}); | ||
if (historyIndex !== -1) { | ||
_this.setPreviewPosition(historyIndex); | ||
} | ||
if (_this.state.reverseSearchString === '') { | ||
_this.setPreviewPosition(Infinity); | ||
} | ||
}; | ||
/** | ||
* This function sets a given history entry as a main input element content. | ||
* It's called when changing the preview of a 'current' history entry e.g. by ctrl+r call, or | ||
* when pressing up/down arrow. | ||
* @param historyPosition | ||
*/ | ||
_this.setPreviewPosition = function (historyPosition) { | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
_this.setState({ | ||
historyPosition: historyPosition, | ||
input: _this.props.history[historyPosition] || '', | ||
}); | ||
}; | ||
/** | ||
* Enables reverse search. | ||
* The side effect is that we focus o reverse search input. | ||
*/ | ||
_this.onReverseSearch = function () { | ||
// we enabled reverse search | ||
_this.setState({ | ||
reverseSearchString: '', | ||
}, function () { | ||
_this.reverseStringRef.focus(); | ||
}); | ||
}; | ||
/** | ||
* When reverse search is confirmed, we disable reverse search mode and keep the result. | ||
* @param e | ||
*/ | ||
_this.onReverseSearchSubmit = function (event) { | ||
event.preventDefault(); | ||
_this.disableReverseSearch(); | ||
}; | ||
/** | ||
* Main input change handler. | ||
* @param event | ||
*/ | ||
_this.onInputChange = function (event) { | ||
_this.setState({ | ||
input: event.target.value, | ||
}); | ||
}; | ||
/** | ||
* Helper function to determine whether reverse search is active or not. | ||
*/ | ||
_this.isReverseSearchOn = function () { return _this.state.reverseSearchString !== undefined; }; | ||
/** | ||
* Disables reverse search mode. | ||
* @param keepPreviewString - determines whether the result of a reverse search should be kept or not | ||
*/ | ||
_this.disableReverseSearch = function (keepPreviewString) { | ||
if (keepPreviewString === void 0) { keepPreviewString = true; } | ||
_this.setState({ | ||
reverseSearchString: undefined, | ||
}); | ||
if (!keepPreviewString) { | ||
_this.setState({ | ||
input: '', | ||
}); | ||
} | ||
setTimeout(function () { | ||
_this.inputRef.focus(); | ||
}); | ||
}; | ||
/** | ||
* onKeyDown implementation of a reverse search input. | ||
* @param event | ||
*/ | ||
_this.onReverseKeyDown = function (event) { | ||
if (event.which === 38 || event.which === 40) { // up or down | ||
_this.disableReverseSearch(); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 67 && event.ctrlKey) { // ctrl + c | ||
_this.disableReverseSearch(false); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 82 && event.ctrlKey) { // ctrl + r | ||
_this.nextReverseSearch(); | ||
event.preventDefault(); | ||
} | ||
}; | ||
/** | ||
* onKeyDown implementation of a main input. | ||
* @param event | ||
*/ | ||
_this.onKeyDown = function (event) { | ||
if (event.which === 38) { // key up | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
var currentPos = Math.min(_this.state.historyPosition, _this.props.history.length); | ||
var historyPosition = Math.max(0, currentPos - 1); | ||
_this.setPreviewPosition(historyPosition); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 40) { | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
var historyPosition = Math.min(_this.props.history.length, _this.state.historyPosition + 1); | ||
_this.setPreviewPosition(historyPosition); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 82 && event.ctrlKey) { // ctrl + r | ||
if (_this.props.history === undefined) { | ||
return; | ||
} | ||
_this.onReverseSearch(); | ||
event.preventDefault(); | ||
} | ||
else if (event.which === 67 && event.ctrlKey) { // ctrl + c | ||
_this.setState({ | ||
output: __spreadArrays(_this.state.output, [_this.getCurrentTextSnapshot()]), | ||
input: '', | ||
}); | ||
_this.scrollToBottom(); | ||
event.preventDefault(); | ||
} | ||
}; | ||
/** | ||
* Focuses console input. | ||
* Whenever an user clicks on a terminal, we want to focus an actual input where he/she can type. | ||
*/ | ||
_this.focusConsole = function () { | ||
@@ -264,2 +456,15 @@ if (_this.inputRef) { | ||
}; | ||
/** | ||
* Calls onAddHistoryItem property and sets historyPosition to a default value. | ||
* @param inputString | ||
*/ | ||
_this.addHistoryEntry = function (inputString) { | ||
var onAddHistoryItem = _this.props.onAddHistoryItem; | ||
if (typeof onAddHistoryItem === 'function') { | ||
onAddHistoryItem(inputString); | ||
} | ||
_this.setState({ | ||
historyPosition: Infinity, | ||
}); | ||
}; | ||
return _this; | ||
@@ -274,19 +479,24 @@ } | ||
} | ||
if (this.props.history !== undefined) { | ||
this.setState({ | ||
historyPosition: this.props.history.length, | ||
}); | ||
} | ||
}; | ||
ReactConsole.prototype.render = function () { | ||
var _this = this; | ||
var _a = this.props, wrapperClassName = _a.wrapperClassName, promptClassName = _a.promptClassName, inputClassName = _a.inputClassName, wrapperStyle = _a.wrapperStyle, promptStyle = _a.promptStyle, inputStyle = _a.inputStyle, prompt = _a.prompt, autoFocus = _a.autoFocus; | ||
var promptClass = promptClassName | ||
? styles.prompt + " " + promptClassName | ||
: styles.prompt; | ||
return (React.createElement("div", { className: classnames([styles.wrapper, wrapperClassName]), style: __assign({ overflowY: isIE11 ? "scroll" : "auto" }, wrapperStyle), onClick: this.focusConsole, ref: function (ref) { return _this.wrapperRef = ref; } }, | ||
var _a = this.props, wrapperClassName = _a.wrapperClassName, promptWrapperClassName = _a.promptWrapperClassName, promptClassName = _a.promptClassName, lineClassName = _a.lineClassName, inputClassName = _a.inputClassName, wrapperStyle = _a.wrapperStyle, promptWrapperStyle = _a.promptWrapperStyle, promptStyle = _a.promptStyle, lineStyle = _a.lineStyle, inputStyle = _a.inputStyle, prompt = _a.prompt, autoFocus = _a.autoFocus; | ||
return (React.createElement("div", { className: classnames(styles.wrapper, wrapperClassName), style: __assign({ overflowY: isIE11 ? "scroll" : "auto" }, wrapperStyle), onClick: this.focusConsole, ref: function (ref) { return _this.wrapperRef = ref; } }, | ||
React.createElement("div", null, this.state.output.map(function (line, key) { | ||
return React.createElement("pre", { key: key, className: styles.line, dangerouslySetInnerHTML: { __html: line } }); | ||
return React.createElement("pre", { key: key, className: classnames(styles.line, lineClassName), style: lineStyle, dangerouslySetInnerHTML: { __html: line } }); | ||
})), | ||
React.createElement("form", { onSubmit: this.onSubmit }, | ||
React.createElement("div", { className: classnames([styles.promptWrapper, promptClassName]), style: promptStyle }, | ||
React.createElement("span", { className: promptClass }, | ||
React.createElement("div", { className: classnames(styles.promptWrapper, promptWrapperClassName), style: promptWrapperStyle }, | ||
React.createElement("span", { className: classnames(styles.prompt, promptClassName), style: promptStyle }, | ||
prompt, | ||
"\u00A0"), | ||
React.createElement("input", { disabled: this.state.commandInProgress, ref: function (ref) { return _this.inputRef = ref; }, autoFocus: autoFocus, value: this.state.input, onChange: this.onInputChange, autoComplete: 'off', spellCheck: false, autoCapitalize: 'false', name: "input", className: classnames([styles.input, inputClassName]), style: inputStyle }))))); | ||
React.createElement("input", { disabled: this.state.commandInProgress || this.isReverseSearchOn(), ref: function (ref) { return _this.inputRef = ref; }, autoFocus: autoFocus, value: this.state.input, onChange: this.onInputChange, onKeyDown: this.onKeyDown, autoComplete: 'off', spellCheck: false, autoCapitalize: 'false', name: "input", className: classnames([styles.input, inputClassName]), style: inputStyle }))), | ||
this.isReverseSearchOn() && React.createElement("form", { onSubmit: this.onReverseSearchSubmit }, | ||
"bck-i-search: ", | ||
React.createElement("input", { value: this.state.reverseSearchString, ref: function (ref) { return _this.reverseStringRef = ref; }, onKeyDown: this.onReverseKeyDown, className: classnames([styles.input, inputClassName]), onChange: this.onReverseStringInputChange })))); | ||
}; | ||
@@ -298,3 +508,5 @@ ReactConsole.defaultProps = { | ||
wrapperStyle: {}, | ||
promptWrapperStyle: {}, | ||
promptStyle: {}, | ||
lineStyle: {}, | ||
inputStyle: {}, | ||
@@ -301,0 +513,0 @@ }; |
{ | ||
"name": "@webscopeio/react-console", | ||
"version": "1.1.3", | ||
"version": "1.2.0", | ||
"description": "React component that emulates console behaviour", | ||
@@ -18,6 +18,8 @@ "author": "jvorcak", | ||
"test:watch": "react-scripts-ts test --env=jsdom", | ||
"tsc": "tsc", | ||
"tsc:watch": "tsc --watch", | ||
"build": "rollup -c", | ||
"start": "rollup -c -w", | ||
"prepare": "yarn run build", | ||
"predeploy": "cd example && yarn install && yarn run build", | ||
"prepare": "npm run build", | ||
"predeploy": "cd example && npm install && npm run build", | ||
"deploy": "gh-pages -d example/build" | ||
@@ -36,3 +38,5 @@ }, | ||
"@types/classnames": "2.2.7", | ||
"@types/jest": "^23.1.5", | ||
"@types/enzyme": "3.10.5", | ||
"@types/enzyme-adapter-react-16": "1.0.6", | ||
"@types/jest": "26.0.7", | ||
"@types/react": "^16.3.13", | ||
@@ -43,6 +47,9 @@ "@types/react-dom": "^16.0.5", | ||
"cross-env": "^5.1.4", | ||
"enzyme": "3.11.0", | ||
"enzyme-adapter-react-16": "1.15.2", | ||
"gh-pages": "^1.2.0", | ||
"react": "^16.4.1", | ||
"react-dom": "^16.4.1", | ||
"react-scripts-ts": "^2.16.0", | ||
"react-scripts": "3.4.1", | ||
"react-scripts-ts": "3.1.0", | ||
"rollup": "^0.62.0", | ||
@@ -56,3 +63,3 @@ "rollup-plugin-babel": "^3.0.7", | ||
"rollup-plugin-url": "^1.4.0", | ||
"typescript": "^2.8.3" | ||
"typescript": "^3.9" | ||
}, | ||
@@ -59,0 +66,0 @@ "files": [ |
107
README.md
@@ -28,3 +28,3 @@ MIT-licensed console emulator in React. Documentation and more advanced features coming soon! | ||
| :--------------------- | :-------------------------------------------------------------------- |:--------------| | ||
| **commands*** | Object | | ||
| **commands*** | **CommandsProp** | | ||
| prompt | string | | ||
@@ -34,8 +34,14 @@ | welcomeMessage | string | | ||
| noCommandFound | (...str: string[]) => Promise<string> | | ||
| wrapperStyle | Object | styles for the wrapper | | ||
| promptStyle | Object | styles for the prompt | | ||
| inputStyle | Object | styles for the input | | ||
| wrapperClassName | string | className for the wrapper | | ||
| promptClassName | string | className for the prompt | | ||
| inputClassName | string | className for the input | | ||
| wrapperStyle | React.CSSProperties | styles for `wrapper` | | ||
| promptWrapperStyle | React.CSSProperties | styles for `promptWrapper` | | ||
| promptStyle | React.CSSProperties | styles for `prompt` | | ||
| lineStyle | React.CSSProperties | styles for `line` | | ||
| inputStyle | React.CSSProperties | styles for `input` | | ||
| wrapperClassName | string | className for `wrapper` | | ||
| promptWrapperClassName | string | className for `promptWrapper` | | ||
| promptClassName | string | className for `prompt` | | ||
| lineClassName | string | className for `line` | | ||
| inputClassName | string | className for `input` | | ||
| history | string[] | history array | | ||
| onAddHistoryItem | (entry: string) => void | callback called when a new history entry is created | | ||
@@ -51,41 +57,60 @@ \*_are mandatory_ | ||
export default class App extends Component { | ||
render () { | ||
return ( | ||
<div> | ||
<ReactConsole | ||
autoFocus | ||
welcomeMessage="Welcome" | ||
commands={{ | ||
echo: { | ||
description: 'Echo', | ||
fn: (...args) => { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
resolve(`${args.join(' ')}`) | ||
}, 2000) | ||
}) | ||
} | ||
}, | ||
test: { | ||
description: 'Test', | ||
fn: (...args) => { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
resolve('Hello world \n\n hello \n') | ||
}, 2000) | ||
}) | ||
} | ||
const App = () => { | ||
const [history, setHistory] = useState([ | ||
"echo hello world", | ||
"sleep 1000", | ||
"sleep 2000", | ||
"sleep 3000", | ||
"echo ola", | ||
"not found", | ||
]) | ||
return ( | ||
<div> | ||
<ReactConsole | ||
autoFocus | ||
welcomeMessage="Welcome" | ||
commands={{ | ||
history: { | ||
description: 'History', | ||
fn: () => new Promise(resolve => { | ||
resolve(`${history.join('\r\n')}`) | ||
}) | ||
}, | ||
echo: { | ||
description: 'Echo', | ||
fn: (...args) => { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
resolve(`${args.join(' ')}`) | ||
}, 2000) | ||
}) | ||
} | ||
}} | ||
/> | ||
</div> | ||
) | ||
} | ||
}, | ||
test: { | ||
description: 'Test', | ||
fn: (...args) => { | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
resolve('Hello world \n\n hello \n') | ||
}, 2000) | ||
}) | ||
} | ||
} | ||
}} | ||
/> | ||
</div> | ||
) | ||
} | ||
export default App | ||
``` | ||
## History implementation | ||
You can provide your own history implementation by providing `onAddHistoryItem` and `history` properties. | ||
If you don't provide `history`, up/down arrows & reverse search won't work. | ||
## License | ||
MIT © [jvorcak](https://github.com/jvorcak) | ||
[MIT](https://opensource.org/licenses/MIT) © [jvorcak](https://github.com/jvorcak) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
138967
55.54%10
25%1098
88.01%114
28.09%26
23.81%1
Infinity%