Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore log component #935

Merged
merged 10 commits into from
Oct 4, 2018
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
app/node_modules/
app/public/
client/node_modules/
coverage/
dist/
docs/
27 changes: 25 additions & 2 deletions client/src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { debounce } from 'min-dash';
import Toolbar from './Toolbar';
import EmptyTab from './EmptyTab';

import Log from './Log';

import debug from 'debug';

import {
Expand Down Expand Up @@ -49,7 +51,8 @@ const INITIAL_STATE = {
dirtyTabs: {},
layout: {},
tabs: [],
tabState: {}
tabState: {},
logEntries: []
};


Expand Down Expand Up @@ -607,6 +610,18 @@ export class App extends Component {
return pSeries(saveTasks);
}

clearLog = () => {
this.setState({
logEntries: []
});
};

toggleLog = (open) => {
this.handleLayoutChanged({
log: { open }
});
};

closeTabs = (matcher) => {

const {
Expand Down Expand Up @@ -792,7 +807,8 @@ export class App extends Component {
tabs,
activeTab,
tabState,
layout
layout,
logEntries
} = this.state;

const Tab = this.state.Tab;
Expand Down Expand Up @@ -920,6 +936,13 @@ export class App extends Component {
}
</TabContainer>
</div>

<Log
entries={ logEntries }
expanded={ layout.log && layout.log.open }
onToggle={ this.toggleLog }
onClear={ this.clearLog }
/>
</SlotFillRoot>
</div>
);
Expand Down
270 changes: 270 additions & 0 deletions client/src/app/Log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import React, { Component } from 'react';

import css from './Log.less';

import classNames from 'classnames';

import dragger from '../util/dom/dragger';


const DEFAULT_HEIGHT = 130;

/**
* A log component
*
* <Log
* entries={ [ { message, category }, ... ] }
* expanded={ true | false}
* onToggle={ (expanded) => ... }
* onClear={ () => { } } />
*
*/
export default class Log extends Component {

constructor(props) {
super(props);

this.state = {};

this.panelRef = React.createRef();
}

toggleLog = () => {
const {
expanded,
onToggle
} = this.props;

onToggle(!expanded);
}

handleHover = () => {
this.setState({
hover: true
});
}

handleOut = () => {
this.setState({
hover: false
});
}

handleFocus = () => {
this.setState({
focus: true
});
}

handleBlur = () => {
this.setState({
focus: false
});
}

/**
* Returns dragger with cached properties panel width.
*/
handleResize = (originalHeight) => {

return dragger((event, delta) => {
const {
y
} = delta;

const newHeight = originalHeight - y;

const newExpanded = newHeight > 25;

const height = (newExpanded ? newHeight : DEFAULT_HEIGHT);

const {
expanded,
onToggle
} = this.props;

this.setState({
height
});

if (expanded !== newExpanded) {
onToggle(newExpanded);
}
});
}

checkFocus = () => {

const panel = this.panelRef.current;

const {
entries
} = this.props;

const lastIdx = entries.length - 1;

if (lastIdx !== -1) {
const node = panel.querySelector(`*[data-idx='${lastIdx}']`);

node.scrollIntoView();
}
}

componentDidUpdate = (prevProps) => {
const {
entries,
expanded
} = this.props;

if (expanded && prevProps.entries !== entries) {
this.checkFocus();
}
}

handleKeyDown = (event) => {

const {
keyCode,
ctrlKey,
metaKey
} = event;

if (keyCode === 27) { // ESC
event.preventDefault();

return this.toggleLog();
}

if (keyCode === 65 && (ctrlKey || metaKey)) { // <A>
event.preventDefault();

return this.handleCopy();
}
}

handleCopy = (event) => {
selectText(this.panelRef.current);

document.execCommand('copy');
}

handleClear = () => {
const {
onClear
} = this.props;

onClear();

this.toggleLog();
}

render() {

const {
expanded,
entries
} = this.props;

const {
hover,
focus,
height
} = this.state;

const focussed = expanded && (hover || focus);

const logHeight = height || DEFAULT_HEIGHT;

return (
<div
className={ classNames(
css.Log, {
expanded,
focussed
}
) }>

<div className="header">
<button
className="toggle-button"
title="Toggle log open state"
onClick={ this.toggleLog }
>Log</button>
</div>

<div
className="resizer"
onDragStart={ this.handleResize(logHeight) }
draggable
></div>

{ expanded &&
<div
className="body"
onMouseEnter={ this.handleHover }
onMouseLeave={ this.handleOut }
onFocus={ this.handleFocus }
onBlur={ this.handleBlur }
style={ { height: logHeight } }>

<div
tabIndex="0"
className="entries"
ref={ this.panelRef }
onKeyDown={ this.handleKeyDown }
>
<div className="controls">
<button className="copy-button" onClick={ this.handleCopy }>Copy</button>
<button className="clear-button" onClick={ this.handleClear }>Clear</button>
</div>

{
entries.map((entry, idx) => {

const {
message,
action,
category
} = entry;

var msg;

if (message) {
msg = message + ' [' + category + ']';
} else {
msg = ' ';
}

return (
<div className="entry" key={ idx } data-idx={ idx }>
{
action
? <a href="#" onClick={ action }>{ msg }</a>
: <span>{ msg }</span>
}
</div>
);
})
}
</div>
</div>
}
</div>
);
}

}



// helpers /////////////////////////////////

function selectText(element) {
var range, selection;

selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
Loading