mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-12 20:18:41 -09:00
Add Windows support and first-class Linux support (#946)
* `child_pty` => `pty.js` * Create a frameless window on Windows and Linux * Add a brand new UI for Linux and Windows 💅 * [Windows] Fix plugin installation * [Windows] Fix the `build` script * [Windows] Add a bigger `icon.ico` * [Mac] Add `WebKitAppRegion: drag` when running on macOS * Fix code style 🤔 * Add `appveyor.yml` * Fix code style (again) * [Windows] Fix AppVeyor's `install` script * [Windows] Try a new AppVeyor config * [Windows] Set the binary path so Spectron can run the tests * [Windows] Try to build on x64 * Try again to build on x64 * Try one more time 😩 * Throw an error to indicate that `pty.js` was built incorrectly * [Win/Linux] Add `display: hidden` to <Tabs /> if tabs.length === 1 * [Win/Linux] Reorganize SVGs – via @CodeTheory * [Win/Linux] Fix the hamburger menu height * Make the SVGs look better with `shape-rendering: crispEdges;` * [Win/Linux] Add config options for the window controls and the 🍔 menu * Add `electron-squirrel-startup` dependency * [Win] Handle Squirrel commands * [Win/Linux] Fix default color for the 🍔 and window controls – via @CodeTheory * [Win/Linux] Add some padding - via @CodeTheory * [Win/Linux] Add hover states – via @CodeTheory * [Win] Fix empty window/tab titles * [Win] Fix opening Preferences (#978) * [Win] Fix opening Preferences * Update ui.js * Update ui.js * Enhance messages and default editor * [Win] Add dependency instructions to the README.md [skip ci] * Fix code style * [Win/Linux] Check the number of open windows before quitting the app
This commit is contained in:
parent
26701d43b5
commit
9c90e19760
21 changed files with 433 additions and 74 deletions
|
|
@ -20,7 +20,9 @@ $ brew cask install hyper
|
|||
|
||||
## Contribute
|
||||
|
||||
1. If you are running Linux, install "icnsutils", "graphicsmagick" and "xz-utils"
|
||||
1. Install the dependencies
|
||||
* If you are running Linux, install `icnsutils`, `graphicsmagick` and `xz-utils`
|
||||
* If you are running Windows, install [VC++ Build Tools Technical Preview](http://go.microsoft.com/fwlink/?LinkId=691126) using the **Default Install option**; Install Python 2.7 and add it to your `%PATH%`; Run `npm config set msvs_version 2015 --global`
|
||||
2. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
|
||||
3. Install the dependencies: `npm install`
|
||||
4. Build the code, watch for changes and run the app: `npm start`
|
||||
|
|
|
|||
|
|
@ -27,6 +27,16 @@ module.exports = {
|
|||
// custom css to embed in the terminal window
|
||||
termCSS: '',
|
||||
|
||||
// set to `true` if you're using a Linux set up
|
||||
// that doesn't shows native menus
|
||||
// default: `false` on Linux, `true` on Windows (ignored on macOS)
|
||||
showHamburgerMenu: '',
|
||||
|
||||
// set to `false` if you want to hide the minimize, maximize and close buttons
|
||||
// additionally, set to `'left'` if you want them on the left, like in Ubuntu
|
||||
// default: `true` on windows and Linux (ignored on macOS)
|
||||
showWindowControls: '',
|
||||
|
||||
// custom padding (css format, i.e.: `top right bottom left`)
|
||||
padding: '12px 14px',
|
||||
|
||||
|
|
|
|||
20
app/index.js
20
app/index.js
|
|
@ -1,3 +1,5 @@
|
|||
// eslint-disable-next-line curly, unicorn/no-process-exit
|
||||
if (require('electron-squirrel-startup')) process.exit();
|
||||
// Native
|
||||
const {resolve} = require('path');
|
||||
|
||||
|
|
@ -108,9 +110,11 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
height,
|
||||
minHeight: 190,
|
||||
minWidth: 370,
|
||||
titleBarStyle: 'hidden-inset',
|
||||
titleBarStyle: 'hidden-inset', // macOS only
|
||||
title: 'Hyper.app',
|
||||
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
|
||||
// we want to go frameless on windows and linux
|
||||
frame: process.platform === 'darwin',
|
||||
transparent: true,
|
||||
icon: resolve(__dirname, 'static/icon.png'),
|
||||
// we only want to show when the prompt is ready for user input
|
||||
|
|
@ -235,6 +239,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();
|
||||
|
|
@ -305,7 +321,7 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
});
|
||||
|
||||
win.on('closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
if (process.platform !== 'darwin' && windowSet.size === 0) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,18 +11,19 @@
|
|||
"repository": "zeit/hyper",
|
||||
"xo": false,
|
||||
"dependencies": {
|
||||
"child_pty": "3.0.1",
|
||||
"color": "0.11.3",
|
||||
"convert-css-color-name-to-hex": "0.1.1",
|
||||
"default-shell": "1.0.1",
|
||||
"electron-config": "0.2.1",
|
||||
"electron-is-dev": "0.1.1",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"file-uri-to-path": "0.0.2",
|
||||
"gaze": "1.1.2",
|
||||
"git-describe": "3.0.2",
|
||||
"mkdirp": "0.5.1",
|
||||
"ms": "0.7.1",
|
||||
"node-fetch": "1.6.3",
|
||||
"pty.js": "https://github.com/Tyriar/pty.js/tarball/c75c2dcb6dcad83b0cb3ef2ae42d0448fb912642",
|
||||
"semver": "5.3.0",
|
||||
"shell-env": "0.2.0",
|
||||
"uuid": "2.0.2"
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ function install(fn) {
|
|||
env.npm_config_target = '1.3.0';
|
||||
env.npm_config_disturl = 'https://atom.io/download/atom-shell';
|
||||
/* eslint-enable camelcase */
|
||||
exec('npm prune; npm install --production', {
|
||||
exec('npm prune && npm install --production', {
|
||||
cwd: path,
|
||||
env,
|
||||
shell
|
||||
|
|
|
|||
|
|
@ -8,16 +8,13 @@ const {getDecoratedEnv} = require('./plugins');
|
|||
const {productName, version} = require('./package');
|
||||
const config = require('./config');
|
||||
|
||||
const createPtyJsError = () => new Error('`pty.js` failed to load. Typically this means that it was built incorrectly. Please check the `README.me` to more info.');
|
||||
|
||||
let spawn;
|
||||
try {
|
||||
spawn = require('child_pty').spawn;
|
||||
spawn = require('pty.js').spawn;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'A native module failed to load. Typically this means ' +
|
||||
'you installed the modules incorrectly.\n Use `scripts/install.sh` ' +
|
||||
'to trigger the installation.\n ' +
|
||||
'More information: https://github.com/zeit/hyper/issues/72'
|
||||
);
|
||||
throw createPtyJsError();
|
||||
}
|
||||
|
||||
const envFromConfig = config.getConfig().env || {};
|
||||
|
|
@ -37,12 +34,20 @@ module.exports = class Session extends EventEmitter {
|
|||
|
||||
const defaultShellArgs = ['--login'];
|
||||
|
||||
this.pty = spawn(shell || defaultShell, shellArgs || defaultShellArgs, {
|
||||
columns,
|
||||
rows,
|
||||
cwd,
|
||||
env: getDecoratedEnv(baseEnv)
|
||||
});
|
||||
try {
|
||||
this.pty = spawn(shell || defaultShell, shellArgs || defaultShellArgs, {
|
||||
columns,
|
||||
rows,
|
||||
cwd,
|
||||
env: getDecoratedEnv(baseEnv)
|
||||
});
|
||||
} catch (err) {
|
||||
if (/is not a function/.test(err.message)) {
|
||||
throw createPtyJsError();
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
this.pty.stdout.on('data', data => {
|
||||
if (this.ended) {
|
||||
|
|
@ -69,9 +74,9 @@ module.exports = class Session extends EventEmitter {
|
|||
this.pty.stdin.write(data);
|
||||
}
|
||||
|
||||
resize({cols: columns, rows}) {
|
||||
resize({cols, rows}) {
|
||||
try {
|
||||
this.pty.stdout.resize({columns, rows});
|
||||
this.pty.stdout.resize(cols, rows);
|
||||
} catch (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
|
|
@ -79,7 +84,7 @@ module.exports = class Session extends EventEmitter {
|
|||
|
||||
destroy() {
|
||||
try {
|
||||
this.pty.kill('SIGHUP');
|
||||
this.pty.kill();
|
||||
} catch (err) {
|
||||
console.error('exit error', err.stack);
|
||||
}
|
||||
|
|
|
|||
31
appveyor.yml
Normal file
31
appveyor.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# https://github.com/sindresorhus/appveyor-node/blob/master/appveyor.yml
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- platform: x64
|
||||
|
||||
image: Visual Studio 2015
|
||||
|
||||
init:
|
||||
- npm config set msvs_version 2015 # we need this to build `pty.js`
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 6 x64
|
||||
- set CI=true
|
||||
- npm -g install npm@latest
|
||||
- npm install
|
||||
|
||||
build: off
|
||||
|
||||
shallow_clone: true
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm run test
|
||||
|
||||
on_success:
|
||||
- npm run dist
|
||||
|
|
@ -1,8 +1,48 @@
|
|||
<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 10">
|
||||
<title>hamburger menu</title>
|
||||
<rect y="0.5" width="10" height="1" fill="currentColor"/>
|
||||
<rect y="4.5" width="10" height="1" fill="currentColor"/>
|
||||
<rect y="8.5" width="10" height="1" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="minimize-window" viewBox="0 0 10 10">
|
||||
<title>minimize window</title>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<polygon points="0 0 10 0 10 10 0 10"/>
|
||||
<path stroke="currentColor" d="M9.5,5 L0.5,5" stroke-linecap="square"/>
|
||||
</g>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<polygon points="0 0 10 0 10 10 0 10"/>
|
||||
<rect width="10" height="1" y="4.5" fill="currentColor"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="maximize-window" viewBox="0 0 10 10">
|
||||
<title>maximize window</title>
|
||||
<defs>
|
||||
<polygon id="maximize-window-a" points="0 0 10 0 10 10 0 10"/>
|
||||
<mask id="maximize-window-b" width="10" height="10" x="0" y="0">
|
||||
<use xlink:href="#maximize-window-a"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<polygon fill="none" points="0 0 10 0 10 10 0 10"/>
|
||||
<use stroke="currentColor" stroke-width="2" mask="url(#maximize-window-b)" xlink:href="#maximize-window-a"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="close-window" viewBox="0 0 10 10">
|
||||
<title>close window</title>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g stroke="currentColor" transform="translate(.25 .25)" stroke-linecap="square">
|
||||
<path d="M0.5,0.5 L9,9"/>
|
||||
<path d="M0.5,0.5 L9,9" transform="matrix(-1 0 0 1 9.5 0)"/>
|
||||
</g>
|
||||
<polygon points="0 0 10 0 10 10 0 10"/>
|
||||
</g>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 2.7 KiB |
BIN
build/icon.ico
BIN
build/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 361 KiB |
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,12 @@ export function moveTo(i) {
|
|||
}
|
||||
|
||||
export function showPreferences() {
|
||||
const editorFallback = process.platform === 'win32' ? 'notepad' : 'nano';
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
const command = process.platform === 'win32' ? ' start notepad "%userprofile%\\.hyper.js"' : ' bash -c \'exec env ${EDITOR:=nano} ~/.hyper.js\'';
|
||||
const message = process.platform === 'win32' ?
|
||||
' echo Attempting to open ^%userprofile^%\\.hyper.js with notepad' :
|
||||
' echo Attempting to open ~/.hyper.js with your \$EDITOR'; // eslint-disable-line no-useless-escape
|
||||
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: UI_SHOW_PREFERENCES,
|
||||
|
|
@ -212,12 +217,12 @@ export function showPreferences() {
|
|||
rpc.once('session data', () => {
|
||||
dispatch(sendSessionData(
|
||||
uid,
|
||||
// Leading space prevents command to be stored in shell history
|
||||
[' echo Attempting to open ~/.hyper.js with your \$EDITOR', // eslint-disable-line no-useless-escape
|
||||
' echo If it fails, open it manually with your favorite editor!',
|
||||
' bash -c \'exec env ${EDITOR:=' + editorFallback + '} ~/.hyper.js\'',
|
||||
''
|
||||
].join('\n')
|
||||
[ // Leading space prevents command to be stored in shell history
|
||||
' echo Attempting to open ~/.hyper.js with your \$EDITOR', // eslint-disable-line no-useless-escape
|
||||
message,
|
||||
command,
|
||||
''
|
||||
].join('\n\r')
|
||||
));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,11 +72,51 @@ export default class Header extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleHamburgerMenuClick(event) {
|
||||
let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect();
|
||||
x -= 15; // to compensate padding
|
||||
y -= 12; // ^ same
|
||||
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);
|
||||
}
|
||||
|
||||
getWindowHeaderConfig() {
|
||||
const {showHamburgerMenu, showWindowControls} = this.props;
|
||||
const ret = {
|
||||
hambMenu: process.platform === 'win32', // show by default on windows
|
||||
winCtrls: !this.props.isMac // show by default on windows and linux
|
||||
};
|
||||
if (!this.props.isMac) { // allow the user to override the defaults if not on macOS
|
||||
if (showHamburgerMenu !== '') {
|
||||
ret.hambMenu = showHamburgerMenu;
|
||||
}
|
||||
if (showWindowControls !== '') {
|
||||
ret.winCtrls = showWindowControls;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template(css) {
|
||||
const {isMac} = this.props;
|
||||
const props = getTabsProps(this.props, {
|
||||
|
|
@ -81,11 +125,60 @@ 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;
|
||||
}
|
||||
const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
|
||||
const left = winCtrls === 'left';
|
||||
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}}
|
||||
>
|
||||
{
|
||||
hambMenu &&
|
||||
<svg
|
||||
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
|
||||
onClick={this.handleHamburgerMenuClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#hamburger-menu"/>
|
||||
</svg>
|
||||
}
|
||||
<span className={css('appTitle')}>{title}</span>
|
||||
{
|
||||
winCtrls &&
|
||||
<div className={css('windowControls', left && 'windowControlsLeft')}>
|
||||
<svg
|
||||
className={css('shape', left && 'minimizeWindowLeft')}
|
||||
onClick={this.handleMinimizeClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#minimize-window"/>
|
||||
</svg>
|
||||
<svg
|
||||
className={css('shape', left && 'maximizeWindowLeft')}
|
||||
onClick={this.handleMaximizeClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#maximize-window"/>
|
||||
</svg>
|
||||
<svg
|
||||
className={css('shape', 'closeWindow', left && 'closeWindowLeft')}
|
||||
onClick={this.handleCloseClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#close-window"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{ this.props.customChildrenBefore }
|
||||
<Tabs {...props}/>
|
||||
{ this.props.customChildren }
|
||||
|
|
@ -105,7 +198,77 @@ 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: '40px',
|
||||
height: '34px',
|
||||
padding: '12px 15px 12px 15px',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
color: '#808080',
|
||||
shapeRendering: 'crispEdges',
|
||||
':hover': {
|
||||
color: '#FFFFFF'
|
||||
}
|
||||
},
|
||||
|
||||
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'}}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -185,7 +185,8 @@ export default class Tab extends Component {
|
|||
width: '6px',
|
||||
height: '6px',
|
||||
verticalAlign: 'middle',
|
||||
fill: 'currentColor'
|
||||
fill: 'currentColor',
|
||||
shapeRendering: 'crispEdges'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,40 +18,45 @@ export default class Tabs extends Component {
|
|||
onClose
|
||||
} = this.props;
|
||||
|
||||
return (<nav className={css('nav')}>
|
||||
const hide = !isMac && tabs.length === 1;
|
||||
|
||||
return (<nav className={css('nav', hide && 'hiddenNav')}>
|
||||
{ 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 +78,12 @@ export default class Tabs extends Component {
|
|||
cursor: 'default',
|
||||
position: 'relative',
|
||||
WebkitUserSelect: 'none',
|
||||
WebkitAppRegion: 'drag'
|
||||
WebkitAppRegion: isMac ? 'drag' : '',
|
||||
top: isMac ? '' : '34px'
|
||||
},
|
||||
|
||||
hiddenNav: {
|
||||
display: 'none'
|
||||
},
|
||||
|
||||
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: {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import {createSelector} from 'reselect';
|
||||
|
||||
import Header from '../components/header';
|
||||
import {closeTab, changeTab, maximize, unmaximize} from '../actions/header';
|
||||
import {closeTab, changeTab, maximize, openHamburgerMenu, unmaximize, minimize, close} from '../actions/header';
|
||||
import {connect} from '../utils/plugins';
|
||||
import getRootGroups from '../selectors';
|
||||
|
||||
|
|
@ -35,7 +35,9 @@ const HeaderContainer = connect(
|
|||
activeMarkers: state.ui.activityMarkers,
|
||||
borderColor: state.ui.borderColor,
|
||||
backgroundColor: state.ui.backgroundColor,
|
||||
maximized: state.ui.maximized
|
||||
maximized: state.ui.maximized,
|
||||
showHamburgerMenu: state.ui.showHamburgerMenu,
|
||||
showWindowControls: state.ui.showWindowControls
|
||||
};
|
||||
},
|
||||
dispatch => {
|
||||
|
|
@ -54,6 +56,18 @@ const HeaderContainer = connect(
|
|||
|
||||
unmaximize: () => {
|
||||
dispatch(unmaximize());
|
||||
},
|
||||
|
||||
openHamburgerMenu: coordinates => {
|
||||
dispatch(openHamburgerMenu(coordinates));
|
||||
},
|
||||
|
||||
minimize: () => {
|
||||
dispatch(minimize());
|
||||
},
|
||||
|
||||
close: () => {
|
||||
dispatch(close());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ const TermsContainer = connect(
|
|||
},
|
||||
|
||||
onTitle(uid, title) {
|
||||
dispatch(setSessionXtermTitle(uid, title));
|
||||
// we need to trim the title because `cmd.exe` likes to report ' ' as the title
|
||||
dispatch(setSessionXtermTitle(uid, title.trim()));
|
||||
},
|
||||
|
||||
onResize(uid, cols, rows) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import {values} from '../utils/object';
|
|||
|
||||
const allowedCursorShapes = new Set(['BEAM', 'BLOCK', 'UNDERLINE']);
|
||||
const allowedBells = new Set(['SOUND', false]);
|
||||
const allowedHamburgerMenuValues = new Set([true, false]);
|
||||
const allowedWindowControlsValues = new Set([true, false, 'left']);
|
||||
|
||||
// Populate `config-default.js` from this :)
|
||||
const initial = Immutable({
|
||||
|
|
@ -79,7 +81,9 @@ const initial = Immutable({
|
|||
modifierKeys: {
|
||||
altIsMeta: false,
|
||||
cmdIsMeta: false
|
||||
}
|
||||
},
|
||||
showHamburgerMenu: '',
|
||||
showWindowControls: ''
|
||||
});
|
||||
|
||||
const reducer = (state = initial, action) => {
|
||||
|
|
@ -171,6 +175,14 @@ const reducer = (state = initial, action) => {
|
|||
ret.modifierKeys = config.modifierKeys;
|
||||
}
|
||||
|
||||
if (allowedHamburgerMenuValues.has(config.showHamburgerMenu)) {
|
||||
ret.showHamburgerMenu = config.showHamburgerMenu;
|
||||
}
|
||||
|
||||
if (allowedWindowControlsValues.has(config.showWindowControls)) {
|
||||
ret.showWindowControls = config.showWindowControls;
|
||||
}
|
||||
|
||||
return ret;
|
||||
})());
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"start": "concurrently --kill-others --raw \"npm run dev\" \"npm run app\"",
|
||||
"app": "electron app",
|
||||
"dev": "webpack -w",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"test": "npm run dist && xo && ava",
|
||||
"prepush": "npm test",
|
||||
"postinstall": "install-app-deps",
|
||||
|
|
@ -92,6 +92,7 @@
|
|||
"babel-preset-react": "^6.11.1",
|
||||
"concurrently": "^3.0.0",
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
"cross-env": "3.1.3",
|
||||
"electron": "1.4.5",
|
||||
"electron-builder": "^7.11.4",
|
||||
"electron-devtools-installer": "^2.0.0",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ test.before(async () => {
|
|||
pathToBinary = path.join(__dirname, '../dist/mac/Hyper.app/Contents/MacOS/Hyper');
|
||||
break;
|
||||
|
||||
case 'win32':
|
||||
pathToBinary = path.join(__dirname, '../dist/win-unpacked/Hyper.exe');
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Path to the built binary needs to be defined for this platform in test/index.js');
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue