Migrate styling to styled-jsx (#2761)

Fixes #2737
This commit is contained in:
Timothy 2018-03-17 12:51:36 +00:00 committed by CHaBou
parent cf7347fd3c
commit 20173f0e86
15 changed files with 648 additions and 695 deletions

View file

@ -1,69 +0,0 @@
import React from 'react';
import {StyleSheet, css} from 'aphrodite-simple';
const decorateBaseComponent = Component => {
return class BaseComponent extends Component {
constructor() {
super();
this.styles_ = this.createStyleSheet();
this.cssHelper = this.cssHelper.bind(this);
}
createStyleSheet() {
if (!this.styles) {
return {};
}
const styles = this.styles();
if (typeof styles !== 'object') {
throw new TypeError('Component `styles` returns a non-object');
}
return StyleSheet.create(this.styles());
}
// wrap aphrodite's css helper for two reasons:
// - we can give the element an unaltered global classname
// that can be used to introduce global css side effects
// for example, through the configuration, web inspector
// or user agent extensions
// - the user doesn't need to keep track of both `css`
// and `style`, and we make that whole ordeal easier
cssHelper(...args) {
const classes = args
.map(c => {
if (c) {
// we compute the global name from the given
// css class and we prepend the component name
//
// it's important classes never get mangled by
// uglifiers so that we can avoid collisions
const component = this.constructor.name.toString().toLowerCase();
const globalName = `${component}_${c}`;
return [globalName, css(this.styles_[c])];
}
return null;
})
// skip nulls
.filter(v => Boolean(v))
// flatten
.reduce((a, b) => a.concat(b));
return classes.length ? classes.join(' ') : null;
}
render() {
// convert static objects from `babel-plugin-transform-jsx`
// to `React.Element`.
if (!this.template) {
throw new TypeError("Component doesn't define `template`");
}
// invoke the template creator passing our css helper
return this.template(this.cssHelper);
}
};
};
export const PureComponent = decorateBaseComponent(React.PureComponent);
export const Component = decorateBaseComponent(React.Component);

View file

@ -1,13 +1,12 @@
import React from 'react';
import {PureComponent} from '../base-components';
import {decorate, getTabsProps} from '../utils/plugins';
import Tabs_ from './tabs';
const Tabs = decorate(Tabs_, 'Tabs');
export default class Header extends PureComponent {
export default class Header extends React.PureComponent {
constructor() {
super();
this.onChangeIntent = this.onChangeIntent.bind(this);
@ -87,7 +86,7 @@ export default class Header extends PureComponent {
};
}
template(css) {
render() {
const {isMac} = this.props;
const props = getTabsProps(this.props, {
tabs: this.props.tabs,
@ -109,30 +108,42 @@ export default class Header extends PureComponent {
return (
<header
className={css('header', isMac && 'headerRounded')}
className={`header_header ${isMac && 'header_headerRounded'}`}
onMouseDown={this.handleHeaderMouseDown}
onDoubleClick={this.handleMaximizeClick}
>
{!isMac && (
<div className={css('windowHeader', props.tabs.length > 1 && 'windowHeaderWithBorder')} style={{borderColor}}>
<div
className={`header_windowHeader ${props.tabs.length > 1 ? 'header_windowHeaderWithBorder' : ''}`}
style={{borderColor}}
>
{hambMenu && (
<svg
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
className={`header_shape ${left ? 'header_hamburgerMenuRight' : 'header_hamburgerMenuLeft'}`}
onClick={this.handleHamburgerMenuClick}
>
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
</svg>
)}
<span className={css('appTitle')}>{title}</span>
<span className="header_appTitle">{title}</span>
{winCtrls && (
<div className={css('windowControls', left && 'windowControlsLeft')}>
<svg className={css('shape', left && 'minimizeWindowLeft')} onClick={this.handleMinimizeClick}>
<div className={`header_windowControls ${left ? 'header_windowControlsLeft' : ''}`}>
<svg
className={`header_shape ${left ? 'header_minimizeWindowLeft' : ''}`}
onClick={this.handleMinimizeClick}
>
<use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
</svg>
<svg className={css('shape', left && 'maximizeWindowLeft')} onClick={this.handleMaximizeClick}>
<svg
className={`header_shape ${left ? 'header_maximizeWindowLeft' : ''}`}
onClick={this.handleMaximizeClick}
>
<use xlinkHref={maxButtonHref} />
</svg>
<svg className={css('shape', 'closeWindow', left && 'closeWindowLeft')} onClick={this.handleCloseClick}>
<svg
className={`header_shape header_closeWindow ${left ? 'header_closeWindowLeft' : ''}`}
onClick={this.handleCloseClick}
>
<use xlinkHref="./renderer/assets/icons.svg#close-window" />
</svg>
</div>
@ -142,95 +153,109 @@ export default class Header extends PureComponent {
{this.props.customChildrenBefore}
<Tabs {...props} />
{this.props.customChildren}
<style jsx>{`
.header_header {
position: fixed;
top: 1px;
left: 1px;
right: 1px;
z-index: 100;
}
.header_headerRounded {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.header_windowHeader {
height: 34px;
width: 100%;
position: fixed;
top: 1px;
left: 1px;
right: 1px;
-webkit-app-region: drag;
-webkit-user-select: none;
display: flex;
justify-content: center;
align-items: center;
}
.header_windowHeaderWithBorder {
border-color: #ccc;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.header_appTitle {
font-size: 12px;
}
.header_shape {
width: 40px;
height: 34px;
padding: 12px 15px 12px 15px;
-webkit-app-region: no-drag;
color: #fff;
opacity: 0.5;
shape-rendering: crispEdges;
}
.header_shape:hover {
opacity: 1;
}
.header_shape:active {
opacity: 0.3;
}
.header_hamburgerMenuLeft {
position: fixed;
top: 0;
left: 0;
}
.header_hamburgerMenuRight {
position: fixed;
top: 0;
right: 0;
}
.header_windowControls {
display: flex;
width: 120px;
height: 34px;
justify-content: space-between;
position: fixed;
right: 0;
}
.header_windowControlsLeft {
left: 0px;
}
.header_closeWindowLeft {
order: 1;
}
.header_minimizeWindowLeft {
order: 2;
}
.header_maximizeWindowLeft {
order: 3;
}
.header_closeWindow:hover {
color: #fe354e;
}
.header_closeWindow:active {
color: #fe354e;
}
`}</style>
</header>
);
}
styles() {
return {
header: {
position: 'fixed',
top: '1px',
left: '1px',
right: '1px',
zIndex: '100'
},
headerRounded: {
borderTopLeftRadius: '4px',
borderTopRightRadius: '4px'
},
windowHeader: {
height: '34px',
width: '100%',
position: 'fixed',
top: '1px',
left: '1px',
right: '1px',
WebkitAppRegion: 'drag',
WebkitUserSelect: 'none',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
windowHeaderWithBorder: {
borderColor: '#ccc',
borderBottomStyle: 'solid',
borderBottomWidth: '1px'
},
appTitle: {
fontSize: '12px'
},
shape: {
width: '40px',
height: '34px',
padding: '12px 15px 12px 15px',
WebkitAppRegion: 'no-drag',
color: '#FFFFFF',
opacity: 0.5,
shapeRendering: 'crispEdges',
':hover': {
opacity: 1
},
':active': {
opacity: 0.3
}
},
hamburgerMenuLeft: {
position: 'fixed',
top: '0',
left: '0'
},
hamburgerMenuRight: {
position: 'fixed',
top: '0',
right: '0'
},
windowControls: {
display: 'flex',
width: '120px',
height: '34px',
justifyContent: 'space-between',
position: 'fixed',
right: '0'
},
windowControlsLeft: {left: '0px'},
closeWindowLeft: {order: 1},
minimizeWindowLeft: {order: 2},
maximizeWindowLeft: {order: 3},
closeWindow: {':hover': {color: '#FE354E'}, ':active': {color: '#FE354E'}}
};
}
}

View file

@ -1,7 +1,6 @@
import React from 'react';
import {PureComponent} from '../base-components';
export default class Notification extends PureComponent {
export default class Notification extends React.PureComponent {
constructor() {
super();
this.state = {
@ -63,51 +62,53 @@ export default class Notification extends PureComponent {
clearTimeout(this.dismissTimer);
}
template(css) {
render() {
const {backgroundColor} = this.props;
const opacity = this.state.dismissing ? 0 : 1;
return (
<div ref={this.onElement} style={{opacity, backgroundColor}} className={css('indicator')}>
<div ref={this.onElement} style={{opacity, backgroundColor}} className="notification_indicator">
{this.props.customChildrenBefore}
{this.props.children || this.props.text}
{this.props.userDismissable ? (
<a className={css('dismissLink')} onClick={this.handleDismiss} style={{color: this.props.userDismissColor}}>
<a
className="notification_dismissLink"
onClick={this.handleDismiss}
style={{color: this.props.userDismissColor}}
>
[x]
</a>
) : null}
{this.props.customChildren}
<style jsx>{`
.notification_indicator {
display: inline-block;
cursor: default;
-webkit-user-select: none;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
padding: 8px 14px 9px;
margin-left: 10px;
transition: 150ms opacity ease;
color: #fff;
font-size: 11px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
}
.notification_dismissLink {
position: relative;
left: 4px;
cursor: pointer;
color: #528d11;
}
.notification_dismissLink:hover,
.notification_dismissLink:focus {
color: #2a5100;
}
`}</style>
</div>
);
}
styles() {
return {
indicator: {
display: 'inline-block',
cursor: 'default',
WebkitUserSelect: 'none',
background: 'rgba(255, 255, 255, .2)',
borderRadius: '2px',
padding: '8px 14px 9px',
marginLeft: '10px',
transition: '150ms opacity ease',
color: '#fff',
fontSize: '11px',
fontFamily: `-apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans",
"Droid Sans", "Helvetica Neue", sans-serif`
},
dismissLink: {
position: 'relative',
left: '4px',
cursor: 'pointer',
color: '#528D11',
':hover': {
color: '#2A5100'
}
}
};
}
}

View file

@ -1,16 +1,15 @@
import React from 'react';
import {PureComponent} from '../base-components';
import {decorate} from '../utils/plugins';
import Notification_ from './notification';
const Notification = decorate(Notification_, 'Notification');
export default class Notifications extends PureComponent {
template(css) {
export default class Notifications extends React.PureComponent {
render() {
return (
<div className={css('view')}>
<div className="notifications_view">
{this.props.customChildrenBefore}
{this.props.fontShowing && (
<Notification
@ -114,17 +113,15 @@ export default class Notifications extends PureComponent {
</Notification>
)}
{this.props.customChildren}
<style jsx>{`
.notifications_view {
position: fixed;
bottom: 20px;
right: 20px;
}
`}</style>
</div>
);
}
styles() {
return {
view: {
position: 'fixed',
bottom: '20px',
right: '20px'
}
};
}
}

View file

@ -1,9 +1,8 @@
/* eslint-disable quote-props */
import React from 'react';
import {PureComponent} from '../base-components';
import _ from 'lodash';
export default class SplitPane extends PureComponent {
export default class SplitPane extends React.PureComponent {
constructor(props) {
super(props);
this.handleDragStart = this.handleDragStart.bind(this);
@ -104,7 +103,7 @@ export default class SplitPane extends PureComponent {
}
}
template(css) {
render() {
const children = this.props.children;
const {direction, borderColor} = this.props;
const sizeProperty = direction === 'horizontal' ? 'height' : 'flexBasis';
@ -116,7 +115,7 @@ export default class SplitPane extends PureComponent {
sizes = new Array(children.length).fill(1 / children.length);
}
return (
<div className={css('panes', `panes_${direction}`)}>
<div className={`splitpane_panes splitpane_panes_${direction}`}>
{React.Children.map(children, (child, i) => {
const style = {
// flexBasis doesn't work for the first horizontal pane, height need to be specified
@ -125,7 +124,7 @@ export default class SplitPane extends PureComponent {
flexGrow: 0
};
return [
<div key="pane" className={css('pane')} style={style}>
<div key="pane" className="splitpane_pane" style={style}>
{child}
</div>,
i < children.length - 1 ? (
@ -134,80 +133,79 @@ export default class SplitPane extends PureComponent {
onMouseDown={this.handleDragStart}
onDoubleClick={this.handleAutoResize}
style={{backgroundColor: borderColor}}
className={css('divider', `divider_${direction}`)}
className={`splitpane_divider splitpane_divider_${direction}`}
/>
) : null
];
})}
<div style={{display: this.state.dragging ? 'block' : 'none'}} className={css('shim')} />
<div style={{display: this.state.dragging ? 'block' : 'none'}} className="splitpane_shim" />
<style jsx>{`
.splitpane_panes {
display: flex;
flex: 1;
outline: none;
position: relative;
width: 100%;
height: 100%;
}
.splitpane_panes_vertical {
flex-direction: row;
}
.splitpane_panes_horizontal {
flex-direction: column;
}
.splitpane_pane {
flex: 1;
outline: none;
position: relative;
}
.splitpane_divider {
box-sizing: border-box;
z-index: 1;
background-clip: padding-box;
flex-shrink: 0;
}
.splitpane_divider_vertical {
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
width: 11px;
margin: 0 -5px;
cursor: col-resize;
}
.splitpane_divider_horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
/*
this shim is used to make sure mousemove events
trigger in all the draggable area of the screen
this is not the case due to hterm's <iframe>
*/
.splitpane_shim {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
}
`}</style>
</div>
);
}
styles() {
return {
panes: {
display: 'flex',
flex: 1,
outline: 'none',
position: 'relative',
width: '100%',
height: '100%'
},
panes_vertical: {
flexDirection: 'row'
},
panes_horizontal: {
flexDirection: 'column'
},
pane: {
flex: 1,
outline: 'none',
position: 'relative'
},
divider: {
boxSizing: 'border-box',
zIndex: '1',
backgroundClip: 'padding-box',
flexShrink: 0
},
divider_vertical: {
borderLeft: '5px solid rgba(255, 255, 255, 0)',
borderRight: '5px solid rgba(255, 255, 255, 0)',
width: '11px',
margin: '0 -5px',
cursor: 'col-resize'
},
divider_horizontal: {
height: '11px',
margin: '-5px 0',
borderTop: '5px solid rgba(255, 255, 255, 0)',
borderBottom: '5px solid rgba(255, 255, 255, 0)',
cursor: 'row-resize',
width: '100%'
},
// this shim is used to make sure mousemove events
// trigger in all the draggable area of the screen
//
// this is not the case due to hterm's <iframe>
shim: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'transparent'
}
};
}
componentWillUnmount() {
// ensure drag end
if (this.dragging) {

View file

@ -2,116 +2,110 @@ import React from 'react';
export default class StyleSheet extends React.PureComponent {
render() {
const {customCSS, backgroundColor, fontFamily, foregroundColor, borderColor} = this.props;
const {backgroundColor, fontFamily, foregroundColor, borderColor} = this.props;
return (
<style
dangerouslySetInnerHTML={{
__html: `
.xterm {
font-family: ${fontFamily};
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
<style jsx global>{`
.xterm {
font-family: ${fontFamily};
font-feature-settings: 'liga' 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: ${backgroundColor};
color: ${foregroundColor};
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: ${backgroundColor};
color: ${foregroundColor};
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: ${backgroundColor};
overflow-y: scroll;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: ${backgroundColor};
overflow-y: scroll;
}
.xterm canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm .xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
left: -9999em;
}
.xterm .xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
left: -9999em;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm:not(.enable-mouse-events) {
cursor: text;
}
.xterm:not(.enable-mouse-events) {
cursor: text;
}
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: ${borderColor};
}
::-webkit-scrollbar-thumb:window-inactive {
background: ${borderColor};
}
${customCSS}
`
}}
/>
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: ${borderColor};
}
::-webkit-scrollbar-thumb:window-inactive {
background: ${borderColor};
}
`}</style>
);
}
}

View file

@ -1,7 +1,6 @@
import React from 'react';
import {PureComponent} from '../base-components';
export default class Tab extends PureComponent {
export default class Tab extends React.PureComponent {
constructor() {
super();
@ -37,145 +36,145 @@ export default class Tab extends PureComponent {
}
}
template(css) {
render() {
const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props;
const {hovered} = this.state;
return (
<li
onMouseEnter={this.handleHover}
onMouseLeave={this.handleBlur}
onClick={this.props.onClick}
style={{borderColor}}
className={css(
'tab',
isFirst && 'first',
isActive && 'active',
isFirst && isActive && 'firstActive',
hasActivity && 'hasActivity'
)}
>
{this.props.customChildrenBefore}
<span className={css('text', isLast && 'textLast', isActive && 'textActive')} onClick={this.handleClick}>
<span title={this.props.text} className={css('textInner')}>
{this.props.text}
<React.Fragment>
<li
onMouseEnter={this.handleHover}
onMouseLeave={this.handleBlur}
onClick={this.props.onClick}
style={{borderColor}}
className={`tab_tab ${isFirst ? 'tab_first' : ''} ${isActive ? 'tab_active' : ''} ${
isFirst && isActive ? 'tab_firstActive' : ''
} ${hasActivity ? 'tab_hasActivity' : ''}`}
>
{this.props.customChildrenBefore}
<span
className={`tab_text ${isLast ? 'tab_textLast' : ''} ${isActive ? 'tab_textActive' : ''}`}
onClick={this.handleClick}
>
<span title={this.props.text} className="tab_textInner">
{this.props.text}
</span>
</span>
</span>
<i className={css('icon', hovered && 'iconHovered')} onClick={this.props.onClose}>
<svg className={css('shape')}>
<use xlinkHref="./renderer/assets/icons.svg#close-tab" />
</svg>
</i>
{this.props.customChildren}
</li>
<i className={`tab_icon ${hovered ? 'tab_iconHovered' : ''}`} onClick={this.props.onClose}>
<svg className="tab_shape">
<use xlinkHref="./renderer/assets/icons.svg#close-tab" />
</svg>
</i>
{this.props.customChildren}
</li>
<style jsx>{`
.tab_tab {
color: #ccc;
border-color: #ccc;
border-bottom-width: 1px;
border-bottom-style: solid;
border-left-width: 1px;
border-left-style: solid;
list-style-type: none;
flex-grow: 1;
position: relative;
}
.tab_tab:hover {
color: #ccc;
}
.tab_first {
border-left-width: 0;
padding-left: 1px;
}
.tab_firstActive {
border-left-width: 1px;
padding-left: 0;
}
.tab_active {
color: #fff;
border-bottom-width: 0;
}
.tab_active:hover {
color: #fff;
}
.tab_hasActivity {
color: #50e3c2;
}
.tab_hasActivity:hover {
color: #50e3c2;
}
.tab_text {
transition: color 0.2s ease;
height: 34px;
display: block;
width: 100%;
position: relative;
overflow: hidden;
}
.tab_textInner {
position: absolute;
left: 24px;
right: 24px;
top: 0;
bottom: 0;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.tab_icon {
transition: opacity 0.2s ease, color 0.2s ease, transform 0.25s ease, background-color 0.1s ease;
pointer-events: none;
position: absolute;
right: 7px;
top: 10px;
display: inline-block;
width: 14px;
height: 14px;
border-radius: 100%;
color: #e9e9e9;
opacity: 0;
transform: scale(0.95);
}
.tab_icon:hover {
background-color: rgba(255, 255, 255, 0.13);
color: #fff;
}
.tab_icon:active {
background-color: rgba(255, 255, 255, 0.1);
color: #909090;
}
.tab_iconHovered {
opacity: 1;
transform: none;
pointer-events: all;
}
.tab_shape {
position: absolute;
left: 4px;
top: 4px;
width: 6px;
height: 6px;
vertical-align: middle;
fill: currentColor;
shape-rendering: crispEdges;
}
`}</style>
</React.Fragment>
);
}
styles() {
return {
tab: {
color: '#ccc',
borderColor: '#ccc',
borderBottomWidth: 1,
borderBottomStyle: 'solid',
borderLeftWidth: 1,
borderLeftStyle: 'solid',
listStyleType: 'none',
flexGrow: 1,
position: 'relative',
':hover': {
color: '#ccc'
}
},
first: {
borderLeftWidth: 0,
paddingLeft: 1
},
firstActive: {
borderLeftWidth: 1,
paddingLeft: 0
},
active: {
color: '#fff',
borderBottomWidth: 0,
':hover': {
color: '#fff'
}
},
hasActivity: {
color: '#50E3C2',
':hover': {
color: '#50E3C2'
}
},
text: {
transition: 'color .2s ease',
height: '34px',
display: 'block',
width: '100%',
position: 'relative',
overflow: 'hidden'
},
textInner: {
position: 'absolute',
left: '24px',
right: '24px',
top: 0,
bottom: 0,
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden'
},
icon: {
transition: `opacity .2s ease, color .2s ease,
transform .25s ease, background-color .1s ease`,
pointerEvents: 'none',
position: 'absolute',
right: '7px',
top: '10px',
display: 'inline-block',
width: '14px',
height: '14px',
borderRadius: '100%',
color: '#e9e9e9',
opacity: 0,
transform: 'scale(.95)',
':hover': {
backgroundColor: 'rgba(255,255,255, .13)',
color: '#fff'
},
':active': {
backgroundColor: 'rgba(255,255,255, .1)',
color: '#909090'
}
},
iconHovered: {
opacity: 1,
transform: 'none',
pointerEvents: 'all'
},
shape: {
position: 'absolute',
left: '4px',
top: '4px',
width: '6px',
height: '6px',
verticalAlign: 'middle',
fill: 'currentColor',
shapeRendering: 'crispEdges'
}
};
}
}

View file

@ -1,6 +1,5 @@
import React from 'react';
import {PureComponent} from '../base-components';
import {decorate, getTabProps} from '../utils/plugins';
import Tab_ from './tab';
@ -8,19 +7,19 @@ import Tab_ from './tab';
const Tab = decorate(Tab_, 'Tab');
const isMac = /Mac/.test(navigator.userAgent);
export default class Tabs extends PureComponent {
template(css) {
export default class Tabs extends React.PureComponent {
render() {
const {tabs = [], borderColor, onChange, onClose} = this.props;
const hide = !isMac && tabs.length === 1;
return (
<nav className={css('nav', hide && 'hiddenNav')}>
<nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`}>
{this.props.customChildrenBefore}
{tabs.length === 1 && isMac ? <div className={css('title')}>{tabs[0].title}</div> : null}
{tabs.length === 1 && isMac ? <div className="tabs_title">{tabs[0].title}</div> : null}
{tabs.length > 1
? [
<ul key="list" className={css('list')}>
<ul key="list" className="tabs_list">
{tabs.map((tab, i) => {
const {uid, title, isActive, hasActivity} = tab;
const props = getTabProps(tab, this.props, {
@ -36,58 +35,56 @@ export default class Tabs extends PureComponent {
return <Tab key={`tab-${uid}`} {...props} />;
})}
</ul>,
isMac && <div key="shim" style={{borderColor}} className={css('borderShim')} />
isMac && <div key="shim" style={{borderColor}} className="tabs_borderShim" />
]
: null}
{this.props.customChildren}
<style jsx>{`
.tabs_nav {
font-size: 12px;
height: 34px;
line-height: 34px;
vertical-align: middle;
color: #9b9b9b;
cursor: default;
position: relative;
-webkit-user-select: none;
-webkit-app-region: ${isMac ? 'drag' : ''};
top: ${isMac ? '0px' : '34px'};
}
.tabs_hiddenNav {
display: none;
}
.tabs_title {
text-align: center;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 76px;
padding-right: 76px;
}
.tabs_list {
max-height: 34px;
display: flex;
flex-flow: row;
margin-left: ${isMac ? '76px' : '0'};
}
.tabs_borderShim {
position: absolute;
width: 76px;
bottom: 0;
border-color: #ccc;
border-bottom-style: solid;
border-bottom-width: 1px;
}
`}</style>
</nav>
);
}
styles() {
return {
nav: {
fontSize: '12px',
height: '34px',
lineHeight: '34px',
verticalAlign: 'middle',
color: '#9B9B9B',
cursor: 'default',
position: 'relative',
WebkitUserSelect: 'none',
WebkitAppRegion: isMac ? 'drag' : '',
top: isMac ? '0px' : '34px'
},
hiddenNav: {
display: 'none'
},
title: {
textAlign: 'center',
color: '#fff',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
paddingLeft: 76,
paddingRight: 76
},
list: {
maxHeight: '34px',
display: 'flex',
flexFlow: 'row',
marginLeft: isMac ? 76 : 0
},
borderShim: {
position: 'absolute',
width: '76px',
bottom: 0,
borderColor: '#ccc',
borderBottomStyle: 'solid',
borderBottomWidth: '1px'
}
};
}
}

View file

@ -1,6 +1,5 @@
import React from 'react';
import {connect} from 'react-redux';
import {PureComponent} from '../base-components';
import {decorate, getTermProps, getTermGroupProps} from '../utils/plugins';
import {resizeTermGroup} from '../actions/term-groups';
import Term_ from './term';
@ -9,7 +8,7 @@ import SplitPane_ from './split-pane';
const Term = decorate(Term_, 'Term');
const SplitPane = decorate(SplitPane_, 'SplitPane');
class TermGroup_ extends PureComponent {
class TermGroup_ extends React.PureComponent {
constructor(props, context) {
super(props, context);
this.bound = new WeakMap();
@ -109,7 +108,7 @@ class TermGroup_ extends PureComponent {
}
}
template() {
render() {
const {childGroups, termGroup} = this.props;
if (termGroup.sessionUid) {
return this.renderTerm(termGroup.sessionUid);

View file

@ -4,7 +4,6 @@ import {Terminal} from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
import {clipboard} from 'electron';
import * as Color from 'color';
import {PureComponent} from '../base-components';
import terms from '../terms';
import processClipboard from '../utils/paste';
@ -56,7 +55,7 @@ const getTermOptions = props => {
};
};
export default class Term extends PureComponent {
export default class Term extends React.PureComponent {
constructor(props) {
super(props);
props.ref_(props.uid, this);
@ -133,7 +132,7 @@ export default class Term extends PureComponent {
}
onOpen(termOptions) {
// we need to delay one frame so that aphrodite styles
// we need to delay one frame so that styles
// get applied and we can make an accurate measurement
// of the container width and height
requestAnimationFrame(() => {
@ -297,35 +296,32 @@ export default class Term extends PureComponent {
});
}
template(css) {
render() {
return (
<div
className={css('fit', this.props.isTermActive && 'active')}
className={`term_fit ${this.props.isTermActive ? 'term_active' : ''}`}
style={{padding: this.props.padding}}
onMouseUp={this.onMouseUp}
>
{this.props.customChildrenBefore}
<div ref={this.onTermWrapperRef} className={css('fit', 'wrapper')}>
<div ref={this.onTermRef} className={css('fit', 'term')} />
<div ref={this.onTermWrapperRef} className="term_fit term_wrapper">
<div ref={this.onTermRef} className="term_fit term_term" />
</div>
{this.props.customChildren}
<style jsx>{`
.term_fit {
display: block;
width: 100%;
height: 100%;
}
.term_wrapper {
/* TODO: decide whether to keep this or not based on understanding what xterm-selection is for */
overflow: hidden;
}
`}</style>
</div>
);
}
styles() {
return {
fit: {
display: 'block',
width: '100%',
height: '100%'
},
wrapper: {
// TODO: decide whether to keep this or not based on
// understanding what xterm-selection is for
overflow: 'hidden'
},
term: {}
};
}
}

View file

@ -1,5 +1,4 @@
import React from 'react';
import {Component} from '../base-components';
import {decorate, getTermGroupProps} from '../utils/plugins';
import {registerCommandHandlers} from '../command-registry';
import TermGroup_ from './term-group';
@ -10,7 +9,7 @@ const StyleSheet = decorate(StyleSheet_, 'StyleSheet');
const isMac = /Mac/.test(navigator.userAgent);
export default class Terms extends Component {
export default class Terms extends React.Component {
constructor(props, context) {
super(props, context);
this.terms = {};
@ -76,10 +75,10 @@ export default class Terms extends Component {
this.props.ref_(null);
}
template(css) {
render() {
const shift = !isMac && this.props.termGroups.length > 1;
return (
<div className={css('terms', shift && 'termsShifted')}>
<div className={`terms_terms ${shift ? 'terms_termsShifted' : ''}`}>
{this.props.customChildrenBefore}
{this.props.termGroups.map(termGroup => {
const {uid} = termGroup;
@ -118,7 +117,7 @@ export default class Terms extends Component {
});
return (
<div key={`d${uid}`} className={css('termGroup', isActive && 'termGroupActive')}>
<div key={`d${uid}`} className={`terms_termGroup ${isActive ? 'terms_termGroupActive' : ''}`}>
<TermGroup key={uid} ref_={this.onRef} {...props} />
</div>
);
@ -131,39 +130,37 @@ export default class Terms extends Component {
foregroundColor={this.props.foregroundColor}
borderColor={this.props.borderColor}
/>
<style jsx>{`
.terms_terms {
position: absolute;
margin-top: 34px;
top: 0;
right: 0;
left: 0;
bottom: 0;
color: #fff;
transition: ${isMac ? 'none' : 'margin-top 0.3s ease'};
}
.terms_termsShifted {
margin-top: 68px;
}
.terms_termGroup {
display: block;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: -9999em; /* Offscreen to pause xterm rendering, thanks to IntersectionObserver */
}
.terms_termGroupActive {
left: 0;
}
`}</style>
</div>
);
}
styles() {
return {
terms: {
position: 'absolute',
marginTop: '34px',
top: 0,
right: 0,
left: 0,
bottom: 0,
color: '#fff',
transition: isMac ? '' : 'margin-top 0.3s ease'
},
termsShifted: {
marginTop: '68px'
},
termGroup: {
display: 'block',
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
left: '-9999em' // Offscreen to pause xterm rendering, thanks to IntersectionObserver
},
termGroupActive: {
left: 0
}
};
}
}

View file

@ -3,10 +3,10 @@
import React from 'react';
import Mousetrap from 'mousetrap';
import {PureComponent} from '../base-components';
import {connect} from '../utils/plugins';
import * as uiActions from '../actions/ui';
import {getRegisteredKeys, getCommandHandler, shouldPreventDefault} from '../command-registry';
import stylis from 'stylis';
import HeaderContainer from './header';
import TermsContainer from './terms';
@ -14,7 +14,7 @@ import NotificationsContainer from './notifications';
const isMac = /Mac/.test(navigator.userAgent);
class Hyper extends PureComponent {
class Hyper extends React.PureComponent {
constructor(props) {
super(props);
this.handleFocusActive = this.handleFocusActive.bind(this);
@ -99,15 +99,15 @@ class Hyper extends PureComponent {
document.body.style.backgroundColor = 'inherit';
}
template(css) {
render() {
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized} = this.props;
const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
return (
<div>
<div id="hyper">
<div
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={css('main', isMac_ && 'mainRounded')}
className={`hyper_main ${isMac_ && 'hyper_mainRounded'}`}
>
<HeaderContainer />
<TermsContainer ref_={this.onTermsRef} />
@ -115,29 +115,34 @@ class Hyper extends PureComponent {
</div>
<NotificationsContainer />
<style dangerouslySetInnerHTML={{__html: customCSS}} />
{this.props.customChildren}
<style jsx>
{`
.hyper_main {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid #333;
}
.hyper_mainRounded {
border-radius: 5px;
}
`}
</style>
{/*
Add custom CSS to Hyper.
We add a scope to the customCSS so that it can get around the weighting applied by styled-jsx
*/}
<style dangerouslySetInnerHTML={{__html: stylis('#hyper', customCSS, {prefix: false})}} />
</div>
);
}
styles() {
return {
main: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
// can be overridden by inline style above
border: '1px solid #333'
},
mainRounded: {
borderRadius: '5px'
}
};
}
}
const HyperContainer = connect(

View file

@ -5,9 +5,8 @@ import {basename} from 'path';
// patching Module._load
// so plugins can `require` them without needing their own version
// https://github.com/zeit/hyper/issues/619
import React from 'react';
import React, {PureComponent} from 'react';
import ReactDOM from 'react-dom';
import {PureComponent} from '../base-components';
import Notification from '../components/notification';
import notify from './notify';

View file

@ -93,6 +93,14 @@
"presets": [
"react"
],
"plugins": [
[
"styled-jsx/babel",
{
"vendorPrefixes": false
}
]
],
"env": {
"production": {
"plugins": [
@ -185,7 +193,6 @@
"email": "team@zeit.co"
},
"dependencies": {
"aphrodite-simple": "0.4.1",
"args": "3.0.8",
"chalk": "2.3.0",
"color": "2.0.1",
@ -211,6 +218,8 @@
"seamless-immutable": "7.1.2",
"semver": "5.4.1",
"shebang-loader": "0.0.1",
"styled-jsx": "2.2.6",
"stylis": "3.5.0",
"uuid": "3.1.0",
"xterm": "chabou/xterm.js#b9dac85"
},

View file

@ -191,13 +191,6 @@ anymatch@^1.3.0:
micromatch "^2.1.5"
normalize-path "^2.0.0"
aphrodite-simple@0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/aphrodite-simple/-/aphrodite-simple-0.4.1.tgz#ceac064e5ecb2bca2b80a3bf84f172729bc926d8"
dependencies:
asap "^2.0.3"
inline-style-prefixer "^2.0.0"
aproba@^1.0.3, aproba@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@ -296,7 +289,7 @@ arrify@^1.0.0, arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
asap@^2.0.3, asap@~2.0.3:
asap@~2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@ -768,7 +761,7 @@ babel-plugin-syntax-flow@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
babel-plugin-syntax-jsx@6.18.0, babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
@ -1036,7 +1029,7 @@ babel-traverse@^6.24.1, babel-traverse@^6.26.0:
invariant "^2.2.2"
lodash "^4.17.4"
babel-types@^6.24.1, babel-types@^6.26.0:
babel-types@6.26.0, babel-types@^6.24.1, babel-types@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
dependencies:
@ -1123,10 +1116,6 @@ boom@5.x.x:
dependencies:
hoek "4.x.x"
bowser@^1.0.0:
version "1.9.2"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.2.tgz#d66fc868ca5f4ba895bee1363c343fe7b37d3394"
boxen@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
@ -1725,7 +1714,7 @@ constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
convert-source-map@^1.2.0, convert-source-map@^1.5.0:
convert-source-map@1.5.1, convert-source-map@^1.2.0, convert-source-map@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
@ -3318,10 +3307,6 @@ husky@0.14.3:
normalize-path "^1.0.0"
strip-indent "^2.0.0"
hyphenate-style-name@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b"
iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv-lite@~0.4.13:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
@ -3404,13 +3389,6 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
inline-style-prefixer@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz#c153c7e88fd84fef5c602e95a8168b2770671fe7"
dependencies:
bowser "^1.0.0"
hyphenate-style-name "^1.0.1"
inquirer@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.0.0.tgz#261b77cdb535495509f1b90197108ffb96c02db5"
@ -5833,6 +5811,10 @@ source-map-url@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9"
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
source-map@^0.1.38:
version "0.1.43"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
@ -5843,10 +5825,6 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, sour
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
spawn-rx@^2.0.10:
version "2.0.12"
resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-2.0.12.tgz#b6285294499426089beea0c3c1ec32d7fc57a376"
@ -5953,6 +5931,10 @@ strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
string-hash@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
string-similarity@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-1.2.0.tgz#d75153cb383846318b7a39a8d9292bb4db4e9c30"
@ -6045,6 +6027,30 @@ style-loader@0.19.1:
loader-utils "^1.0.2"
schema-utils "^0.3.0"
styled-jsx@2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-2.2.6.tgz#7e826279e1ef718213ef9cc42ac7370b5008449d"
dependencies:
babel-plugin-syntax-jsx "6.18.0"
babel-types "6.26.0"
convert-source-map "1.5.1"
source-map "0.6.1"
string-hash "1.1.3"
stylis "3.4.10"
stylis-rule-sheet "0.0.8"
stylis-rule-sheet@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.8.tgz#b0d0a126c945b1f3047447a3aae0647013e8d166"
stylis@3.4.10:
version "3.4.10"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.10.tgz#a135cab4b9ff208e327fbb5a6fde3fa991c638ee"
stylis@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
sumchecker@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d"