diff options
author | Maximilian Hils <git@maximilianhils.com> | 2016-03-10 15:13:24 +0100 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2016-03-10 15:13:24 +0100 |
commit | 4a6edd92e661fa228f6f8607f045ef5489f58a05 (patch) | |
tree | 7a2134ac48cce29694313e5a5b3105f2cf7e93ff /web/src/js/components/flowtable.js | |
parent | b413a052f96896d387f58e903203cf2e29c6f1f6 (diff) | |
parent | 1c0496e051d8b1af297138732475b1689ada5eb8 (diff) | |
download | mitmproxy-4a6edd92e661fa228f6f8607f045ef5489f58a05.tar.gz mitmproxy-4a6edd92e661fa228f6f8607f045ef5489f58a05.tar.bz2 mitmproxy-4a6edd92e661fa228f6f8607f045ef5489f58a05.zip |
Merge pull request #1004 from gzzhanghao/vscroll
[web] VirtualScroll and AutoScroll helper
Diffstat (limited to 'web/src/js/components/flowtable.js')
-rw-r--r-- | web/src/js/components/flowtable.js | 340 |
1 files changed, 184 insertions, 156 deletions
diff --git a/web/src/js/components/flowtable.js b/web/src/js/components/flowtable.js index 988d1895..f03b8ec0 100644 --- a/web/src/js/components/flowtable.js +++ b/web/src/js/components/flowtable.js @@ -1,188 +1,216 @@ import React from "react"; -import ReactDOM from 'react-dom'; -import {StickyHeadMixin, AutoScrollMixin} from "./common.js"; +import ReactDOM from "react-dom"; +import classNames from "classnames"; import {reverseString} from "../utils.js"; import _ from "lodash"; - -import { VirtualScrollMixin } from "./virtualscroll.js" +import shallowEqual from "shallowequal"; +import AutoScroll from "./helpers/AutoScroll"; +import {calcVScroll} from "./helpers/VirtualScroll"; import flowtable_columns from "./flowtable-columns.js"; -var FlowRow = React.createClass({ - render: function () { - var flow = this.props.flow; - var columns = this.props.columns.map(function (Column) { - return <Column key={Column.displayName} flow={flow}/>; - }.bind(this)); - var className = ""; - if (this.props.selected) { - className += " selected"; - } - if (this.props.highlighted) { - className += " highlighted"; - } - if (flow.intercepted) { - className += " intercepted"; - } - if (flow.request) { - className += " has-request"; - } - if (flow.response) { - className += " has-response"; - } +FlowRow.propTypes = { + selectFlow: React.PropTypes.func.isRequired, + columns: React.PropTypes.array.isRequired, + flow: React.PropTypes.object.isRequired, + highlighted: React.PropTypes.bool, + selected: React.PropTypes.bool, +}; - return ( - <tr className={className} onClick={this.props.selectFlow.bind(null, flow)}> - {columns} - </tr>); - }, - shouldComponentUpdate: function (nextProps) { - return true; - // Further optimization could be done here - // by calling forceUpdate on flow updates, selection changes and column changes. - //return ( - //(this.props.columns.length !== nextProps.columns.length) || - //(this.props.selected !== nextProps.selected) - //); +function FlowRow(props) { + const flow = props.flow; + + const className = classNames({ + "selected": props.selected, + "highlighted": props.highlighted, + "intercepted": flow.intercepted, + "has-request": flow.request, + "has-response": flow.response, + }); + + return ( + <tr className={className} onClick={() => props.selectFlow(flow)}> + {props.columns.map(Column => ( + <Column key={Column.displayName} flow={flow}/> + ))} + </tr> + ); +} + +class FlowTableHead extends React.Component { + + static propTypes = { + setSortKeyFun: React.PropTypes.func.isRequired, + columns: React.PropTypes.array.isRequired, + }; + + constructor(props, context) { + super(props, context); + this.state = { sortColumn: undefined, sortDesc: false }; } -}); - -var FlowTableHead = React.createClass({ - getInitialState: function(){ - return { - sortColumn: undefined, - sortDesc: false - }; - }, - onClick: function(Column){ - var sortDesc = this.state.sortDesc; - var hasSort = Column.sortKeyFun; - if(Column === this.state.sortColumn){ + + onClick(Column) { + const hasSort = Column.sortKeyFun; + + let sortDesc = this.state.sortDesc; + + if (Column === this.state.sortColumn) { sortDesc = !sortDesc; - this.setState({ - sortDesc: sortDesc - }); + this.setState({ sortDesc }); } else { - this.setState({ - sortColumn: hasSort && Column, - sortDesc: false - }) + this.setState({ sortColumn: hasSort && Column, sortDesc: false }); } - var sortKeyFun; - if(!sortDesc){ - sortKeyFun = Column.sortKeyFun; - } else { - sortKeyFun = hasSort && function(){ - var k = Column.sortKeyFun.apply(this, arguments); - if(_.isString(k)){ - return reverseString(""+k); - } else { - return -k; + + let sortKeyFun = Column.sortKeyFun; + if (sortDesc) { + sortKeyFun = hasSort && function() { + const k = Column.sortKeyFun.apply(this, arguments); + if (_.isString(k)) { + return reverseString("" + k); } - } + return -k; + }; } + this.props.setSortKeyFun(sortKeyFun); - }, - render: function () { - var columns = this.props.columns.map(function (Column) { - var onClick = this.onClick.bind(this, Column); - var className; - if(this.state.sortColumn === Column) { - if(this.state.sortDesc){ - className = "sort-desc"; - } else { - className = "sort-asc"; - } - } - return <Column.Title + } + + render() { + const sortColumn = this.state.sortColumn; + const sortType = this.state.sortDesc ? "sort-desc" : "sort-asc"; + return ( + <tr> + {this.props.columns.map(Column => ( + <Column.Title key={Column.displayName} - onClick={onClick} - className={className} />; - }.bind(this)); - return <thead> - <tr>{columns}</tr> - </thead>; + onClick={() => this.onClick(Column)} + className={sortColumn === Column && sortType} + /> + ))} + </tr> + ); + } +} + +class FlowTable extends React.Component { + + static contextTypes = { + view: React.PropTypes.object.isRequired, + }; + + static propTypes = { + rowHeight: React.PropTypes.number, + }; + + static defaultProps = { + rowHeight: 32, + }; + + constructor(props, context) { + super(props, context); + + this.state = { flows: [], vScroll: calcVScroll() }; + + this.onChange = this.onChange.bind(this); + this.onViewportUpdate = this.onViewportUpdate.bind(this); } -}); - - -var ROW_HEIGHT = 32; - -var FlowTable = React.createClass({ - mixins: [StickyHeadMixin, AutoScrollMixin, VirtualScrollMixin], - contextTypes: { - view: React.PropTypes.object.isRequired - }, - getInitialState: function () { - return { - columns: flowtable_columns - }; - }, - componentWillMount: function () { + + componentWillMount() { + window.addEventListener("resize", this.onViewportUpdate); this.context.view.addListener("add", this.onChange); this.context.view.addListener("update", this.onChange); this.context.view.addListener("remove", this.onChange); this.context.view.addListener("recalculate", this.onChange); - }, - componentWillUnmount: function(){ + } + + componentWillUnmount() { + window.removeEventListener("resize", this.onViewportUpdate); this.context.view.removeListener("add", this.onChange); this.context.view.removeListener("update", this.onChange); this.context.view.removeListener("remove", this.onChange); this.context.view.removeListener("recalculate", this.onChange); - }, - getDefaultProps: function () { - return { - rowHeight: ROW_HEIGHT - }; - }, - onScrollFlowTable: function () { - this.adjustHead(); - this.onScroll(); - }, - onChange: function () { - this.forceUpdate(); - }, - scrollIntoView: function (flow) { - this.scrollRowIntoView( - this.context.view.indexOf(flow), - ReactDOM.findDOMNode(this.refs.body).offsetTop - ); - }, - renderRow: function (flow) { - var selected = (flow === this.props.selected); - var highlighted = - ( - this.context.view._highlight && - this.context.view._highlight[flow.id] - ); - - return <FlowRow key={flow.id} - ref={flow.id} - flow={flow} - columns={this.state.columns} - selected={selected} - highlighted={highlighted} - selectFlow={this.props.selectFlow} - />; - }, - render: function () { - var flows = this.context.view.list; - var rows = this.renderRows(flows); + } + + componentDidUpdate() { + this.onViewportUpdate(); + } + + onViewportUpdate() { + const viewport = ReactDOM.findDOMNode(this); + const viewportTop = viewport.scrollTop; + + const vScroll = calcVScroll({ + viewportTop, + viewportHeight: viewport.offsetHeight, + itemCount: this.state.flows.length, + rowHeight: this.props.rowHeight, + }); + + if (!shallowEqual(this.state.vScroll, vScroll) || + this.state.viewportTop !== viewportTop) { + this.setState({ vScroll, viewportTop }); + } + } + + onChange() { + this.setState({ flows: this.context.view.list }); + } + + scrollIntoView(flow) { + const viewport = ReactDOM.findDOMNode(this); + const index = this.context.view.indexOf(flow); + const rowHeight = this.props.rowHeight; + const head = ReactDOM.findDOMNode(this.refs.head); + + const headHeight = head ? head.offsetHeight : 0; + + const rowTop = (index * rowHeight) + headHeight; + const rowBottom = rowTop + rowHeight; + + const viewportTop = viewport.scrollTop; + const viewportHeight = viewport.offsetHeight; + + // Account for pinned thead + if (rowTop - headHeight < viewportTop) { + viewport.scrollTop = rowTop - headHeight; + } else if (rowBottom > viewportTop + viewportHeight) { + viewport.scrollTop = rowBottom - viewportHeight; + } + } + + render() { + const vScroll = this.state.vScroll; + const highlight = this.context.view._highlight; + const flows = this.state.flows.slice(vScroll.start, vScroll.end); + + const transform = `translate(0,${this.state.viewportTop}px)`; return ( - <div className="flow-table" onScroll={this.onScrollFlowTable}> + <div className="flow-table" onScroll={this.onViewportUpdate}> <table> - <FlowTableHead ref="head" - columns={this.state.columns} - setSortKeyFun={this.props.setSortKeyFun}/> - <tbody ref="body"> - { this.getPlaceholderTop(flows.length) } - {rows} - { this.getPlaceholderBottom(flows.length) } + <thead ref="head" style={{ transform }}> + <FlowTableHead + columns={flowtable_columns} + setSortKeyFun={this.props.setSortKeyFun} + /> + </thead> + <tbody> + <tr style={{ height: vScroll.paddingTop }}></tr> + {flows.map(flow => ( + <FlowRow + key={flow.id} + flow={flow} + columns={flowtable_columns} + selected={flow === this.props.selected} + highlighted={highlight && highlight[flow.id]} + selectFlow={this.props.selectFlow} + /> + ))} + <tr style={{ height: vScroll.paddingBottom }}></tr> </tbody> </table> </div> ); } -}); +} -export default FlowTable; +export default AutoScroll(FlowTable); |