Skip to content

Commit

Permalink
add tour
Browse files Browse the repository at this point in the history
  • Loading branch information
cs01 committed May 26, 2018
1 parent 71e669d commit 77fd55a
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## master
* Update Rust documentation
* Run `set breakpoint pending on` on initial connection
* Add tour

## 0.11.3.1
* Limit maximum Flask version to prevent `Session expired. Please refresh this webpage.` error
Expand Down
53 changes: 51 additions & 2 deletions gdbgui/src/js/BinaryLoader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import constants from './constants.js'
import Actions from './Actions.js'
import Util from './Util.js'
import ToolTipTourguide from './ToolTipTourguide.jsx'

const TARGET_TYPES = {
file: 'file',
Expand Down Expand Up @@ -60,9 +61,13 @@ class BinaryLoader extends React.Component {
<form style={{marginBottom: 1, flex: '2 0 0'}}>
<div className="input-group input-group-sm">
<div className="dropdown input-group-btn">
<button className="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
<button
className="btn btn-primary dropdown-toggle" type="button"
data-toggle="dropdown"
>
<span className="caret" />
</button>

<ul className="dropdown-menu">
<li>
<a className="pointer" onClick={() => this.setState({target_type: TARGET_TYPES.file})}>
Expand All @@ -81,7 +86,12 @@ class BinaryLoader extends React.Component {
</li>
</ul>

<button type="button" title={title} onClick={this.click_set_target_app.bind(this)} className="btn btn-primary">
<button
type="button"
title={title}
onClick={this.click_set_target_app.bind(this)}
className="btn btn-primary"
>
{button_text}
</button>
</div>
Expand All @@ -97,7 +107,46 @@ class BinaryLoader extends React.Component {
value={this.state.user_input}
/>
</div>
<ToolTipTourguide
step_num={1}
position={'bottomcenter'}
content={
<div>
<h5>
Enter the path to the binary you wish to debug here.
</h5>
<p>
This is the first thing you should do.
</p>
<p>
The path can be absolute, or relative to where gdbgui was launched from.
</p>
</div>}

/>
<ToolTipTourguide
step_num={2}
position={'bottomleft'}
content={
<div>
<h5>
Press this button to load the executable specified in the input.
</h5>
<p>
This is the second thing you should do.
</p>

<p>
Debugging won't start, but you will be able to set breakpoints.

If present, <a href='https://en.wikipedia.org/wiki/Debug_symbol'>debugging symbols</a> in the binary are also loaded.
</p>
<p>If you don't want to debug a binary, click the dropdown to choose a different target type.</p>
</div>
}
/>
<datalist id="past_binaries">{this.state.past_binaries.map((b, i) => <option key={i}>{b}</option>)}</datalist>

</form>
)
}
Expand Down
35 changes: 20 additions & 15 deletions gdbgui/src/js/InitialStoreData.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import constants from './constants.js'
* The initial store data. Keys cannot be added after initialization.
* All fields in here should be shared by > 1 component, otherwise they should
* exist as local state for that component.
*
*/
const initial_store_data = {
// environment
Expand All @@ -25,6 +24,9 @@ const initial_store_data = {
modal_header: null,
modal_body: null,

show_tour_guide: true,
tour_guide_step: 0,
num_tour_guide_steps: 0,
tooltip: {hidden: false, content: 'placeholder', node: null, show_for_n_sec: null},
textarea_to_copy_to_clipboard: {}, // will be replaced with textarea dom node

Expand Down Expand Up @@ -104,23 +106,26 @@ const initial_store_data = {
middle_panes_split_obj: {},
}

// restore saved localStorage data
for (let key in initial_store_data) {
try {
if (typeof initial_store_data[key] === 'boolean') {
if (localStorage.hasOwnProperty(key)) {
let savedval = JSON.parse(localStorage.getItem(key)),
oldval = initial_store_data[key]

if (typeof oldval === typeof savedval) {
initial_store_data[key] = savedval
}
function get_stored(key, default_val){
try{
if (localStorage.hasOwnProperty(key)) {
let cached = JSON.parse(localStorage.getItem(key))
if (typeof cached === typeof default_val) {
return cached
}
return default_val
}
} catch (err) {
console.log(err)
localStorage.removeItem(key)
}catch(err){
console.error(err)
}
localStorage.removeItem(key)
return default_val
}

// restore saved localStorage data
for (let key in initial_store_data) {
let default_val = initial_store_data[key]
initial_store_data[key] = get_stored(key, default_val)
}

if (localStorage.hasOwnProperty('max_lines_of_code_to_fetch')) {
Expand Down
40 changes: 34 additions & 6 deletions gdbgui/src/js/RightSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
*/

import React from 'react'
import InferiorProgramInfo from './InferiorProgramInfo.jsx'

import Breakpoints from './Breakpoints.jsx'
import constants from './constants.js'
import Expressions from './Expressions.jsx'
import GdbMiOutput from './GdbMiOutput.jsx'
import InferiorProgramInfo from './InferiorProgramInfo.jsx'
import Locals from './Locals.jsx'
import Tree from './Tree.js'
import Memory from './Memory.jsx'
import Registers from './Registers.jsx'
import Tree from './Tree.js'
import Threads from './Threads.jsx'
import Memory from './Memory.jsx'
import GdbMiOutput from './GdbMiOutput.jsx'
import constants from './constants.js'
import ToolTipTourguide from './ToolTipTourguide.jsx'

let onmouseup_in_parent_callbacks = [],
onmousemove_in_parent_callbacks = []
Expand Down Expand Up @@ -138,7 +140,33 @@ class RightSidebar extends React.Component {

return (
<div className="content" onMouseUp={onmouseup_in_parent_callback} onMouseMove={onmousemove_in_parent_callback}>
<Collapser title="signals" content={<InferiorProgramInfo signals={this.props.signals} />} />
<Collapser
title="signals"
content={<InferiorProgramInfo
signals={this.props.signals} />}
/>
<ToolTipTourguide
position={'topleft'}
content={
<div>
<h5>This sidebar contains visuals of the state of your program</h5>
<p>
You can see which function the process is stopped in, explore vaiables, and much more.
</p>
<p>
There is more to discover, but this should be enough to get you started.
</p>
<p>
Something missing? Found a bug? <a href="https://github.com/cs01/gdbgui/issues/">Create an issue</a> on github.
</p>

<p>
Happy debugging!
</p>

</div>}
step_num={5}
/>

<Collapser title="threads" content={<Threads />} />

Expand Down
15 changes: 8 additions & 7 deletions gdbgui/src/js/ToolTip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ class ToolTip extends React.Component {
render() {
clearTimeout(this.timeout)
const tooltip = store.get('tooltip')
let left = '200px'
let top = '100px'
if (tooltip.node && !tooltip.hidden) {
let rect = tooltip.node.getBoundingClientRect()
left = rect.x + 'px'
top = rect.y + tooltip.node.offsetHeight + 'px'
} else {
if (!tooltip.node || tooltip.hidden) {
return null
}
let rect = tooltip.node.getBoundingClientRect()
, assumed_width_px = 200
, distance_to_right_edge = (window.innerWidth - rect.x)
, horizontal_buffer = distance_to_right_edge < assumed_width_px ? assumed_width_px - distance_to_right_edge : 0
, left = (rect.x - horizontal_buffer) + 'px'
, top = rect.y + tooltip.node.offsetHeight + 'px'
if (_.isInteger(tooltip.show_for_n_sec)) {
this.timeout = setTimeout(ToolTip.hide_tooltip, tooltip.show_for_n_sec * 1000)
}
Expand All @@ -41,6 +41,7 @@ class ToolTip extends React.Component {
style={{
top: top,
left: left,
maxWidth: '350px',
background: 'white',
border: '1px solid',
position: 'fixed',
Expand Down
128 changes: 128 additions & 0 deletions gdbgui/src/js/ToolTipTourguide.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from 'react'
import Util from './Util.js'
import {store} from 'statorgfc'

class ToolTipTourguide extends React.Component {
constructor(props) {
super(props)
if(!props.position && !(props.top && props.left)){
console.warn('did not receive position')
}
this.ref = React.createRef()
store.connectComponentState(this,
['tour_guide_step', 'num_tour_guide_steps', 'show_tour_guide']
)
}
componentWillMount(){
store.set('num_tour_guide_steps', store.get('num_tour_guide_steps') + 1)
}
static dismiss(){
store.set('show_tour_guide', false)
store.set('tour_guide_step', 0)
Util.persist_value_for_key('show_tour_guide')
}
static next(){
store.set('tour_guide_step', store.get('tour_guide_step') + 1)
}
guide_finshed(){
store.set('tour_guide_step', 0)
}
static start_guide(){
store.set('tour_guide_step', 0)
store.set('show_tour_guide', true)
Util.persist_value_for_key('show_tour_guide')
}
componentDidUpdate() {
if(this.state.show_tour_guide && this.ref.current){
// need to ensure absolute position is respected by setting parent to
// relative
this.ref.current.parentNode.style.position = 'relative'
}
}
get_position(position_name){
let top, left
switch (position_name){
case 'left':
top = '100%'
left = '-50%'
break
case 'right':
top = '50%'
left = '0px'
break
case 'bottom':
case 'bottomcenter':
top = '100%'
left = '50%'
break
case 'bottomleft':
top = '100%'
left = '0'
break
case 'topleft':
top = '0'
left = '0'
break
case 'overlay':
top = '50%'
left = '50%'
break
default:
console.warn('invalid position ' + this.props.position)
top = '100%'
left = '50%'
break
}
return [top, left]
}
render() {
if(!this.state.show_tour_guide){
return null
}else if(this.props.step_num !== this.state.tour_guide_step){
return null
}

let top, left
if(this.props.top && this.props.left){
top = this.props.top
left = this.props.left
}else{
[top, left] = this.get_position(this.props.position)
}

let is_last_step = (this.props.step_num + 1) === this.state.num_tour_guide_steps
, dismiss = is_last_step ? null : <span className='btn btn-default pointer' onClick={ToolTipTourguide.dismiss}>dismiss</span>
return (
<div
ref={this.ref}
style={{
minWidth: '200px',
maxWidth: '350px',
background: 'white',
border: '1px solid',
padding: '5px',
zIndex: '1000',
position: 'absolute',
overflow: 'auto',
whiteSpace: 'normal',
left: left,
top: top,
fontSize: 'small',
'pointer': 'normal',
}}>
{this.props.content}

<p/>
{this.props.step_num + 1} of {this.state.num_tour_guide_steps}
<p/>

{dismiss}
<span className='btn btn-primary pointer' onClick={ToolTipTourguide.next}>{
is_last_step ? "Let's Do This!" : 'next'}
</span>
</div>
)
}
}

export default ToolTipTourguide
Loading

0 comments on commit 77fd55a

Please sign in to comment.