aboutsummaryrefslogtreecommitdiff
path: root/src/web/js/components/Menu.react.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/web/js/components/Menu.react.js')
-rw-r--r--src/web/js/components/Menu.react.js286
1 files changed, 286 insertions, 0 deletions
diff --git a/src/web/js/components/Menu.react.js b/src/web/js/components/Menu.react.js
new file mode 100644
index 00000000..60578f8a
--- /dev/null
+++ b/src/web/js/components/Menu.react.js
@@ -0,0 +1,286 @@
+var React = require('react');
+var Col = require('react-bootstrap/lib/Col');
+var Badge = require('react-bootstrap/lib/Badge');
+var Button = require('react-bootstrap/lib/Button');
+var ButtonGroup = require('react-bootstrap/lib/ButtonGroup');
+var Glyphicon = require('react-bootstrap/lib/Glyphicon');
+
+var MenuStore = require('../stores/MenuStore');
+var MenuActions = require('../actions/MenuActions');
+var MiddlePanelActions = require('../actions/MiddlePanelActions');
+
+var FeedItem = React.createClass({
+ propTypes: {feed_id: React.PropTypes.number.isRequired,
+ title: React.PropTypes.string.isRequired,
+ unread: React.PropTypes.number.isRequired,
+ error_count: React.PropTypes.number.isRequired,
+ icon_url: React.PropTypes.string,
+ active: React.PropTypes.bool.isRequired,
+ },
+ render: function() {
+ var icon = null;
+ var badge_unread = null;
+ if(this.props.icon_url){
+ icon = (<img width="16px" src={this.props.icon_url} />);
+ } else {
+ icon = <Glyphicon glyph="ban-circle" />;
+ }
+ if(this.props.unread){
+ badge_unread = <Badge pullRight>{this.props.unread}</Badge>;
+ }
+ var classes = "nav-feed";
+ if(this.props.active) {
+ classes += " bg-primary";
+ }
+ if(this.props.error_count >= MenuStore._datas.max_error) {
+ classes += " bg-danger";
+ } else if(this.props.error_count > MenuStore._datas.error_threshold) {
+ classes += " bg-warning";
+ }
+ var title = <span className="title">{this.props.title}</span>;
+ return (<li className={classes} onClick={this.handleClick}>
+ {icon}{title}{badge_unread}
+ </li>
+ );
+ },
+ handleClick: function() {
+ MiddlePanelActions.setFeedFilter(this.props.feed_id);
+ },
+});
+
+var Category = React.createClass({
+ propTypes: {category_id: React.PropTypes.number,
+ active_type: React.PropTypes.string,
+ active_id: React.PropTypes.number},
+ render: function() {
+ var classes = "nav-cat";
+ if((this.props.active_type == 'category_id'
+ || this.props.category_id == null)
+ && this.props.active_id == this.props.category_id) {
+ classes += " bg-primary";
+ }
+ return (<li className={classes} onClick={this.handleClick}>
+ {this.props.children}
+ </li>
+ );
+ },
+ handleClick: function(evnt) {
+ // hack to avoid selection when clicking on folding icon
+ if(!evnt.target.classList.contains('glyphicon')) {
+ if(this.props.category_id != null) {
+ MiddlePanelActions.setCategoryFilter(this.props.category_id);
+ } else {
+ MiddlePanelActions.removeParentFilter();
+ }
+ }
+ },
+});
+
+var CategoryGroup = React.createClass({
+ propTypes: {cat_id: React.PropTypes.number.isRequired,
+ filter: React.PropTypes.string.isRequired,
+ active_type: React.PropTypes.string,
+ active_id: React.PropTypes.number,
+ name: React.PropTypes.string.isRequired,
+ feeds: React.PropTypes.array.isRequired,
+ unread: React.PropTypes.number.isRequired,
+ folded: React.PropTypes.bool.isRequired,
+ },
+ getInitialState: function() {
+ return {folded: this.props.folded};
+ },
+ componentWillReceiveProps: function(nextProps) {
+ this.setState({folded: nextProps.folded});
+ },
+ render: function() {
+ // hidden the no category if empty
+ if(!this.props.cat_id && !this.props.feeds.length) {
+ return <ul className="hidden" />;
+ }
+ var filter = this.props.filter;
+ var a_type = this.props.active_type;
+ var a_id = this.props.active_id;
+ if(!this.state.folded) {
+ // filtering according to this.props.filter
+ var feeds = this.props.feeds.filter(function(feed) {
+ if (filter == 'unread' && feed.unread <= 0) {
+ return false;
+ } else if (filter == 'error' && feed.error_count <= MenuStore._datas.error_threshold) {
+ return false;
+ }
+ return true;
+ }).sort(function(feed_a, feed_b){
+ return feed_b.unread - feed_a.unread;
+ }).map(function(feed) {
+ return (<FeedItem key={"f" + feed.id} feed_id={feed.id}
+ title={feed.title} unread={feed.unread}
+ error_count={feed.error_count}
+ active={a_type == 'feed_id' && a_id == feed.id}
+ icon_url={feed.icon_url} />
+ );
+ });
+ } else {
+ var feeds = [];
+ }
+ var unread = null;
+ if(this.props.unread) {
+ unread = <Badge pullRight>{this.props.unread}</Badge>;
+ }
+ var ctrl = (<Glyphicon onClick={this.toggleFolding} pullLeft
+ glyph={this.state.folded?"menu-right":"menu-down"} />
+ );
+
+ return (<ul className="nav nav-sidebar">
+ <Category category_id={this.props.cat_id}
+ active_id={this.props.active_id}
+ active_type={this.props.active_type}>
+ {ctrl}<h4>{this.props.name}</h4>{unread}
+ </Category>
+ {feeds}
+ </ul>
+ );
+ },
+ toggleFolding: function(evnt) {
+ this.setState({folded: !this.state.folded});
+ evnt.stopPropagation();
+ },
+});
+
+var MenuFilter = React.createClass({
+ propTypes: {feed_in_error: React.PropTypes.bool,
+ filter: React.PropTypes.string.isRequired},
+ getInitialState: function() {
+ return {allFolded: false};
+ },
+ render: function() {
+ var error_button = null;
+ if (this.props.feed_in_error) {
+ error_button = (
+ <Button active={this.props.filter == "error"}
+ title="Some of your feeds are in error, click here to list them"
+ onClick={this.setErrorFilter}
+ bsSize="small" bsStyle="warning">
+ <Glyphicon glyph="exclamation-sign" />
+ </Button>
+ );
+ }
+ if(this.state.allFolded) {
+ var foldBtnTitle = "Unfold all categories";
+ var foldBtnGlyph = "option-horizontal";
+ } else {
+ var foldBtnTitle = "Fold all categories";
+ var foldBtnGlyph = "option-vertical";
+ }
+ return (<div>
+ <ButtonGroup className="nav nav-sidebar">
+ <Button active={this.props.filter == "all"}
+ title="Display all feeds"
+ onClick={this.setAllFilter} bsSize="small">
+ <Glyphicon glyph="menu-hamburger" />
+ </Button>
+ <Button active={this.props.filter == "unread"}
+ title="Display only feed with unread article"
+ onClick={this.setUnreadFilter}
+ bsSize="small">
+ <Glyphicon glyph="unchecked" />
+ </Button>
+ {error_button}
+ </ButtonGroup>
+ <ButtonGroup className="nav nav-sidebar">
+ <Button onClick={MenuActions.reload}
+ title="Refresh all feeds" bsSize="small">
+ <Glyphicon glyph="refresh" />
+ </Button>
+ </ButtonGroup>
+ <ButtonGroup className="nav nav-sidebar">
+ <Button title={foldBtnTitle} bsSize="small"
+ onClick={this.toggleFold}>
+ <Glyphicon glyph={foldBtnGlyph} />
+ </Button>
+ </ButtonGroup>
+ </div>
+ );
+ },
+ setAllFilter: function() {
+ MenuActions.setFilter("all");
+ },
+ setUnreadFilter: function() {
+ MenuActions.setFilter("unread");
+ },
+ setErrorFilter: function() {
+ MenuActions.setFilter("error");
+ },
+ toggleFold: function() {
+ this.setState({allFolded: !this.state.allFolded}, function() {
+ MenuActions.toggleAllFolding(this.state.allFolded);
+ }.bind(this));
+ },
+});
+
+var Menu = React.createClass({
+ getInitialState: function() {
+ return {filter: 'all', categories: {}, feeds: {},
+ all_folded: false, active_type: null, active_id: null};
+ },
+ render: function() {
+ var feed_in_error = false;
+ var rmPrntFilt = (
+ <ul className="nav nav-sidebar">
+ <Category category_id={null}
+ active_id={this.state.active_id}
+ active_type={this.state.active_type}>
+ <h4>All</h4>
+ </Category>
+ </ul>
+ );
+ var categories = [];
+ for(var index in this.state.categories_order) {
+ var cat_id = this.state.categories_order[index];
+ var feeds = [];
+ var unread = 0;
+ this.state.categories[cat_id].feeds.map(function(feed_id) {
+ if(this.state.feeds[feed_id].error_count > MenuStore._datas.error_threshold) {
+ feed_in_error = true;
+ }
+ unread += this.state.feeds[feed_id].unread;
+ feeds.push(this.state.feeds[feed_id]);
+ }.bind(this));
+ categories.push(<CategoryGroup key={"c" + cat_id} feeds={feeds}
+ filter={this.state.filter}
+ active_type={this.state.active_type}
+ active_id={this.state.active_id}
+ name={this.state.categories[cat_id].name}
+ cat_id={this.state.categories[cat_id].id}
+ folded={this.state.all_folded}
+ unread={unread} />);
+ }
+
+ return (<Col xsHidden smHidden md={3} lg={2}
+ id="menu" className="sidebar">
+ <MenuFilter filter={this.state.filter}
+ feed_in_error={feed_in_error} />
+ {rmPrntFilt}
+ {categories}
+ </Col>
+ );
+ },
+ componentDidMount: function() {
+ MenuActions.reload();
+ MenuStore.addChangeListener(this._onChange);
+ },
+ componentWillUnmount: function() {
+ MenuStore.removeChangeListener(this._onChange);
+ },
+ _onChange: function() {
+ var datas = MenuStore.getAll();
+ this.setState({filter: datas.filter,
+ feeds: datas.feeds,
+ categories: datas.categories,
+ categories_order: datas.categories_order,
+ active_type: datas.active_type,
+ active_id: datas.active_id,
+ all_folded: datas.all_folded});
+ },
+});
+
+module.exports = Menu;
bgstack15