From 1c0496e051d8b1af297138732475b1689ada5eb8 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 10 Mar 2016 21:40:07 +0800 Subject: [web] VirtualScroll and AutoScroll helper --- web/src/js/components/eventlog.js | 230 ++++++++++++++++++++++---------------- 1 file changed, 132 insertions(+), 98 deletions(-) (limited to 'web/src/js/components/eventlog.js') diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index 51f1578e..d1b23ace 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -1,116 +1,151 @@ import React from "react" -import {AutoScrollMixin, Router} from "./common.js" +import ReactDOM from "react-dom" +import shallowEqual from "shallowequal" +import {Router} from "./common.js" import {Query} from "../actions.js" -import { VirtualScrollMixin } from "./virtualscroll.js" +import AutoScroll from "./helpers/AutoScroll"; +import {calcVScroll} from "./helpers/VirtualScroll" import {StoreView} from "../store/view.js" import _ from "lodash" -var LogMessage = React.createClass({ - render: function () { - var entry = this.props.entry; - var indicator; - switch (entry.level) { - case "web": - indicator = ; - break; - case "debug": - indicator = ; - break; - default: - indicator = ; - } - return ( -
- { indicator } {entry.message} -
+class EventLogContents extends React.Component { + + static contextTypes = { + eventStore: React.PropTypes.object.isRequired, + }; + + static defaultProps = { + rowHeight: 18, + }; + + constructor(props, context) { + super(props, context); + + this.view = new StoreView( + this.context.eventStore, + entry => this.props.filter[entry.level] ); - }, - shouldComponentUpdate: function () { - return false; // log entries are immutable. + + this.heights = {}; + this.state = { entries: this.view.list, vScroll: calcVScroll() }; + + this.onChange = this.onChange.bind(this); + this.onViewportUpdate = this.onViewportUpdate.bind(this); } -}); -var EventLogContents = React.createClass({ - contextTypes: { - eventStore: React.PropTypes.object.isRequired - }, - mixins: [AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - var filterFn = function (entry) { - return this.props.filter[entry.level]; - }; - var view = new StoreView(this.context.eventStore, filterFn.bind(this)); - view.addListener("add", this.onEventLogChange); - view.addListener("recalculate", this.onEventLogChange); + componentDidMount() { + window.addEventListener("resize", this.onViewportUpdate); + this.view.addListener("add", this.onChange); + this.view.addListener("recalculate", this.onChange); + this.onViewportUpdate(); + } - return { - view: view - }; - }, - componentWillUnmount: function () { - this.state.view.close(); - }, - filter: function (entry) { - return this.props.filter[entry.level]; - }, - onEventLogChange: function () { - this.forceUpdate(); - }, - componentWillReceiveProps: function (nextProps) { + componentWillUnmount() { + window.removeEventListener("resize", this.onViewportUpdate); + this.view.removeListener("add", this.onChange); + this.view.removeListener("recalculate", this.onChange); + this.view.close(); + } + + componentDidUpdate() { + this.onViewportUpdate(); + } + + componentWillReceiveProps(nextProps) { if (nextProps.filter !== this.props.filter) { - this.state.view.recalculate(entry => - nextProps.filter[entry.level] + this.view.recalculate( + entry => nextProps.filter[entry.level] ); } - }, - getDefaultProps: function () { - return { - rowHeight: 45, - rowHeightMin: 15, - placeholderTagName: "div" - }; - }, - renderRow: function (elem) { - return ; - }, - render: function () { - var entries = this.state.view.list; - var rows = this.renderRows(entries); - - return
-            { this.getPlaceholderTop(entries.length) }
-            {rows}
-            { this.getPlaceholderBottom(entries.length) }
-        
; } -}); -var ToggleFilter = React.createClass({ - toggle: function (e) { - e.preventDefault(); - return this.props.toggleLevel(this.props.name); - }, - render: function () { - var className = "label "; - if (this.props.active) { - className += "label-primary"; - } else { - className += "label-default"; + onViewportUpdate() { + const viewport = ReactDOM.findDOMNode(this); + + const vScroll = calcVScroll({ + itemCount: this.state.entries.length, + rowHeight: this.props.rowHeight, + viewportTop: viewport.scrollTop, + viewportHeight: viewport.offsetHeight, + itemHeights: this.state.entries.map(entry => this.heights[entry.id]), + }); + + if (!shallowEqual(this.state.vScroll, vScroll)) { + this.setState({ vScroll }); + } + } + + onChange() { + this.setState({ entries: this.view.list }); + } + + setHeight(id, ref) { + if (ref && !this.heights[id]) { + const height = ReactDOM.findDOMNode(ref).offsetHeight; + if (this.heights[id] !== height) { + this.heights[id] = height; + this.onViewportUpdate(); + } } + } + + getIcon(level) { + return { web: "html5", debug: "bug" }[level] || "info"; + } + + render() { + const vScroll = this.state.vScroll; + const entries = this.state.entries.slice(vScroll.start, vScroll.end); + return ( - - {this.props.name} - +
+                
+ {entries.map((entry, index) => ( +
+ + {entry.message} +
+ ))} +
+
); } -}); +} + +ToggleFilter.propTypes = { + name: React.PropTypes.string.isRequired, + toggleLevel: React.PropTypes.func.isRequired, + active: React.PropTypes.bool, +}; + +function ToggleFilter ({ name, active, toggleLevel }) { + let className = "label "; + if (active) { + className += "label-primary"; + } else { + className += "label-default"; + } + + function onClick(event) { + event.preventDefault(); + toggleLevel(name); + } + + return ( + + {name} + + ); +} + +const AutoScrollEventLog = AutoScroll(EventLogContents); var EventLog = React.createClass({ mixins: [Router], - getInitialState: function () { + getInitialState() { return { filter: { "debug": false, @@ -119,18 +154,17 @@ var EventLog = React.createClass({ } }; }, - close: function () { + close() { var d = {}; d[Query.SHOW_EVENTLOG] = undefined; - this.updateLocation(undefined, d); }, - toggleLevel: function (level) { + toggleLevel(level) { var filter = _.extend({}, this.state.filter); filter[level] = !filter[level]; this.setState({filter: filter}); }, - render: function () { + render() { return (
@@ -143,10 +177,10 @@ var EventLog = React.createClass({
- + ); } }); -export default EventLog; \ No newline at end of file +export default EventLog; -- cgit v1.2.3