From 3644d4ef190d2d509c64fdf5c29382cb8a41e235 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sat, 23 Jan 2016 12:47:18 +0100 Subject: doing some design wip toogle read / like --- src/web/js/actions/MenuActions.js | 11 ++- src/web/js/actions/MiddlePanelActions.js | 57 ++++++++++-- src/web/js/components/MainApp.react.js | 21 +++-- src/web/js/components/Menu.react.js | 143 ++++++++++++++++++++--------- src/web/js/components/MiddlePanel.react.js | 111 ++++++++++++---------- src/web/js/constants/JarrConstants.js | 8 +- src/web/js/stores/MenuStore.js | 24 +++-- src/web/js/stores/MiddlePanelStore.js | 19 +++- src/web/views/views.py | 2 +- 9 files changed, 273 insertions(+), 123 deletions(-) diff --git a/src/web/js/actions/MenuActions.js b/src/web/js/actions/MenuActions.js index c3cc95bc..1f1eea01 100644 --- a/src/web/js/actions/MenuActions.js +++ b/src/web/js/actions/MenuActions.js @@ -17,19 +17,20 @@ var MenuActions = { }, setFilterAll: function() { JarrDispatcher.dispatch({ - component: 'menu', - type: MenuActionTypes.MENU_FILTER_ALL, + type: MenuActionTypes.MENU_FILTER, + filter: 'all', }); }, setFilterUnread: function() { JarrDispatcher.dispatch({ - component: 'menu', - type: MenuActionTypes.MENU_FILTER_UNREAD, + type: MenuActionTypes.MENU_FILTER, + filter: 'unread', }); }, setFilterError: function() { JarrDispatcher.dispatch({ - type: MenuActionTypes.MENU_FILTER_ERROR, + type: MenuActionTypes.MENU_FILTER, + filter: 'error', }); }, diff --git a/src/web/js/actions/MiddlePanelActions.js b/src/web/js/actions/MiddlePanelActions.js index 159ba91b..7a944ecd 100644 --- a/src/web/js/actions/MiddlePanelActions.js +++ b/src/web/js/actions/MiddlePanelActions.js @@ -28,10 +28,11 @@ var reloadIfNecessaryAndDispatch = function(dispath_payload) { filters[key] = dispath_payload[key]; } } - jquery.getJSON('/middle_panel', dispath_payload, function(payload) { - dispath_payload.articles = payload.articles; - JarrDispatcher.dispatch(dispath_payload); - _last_fetched_with = MiddlePanelStore.getRequestFilter(); + jquery.getJSON('/middle_panel', dispath_payload, + function(payload) { + dispath_payload.articles = payload.articles; + JarrDispatcher.dispatch(dispath_payload); + _last_fetched_with = MiddlePanelStore.getRequestFilter(); }); } else { JarrDispatcher.dispatch(dispath_payload); @@ -50,24 +51,24 @@ var MiddlePanelActions = { }); }); }, - removeParentFilter: function(filter_type, filter_id) { + removeParentFilter: function() { reloadIfNecessaryAndDispatch({ - type: MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER, + type: MiddlePanelActionTypes.PARENT_FILTER, filter_type: null, filter_id: null, }); }, setCategoryFilter: function(category_id) { reloadIfNecessaryAndDispatch({ - type: MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER, - filter_type: 'category', + type: MiddlePanelActionTypes.PARENT_FILTER, + filter_type: 'category_id', filter_id: category_id, }); }, setFeedFilter: function(feed_id) { reloadIfNecessaryAndDispatch({ - type: MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER, - filter_type: 'feed', + type: MiddlePanelActionTypes.PARENT_FILTER, + filter_type: 'feed_id', filter_id: feed_id, }); }, @@ -89,6 +90,42 @@ var MiddlePanelActions = { filter: 'liked', }); }, + changeRead: function(article_id, new_value){ + jquery.ajax({type: 'PUT', + contentType: 'application/json', + data: JSON.stringify({readed: new_value}), + url: "api/v2.0/article/" + article_id, + success: function (result) { + JarrDispatcher.dispatch({ + type: MiddlePanelActionTypes.CHANGE_ATTR, + article_id: article_id, + attribute: 'read', + value: new_value, + }); + }, + error: function(XMLHttpRequest, textStatus, errorThrown){ + console.log(XMLHttpRequest.responseText); + }, + }); + }, + changeLike: function(article_id, new_value){ + jquery.ajax({type: 'PUT', + contentType: 'application/json', + data: JSON.stringify({like: new_value}), + url: "api/v2.0/article/" + article_id, + success: function (result) { + JarrDispatcher.dispatch({ + type: MiddlePanelActionTypes.CHANGE_ATTR, + article_id: article_id, + attribute: 'liked', + value: new_value, + }); + }, + error: function(XMLHttpRequest, textStatus, errorThrown){ + console.log(XMLHttpRequest.responseText); + }, + }); + }, }; module.exports = MiddlePanelActions; diff --git a/src/web/js/components/MainApp.react.js b/src/web/js/components/MainApp.react.js index 743c9510..059d2646 100644 --- a/src/web/js/components/MainApp.react.js +++ b/src/web/js/components/MainApp.react.js @@ -1,16 +1,23 @@ +var React = require('react'); +var Col = require('react-bootstrap/lib/Col'); +var Grid = require('react-bootstrap/lib/Grid'); + var Menu = require('./Menu.react'); var MiddlePanel = require('./MiddlePanel.react'); -var React = require('react'); var MainApp = React.createClass({ render: function() { - return (
-
- - -
-
+ return ( + + + + + + + + + ); }, }); diff --git a/src/web/js/components/Menu.react.js b/src/web/js/components/Menu.react.js index d44e7bd3..3e5f4156 100644 --- a/src/web/js/components/Menu.react.js +++ b/src/web/js/components/Menu.react.js @@ -1,7 +1,11 @@ var React = require('react'); +var Row = require('react-bootstrap/lib/Row'); +var Badge = require('react-bootstrap/lib/Badge'); var Button = require('react-bootstrap/lib/Button'); var ButtonGroup = require('react-bootstrap/lib/ButtonGroup'); -var Badge = require('react-bootstrap/lib/Badge'); +var ListGroup = require('react-bootstrap/lib/ListGroup'); +var ListGroupItem = require('react-bootstrap/lib/ListGroupItem'); +var Glyphicon = require('react-bootstrap/lib/Glyphicon'); var MenuStore = require('../stores/MenuStore'); var MenuActions = require('../actions/MenuActions'); @@ -13,27 +17,29 @@ var FeedItem = React.createClass({ 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; - var badge_error = null; + var style = null; if(this.props.icon_url){ icon = (); } else { - icon = (); + icon = ; } if(this.props.unread){ badge_unread = {this.props.unread}; } if(this.props.error_count == 6) { - badge_unread = error; + style = "danger"; } else if(this.props.error_count > 3) { - badge_unread = warn; + style = "warning"; } - return (
  • - {icon}{this.props.title}{badge_unread}{badge_error} -
  • + return ( + {icon}{this.props.title}{badge_unread} + ); }, handleClick: function() { @@ -42,53 +48,76 @@ var FeedItem = React.createClass({ }); var Category = React.createClass({ - propTypes: {category_id: React.PropTypes.number.isRequired, + 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, }, + getInitialState: function() { + return {unfolded: true}; + }, render: function() { var filter = this.props.filter; + var a_type = this.props.active_type; + var a_id = this.props.active_id; // filtering according to this.props.filter + if(this.state.unfolded) { var feeds = this.props.feeds.filter(function(feed) { - if (filter == 'unread' && feed.unread <= 0) {return false;} - else if (filter == 'error' && feed.error_count <= 3){return false;} + if (filter == 'unread' && feed.unread <= 0) { + return false; + } else if (filter == 'error' && feed.error_count <= 3) { + return false; + } return true; }).sort(function(feed_a, feed_b){ return feed_b.unread - feed_a.unread; }).map(function(feed) { - return (); + active={a_type == 'feed_id' && a_id == feed.id} + icon_url={feed.icon_url} /> + ); }); - var unread = undefined; + } else { + var feeds = []; + } + var unread = null; if(this.props.unread){ - unread = ( - {this.props.unread} - ); + unread = {this.props.unread}; } - return (
    -

    - {this.props.name} {unread} -

    -
      {feeds}
    -
    + var active = a_type == 'category_id' && a_id == this.props.cat_id; + var ctrl = ( + ); + + return ( + + {ctrl} {this.props.name} {unread} + + {feeds} + ); }, handleClick: function() { - MiddlePanelActions.setCategoryFilter(this.props.category_id); + MiddlePanelActions.setCategoryFilter(this.props.cat_id); + }, + toggleFolding: function(evnt) { + this.setState({unfolded: !this.state.unfolded}); + evnt.stopPropagation(); }, }); -var Menu = React.createClass({ +var MenuFilter = React.createClass({ getInitialState: function() { - return {filter: 'all', categories: [], - feed_in_error: false, all_unread_count: 0}; + return {filter: 'all', feed_in_error: false}; }, render: function() { - var filter = this.state.filter; var error_button = null; if (this.state.feed_in_error) { error_button = ( ); } - return ( + ); }, componentDidMount: function() { @@ -130,9 +190,10 @@ var Menu = React.createClass({ var datas = MenuStore.getAll(); this.setState({filter: datas.filter, categories: datas.categories, - feed_in_error: datas.feed_in_error, + active_type: datas.active_type, + active_id: datas.active_id, all_unread_count: datas.all_unread_count}); }, }); -module.exports = Menu; +module.exports = {Menu: Menu, MenuFilter: MenuFilter}; diff --git a/src/web/js/components/MiddlePanel.react.js b/src/web/js/components/MiddlePanel.react.js index f95554b8..49365a6a 100644 --- a/src/web/js/components/MiddlePanel.react.js +++ b/src/web/js/components/MiddlePanel.react.js @@ -1,6 +1,10 @@ var React = require('react'); +var Row = require('react-bootstrap/lib/Row'); var Button = require('react-bootstrap/lib/Button'); var ButtonGroup = require('react-bootstrap/lib/ButtonGroup'); +var ListGroup = require('react-bootstrap/lib/ListGroup'); +var ListGroupItem = require('react-bootstrap/lib/ListGroupItem'); +var Glyphicon = require('react-bootstrap/lib/Glyphicon'); var MiddlePanelStore = require('../stores/MiddlePanelStore'); var MiddlePanelActions = require('../actions/MiddlePanelActions'); @@ -18,54 +22,78 @@ var TableLine = React.createClass({ return {read: this.props.read, liked: this.props.liked}; }, render: function() { - var read = this.state.read ? 'r' : ''; var liked = this.state.liked ? 'l' : ''; var icon = null; if(this.props.icon_url){ icon = (); } else { - icon = (); + icon = ; } + var title = ( + {icon} {this.props.feed_title} + ); + var read = (); + var liked = (); return ( - - {icon}{liked} - - - {this.props.feed_title} - - - - - {this.props.title} - - - {this.props.date} - + + {read} + {liked} + {this.props.title} + ); }, + toogleRead: function() { + this.setState({read: !this.state.read}); + MiddlePanelActions.changeRead(this.props.article_id, !this.state.read); + }, + toogleLike: function() { + this.setState({liked: !this.state.liked}); + MiddlePanelActions.changeLike(this.props.article_id, !this.state.liked); + }, +}); + +var MiddlePanelFilter = React.createClass({ + getInitialState: function() { + return {filter: MiddlePanelStore._datas.filter}; + }, + render: function() { + return ( + + + + + + + ); + }, + componentDidMount: function() { + MiddlePanelStore.addChangeListener(this._onChange); + }, + componentWillUnmount: function() { + MiddlePanelStore.removeChangeListener(this._onChange); + }, + _onChange: function() { + this.setState({filter: MiddlePanelStore._datas.filter}); + }, }); -var TableBody = React.createClass({ +var MiddlePanel = React.createClass({ getInitialState: function() { - return {filter: 'unread', articles: []}; + return {filter: MiddlePanelStore._datas.filter, articles: []}; }, render: function() { - return (
    - - - - - - - + return ( + {this.state.articles.map(function(article){ - return ();})} - -
    -
    + + ); }, componentDidMount: function() { @@ -91,13 +118,5 @@ var TableBody = React.createClass({ }, }); -var MiddlePanel = React.createClass({ - render: function() { - return (
    - -
    - ); - }, -}); - -module.exports = MiddlePanel; +module.exports = {MiddlePanel: MiddlePanel, + MiddlePanelFilter: MiddlePanelFilter}; diff --git a/src/web/js/constants/JarrConstants.js b/src/web/js/constants/JarrConstants.js index 3d334ee3..d00f24a8 100644 --- a/src/web/js/constants/JarrConstants.js +++ b/src/web/js/constants/JarrConstants.js @@ -14,13 +14,13 @@ var keyMirror = require('keymirror'); module.exports = { MenuActionTypes: keyMirror({ RELOAD_MENU: null, - MENU_FILTER_ALL: null, - MENU_FILTER_UNREAD: null, - MENU_FILTER_ERROR: null, + PARENT_FILTER: null, + MENU_FILTER: null, }), MiddlePanelActionTypes: keyMirror({ + PARENT_FILTER: null, RELOAD_MIDDLE_PANEL: null, - MIDDLE_PANEL_PARENT_FILTER: null, MIDDLE_PANEL_FILTER: null, + CHANGE_ATTR: null, }), }; diff --git a/src/web/js/stores/MenuStore.js b/src/web/js/stores/MenuStore.js index 016b33f2..6809d8b0 100644 --- a/src/web/js/stores/MenuStore.js +++ b/src/web/js/stores/MenuStore.js @@ -6,7 +6,7 @@ var assign = require('object-assign'); var MenuStore = assign({}, EventEmitter.prototype, { - _datas: {filter: 'all', categories: [], + _datas: {filter: 'all', categories: [], active_type: null, active_id: null, all_unread_count: 0, feed_in_error: false}, getAll: function() { return this._datas; @@ -17,6 +17,13 @@ var MenuStore = assign({}, EventEmitter.prototype, { this.emitChange(); } }, + setActive: function(type, value) { + if(this._datas.active_id != value || this._datas.active_type != type) { + this._datas.active_type = type; + this._datas.active_id = value; + this.emitChange(); + } + }, readFeedArticle: function(feed_id) { // TODO }, @@ -40,14 +47,17 @@ MenuStore.dispatchToken = JarrDispatcher.register(function(action) { MenuStore._datas['all_unread_count'] = action.all_unread_count; MenuStore.emitChange(); break; - case MenuActionTypes.MENU_FILTER_ALL: - MenuStore.setFilter('all'); + case MenuActionTypes.PARENT_FILTER: + MenuStore.setActive(action.filter_type, action.filter_id); + break; + case MenuActionTypes.MENU_FILTER: + MenuStore.setFilter(action.filter); break; - case MenuActionTypes.MENU_FILTER_UNREAD: - MenuStore.setFilter('unread'); + case MenuActionTypes.MENU_FILTER: + MenuStore.setFilter(action.filter); break; - case MenuActionTypes.MENU_FILTER_ERROR: - MenuStore.setFilter('error'); + case MenuActionTypes.MENU_FILTER: + MenuStore.setFilter(action.filter); break; default: diff --git a/src/web/js/stores/MiddlePanelStore.js b/src/web/js/stores/MiddlePanelStore.js index 80ac8198..201bebd1 100644 --- a/src/web/js/stores/MiddlePanelStore.js +++ b/src/web/js/stores/MiddlePanelStore.js @@ -1,5 +1,6 @@ var JarrDispatcher = require('../dispatcher/JarrDispatcher'); var MiddlePanelActionTypes = require('../constants/JarrConstants').MiddlePanelActionTypes; +var MenuActionTypes = require('../constants/JarrConstants').MenuActionTypes; var EventEmitter = require('events').EventEmitter; var CHANGE_EVENT = 'change_middle_panel'; var assign = require('object-assign'); @@ -21,7 +22,7 @@ var MiddlePanelStore = assign({}, EventEmitter.prototype, { var id = null; var filter = this._datas.filter; if (this._datas.filter_type) { - key = this._datas.filter_type + '_id'; + key = this._datas.filter_type; id = this._datas.filter_id; } return this._datas.articles.filter(function(article) { @@ -72,7 +73,7 @@ MiddlePanelStore.dispatchToken = JarrDispatcher.register(function(action) { MiddlePanelStore.setArticles(action.articles); MiddlePanelStore.emitChange(); break; - case MiddlePanelActionTypes.MIDDLE_PANEL_PARENT_FILTER: + case MiddlePanelActionTypes.PARENT_FILTER: changed = MiddlePanelStore.setParentFilter(action.filter_type, action.filter_id); changed = MiddlePanelStore.setArticles(action.articles) || changed; @@ -83,6 +84,20 @@ MiddlePanelStore.dispatchToken = JarrDispatcher.register(function(action) { changed = MiddlePanelStore.setArticles(action.articles) || changed; if(changed) {MiddlePanelStore.emitChange();} break; + case MiddlePanelActionTypes.CHANGE_ATTR: + var id = action.article_id; + var attr = action.attribute; + var val = action.value; + for (var i in MiddlePanelStore._datas.articles) { + if(MiddlePanelStore._datas.articles[i].article_id == id) { + if (MiddlePanelStore._datas.articles[i][attr] != val) { + MiddlePanelStore._datas.articles[i][attr] = val; + MiddlePanelStore.emitChange(); + } + break; + } + } + break; default: // pass } diff --git a/src/web/views/views.py b/src/web/views/views.py index 25788c42..297cae28 100644 --- a/src/web/views/views.py +++ b/src/web/views/views.py @@ -273,7 +273,7 @@ def get_middle_panel(): filters['like'] = True filter_type = request.args.get('filter_type') if filter_type in {'feed', 'category'} and request.args.get('filter_id'): - filters[filter_type + '_id'] = int(request.args['filter_id']) + filters[filter_type + '_id'] = int(request.args['filter_id']) or None fd_hash = {feed.id: {'title': feed.title, 'icon_url': url_for('icon.icon', url=feed.icon_url) -- cgit