Last active
November 28, 2016 11:25
-
-
Save firejune/6b6124561fb1b5da6c07ee9aa6bb7276 to your computer and use it in GitHub Desktop.
Psyclone Studio - Workspace
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
<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