Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.x.x #29

Merged
merged 21 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ class App extends Component {

render() {
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh"
}}
>
<Terminal
color='green'
backgroundColor='black'
Expand Down Expand Up @@ -222,7 +229,7 @@ Let's take an another example -
<img src="https://i.gyazo.com/ef2427464989b1ce14bc44bb4fc94689.gif" />
</p>

## Using plugins 🔥
## Using plugins 🔥 [WIP]

We have also developed a plugin system for the `<Terminal />` component which helps you develop custom plugins. Here is one example of plugin which creates a fake file system called [terminal-in-react-pseudo-file-system-plugin](https://github.com/jcgertig/terminal-in-react-pseudo-file-system-plugin).

Expand Down Expand Up @@ -323,6 +330,31 @@ You can mix and match

The value of the shortcut should be a command to run.


## Override the top bar buttons actionHandlers

Use the prop `actionHandlers`.

The object allows for 3 methods `handleClose`, `handleMaximise`, `handleMinimise`;

Each one is a function and will pass in the default method as the first param.
Any method not passed in will use the default.

```jsx
<Terminal
actionHandlers={{
handleClose: (toggleClose) => {
// do something on close
toggleClose();
},
handleMaximise: (toggleMaximise) => {
// do something on maximise
toggleMaximise();
}
}}
/>
```

## Customization

Use
Expand All @@ -331,6 +363,9 @@ Use
* prop `backgroundColor` to change the background.
* prop `barColor` to change the color of bar.
* prop `prompt` to change the prompt (`>`) color.
* prop `showActions` to change if the three circles are shown.
* prop `hideTopBar` to hide the top bar altogether.
* prop `allowTabs` to allow multiple tabs.

Follow me on Twitter [@NTulswani](https://twitter.com/NTulswani) for new updates and progress 😄

Expand All @@ -351,6 +386,11 @@ Follow me on Twitter [@NTulswani](https://twitter.com/NTulswani) for new updates
| **commandPassThrough** | function | null |
| **promptSymbol** | string | > |
| **plugins** | array | [ { name: '', load: new Plugin(), commands: {} descriptions: {} } ] |
| **startState** | string ['open', 'maximised', 'minimised', 'closed'] | 'open' |
| **showActions** | bool | true |
| **hideTopBar** | bool | false |
| **allowTabs** | bool | true |
| **actionHandlers** | object | undefined |

## Built-in commands

Expand All @@ -369,7 +409,7 @@ Follow me on Twitter [@NTulswani](https://twitter.com/NTulswani) for new updates

## You want a X feature

Sure! Check our [todolist](./todo.md) or create an issue and I will look into it.
Sure! Check our [todolist](./todo.md) or create an issue and I will look into it.

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "terminal-in-react",
"version": "3.3.3",
"version": "3.4.0",
"description": "A component for making a terminal in React",
"main": "lib/js/index.js",
"module": "src/index.js",
Expand Down
66 changes: 43 additions & 23 deletions src/js/components/Bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ class Bar extends Component {
static displayName = 'Bar';

static propTypes = {
style: PropTypes.object // eslint-disable-line
style: PropTypes.object, // eslint-disable-line
showActions: PropTypes.bool,
handleMinimise: PropTypes.func,
handleMaximise: PropTypes.func,
handleClose: PropTypes.func,
};

static defaultProps = {
style: {},
showActions: true,
};

static contextTypes = {
Expand All @@ -21,45 +26,60 @@ class Bar extends Component {

// Close the window
handleClose = () => {
this.context.toggleShow();
if (this.props.handleClose) {
this.props.handleClose(this.context.toggleShow);
} else {
this.context.toggleShow();
}
};

// Minimise the window
handleMinimise = () => {
this.context.toggleMinimize();
if (this.props.handleMinimise) {
this.props.handleMinimise(this.context.toggleMinimize);
} else {
this.context.toggleMinimize();
}
};

// Maximise the window
handleMaximise = () => {
this.context.toggleMaximise();
if (this.props.handleMaximise) {
this.props.handleMaximise(this.context.toggleMaximise);
} else {
this.context.toggleMaximise();
}
};

render() {
const { style, showActions } = this.props;
return (
<div
style={{
...this.props.style,
...style,
...(this.context.maximise ? { maxWidth: '100%' } : {}),
}}
className="terminal-top-bar adjust-bar"
className="terminal-top-bar"
>
<svg height="20" width="100">
<circle cx="24" cy="14" r="5" fill="red" onClick={this.handleClose} />
<circle
cx="44"
cy="14"
r="5"
fill="orange"
onClick={this.handleMinimise}
/>
<circle
cx="64"
cy="14"
r="5"
fill="green"
onClick={this.handleMaximise}
/>
</svg>
{ showActions && (
<svg height="20" width="100">
<circle cx="24" cy="14" r="5" fill="red" onClick={this.handleClose} />
<circle
cx="44"
cy="14"
r="5"
fill="orange"
onClick={this.handleMinimise}
/>
<circle
cx="64"
cy="14"
r="5"
fill="green"
onClick={this.handleMaximise}
/>
</svg>
) }
</div>
);
}
Expand Down
110 changes: 93 additions & 17 deletions src/js/components/Content.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,131 @@
import React, { Component } from 'react'; // eslint-disable-line
import PropTypes from 'prop-types';
import whatkey, { unprintableKeys } from 'whatkey';

class Content extends Component {
static displayName = 'Content';

static propTypes = {
id: PropTypes.string,
oldData: PropTypes.object, // eslint-disable-line
backgroundColor: PropTypes.objectOf(PropTypes.string),
output: PropTypes.arrayOf(PropTypes.element),
prompt: PropTypes.objectOf(PropTypes.string),
inputStyles: PropTypes.objectOf(PropTypes.string),
register: PropTypes.func,
handleChange: PropTypes.func,
handlerKeyPress: PropTypes.func.isRequired,
};

static defaultProps = {
oldData: {},
};

static contextTypes = {
symbol: PropTypes.string,
maximise: PropTypes.bool,
instances: PropTypes.array,
activeTab: PropTypes.string,
barShowing: PropTypes.bool,
tabsShowing: PropTypes.bool,
};

state = {
summary: [],
promptPrefix: '',
history: [],
historyCounter: 0,
input: [],
keyInputs: [],
};

componentWillMount = () => {
const data = this.context.instances.find(i => i.index === this.props.id);
if (data) {
this.setState(data.oldData);
}
};

componentDidMount = () => {
this.focusInput();
const data = this.context.instances.find(i => i.index === this.props.id);
this.unregister = this.props.register(this);
if (!data || Object.keys(data.oldData).length === 0) {
this.handleChange({ target: { value: 'show' }, key: 'Enter', dontShowCommand: true });
}
};

// Adjust scrolling
componentDidUpdate = () => {
if (this.inputWrapper !== null) this.inputWrapper.scrollIntoView(false);
if (this.inputWrapper !== null) {
this.inputWrapper.scrollIntoView(false);
}
};

componentWillUnmount() {
this.unregister(this.state);
}

focusInput = () => {
if (this.com !== null) this.com.focus();
if (this.com !== null) {
this.com.focus();
}
};

handleChange = (e) => {
this.props.handleChange(this, e);
}

handleKeyPress = (e) => {
this.props.handlerKeyPress(this, e, this.com);
}

handleOuterKeypress = (e) => {
const key = whatkey(e).key;
const actionKeys = ['up', 'down', 'left', 'right', 'enter'];
if (unprintableKeys.indexOf(key) < 0) {
if (document.activeElement !== this.com) {
this.com.focus();
this.com.value += whatkey(e).char;
}
} else if (actionKeys.indexOf(key) > -1) {
this.com.focus();
}
}

render() {
const {
output,
prompt,
inputStyles,
handleChange,
backgroundColor,
handlerKeyPress,
} = this.props;
const { symbol, maximise } = this.context;
const { prompt, inputStyles, backgroundColor, id } = this.props;
const { symbol, maximise, activeTab, barShowing, tabsShowing } = this.context;

if (id !== activeTab) {
return null;
}

const output = this.state.summary.map((content, i) => {
if (typeof content === 'string' && content.length === 0) {
return <div className="terminal-output-line" key={i}>&nbsp;</div>;
}
return <pre className="terminal-output-line" key={i}>{content}</pre>;
});

let toSubtract = 30;
if (!barShowing) {
toSubtract -= 30;
}
if (tabsShowing) {
toSubtract += 30;
}

return (
<div
className="terminal-container terminal-container-main"
style={{
...backgroundColor,
...(maximise
? { maxWidth: '100%', maxHeight: 'calc(100% - 30px)' }
? { maxWidth: '100%', maxHeight: `calc(100% - ${toSubtract}px)` }
: {}),
}}
onClick={this.focusInput}
tabIndex="0"
onKeyUp={this.handleOuterKeypress}
>
<div className="terminal-holder">
<div className="terminal-content">
Expand All @@ -61,15 +135,17 @@ class Content extends Component {
className="terminal-input"
ref={elm => (this.inputWrapper = elm)}
>
<span className="terminal-prompt" style={prompt}>{symbol}</span>
<span className="terminal-prompt" style={prompt}>
{this.state.promptPrefix + symbol}
</span>
<input
className="terminal-main-input"
style={inputStyles}
type="text"
tabIndex="-1"
ref={com => (this.com = com)}
onKeyPress={handleChange}
onKeyDown={e => handlerKeyPress(e, this.com)}
onKeyPress={this.handleChange}
onKeyDown={this.handleKeyPress}
/>
</div>
</div>
Expand Down
23 changes: 15 additions & 8 deletions src/js/components/Plugin.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
export default class Plugin {
constructor(name = '', version = '1.0.0') {
this.name = name;
this.version = version;
}
static displayName = '';
static version = '1.0.0';
static defaultData = '';

load = () => {};
static commands = {};
static descriptions = {};

afterLoad = () => {};
static defaultConfig = {};

getPublicMethods = () => ({});
constructor(api, config = Plugin.defaultConfig) {
this.api = api;
this.config = config;
this.commands = {};
this.descriptions = {};

readStdOut = () => true;
this.updateApi = newApi => (this.api = newApi);
this.getPublicMethods = () => ({});
this.readStdOut = () => true;
}
}
Loading