mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-13 04:28:41 -09:00
* npm: add .npmrc with save-exact=true * split panes: create initial implementation This allows users to split their Hyperterm terms into multiple nested splits, both vertical and horizontal. Fixes #56 * split panes: suport closing tabs and individual panes * split panes: ensure new splits are placed at the correct index New split panes should be placed after the currently active pane, not at the end like they were previously. * split panes: add explicit dependency to uuid * split panes: implement split pane cycling This adds menu buttons for moving back and forward between open split panes in the currect terminal tab. Doesn't add a hotkey yet, needs some bikeshedding. * split panes: move activeSessionUid to its own object It made little sense to have so many objects with `activeSessionUid` set to `null` when it only mattered on the top level. Now it's an object mapping term-group `uid` to `sessionUid` instead. * split panes: make sure closing the last split pane exits the app * split panes: fix a crash after closing specific panes Sometimes the terminal would crash when a specific split pane was closed, because the `activeSessions` mapping wasn't updated correctly. * split panes: fix a bug that caused initial session sizing to be wrong * fix all our focus / blur issues in one fell swoop :O (famous last words) * get rid of react warning * hterm: make sure not to lose focus when VT listens on clicks * term: restore onactive callback * add missing `return` to override (just in case) * split pane: new split pane implementation * goodbye react-split-pane * added term group resizing action and reducer * terms: supply border color so that we can use it for splits * term-group: add resizing hook * term-groups: add resizing constant * remove split pane css side-effect * split panes: pass existing hterm instances to Term * split panes: add keybindings for split pane cycling * split panes: remove unused action * split panes: remove unused styling * split-pane: remove `console.log` * split-pane: remove `console.log` * split panes: rebalance sizes on insert/removal * split panes: pass existing hterm instances to Term * split panes: add keybindings for split pane cycling * split panes: remove unused action * split panes: remove unused styling * split panes: rebalance sizes on insert/removal * split panes: set a minimum size for resizing * split-pane: fix vertical splits * css :| * package: bump electron * split panes: attach onFocus listener to webviews * 1.4.1 and 1.4.2 are broken. they have the following regression: - open google.com on the main window - open a new tab - come back to previous tab. webview is gone :| * split panes: handle PTY exits * split panes: add linux friendly keybindings
184 lines
4.7 KiB
JavaScript
184 lines
4.7 KiB
JavaScript
import React from 'react';
|
|
import Component from '../component';
|
|
|
|
export default class SplitPane extends Component {
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.handleDragStart = this.handleDragStart.bind(this);
|
|
this.onDrag = this.onDrag.bind(this);
|
|
this.onDragEnd = this.onDragEnd.bind(this);
|
|
this.state = {dragging: false};
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (this.state.dragging && prevProps.sizes !== this.props.sizes) {
|
|
// recompute positions for ongoing dragging
|
|
this.dragPanePosition = this.dragTarget.getBoundingClientRect()[this.d2];
|
|
}
|
|
}
|
|
|
|
handleDragStart(ev) {
|
|
ev.preventDefault();
|
|
this.setState({dragging: true});
|
|
window.addEventListener('mousemove', this.onDrag);
|
|
window.addEventListener('mouseup', this.onDragEnd);
|
|
|
|
// dimensions to consider
|
|
if (this.props.direction === 'horizontal') {
|
|
this.d1 = 'height';
|
|
this.d2 = 'top';
|
|
this.d3 = 'clientY';
|
|
} else {
|
|
this.d1 = 'width';
|
|
this.d2 = 'left';
|
|
this.d3 = 'clientX';
|
|
}
|
|
|
|
this.dragTarget = ev.target;
|
|
this.dragPanePosition = this.dragTarget.getBoundingClientRect()[this.d2];
|
|
this.panes = Array.from(ev.target.parentNode.childNodes);
|
|
this.panesSize = ev.target.parentNode.getBoundingClientRect()[this.d1];
|
|
this.paneIndex = this.panes.indexOf(ev.target);
|
|
this.paneIndex -= Math.ceil(this.paneIndex / 2);
|
|
}
|
|
|
|
onDrag(ev) {
|
|
let {sizes} = this.props;
|
|
let sizes_;
|
|
if (sizes) {
|
|
sizes_ = [].concat(sizes);
|
|
} else {
|
|
const total = this.props.children.length;
|
|
sizes = sizes_ = new Array(total).fill(1 / total);
|
|
}
|
|
const i = this.paneIndex;
|
|
const pos = ev[this.d3];
|
|
const d = Math.abs(this.dragPanePosition - pos) / this.panesSize;
|
|
if (pos > this.dragPanePosition) {
|
|
sizes_[i] += d;
|
|
sizes_[i + 1] -= d;
|
|
} else {
|
|
sizes_[i] -= d;
|
|
sizes_[i + 1] += d;
|
|
}
|
|
this.props.onResize(sizes_);
|
|
}
|
|
|
|
onDragEnd() {
|
|
if (this.state.dragging) {
|
|
window.removeEventListener('mousemove', this.onDrag);
|
|
window.removeEventListener('mouseup', this.onDragEnd);
|
|
this.setState({dragging: false});
|
|
}
|
|
}
|
|
|
|
template(css) {
|
|
const children = this.props.children;
|
|
const {direction, borderColor} = this.props;
|
|
let {sizes} = this.props;
|
|
if (!sizes) {
|
|
// workaround for the fact that if we don't specify
|
|
// sizes, sometimes flex fails to calculate the
|
|
// right height for the horizontal panes
|
|
sizes = new Array(children.length).fill(1 / children.length);
|
|
}
|
|
return (<div className={css('panes', `panes_${direction}`)}>
|
|
{
|
|
React.Children.map(children, (child, i) => {
|
|
const style = {
|
|
flexBasis: (sizes[i] * 100) + '%',
|
|
flexGrow: 0
|
|
};
|
|
return [
|
|
<div className={css('pane')} style={style}>
|
|
{ child }
|
|
</div>,
|
|
i < children.length - 1 ?
|
|
<div
|
|
onMouseDown={this.handleDragStart}
|
|
style={{backgroundColor: borderColor}}
|
|
className={css('divider', `divider_${direction}`)}
|
|
/> :
|
|
null
|
|
];
|
|
})
|
|
}
|
|
<div
|
|
style={{display: this.state.dragging ? 'block' : 'none'}}
|
|
className={css('shim')}
|
|
/>
|
|
</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) {
|
|
this.onDragEnd();
|
|
}
|
|
}
|
|
|
|
}
|