Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions plugins/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ const TransmitPane = require('./transmit-pane');

const makeToasts = require('../../src/lib/toasts');

const Scroller = require('./scroller');

function editor(app, opts, done){

var codeEditor;
var outputConsole;
var transmission;
var transmitPane;
var scroller = new Scroller();

function refreshConsole(){
const { text } = consoleStore.getState();

if(outputConsole){
outputConsole.innerHTML = text;
outputConsole.scrollTop = outputConsole.scrollHeight;
}
const { lines } = consoleStore.getState();
scroller.setLines(lines);
scroller.requestRefresh();
}

function highlighter(position, length) {
Expand Down Expand Up @@ -127,6 +127,10 @@ function editor(app, opts, done){
outputConsole.style.overflow = 'auto';
outputConsole.style.whiteSpace = 'pre-wrap';
el.appendChild(outputConsole);

scroller.setConsole(outputConsole);

outputConsole.addEventListener('scroll', scroller.scroll, false);
}
if(!transmission) {
transmission = document.createElement('div');
Expand Down
60 changes: 49 additions & 11 deletions plugins/editor/indicators.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,65 @@
'use strict';

const _ = require('lodash');
const React = require('react');

const styles = require('./styles');
const transmissionStore = require('../../src/stores/transmission');

class Indicators extends React.Component {

render() {
const { flashRx, flashTx } = this.props;
constructor(){
this._updateIndicators = this._updateIndicators.bind(this);
}

let indicatorRx = [styles.indicator];
let indicatorTx = [styles.indicator];
if(flashRx) {
indicatorRx.push(styles.rx);
_updateIndicators(){
const { flashRx, flashTx } = transmissionStore.getState();
if(flashRx){
this.rx.style.backgroundColor = styles.rx.backgroundColor;
} else {
this.rx.style.backgroundColor = styles.indicator.backgroundColor;
}
if(flashTx) {
indicatorTx.push(styles.tx);
if(flashTx){
this.tx.style.backgroundColor = styles.tx.backgroundColor;
} else {
this.tx.style.backgroundColor = styles.indicator.backgroundColor;
}
}

componentDidMount() {
var parent = React.findDOMNode(this);
var tx = this.tx = document.createElement('span');
var rx = this.rx = document.createElement('span');
this.txLabel = document.createTextNode('TX');
this.rxLabel = document.createTextNode(' RX');
parent.appendChild(this.txLabel);
parent.appendChild(tx);
parent.appendChild(this.rxLabel);
parent.appendChild(rx);
_.forEach(styles.indicator, function(style, name){
tx.style[name] = style;
rx.style[name] = style;
});

transmissionStore.listen(this._updateIndicators);
}

componentWillUnmount() {
var parent = React.findDOMNode(this);
parent.removeChild(this.tx);
parent.removeChild(this.rx);
this.tx = null;
this.rx = null;
transmissionStore.unlisten(this._updateIndicators);
}

shouldComponentUpdate() {
return false;
}

render() {
return (
<span style={styles.rxtx}>
TX<span styles={indicatorTx} />RX<span styles={indicatorRx} />
</span>
<span style={styles.rxtx}></span>
);
}
}
Expand Down
146 changes: 146 additions & 0 deletions plugins/editor/scroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'use strict';

var _ = require('lodash');

function generateContent(lines, start, end, minLength) {
return _(lines)
.slice(start, end)
.thru(function(array){
if(array.length < minLength){
// pad whitespace at top of array
return _(new Array(minLength - array.length))
.fill('\u2009')
.concat(array)
.value();
}else{
return array;
}
})
.map(function(line){
if(line.length === 0){
// insert a blank space to prevent pre omitting a trailing newline,
// even though pre/pre-nowrap/pre-line are specified.
return '\u2009';
}
return line;
})
.join('\n');
}

function Scroller() {
this.lines = [];
this.minVisible = 30;
this.startPosition = 0;
this.animateRequest = null;
this.sticky = true;
this.jumpToBottom = true;
this.dirty = false;
this.console = null;

//pre-bind functions and throttle expansion
this.refresh = this._renderVisible.bind(this);
this.scroll = this._onScroll.bind(this);
this.expand = _.throttle(this._expand.bind(this), 150, {
leading: true,
trailing: true
});
}

Scroller.prototype.setLines = function(newLines) {
var len = newLines.length;
this.lines = newLines;
if(this.sticky){
this.startPosition = Math.max(0, len - this.minVisible);
}else if(len === 1 && newLines[0].length === 0){
// ^^ `lines` is reset to an array with one empty line. ugh.

// handle the reset case when lines is replaced with an empty array
// we don't have a direct event that can call this
this.reset();
}else if(len < this.startPosition){
// handle buffer rollover, where number of lines will go from 2048 to ~1900
this.startPosition = Math.max(0, len - this.minVisible);
}
this.dirty = true;
};

Scroller.prototype.reset = function(){
this.startPosition = Math.max(0, this.lines.length - this.minVisible);
this.jumpToBottom = true;
this.sticky = true;
this.dirty = true;
};

Scroller.prototype.requestRefresh = function(){
if(this.console){
this.animateRequest = requestAnimationFrame(this.refresh);
}
};

Scroller.prototype._renderVisible = function(){
this.animateRequest = null;
if(this.dirty && this.console){
var top = this.console.scrollTop;
if(this.sticky){
this.startPosition = Math.max(0, this.lines.length - this.minVisible);
}
this.console.innerHTML = generateContent(this.lines, this.startPosition, this.lines.length, this.minVisible);
if(this.jumpToBottom){
this.console.scrollTop = 2000;
this.jumpToBottom = false;
}else if(!this.sticky && this.startPosition > 0 && top === 0){
//cover the situation where the window was fully scrolled faster than expand could keep up and locked to the top
requestAnimationFrame(this.expand);
}
this.dirty = false;
}
};

Scroller.prototype._expand = function(){
this.startPosition = Math.max(0, this.startPosition - this.minVisible);
this.sticky = false;
if(this.console){
var scrollHeight = this.console.scrollHeight;
var scrollTop = this.console.scrollTop;

// do an inline scroll to avoid potential scroll interleaving
this.console.innerHTML = generateContent(this.lines, this.startPosition, this.lines.length, this.minVisible);
var newScrollHeight = this.console.scrollHeight;
this.console.scrollTop = scrollTop + newScrollHeight - scrollHeight;

this.dirty = false;
}
};

Scroller.prototype._onScroll = function(){
if(this.jumpToBottom){
// do nothing, prepare to jump
return;
}
var height = this.console.offsetHeight;
var scrollHeight = this.console.scrollHeight;
var scrollTop = this.console.scrollTop;
if(this.sticky){
if(scrollTop + height < scrollHeight - 30){
this.sticky = false;
}
}else{
if(scrollTop < 15 && this.startPosition > 0){
this.expand();
}else if(scrollTop + height > scrollHeight - 30){
this.jumpToBottom = true;
this.sticky = true;
this.dirty = true;
}
}

if(this.dirty && !this.animateRequest){
this.animateRequest = requestAnimationFrame(this.refresh);
}
};

Scroller.prototype.setConsole = function(console){
this.console = console;
};

module.exports = Scroller;
4 changes: 2 additions & 2 deletions plugins/editor/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const styles = {
},
indicator: {
backgroundColor: grey,
height: 10,
width: 10,
height: '10px',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ints here are just shortcuts to px styles, but this is fine

width: '10px',
borderRadius: '100%',
margin: '0px 10px',
display: 'inline-block'
Expand Down
22 changes: 6 additions & 16 deletions plugins/editor/transmission-bar.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
'use strict';

const React = require('react');
const { createContainer } = require('sovereign');

const Indicators = require('./indicators');
const styles = require('./styles');
const transmissionStore = require('../../src/stores/transmission');

class TransmissionBar extends React.Component {

render() {
const { flashRx, flashTx } = this.props;
shouldComponentUpdate() {
return false;
}

render() {
return (
<div style={styles.bar}>
<Indicators flashRx={flashRx} flashTx={flashTx} />
<Indicators />
</div>
);
}
}

module.exports = createContainer(TransmissionBar, {
getStores(){
return {
deviceStore: transmissionStore
};
},

getPropsFromStores() {
return transmissionStore.getState();
}
});
module.exports = TransmissionBar;
5 changes: 5 additions & 0 deletions plugins/editor/transmit-pane.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class TransmitPane extends React.Component {
transmitInput(data);
}

shouldComponentUpdate(nextProps){
const { connected, text } = this.props;
return (connected !== nextProps.connected || text !== nextProps.text);
}

render() {
const { connected, text } = this.props;
return (
Expand Down