+
+ {showingTerminal && }
);
}
}
-export default connect()(DetailsCard);
+
+function mapStateToProps(state, props) {
+ const pipe = state.get('controlPipes').last();
+ return {
+ showingTerminal: pipe && pipe.get('nodeId') === props.id,
+ };
+}
+
+
+export default connect(mapStateToProps)(DetailsCard);
diff --git a/client/app/scripts/components/embedded-terminal.js b/client/app/scripts/components/embedded-terminal.js
index 2ad699449..149d5998e 100644
--- a/client/app/scripts/components/embedded-terminal.js
+++ b/client/app/scripts/components/embedded-terminal.js
@@ -1,33 +1,63 @@
import React from 'react';
import { connect } from 'react-redux';
-import { getNodeColor, getNodeColorDark } from '../utils/color-utils';
+import { brightenColor, getNodeColorDark } from '../utils/color-utils';
+import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS } from '../constants/styles';
import Terminal from './terminal';
-import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS,
- DETAILS_PANEL_OFFSET } from '../constants/styles';
class EmeddedTerminal extends React.Component {
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ mounted: null,
+ animated: null,
+ };
+
+ this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
+ }
+
+ componentDidMount() {
+ setTimeout(() => {
+ this.setState({mounted: true});
+ });
+ }
+
+ getTransform() {
+ const dx = this.state.mounted ? 0 :
+ window.innerWidth - DETAILS_PANEL_WIDTH - DETAILS_PANEL_MARGINS.right;
+ return `translateX(${dx}px)`;
+ }
+
+ handleTransitionEnd() {
+ this.setState({ animated: true });
+ }
+
render() {
const { pipe, details } = this.props;
const nodeId = pipe.get('nodeId');
const node = details.get(nodeId);
const d = node && node.details;
- const titleBarColor = d && getNodeColorDark(d.rank, d.label);
- const statusBarColor = d && getNodeColor(d.rank, d.label);
+ const titleBarColor = d && getNodeColorDark(d.rank, d.label, d.pseudo);
+ const statusBarColor = d && brightenColor(titleBarColor);
const title = d && d.label;
- const style = {
- right: DETAILS_PANEL_MARGINS.right + DETAILS_PANEL_WIDTH + 10 +
- (details.size * DETAILS_PANEL_OFFSET)
- };
-
// React unmount/remounts when key changes, this is important for cleaning up
// the term.js and creating a new one for the new pipe.
return (
-
-
+
);
}
diff --git a/client/app/scripts/components/terminal.js b/client/app/scripts/components/terminal.js
index a514a7b3b..570cdd06c 100644
--- a/client/app/scripts/components/terminal.js
+++ b/client/app/scripts/components/terminal.js
@@ -88,9 +88,11 @@ class Terminal extends React.Component {
pixelPerCol: 0,
pixelPerRow: 0
};
+
this.handleCloseClick = this.handleCloseClick.bind(this);
this.handlePopoutTerminal = this.handlePopoutTerminal.bind(this);
this.handleResize = this.handleResize.bind(this);
+ this.focusTerminal = this.focusTerminal.bind(this);
}
createWebsocket(term) {
@@ -106,7 +108,13 @@ class Terminal extends React.Component {
};
socket.onclose = () => {
- log('socket closed');
+ //
+ // componentWillUnmount has called close and tidied up! don't try and do it again here
+ // (setState etc), its too late.
+ //
+ if (!this.socket) {
+ return;
+ }
this.socket = null;
const wereConnected = this.state.connected;
this.setState({connected: false});
@@ -133,16 +141,26 @@ class Terminal extends React.Component {
this.socket = socket;
}
- componentDidMount() {
- const component = this;
+ componentWillReceiveProps(nextProps) {
+ if (this.props.connect !== nextProps.connect && nextProps.connect) {
+ this.mountTerminal();
+ }
+ }
+ componentDidMount() {
+ if (this.props.connect) {
+ this.mountTerminal();
+ }
+ }
+
+ mountTerminal() {
this.term = new Term({
cols: this.state.cols,
rows: this.state.rows,
convertEol: !this.props.raw
});
- const innerNode = ReactDOM.findDOMNode(component.inner);
+ const innerNode = ReactDOM.findDOMNode(this.inner);
this.term.open(innerNode);
this.term.on('data', (data) => {
if (this.socket) {
@@ -180,6 +198,7 @@ class Terminal extends React.Component {
this.term.destroy();
this.term = null;
}
+
if (this.socket) {
log('close socket');
this.socket.close();
@@ -187,14 +206,6 @@ class Terminal extends React.Component {
}
}
- componentWillReceiveProps(nextProps) {
- const containerMarginChanged = nextProps.containerMargin !== this.props.containerMargin;
- log(nextProps.containerMargin);
- if (containerMarginChanged) {
- this.handleResize();
- }
- }
-
componentDidUpdate(prevProps, prevState) {
const sizeChanged = (
prevState.cols !== this.state.cols ||
@@ -213,6 +224,12 @@ class Terminal extends React.Component {
this.props.dispatch(clickCloseTerminal(this.getPipeId(), true));
}
+ focusTerminal() {
+ if (this.term) {
+ this.term.focus();
+ }
+ }
+
handlePopoutTerminal(ev) {
ev.preventDefault();
const paramString = JSON.stringify(this.props);
@@ -247,8 +264,9 @@ class Terminal extends React.Component {
}
getTerminalHeader() {
+ const light = this.props.statusBarColor || getNeutralColor();
const style = {
- backgroundColor: this.props.titleBarColor || getNeutralColor()
+ backgroundColor: light,
};
return (
@@ -318,8 +336,11 @@ class Terminal extends React.Component {
return (
{this.isEmbedded() && this.getTerminalHeader()}
-
this.innerFlex = ref}
- className={innerClassName} style={innerFlexStyle} >
+
this.innerFlex = ref}
+ className={innerClassName}
+ style={innerFlexStyle} >
this.inner = ref} />
{this.getTerminalStatusBar()}
@@ -328,4 +349,10 @@ class Terminal extends React.Component {
}
}
+
+Terminal.defaultProps = {
+ connect: true
+};
+
+
export default connect()(Terminal);
diff --git a/client/app/styles/main.less b/client/app/styles/main.less
index 975a04914..214cac3ba 100644
--- a/client/app/styles/main.less
+++ b/client/app/styles/main.less
@@ -41,7 +41,7 @@
@details-window-padding-left: 36px;
@border-radius: 4px;
-@terminal-header-height: 34px;
+@terminal-header-height: 44px;
@node-opacity-blurred: 0.25;
@node-highlight-fill-opacity: 0.1;
@@ -578,17 +578,18 @@ h2 {
.details {
&-wrapper {
position: fixed;
+ display: flex;
z-index: 1024;
right: @details-window-padding-left;
top: 24px;
bottom: 48px;
- width: @details-window-width;
transition: transform 0.33333s cubic-bezier(0,0,0.21,1);
}
}
.node-details {
height: 100%;
+ width: @details-window-width;
background-color: rgba(255, 255, 255, 0.86);
display: flex;
flex-flow: column;
@@ -597,6 +598,8 @@ h2 {
border-radius: 2px;
background-color: #fff;
.shadow-2;
+ // keep node-details above the terminal.
+ z-index: 2;
&:last-child {
margin-bottom: 0;
@@ -979,35 +982,38 @@ h2 {
}
&-embedded {
- z-index: 512;
- position: fixed;
- top: 24px;
- bottom: 48px;
- left: 36px;
- right: (@details-window-width + @details-window-padding-left + 10px);
+ position: relative;
+ // shadow of animation-wrapper is 10px, let it fit in here without being
+ // overflow hiddened.
+ padding: 10px 0 10px 10px;
+ flex: 1;
+ overflow-x: hidden;
+ }
+
+ &-animation-wrapper {
+ width: 100%;
+ height: 100%;
+ transition: transform 0.5s cubic-bezier(0.230, 1.000, 0.320, 1.000);
+ .shadow-2;
}
&-wrapper {
- position: absolute;
- bottom: 0;
- top: 0;
- left: 0;
- right: 0;
+ width: 100%;
+ height: 100%;
border: 0px solid #000000;
- border-radius: 4px;
color: #f0f0f0;
- font-family: @mono-font;
- .shadow-2;
}
&-header {
.truncate;
- text-align: center;
color: @white;
height: @terminal-header-height;
padding: 8px 24px;
background-color: @text-color;
position: relative;
+ font-size: 14px;
+ line-height: 28px;
+ border-radius: 4px 0 0 0;
&-title {
cursor: default;
@@ -1030,7 +1036,6 @@ h2 {
font-size: 0.8em;
font-weight: bold;
text-transform: uppercase;
- word-spacing: -4px;
&:hover {
opacity: 1;
@@ -1047,13 +1052,15 @@ h2 {
&-embedded &-inner { top: @terminal-header-height; }
&-app &-inner { top: 0; }
&-inner {
+ cursor: text;
+ font-family: @mono-font;
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.93);
padding: 8px;
- border-radius: 0 0 2px 2px;
+ border-radius: 0 0 0 4px;
.terminal {
background-color: transparent !important;