Add Windows support and first-class Linux support #946

Merged
merged 32 commits into from Nov 11, 2016
Commits
+245 −39
Split
Viewing a subset of changes. View all

Add a brand new UI for Linux and Windows 💅

commit f8a8da645fecc2f7c7e33587bc4b14f267cf9842 @matheuss matheuss committed Oct 30, 2016
View
@@ -237,6 +237,18 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
rpc.emit('move');
});
+ rpc.on('open hamburger menu', ({x, y}) => {
+ Menu.getApplicationMenu().popup(x, y);
+ });
+
+ rpc.on('minimize', () => {
+ win.minimize();
+ });
+
+ rpc.on('close', () => {
+ win.close();
+ });
+
const deleteSessions = () => {
sessions.forEach((session, key) => {
session.removeAllListeners();
View
@@ -1,8 +1,26 @@
<svg display="none" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
- <symbol id="close" viewBox="0 0 24 24">
- <title>close</title>
+ <symbol id="close-tab" viewBox="0 0 24 24">
+ <title>close tab</title>
<g><path d='M13.1919001,11.9997324 L23.7528721,22.5607045 C24.0822321,22.8904941 24.0822321,23.4241533 23.7528721,23.7526581 C23.4235121,24.0824473 22.8898521,24.0824473 22.5609201,23.7526581 L11.999948,13.1916857 L1.43897601,23.7526581 C1.109612,24.0824473 0.575952002,24.0824473 0.247020001,23.7526581 C-0.0823400003,23.4237253 -0.0823400003,22.8900657 0.247020001,22.5607045 L10.80842,11.9997324 L0.247020001,1.43961681 C-0.0823400003,1.110684 -0.0823400003,0.576168002 0.247020001,0.247663201 C0.576384002,-0.0825544003 1.11004,-0.0825544003 1.43897601,0.247663201 L11.999948,10.8082072 L22.5609201,0.247663201 C22.8902801,-0.0825544003 23.4239401,-0.0825544003 23.7528721,0.247663201 C24.0822321,0.576596002 24.0822321,1.111112 23.7528721,1.43961681 L13.1919001,11.9997324 L13.1919001,11.9997324 L13.1919001,11.9997324 Z'></path></g>
</symbol>
+ <symbol id="hamburger-menu" viewBox="0 0 10 9">
+ <title>hamburger menu</title>
+ <rect width="10" height="1"/>
+ <rect y="4" width="10" height="1"/>
+ <rect y="8" width="10" height="1"/>
+ </symbol>
+ <symbol id="minimize-window" viewBox="0 0 10 10">
+ <title>minimize window</title>
+ <rect y="9" width="10" height="1"/>
+ </symbol>
+ <symbol id="maximize-window" viewBox="0 0 10 10">
+ <title>maximize window</title>
+ <rect width="10" height="10" fill="none" stroke-width="1"/>
+ </symbol>
+ <symbol id="close-window" viewBox="0 0 10 10">
+ <title>close window</title>
+ <path d="M0.5,0.5 L9.5,9.5 M0.5,9.5 L9.5,0.5"></path>
+ </symbol>
</defs>
</svg>
View
@@ -1,5 +1,5 @@
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
-import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE} from '../constants/ui';
+import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE, UI_OPEN_HAMBURGER_MENU, UI_WINDOW_MINIMIZE, UI_WINDOW_CLOSE} from '../constants/ui';
import rpc from '../rpc';
import {userExitTermGroup, setActiveGroup} from './term-groups';
@@ -48,3 +48,36 @@ export function unmaximize() {
});
};
}
+
+export function openHamburgerMenu(coordinates) {
+ return dispatch => {
+ dispatch({
+ type: UI_OPEN_HAMBURGER_MENU,
+ effect() {
+ rpc.emit('open hamburger menu', coordinates);
+ }
+ });
+ };
+}
+
+export function minimize() {
+ return dispatch => {
+ dispatch({
+ type: UI_WINDOW_MINIMIZE,
+ effect() {
+ rpc.emit('minimize');
+ }
+ });
+ };
+}
+
+export function close() {
+ return dispatch => {
+ dispatch({
+ type: UI_WINDOW_CLOSE,
+ effect() {
+ rpc.emit('close');
+ }
+ });
+ };
+}
@@ -14,6 +14,10 @@ export default class Header extends Component {
this.onChangeIntent = this.onChangeIntent.bind(this);
this.handleHeaderClick = this.handleHeaderClick.bind(this);
this.handleHeaderMouseDown = this.handleHeaderMouseDown.bind(this);
+ this.handleHamburgerMenuClick = this.handleHamburgerMenuClick.bind(this);
+ this.handleMaximizeClick = this.handleMaximizeClick.bind(this);
+ this.handleMinimizeClick = this.handleMinimizeClick.bind(this);
+ this.handleCloseClick = this.handleCloseClick.bind(this);
}
onChangeIntent(active) {
@@ -68,6 +72,27 @@ export default class Header extends Component {
}
}
+ handleHamburgerMenuClick(event) {
+ const {right: x, bottom: y} = event.currentTarget.getBoundingClientRect();
+ this.props.openHamburgerMenu({x, y});
+ }
+
+ handleMaximizeClick() {
+ if (this.props.maximized) {
+ this.props.unmaximize();
+ } else {
+ this.props.maximize();
+ }
+ }
+
+ handleMinimizeClick() {
+ this.props.minimize();
+ }
+
+ handleCloseClick() {
+ this.props.close();
+ }
+
componentWillUnmount() {
delete this.clicks;
clearTimeout(this.clickTimer);
@@ -81,11 +106,49 @@ export default class Header extends Component {
onClose: this.props.onCloseTab,
onChange: this.onChangeIntent
});
+ const {borderColor} = props;
+ let title = 'Hyper';
+ if (props.tabs.length === 1 && props.tabs[0].title) {
+ // if there's only one tab we use its title as the window title
+ title = props.tabs[0].title;
+ }
return (<header
className={css('header', isMac && 'headerRounded')}
onClick={this.handleHeaderClick}
onMouseDown={this.handleHeaderMouseDown}
>
+ {
+ !isMac && <div
+ className={css('windowHeader', props.tabs.length > 1 && 'windowHeaderWithBorder')}
+ style={{borderColor}}
+ >
+ <svg
+ className={css('shape', 'hamburgerMenu')}
+ onClick={this.handleHamburgerMenuClick}
+ >
+ <use xlinkHref="./dist/assets/icons.svg#hamburger-menu"/>
+ </svg>
+ <span className={css('appTitle')}>{title}</span>
+ <svg
+ className={css('shape', 'minimizeWindow')}
+ onClick={this.handleMinimizeClick}
+ >
+ <use xlinkHref="./dist/assets/icons.svg#minimize-window"/>
+ </svg>
+ <svg
+ className={css('shape', 'maximizeWindow')}
+ onClick={this.handleMaximizeClick}
+ >
+ <use xlinkHref="./dist/assets/icons.svg#maximize-window"/>
+ </svg>
+ <svg
+ className={css('shape', 'closeWindow')}
+ onClick={this.handleCloseClick}
+ >
+ <use xlinkHref="./dist/assets/icons.svg#close-window"/>
+ </svg>
+ </div>
+ }
{ this.props.customChildrenBefore }
<Tabs {...props}/>
{ this.props.customChildren }
@@ -105,6 +168,61 @@ export default class Header extends Component {
headerRounded: {
borderTopLeftRadius: '4px',
borderTopRightRadius: '4px'
+ },
+
+ windowHeader: {
+ height: '34px',
+ width: '100%',
+ position: 'fixed',
+ top: '1px',
+ left: '1px',
+ right: '1px',
+ WebkitAppRegion: 'drag',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+
+ windowHeaderWithBorder: {
+ borderColor: '#ccc',
+ borderBottomStyle: 'solid',
+ borderBottomWidth: '1px'
+ },
+
+ appTitle: {
+ fontSize: '12px',
+ fontFamily: `-apple-system, BlinkMacSystemFont,
+ "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans",
+ "Droid Sans", "Helvetica Neue", sans-serif`
+ },
+
+ shape: {
+ width: '10px',
+ height: '10px',
+ position: 'fixed',
+ WebkitAppRegion: 'no-drag',
+ fill: 'currentColor'
+ },
+
+ hamburgerMenu: {
+ height: '9px', // TODO fix the SVG
+ top: '10px',
+ left: '10px'
+ },
+
+ closeWindow: {
+ right: '10px',
+ stroke: 'currentColor'
+ },
+
+ maximizeWindow: {
+ right: '35px',
+ stroke: 'currentColor'
+ },
+
+ minimizeWindow: {
+ right: '60px'
}
};
}
@@ -79,7 +79,7 @@ export default class Tab extends Component {
onClick={this.props.onClose}
>
<svg className={css('shape')}>
- <use xlinkHref="./dist/assets/icons.svg#close"/>
+ <use xlinkHref="./dist/assets/icons.svg#close-tab"/>
</svg>
</i>
{ this.props.customChildren }
@@ -21,37 +21,40 @@ export default class Tabs extends Component {
return (<nav className={css('nav')}>
{ this.props.customChildrenBefore }
{
- tabs.length ?
- tabs.length === 1 ?
- <div className={css('title')}>{tabs[0].title}</div> :
- [
- <ul
- key="list"
- className={css('list')}
- >
- {
- tabs.map((tab, i) => {
- const {uid, title, isActive, hasActivity} = tab;
- const props = getTabProps(tab, this.props, {
- text: title === '' ? 'Shell' : title,
- isFirst: i === 0,
- isLast: tabs.length - 1 === i,
- borderColor,
- isActive,
- hasActivity,
- onSelect: onChange.bind(null, uid),
- onClose: onClose.bind(null, uid)
- });
- return <Tab key={`tab-${uid}`} {...props}/>;
- })
- }
- </ul>,
- isMac && <div
- key="shim"
- style={{borderColor}}
- className={css('borderShim')}
- />
- ] :
+ tabs.length === 1 && isMac ?
+ <div className={css('title')}>{tabs[0].title}</div> :
+ null
+ }
+ {
+ tabs.length > 1 ?
+ [
+ <ul
+ key="list"
+ className={css('list')}
+ >
+ {
+ tabs.map((tab, i) => {
+ const {uid, title, isActive, hasActivity} = tab;
+ const props = getTabProps(tab, this.props, {
+ text: title === '' ? 'Shell' : title,
+ isFirst: i === 0,
+ isLast: tabs.length - 1 === i,
+ borderColor,
+ isActive,
+ hasActivity,
+ onSelect: onChange.bind(null, uid),
+ onClose: onClose.bind(null, uid)
+ });
+ return <Tab key={`tab-${uid}`} {...props}/>;
+ })
+ }
+ </ul>,
+ isMac && <div
+ key="shim"
+ style={{borderColor}}
+ className={css('borderShim')}
+ />
+ ] :
null
}
{ this.props.customChildren }
@@ -73,7 +76,7 @@ export default class Tabs extends Component {
cursor: 'default',
position: 'relative',
WebkitUserSelect: 'none',
- WebkitAppRegion: 'drag'
+ top: isMac ? '' : '34px'
},
title: {
@@ -4,6 +4,7 @@ import {decorate, getTermGroupProps} from '../utils/plugins';
import TermGroup_ from './term-group';
const TermGroup = decorate(TermGroup_, 'TermGroup');
+const isMac = /Mac/.test(navigator.userAgent);
export default class Terms extends Component {
@@ -71,8 +72,9 @@ export default class Terms extends Component {
}
template(css) {
+ const shift = !isMac && this.props.termGroups.length > 1;
return (<div
- className={css('terms')}
+ className={css('terms', shift && 'termsShifted')}
>
{ this.props.customChildrenBefore }
{
@@ -132,7 +134,12 @@ export default class Terms extends Component {
right: 0,
left: 0,
bottom: 0,
- color: '#fff'
+ color: '#fff',
+ transition: isMac ? '' : 'margin-top 0.3s ease'
+ },
+
+ termsShifted: {
+ marginTop: '68px'
},
termGroup: {
View
@@ -13,3 +13,6 @@ export const UI_WINDOW_MOVE = 'UI_WINDOW_MOVE';
export const UI_WINDOW_MAXIMIZE = 'UI_WINDOW_MAXIMIZE';
export const UI_WINDOW_UNMAXIMIZE = 'UI_WINDOW_UNMAXIMIZE';
export const UI_OPEN_FILE = 'UI_OPEN_FILE';
+export const UI_OPEN_HAMBURGER_MENU = 'UI_OPEN_HAMBURGER_MENU';
+export const UI_WINDOW_MINIMIZE = 'UI_WINDOW_MINIMIZE';
+export const UI_WINDOW_CLOSE = 'UI_WINDOW_CLOSE';
Oops, something went wrong.