├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── demo.gif
├── package.json
└── src
├── Terminal.js
├── TerminalStyle.js
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015", "stage-2", "react" ]
3 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [ "leankit", "leankit/es6", "leankit/react" ]
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | lib
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # nyc test coverage
19 | .nyc_output
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # node-waf configuration
25 | .lock-wscript
26 |
27 | # Compiled binary addons (http://nodejs.org/api/addons.html)
28 | build/Release
29 |
30 | # Dependency directories
31 | node_modules
32 | jspm_packages
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | src
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Elijah Manor
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spectacle-terminal
2 |
3 | Terminal component that can be used in a spectacle slide deck
4 |
5 | ## Install
6 |
7 | ```
8 | $ npm install --save spectacle
9 | $ npm install --save spectacle-terminal
10 | ```
11 |
12 | ## Demo
13 |
14 | 
15 |
16 | [Interactive Demo](http://elijahmanor.github.com/spectacle-terminal)
17 |
18 | ## Usage
19 |
20 | ```jsx
21 | import React from "react";
22 | import { Spectacle, Deck, Slide, Heading } from "spectacle";
23 | import createTheme from "spectacle/lib/themes/default";
24 | import Terminal from "spectacle-terminal";
25 |
26 | const theme = createTheme({ primary: "#ff4081" });
27 |
28 | export default class Presentation extends React.Component {
29 | render() {
30 | return (
31 |
32 |
33 |
34 | Terminal
35 | TOTAL: 174 SUCCESS,
38 |
39 |
=============================== Coverage summary ===============================
40 |
Statements : 51.29% ( 278/542 )
41 |
Branches : 38.78% ( 95/245 )
42 |
Functions : 46.21% ( 61/132 )
43 |
Lines : 52.69% ( 274/520 )
44 |
================================================================================
45 |
]}
46 | />
47 |
48 |
49 |
50 | );
51 | }
52 | }
53 | ```
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elijahmanor/spectacle-terminal/7bbeab91db9d706045240c459be68a439ebe5314/demo.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spectacle-terminal",
3 | "version": "0.5.0",
4 | "description": "Terminal component that can be used in a spectacle slide deck",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "prebuild": "rm -rf lib/",
8 | "build": "babel src -d lib",
9 | "prepublish": "npm run build"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/elijahmanor/spectacle-terminal.git"
14 | },
15 | "keywords": [
16 | "spectacle",
17 | "terminal"
18 | ],
19 | "author": "Elijah Manor",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/elijahmanor/spectacle-terminal/issues"
23 | },
24 | "homepage": "https://github.com/elijahmanor/spectacle-terminal#readme",
25 | "devDependencies": {
26 | "babel-cli": "^6.18.0",
27 | "babel-preset-es2015": "^6.18.0",
28 | "babel-preset-react": "^6.16.0",
29 | "babel-preset-stage-2": "^6.18.0",
30 | "eslint": "^3.12.1",
31 | "eslint-config-leankit": "^3.0.0",
32 | "spectacle": "^1.2.5"
33 | },
34 | "dependencies": {
35 | "classnames": "^2.2.5",
36 | "prop-types": "^15.6.0",
37 | "react": "^15.4.1",
38 | "react-dom": "^15.4.1"
39 | },
40 | "publishConfig": {
41 | "registry": "https://registry.npmjs.org/"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Terminal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {array, bool} from "prop-types";
3 | import classNames from "classnames";
4 | import style from "./TerminalStyle";
5 |
6 | export default class Terminal extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | currentLine: {
11 | index: props.showFirstEntry ? 0 : null,
12 | iteration: null
13 | },
14 | isCollapsed: false,
15 | isMaximized: props.isMaximized,
16 | isAutoScroll: true
17 | };
18 | }
19 |
20 | componentDidMount() {
21 | document.addEventListener("keydown", this.updateCurrentLine);
22 | document.addEventListener("keydown", this.toggleMaximize);
23 | this.terminalRef.closest(".spectacle-content").style.transform = "none";
24 | }
25 |
26 | componentDidUpdate(prevProps, prevState) {
27 | if (this.mainRef) {
28 | if (this.state.isAutoScroll) {
29 | this.mainRef.scrollTop = this.mainRef.scrollHeight;
30 | } else if (this.state.currentLine.scrollTo) {
31 | this.mainRef.scrollTop = this.state.currentLine.scrollTo;
32 | }
33 | }
34 | this.terminalRef.closest(".spectacle-content").style.transform = "none";
35 | }
36 |
37 | componentWillUnmount() {
38 | document.removeEventListener("keydown", this.updateCurrentLine);
39 | document.removeEventListener("keydown", this.toggleMaximize);
40 | }
41 |
42 | toggleMaximize = e => {
43 | if (e.altKey && e.keyCode === 77) {
44 | this[`handle${this.state.isMaximized ? "Minimize" : "Expand"}`]();
45 | }
46 | };
47 |
48 | goDown(line) {
49 | const { output } = this.props;
50 | if (line.index != null) {
51 | let newIndex = line.index < output.length - 1 ? line.index + 1 : line.index;
52 | let nextLine = line.iteration != null ? output[line.index] : output[newIndex];
53 | if (Array.isArray(nextLine)) {
54 | if (line.iteration != null) {
55 | line.iteration = line.iteration < nextLine.length - 1 ? ++line.iteration : null;
56 | if (line.iteration == null) {
57 | line.index = newIndex;
58 | }
59 | } else {
60 | line.index = newIndex;
61 | line.iteration = 0;
62 | }
63 | nextLine = line.iteration != null ? nextLine[line.iteration] : nextLine;
64 | } else {
65 | line.index = newIndex;
66 | line.iteration = null;
67 | nextLine = output[line.index];
68 | }
69 | line.isAutoScroll = nextLine.isAutoScroll != null ? nextLine.isAutoScroll : true;
70 | line.isSolo = nextLine.isSolo != null ? nextLine.isSolo : false;
71 | line.scrollTo = nextLine.scrollTo != null ? nextLine.scrollTo : null;
72 | line.note = nextLine.note;
73 | } else {
74 | line = { index: 0 };
75 | if (Array.isArray(output[line.index])) {
76 | line.iteration = 0;
77 | }
78 | }
79 | return line;
80 | }
81 |
82 | goUp(line) {
83 | const { showFirstEntry, output } = this.props;
84 |
85 | if (line.index > 0) {
86 | line.index--;
87 | } else if (line.index === 0) {
88 | line.index = showFirstEntry ? 0 : null;
89 | }
90 | const previousLine = output[line.index];
91 | if (previousLine) {
92 | line.isAutoScroll = previousLine.isAutoScroll != null ? previousLine.isAutoScroll : true;
93 | line.isSolo = previousLine.isSolo != null ? previousLine.isSolo : false;
94 | line.scrollTo = previousLine.scrollTo != null ? previousLine.scrollTo : null;
95 | line.note = previousLine.note;
96 | }
97 |
98 | return line;
99 | }
100 |
101 | updateCurrentLine = e => {
102 | let { currentLine } = this.state;
103 | if (e.keyCode === 40) {
104 | currentLine = this.goDown(currentLine);
105 | } else if (e.keyCode === 38) {
106 | currentLine = this.goUp(currentLine);
107 | }
108 | this.setState({
109 | currentLine,
110 | isAutoScroll: currentLine.isAutoScroll
111 | });
112 | };
113 |
114 | handleClose = () => {
115 | this.setState({ isCollapsed: !this.state.isCollapsed });
116 | };
117 |
118 | handleMinimize = () => {
119 | this.setState({ isMaximized: false });
120 | };
121 |
122 | handleExpand = () => {
123 | this.setState({ isMaximized: true });
124 | this.terminalRef.closest(".spectacle-content").style.transform = "none";
125 | document.querySelector("button[data-radium]").closest("div").style.display = "none";
126 | };
127 |
128 | renderLines() {
129 | const { output } = this.props;
130 | const { currentLine } = this.state;
131 |
132 | if (!currentLine.isSolo) {
133 | return output.reduce((memo, line, index) => {
134 | if (
135 | (index <= currentLine.index && !line.isSolo) ||
136 | (currentLine.index === 0 && index === 0)
137 | ) {
138 | if (Array.isArray(line)) {
139 | const iteration =
140 | currentLine.iteration != null && currentLine.index === index
141 | ? currentLine.iteration
142 | : line.length - 1;
143 | memo.push(
144 |
145 | {line[iteration].output || line[iteration]}
146 |
147 | );
148 | } else {
149 | memo.push(
150 |
151 | {line.output || line}
152 |
153 | );
154 | }
155 | }
156 | return memo;
157 | }, []);
158 | } else {
159 | const line = output[currentLine.index];
160 | if (currentLine.scrollTo) {
161 | this.mainRef.scrollTop = currentLine.scrollTo;
162 | } else {
163 | this.mainRef.scrollTop = 0;
164 | }
165 | return (
166 |
167 | {line.output || line}
168 |
169 | );
170 | }
171 | }
172 |
173 | renderMain() {
174 | const { output } = this.props;
175 | const { isMaximized, currentLine } = this.state;
176 | return (
177 | {
180 | this.mainRef = elem;
181 | }}
182 | style={Object.assign({}, style.main, isMaximized ? style.mainMaximized : {})}
183 | >
184 | {currentLine.index != null && this.renderLines()}
185 |
186 | );
187 | }
188 |
189 | renderNote() {
190 | const { currentLine } = this.state;
191 | return (
192 |
208 | {currentLine.note}
209 |
210 | );
211 | }
212 |
213 | render() {
214 | const { title } = this.props;
215 | const { isMaximized, isCollapsed, currentLine } = this.state;
216 |
217 | return (
218 | {
222 | this.terminalRef = elem;
223 | }}
224 | >
225 |
226 |
237 | {title}
238 |
239 | {!isCollapsed && this.renderMain()}
240 | {currentLine.note && this.renderNote()}
241 |
242 | );
243 | }
244 | }
245 |
246 | Terminal.propTypes = {
247 | output: array,
248 | showFirstEntry: bool,
249 | isMaximized: false
250 | };
251 |
--------------------------------------------------------------------------------
/src/TerminalStyle.js:
--------------------------------------------------------------------------------
1 | export default {
2 | container: {
3 | position: "relative",
4 | fontSize: "1.75vw",
5 | minWidth: "75vw",
6 | maxWidth: "75vw",
7 | left: "50%",
8 | transform: "translateX(-50%)"
9 | },
10 | containerMaximized: {
11 | position: "absolute",
12 | top: "1rem",
13 | right: "1rem",
14 | bottom: "5rem",
15 | left: "1rem",
16 | fontSize: "1.9vw"
17 | },
18 | header: {
19 | position: "relative",
20 | padding: "10px",
21 | backgroundColor: "#E0E9F0",
22 | borderTopLeftRadius: "10px",
23 | borderTopRightRadius: "10px",
24 | boxShadow: "inset 0px -3px 10px 0px rgba(0, 0, 0, 0.2)"
25 | },
26 | nav: {
27 | display: "flex",
28 | textAlign: "left",
29 | height: "2vw",
30 | alignItems: "center"
31 | },
32 | title: {
33 | position: "absolute",
34 | top: "50%",
35 | left: "50%",
36 | transform: "translate(-50%, -50%)",
37 | fontSize: "1.75vw"
38 | },
39 | button: {
40 | border: "none",
41 | borderRadius: "50%",
42 | height: "1.5vw",
43 | width: "1.5vw",
44 | marginRight: "1vw"
45 | },
46 | buttonClose: {
47 | backgroundColor: "#EE5057"
48 | },
49 | buttonMinimize: {
50 | backgroundColor: "#DEC612"
51 | },
52 | buttonExpand: {
53 | backgroundColor: "#33B969"
54 | },
55 | main: {
56 | padding: "15px",
57 | backgroundColor: "#303539",
58 | color: "white",
59 | height: "50vh",
60 | fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
61 | direction: "ltr",
62 | textAlign: "left",
63 | whiteSpace: "pre",
64 | wordSpacing: "normal",
65 | wordBreak: "normal",
66 | wordWrap: "normal",
67 | lineHeight: "1.5",
68 | tabSize: "4",
69 | hyphens: "none",
70 | overflow: "auto"
71 | },
72 | mainMaximized: {
73 | height: "100%",
74 | maxHeight: "none"
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require( "./Terminal" );
2 |
--------------------------------------------------------------------------------