Skip to content

Instantly share code, notes, and snippets.

@firejune
Last active November 28, 2016 11:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save firejune/6b6124561fb1b5da6c07ee9aa6bb7276 to your computer and use it in GitHub Desktop.
Save firejune/6b6124561fb1b5da6c07ee9aa6bb7276 to your computer and use it in GitHub Desktop.
Psyclone Studio - Workspace
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Immutable, { Map, List } from 'immutable';
import { Actions as DeviceActions } from '../redux/Device';
import { Actions as ExecuteActions } from '../redux/Execute';
import { Actions as ProjectActions } from '../redux/Project';
import { Actions as WorkspaceActions } from '../redux/Workspace';
import { Actions as TerminalActions } from '../redux/Terminal';
import { Actions as MonitorActions } from '../redux/Monitor';
import Dialogs from './Dialogs';
import Scenario from '../components/Workspace/Scenario';
import Terminal from '../components/Workspace/Terminal';
import Toolbar from '../components/Workspace/Toolbar';
import Screen from '../components/Workspace/Screen';
import Monitors from '../components/Workspace/Monitors';
import Details from '../components/Workspace/Details';
import Options from '../components/Workspace/Options';
import Resources from '../components/Workspace/Resources';
import Agent from '../helpers/agent';
import { getEntryProject } from '../helpers/file';
import { readSetting, saveSetting, readSessionSetting } from '../helpers/setting';
import { addToRecent } from '../helpers/recent';
import { autobind, autowrap, ignorePropChanged } from '../helpers/decorators';
function mapStateToProps(store) {
const { device, execute, dialog, terminal, project, workspace } = store;
return {
dialog: dialog.mode,
pids: terminal.get('pids'),
devices: device.get('connect'),
entryProject: project.get('entry'),
entryDeviceId: device.get('entry'),
orientation: workspace.get('orientation'),
message: workspace.get('message'),
connection: workspace.get('connection'),
runnerStarted: execute.get('runnerStarted')
};
}
function mapDispatchToProps(dispatch) {
if (Agent.needGetContext) {
getEntryProject((project, context, path) =>
ProjectActions.setEntryProject(project, context, path)(dispatch));
}
return bindActionCreators({
sendToDevice: DeviceActions.sendDevice,
setEntryDevice: DeviceActions.setEntryDevice,
openLogcatSession: TerminalActions.openLogcatSession,
createXTerm: TerminalActions.createXTerm,
resetGeometry: TerminalActions.resetGeometry,
connectDevice: WorkspaceActions.connectDevice,
setSelectedCase: WorkspaceActions.setSelectedCase,
setSelectedStep: WorkspaceActions.setSelectedStep,
setScreenChanged: ExecuteActions.setScreenChanged,
testRunnerStart: ExecuteActions.testRunnerStart,
testRunnerEnd: ExecuteActions.testRunnerEnd
}, dispatch);
}
@connect(mapStateToProps, mapDispatchToProps)
@autobind
export default class Workspace extends Component {
static propTypes = {
pids: PropTypes.array,
dialog: PropTypes.any,
orientation: PropTypes.any,
runnerStarted: PropTypes.any,
entryDeviceId: PropTypes.string,
entryProject: PropTypes.instanceOf(Immutable.Map).isRequired,
devices: PropTypes.instanceOf(Immutable.List).isRequired,
connection: PropTypes.instanceOf(Immutable.Map).isRequired,
message: PropTypes.instanceOf(Immutable.Map).isRequired,
createXTerm: PropTypes.func.isRequired,
resetGeometry: PropTypes.func.isRequired,
sendToDevice: PropTypes.func.isRequired,
connectDevice: PropTypes.func.isRequired,
setEntryDevice: PropTypes.func.isRequired,
setScreenChanged: PropTypes.func.isRequired,
setSelectedCase: PropTypes.func.isRequired,
setSelectedStep: PropTypes.func.isRequired,
testRunnerStart: PropTypes.func.isRequired,
testRunnerEnd: PropTypes.func.isRequired,
openLogcatSession: PropTypes.func.isRequired
}
constructor(props) {
const path = readSessionSetting('entryProjectPath');
const entryProject = props.entryProject.toObject();
addToRecent(entryProject.name, path, entryProject.icon);
super(props);
this.state = {
toolbar: {
current: new Map(),
devices: new List(),
capture: '',
execute: '',
repeat: '',
rotate: '',
layout: '',
refresh: ''
},
panel: readSetting('bottom-panel:tab') || 'inspector',
message: 'Connecting...',
resolution: null,
ppi: null
};
this.panesWereResized = null;
}
@autowrap
componentDidMount() {
const win = $(window);
const $leftPanel = $(this.refs.leftPanel);
const $rightPanel = $(this.refs.rightPanel);
const $bottomPanel = $(this.refs.bottomPanel);
const leftEditorPanelSize = readSetting('left-editor:size');
const monitorMemPanelSize = readSetting('monitor-mem:size');
let bottomPanelSize = readSetting('bottom-panel:size') || 0;
let leftPanelSize = readSetting('left-panel:size') || 0;
let rightPanelSize = readSetting('right-panel:size') || 0;
const centerPanelMinWidth = parseInt($(this.refs.centerPanel).css('min-width'), 10);
const topPanelMinHeight = parseInt($(this.refs.topPanel).css('min-height'), 10);
this.panelMaxWidth = {
'left-panel': () => win.width() - $rightPanel.outerWidth() - centerPanelMinWidth,
'right-panel': () => win.width() - $leftPanel.outerWidth() - centerPanelMinWidth,
'bottom-panel': () => win.height() - topPanelMinHeight - 1
};
if (leftPanelSize) {
const maxWidth = this.panelMaxWidth['left-panel']();
if (leftPanelSize > maxWidth) {
leftPanelSize = maxWidth;
}
$leftPanel.width(leftPanelSize);
}
if (rightPanelSize) {
const maxWidth = this.panelMaxWidth['right-panel']();
if (rightPanelSize > maxWidth) {
rightPanelSize = maxWidth;
}
$rightPanel.width(rightPanelSize);
}
if (bottomPanelSize) {
const maxHeight = this.panelMaxWidth['bottom-panel']();
if (bottomPanelSize > maxHeight) {
bottomPanelSize = maxHeight;
}
$bottomPanel.height(bottomPanelSize);
}
if (leftEditorPanelSize) $('#left-editor').width(leftEditorPanelSize);
if (monitorMemPanelSize) {
const $monitorMem = $('#monitor-mem');
const handleVerticalSize = $monitorMem.next().width();
$monitorMem.width(monitorMemPanelSize);
$('#monitor-cpu').width(win.width() - monitorMemPanelSize - handleVerticalSize);
}
Agent.app.on('mouseup', this.handleStopDragging);
Agent.app.on('resize', () => {
leftPanelSize = readSetting('left-panel:size') || 0;
rightPanelSize = readSetting('right-panel:size') || 0;
bottomPanelSize = readSetting('bottom-panel:size') || 0;
const rightMaxWidth = this.panelMaxWidth['right-panel']();
if (rightPanelSize > rightMaxWidth) $rightPanel.width(rightMaxWidth);
const leftMaxWidth = this.panelMaxWidth['left-panel']();
if (leftPanelSize > leftMaxWidth) $leftPanel.width(leftMaxWidth);
const bottomMaxHeight = this.panelMaxWidth['bottom-panel']();
if (bottomPanelSize > bottomMaxHeight) $bottomPanel.height(bottomMaxHeight);
this.updateStatus();
});
Agent.app.setup();
this.updateStatus();
this.updatePanels();
this.multiTouchTest();
}
componentWillReceiveProps(props) {
const { entryDeviceId, devices } = props;
const connectedDevice = devices.find(device => device.get('id') === entryDeviceId);
if (!Immutable.is(this.props.pids, props.pids)) {
this.props.openLogcatSession(props.pids);
}
if (connectedDevice && connectedDevice.get('wsport') && !props.dialog && !this.connecting) {
this.refs.screen.setState({connecting: true});
this.props.connectDevice(connectedDevice.toJS());
this.connecting = true;
}
if (this.props.entryDeviceId && !entryDeviceId && !this.props.dialog) {
console.warn('TODO: OOPS! DEVICE CONNECTION LOST!');
}
if (!this.props.connection.size && props.connection.size) {
console.debug('workspace.device.on.connected', props.connection.toJS());
const connectionInfo = props.connection.toJS();
const { capture, execute, repeat, layout } = this.state.toolbar;
const toolbar = {
devices,
current: connectedDevice,
capture: `active ${capture.match('on') ? 'on' : ''}`,
execute: `active ${execute.match('on') ? 'on' : ''}`,
repeat: `active ${repeat.match('on') ? 'on' : ''}`,
rotate: 'active',
layout: `active ${layout.match('on') ? 'on' : ''}`,
refresh: 'active'
};
this.setState({
toolbar,
resolution: {
width: connectionInfo.width,
height: connectionInfo.height
},
ppi: connectionInfo.DPI,
message: 'Device Connected'
});
}
if (!Immutable.is(this.props.message, props.message)) {
this.setState({message: props.message.get('text')});
}
if (props.runnerStarted === null) {
const { toolbar } = this.state;
toolbar.execute = 'active on loading';
toolbar.capture = '';
toolbar.rotate = '';
toolbar.refresh = '';
this.setState({ toolbar });
}
if (props.runnerStarted === true) {
const { toolbar } = this.state;
toolbar.execute = 'active on';
toolbar.capture = '';
toolbar.rotate = '';
toolbar.refresh = '';
this.setState({ toolbar });
}
if (this.props.runnerStarted !== props.runnerStarted && props.runnerStarted === false) {
this.refs.scenario.setState({focusedCase: null});
this.handleChangeTools('execute', 'active', true);
}
}
@ignorePropChanged
shouldComponentUpdate(props, state) {
if (state.message) {
clearTimeout(this.messageTimer);
this.messageTimer = setTimeout(() => this.setState({message: null}), 12000);
}
return false;
}
handleStopDragging() {
$(document.body).removeClass('resizing-x resizing-y');
Agent.app.off('mousemove.panel-resize');
if (this.panesWereResized) {
const { name, size } = this.panesWereResized;
saveSetting(`${name}:size`, size);
if (name === 'terminal-pane' || name === 'left-panel') {
const dimensions = this.refs.terminal.getDimensions();
const { charSize, viewport } = dimensions;
this.props.resetGeometry(charSize, viewport);
}
this.panesWereResized = null;
Agent.app.trigger('pane-resize');
}
}
handleVertical(evt) {
evt.preventDefault();
let target = $(evt.target).parent();
if (evt.target.dataset.target) {
target = $(evt.target.dataset.target);
}
const initialSize = target.outerWidth();
const startX = Agent.app.mousePosition.x;
const reverse = !!evt.target.dataset.reverse;
const max = this.panelMaxWidth[target.attr('id')] && this.panelMaxWidth[target.attr('id')]();
if (max) {
target.css('max-width', max);
}
$(document.body).addClass('resizing-x');
Agent.app.on('mousemove.panel-resize', () => {
let size;
if (reverse) {
size = initialSize + (startX - Agent.app.mousePosition.x);
} else {
size = initialSize + (Agent.app.mousePosition.x - startX);
}
if (size > max) {
size = max;
}
target.width(size);
this.panesWereResized = {
size,
name: target.attr('id')
};
if (this.panesWereResized.name === 'monitor-mem') {
MonitorActions.reflowCharts();
}
if (this.panesWereResized.name === 'left-panel' ||
this.panesWereResized.name === 'right-panel') {
WorkspaceActions.onDeviceResize();
this.updateStatus();
}
});
}
handleHorizontal(evt) {
evt.preventDefault();
const { dataset } = evt.target;
const { toggleClass, toggleEnable, toggleDisable } = dataset;
const targetPane = dataset.target ? $(dataset.target) : $(evt.target).closest('.panel');
const initialSize = targetPane.outerHeight();
const startY = Agent.app.mousePosition.y;
const reverse = !!dataset.reverse;
const topPanelMinHeight = parseInt($(this.refs.topPanel).css('min-height'), 10);
const minHeight = parseInt($(this.refs.bottomPanel).css('min-height'), 10);
const maxHeight = $(window).height() - topPanelMinHeight - 1;
let newHeight = 0;
let oldHeight = targetPane.height();
let toggleStatus = false;
$(document.body).addClass('resizing-y');
Agent.app.on('mousemove.panel-resize', () => {
if (reverse) {
newHeight = initialSize + (startY - Agent.app.mousePosition.y);
} else {
newHeight = initialSize + (Agent.app.mousePosition.y - startY);
}
if (newHeight > maxHeight || newHeight < minHeight) {
return;
}
const direction = newHeight - oldHeight;
if (toggleClass) {
toggleStatus = targetPane.hasClass(toggleClass);
if (direction < 0 && toggleStatus && newHeight < toggleDisable) {
newHeight = minHeight;
this.setState({ panel: 'closed' });
return this.handleStopDragging();
}
if (direction > 0 && !toggleStatus && newHeight > toggleEnable) {
this.setState({ panel: readSetting('bottom-panel:last-tab') || 'inspector' });
}
}
targetPane.height(newHeight);
oldHeight = newHeight;
this.panesWereResized = {
size: newHeight,
name: targetPane.attr('id')
};
if (this.panesWereResized.name === 'bottom-panel') {
WorkspaceActions.onDeviceResize();
this.updatePanels();
}
});
}
handleChangeInspect(type, state) {
console.debug('workspace.handleChangeInspect', type, state);
const { options, resources, scenario } = this.refs;
switch (type) {
case 'analysing':
if (state === true) {
if (!options.state.analysing) options.setState({analysing: true});
if (!resources.state.analysing) resources.setState({analysing: true});
} else {
if (options.state.analysing) options.setState({analysing: false});
if (resources.state.analysing) resources.setState({analysing: false});
}
break;
case 'focus':
if (!options.state.show) options.setState({show: true});
if (resources.state.focusedNode !== state) resources.setState({focusedNode: state});
break;
case 'clear':
if (!resources.state.loading) resources.setState({loading: true, analysing: false});
if (options.state.show) options.setState({show: false});
break;
case 'unfocus':
if (this.props.runnerStarted !== false) return;
if (options.state.show) options.setState({show: false});
if (scenario.state.focusedCase) scenario.setState({focusedCase: null});
this.props.setSelectedCase(null);
this.props.setSelectedStep(null);
break;
}
}
handleChangeTools(action, state, autostop) {
const { toolbar } = this.state;
if (toolbar[action] !== state) {
const trunon = !!state.match('on');
toolbar[action] = state;
switch (action) {
case 'capture':
ExecuteActions.touchbotAction('capture');
toolbar.execute = trunon ? '' : 'active';
toolbar.repeat = trunon ? '' : 'active';
toolbar.layout = trunon ? '' : 'active';
break;
case 'execute':
if (trunon) {
this.props.testRunnerStart();
} else if (!autostop) {
this.props.testRunnerEnd();
toolbar.repeat = 'active';
} else if (toolbar.repeat.match('on') && this.props.connection.size) {
setTimeout(() => this.handleChangeTools('execute', 'active on'), 100);
} else if (autostop) {
Agent.beep();
}
toolbar.capture = trunon ? '' : 'active';
toolbar.rotate = trunon ? '' : 'active';
toolbar.refresh = trunon ? '' : 'active';
break;
case 'repeat':
ExecuteActions.touchbotAction('repeat');
break;
case 'rotate':
this.props.sendToDevice({
cmd: 'orientation',
args: {
orientation: this.props.orientation ? 'portrait' : 'landscape'
}
});
break;
case 'layout':
this.refs.screen.setState({showInspector: trunon});
toolbar.capture = trunon ? '' : 'active';
break;
case 'refresh':
this.props.setScreenChanged();
break;
}
this.setState({ toolbar });
}
}
handleChangePanels(event) {
let target = event.target;
if (target.nodeName !== 'DIV') {
target = event.target.parentNode;
}
let { tab } = target.dataset;
if (tab === 'expanded') {
tab = readSetting('bottom-panel:last-tab') || 'inspector';
setTimeout(WorkspaceActions.onDeviceResize, 200);
}
if (tab === 'closed') {
$(this.refs.bottomPanel).removeClass('expanded');
// css transform animation이 발생하는 동안 컨텐츠 유지
setTimeout(() => this.setState({ panel: tab }, WorkspaceActions.onDeviceResize), 200);
} else {
saveSetting('bottom-panel:last-tab', tab);
this.setState({ panel: tab });
}
saveSetting('bottom-panel:tab', tab);
}
updateStatus() {
$(this.refs.status).css({
left: $(this.refs.leftPanel).outerWidth(),
width: $(this.refs.centerPanel).outerWidth()
});
}
updatePanels() {
switch (this.state.panel) {
case 'console':
TerminalActions.scrollToBottom();
break;
case 'timeline':
MonitorActions.reflowCharts();
break;
}
}
render() {
const { panel, message } = this.state;
const expanded = panel !== 'closed' ? 'expanded' : '';
let { ppi, resolution } = this.state;
ppi = ppi ? `${ppi}ppi` : '';
resolution = resolution
? `${resolution.width}px × ${resolution.height}px`
: '';
return (
<div id="main">
<div ref="topPanel" id="top-panel" className="panel">
<div ref="leftPanel" id="left-panel" className="panel">
<Scenario ref="scenario" />
<div onMouseDown={this.handleVertical} className="handle vertical" />
</div>
<div ref="centerPanel" id="center-panel" className="panel">
<Toolbar
ref="toolbar"
status={this.state.toolbar}
onChangeTools={this.handleChangeTools}
setEntryDevice={this.props.setEntryDevice} />
<Screen ref="screen" onChangeInspect={this.handleChangeInspect} />
</div>
<div ref="rightPanel" id="right-panel" className="panel">
<Details />
<div
onMouseDown={this.handleVertical}
className="handle vertical"
data-reverse="1" />
</div>
</div>
<div ref="bottomPanel" id="bottom-panel" className={`panel ${expanded}`}>
<div
onMouseDown={this.handleHorizontal}
className="handle horizontal visible"
data-reverse="1"
data-toggle-class="expanded"
data-toggle-enable="32"
data-toggle-disable="100" />
<div className="tabs">
<div
className={`tab ${panel === 'inspector' ? 'active' : ''}`}
data-tab="inspector"
onClick={this.handleChangePanels}>Inspector</div>
<div
className={`tab ${panel === 'console' ? 'active' : ''}`}
data-tab="console"
onClick={this.handleChangePanels}>Console</div>
<div
className={`tab ${panel === 'timeline' ? 'active' : ''}`}
data-tab="timeline"
onClick={this.handleChangePanels}>Monitor</div>
<div className="status" ref="status" onDoubleClick={this.handleMultitouchTest}>
<span className="message">{message}</span>
<span className="info">
<span className="resolution">{resolution}</span>&nbsp;
<span className="ppi">{ppi}</span>
</span>
</div>
<div
className="material-icon close"
data-tab="closed"
onClick={this.handleChangePanels}>close</div>
<div
className="material-icon open"
data-tab="expanded"
onClick={this.handleChangePanels}>tab</div>
</div>
<div id="inspector-pane" className={panel === 'inspector' ? 'active' : ''}>
<Resources ref="resources" visible={panel === 'inspector'} />
<div
onMouseDown={this.handleVertical}
className="handle vertical"
data-target="#left-editor" />
<Options ref="options" visible={panel === 'inspector'} />
</div>
<div id="console-pane" className={panel === 'console' ? 'active' : ''}>
<Terminal
ref="terminal"
visible={panel === 'console'}
createXTerm={this.props.createXTerm} />
</div>
<div id="timeline-pane" className={panel === 'timeline' ? 'active' : ''}>
<Monitors
visible={panel === 'timeline'}
onStartResizeConumn={this.handleVertical} />
</div>
</div>
<Dialogs />
</div>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment