From f334fb4b355d90cbf0b8d9e658a87ebeec7fbe90 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sat, 30 Jan 2016 01:44:13 +0100 Subject: wip redoing feed panel --- src/web/js/components/MiddlePanel.react.js | 12 +- src/web/js/components/RightPanel.react.js | 252 +++++++++++++++++++++++------ src/web/js/components/time.react.js | 15 ++ src/web/js/stores/MiddlePanelStore.js | 3 +- src/web/js/stores/RightPanelStore.js | 4 +- src/web/models/feed.py | 2 + src/web/static/css/one-page-app.css | 29 +++- src/web/views/views.py | 11 +- 8 files changed, 265 insertions(+), 63 deletions(-) create mode 100644 src/web/js/components/time.react.js (limited to 'src') diff --git a/src/web/js/components/MiddlePanel.react.js b/src/web/js/components/MiddlePanel.react.js index 6b3eb427..6bfdaaa9 100644 --- a/src/web/js/components/MiddlePanel.react.js +++ b/src/web/js/components/MiddlePanel.react.js @@ -1,6 +1,4 @@ var React = require('react'); -var ReactIntl = require('react-intl'); -var FormattedRelative = ReactIntl.FormattedRelative; var Row = require('react-bootstrap/Row'); var Button = require('react-bootstrap/Button'); @@ -11,8 +9,9 @@ var MiddlePanelStore = require('../stores/MiddlePanelStore'); var MiddlePanelActions = require('../actions/MiddlePanelActions'); var RightPanelActions = require('../actions/RightPanelActions'); +var JarrTime = require('./time.react'); + var TableLine = React.createClass({ - mixins: [ReactIntl.IntlMixin], propTypes: {article_id: React.PropTypes.number.isRequired, feed_title: React.PropTypes.string.isRequired, icon_url: React.PropTypes.string, @@ -49,11 +48,10 @@ var TableLine = React.createClass({ } // FIXME https://github.com/yahoo/react-intl/issues/189 // use FormattedRelative when fixed, will have to upgrade to ReactIntlv2 - var date = (); return (
-
{title}
{date} +
{title}
+
{read} {liked} {this.props.title}
); diff --git a/src/web/js/components/RightPanel.react.js b/src/web/js/components/RightPanel.react.js index a18f0ac3..2e2aac3e 100644 --- a/src/web/js/components/RightPanel.react.js +++ b/src/web/js/components/RightPanel.react.js @@ -1,70 +1,223 @@ var React = require('react'); var Col = require('react-bootstrap/Col'); -var Panel = require('react-bootstrap/Panel'); +var Glyphicon = require('react-bootstrap/Glyphicon'); +var Button = require('react-bootstrap/Button'); +var ButtonGroup = require('react-bootstrap/ButtonGroup'); + var RightPanelStore = require('../stores/RightPanelStore'); +var JarrTime = require('./time.react'); -var Article = React.createClass({ - propTypes: {article: React.PropTypes.object.isRequired}, - render: function() { +var PanelMixin = { + propTypes: {obj: React.PropTypes.object.isRequired}, + getHeader: function() { var icon = null; - if(this.props.article.icon_url){ - icon = (); + if(this.props.obj.icon_url){ + icon = (); + } + var btn_grp = null; + if(this.isEditable() || this.isRemovable()) { + var edit_button = null; + if(this.isEditable()) { + edit_button = (); + } + var rem_button = null; + if(this.isRemovable()) { + rem_button = (); + } + btn_grp = ( + {edit_button} + {rem_button} + ); } - var header = (

- {icon}Title: {this.props.article.title} -

); - return ( -
- + return (
+

{icon}Title: {this.getTitle()}

+ {btn_grp} +
); + }, + getKey: function(prefix, suffix) { + return ((this.state.edit_mode?'edit':'fix') + prefix + + '-' + this.props.obj.id + '-' + suffix); + }, + getCore: function() { + var items = []; + var key; + if(!this.state.edit_mode) { + this.fields.map(function(field) { + key = this.getKey('dt', field.key); + items.push(
{field.title}
); + key = this.getKey('dd', field.key); + if(field.type == 'string') { + items.push(
{this.props.obj[field.key]}
); + } else if(field.type == 'bool') { + if(this.props.obj[field.key]) { + items.push(
); + } else { + items.push(
); + } + } else if (field.type == 'link') { + items.push(
+ + {this.props.obj[field.key]} + +
); + } + }.bind(this)); + } else { + this.fields.map(function(field) { + key = this.getKey('dd', field.key); + items.push(
{field.title}
); + key = this.getKey('dt', field.key); + if(field.type == 'string' || field.type == 'link') { + items.push(
+
); + } else if (field.type == 'bool') { + items.push(
); + } + }.bind(this)); + items.push(
+ +
); + } + return (
{items}
); + }, + render: function() { + return (
+ {this.getHeader()} + {this.getBody()} +
); }, + onClickEdit: function() { + this.setState({edit_mode: !this.state.edit_mode}); + }, + onClickRemove: function() { + }, +}; + +var Article = React.createClass({ + mixins: [PanelMixin], + getInitialState: function() {return {edit_mode: false};}, + fields: [], + isEditable: function() {return false;}, + isRemovable: function() {return true;}, + getTitle: function() {return this.props.obj.title;}, + getBody: function() { + return (
); + }, }); var Feed = React.createClass({ - propTypes: {feed: React.PropTypes.object.isRequired}, - render: function() { - var icon = null; - if(this.props.feed.icon_url){ - icon = (); + mixins: [PanelMixin], + getInitialState: function() { + return {edit_mode: false, filters: this.props.obj.filters}; + }, + isEditable: function() {return true;}, + isRemovable: function() {return true;}, + fields: [{'title': 'Feed title', 'type': 'string', 'key': 'title'}, + {'title': 'Description', 'type': 'string', 'key': 'description'}, + {'title': 'Feed link', 'type': 'link', 'key': 'link'}, + {'title': 'Site link', 'type': 'link', 'key': 'site_link'}, + {'title': 'Enabled', 'type': 'bool', 'key': 'enabled'}, + ], + getTitle: function() {return this.props.obj.title;}, + getFilterRow: function(i, filter) { + return (
+ + + + + + + +
); + }, + getBody: function() { + var filter_rows = []; + for(var i in this.state.filters) { + filter_rows.push(this.getFilterRow(i, this.state.filters[i])); } - var header = (

- {icon}Title: {this.props.feed.title} -

); - return ( + return (
-
Description
-
{this.props.feed.description}
Created on
-
{this.props.feed.created_date}
-
Feed adress
-
- - {this.props.feed.link} - +
+
+
Last fetched
+
-
Site link
+
+ {this.getCore()} +
+
+
Filters
- - {this.props.feed.site_link} - + +
-
Last fetched
-
{this.props.feed.last_retrieved}
-
Enabled
-
{this.props.feed.enabled}
+ {filter_rows} +
- +
); }, + addFilterRow: function() { + var filters = this.state.filters; + filters.push({action: null, action_on: null, + type: null, pattern: null}); + this.setState({filters: filters}); + }, + removeFilterRow: function(evnt) { + var filters = this.state.filters; + delete filters[evnt.target.getAttribute('data-index')]; + this.setState({filters: filters}); + }, }); var Category = React.createClass({ - propTypes: {category: React.PropTypes.object.isRequired}, - render: function() { - return ( - test - - ); + mixins: [PanelMixin], + getInitialState: function() {return {edit_mode: false};}, + isEditable: function() { + if(this.props.obj.id != 0) {return true;} + else {return false;} + }, + isRemovable: function() {return this.isEditable();}, + fields: [{'title': 'Category name', 'type': 'string', 'key': 'name'}], + getTitle: function() {return this.props.obj.name;}, + getBody: function() { + return (
+ {this.getCore()} +
); }, }); @@ -112,18 +265,21 @@ var RightPanel = React.createClass({ ); } if(this.state.current == 'article') { - var content =
; + var cntnt = (
); } else if(this.state.current == 'feed') { - var content = ; + var cntnt = (); } else if(this.state.current == 'category') { - var content = ; + var cntnt = (); } return ( {breadcrum} - {content} + {cntnt} ); }, diff --git a/src/web/js/components/time.react.js b/src/web/js/components/time.react.js new file mode 100644 index 00000000..8b4d47d9 --- /dev/null +++ b/src/web/js/components/time.react.js @@ -0,0 +1,15 @@ +var React = require('react'); +var ReactIntl = require('react-intl'); + +var JarrTime = React.createClass({ + mixins: [ReactIntl.IntlMixin], + propTypes: {stamp: React.PropTypes.number.isRequired, + text: React.PropTypes.string.isRequired}, + render: function() { + return (); + }, +}); + +module.exports = JarrTime; diff --git a/src/web/js/stores/MiddlePanelStore.js b/src/web/js/stores/MiddlePanelStore.js index 1a0a4fab..4a5efd00 100644 --- a/src/web/js/stores/MiddlePanelStore.js +++ b/src/web/js/stores/MiddlePanelStore.js @@ -119,9 +119,10 @@ MiddlePanelStore.dispatchToken = JarrDispatcher.register(function(action) { for (var i in MiddlePanelStore._datas.articles) { if(MiddlePanelStore._datas.articles[i].article_id == action.article.id) { MiddlePanelStore._datas.articles[i].read = true; - console.log(MiddlePanelStore._datas.articles[i]); + break; } } + break; default: // pass } diff --git a/src/web/js/stores/RightPanelStore.js b/src/web/js/stores/RightPanelStore.js index 68d3b7e9..85f336e4 100644 --- a/src/web/js/stores/RightPanelStore.js +++ b/src/web/js/stores/RightPanelStore.js @@ -30,17 +30,19 @@ RightPanelStore.dispatchToken = JarrDispatcher.register(function(action) { if(action.filter_id == null) { RightPanelStore._datas.category = null; RightPanelStore._datas.feed = null; + RightPanelStore._datas.current = null; } else if(action.filter_type == 'category_id') { RightPanelStore._datas.category = MenuStore._datas.categories[action.filter_id]; RightPanelStore._datas.feed = null; RightPanelStore._datas.current = 'category'; + RightPanelStore.emitChange(); } else { RightPanelStore._datas.feed = MenuStore._datas.feeds[action.filter_id]; RightPanelStore._datas.category = MenuStore._datas.categories[RightPanelStore._datas.feed.category_id]; RightPanelStore._datas.current = 'feed'; + RightPanelStore.emitChange(); } - RightPanelStore.emitChange(); break; case ActionTypes.LOAD_ARTICLE: RightPanelStore._datas.feed = MenuStore._datas.feeds[action.article.feed_id]; diff --git a/src/web/models/feed.py b/src/web/models/feed.py index a9c49282..c5fcbe4c 100644 --- a/src/web/models/feed.py +++ b/src/web/models/feed.py @@ -73,6 +73,8 @@ class Feed(db.Model): "link": self.link, "site_link": self.site_link, "etag": self.etag, + "enabled": self.enabled, + "filters": self.filters, "icon_url": self.icon_url, "error_count": self.error_count, "created_date": self.created_date, diff --git a/src/web/static/css/one-page-app.css b/src/web/static/css/one-page-app.css index 9f559ee0..d919a4f7 100644 --- a/src/web/static/css/one-page-app.css +++ b/src/web/static/css/one-page-app.css @@ -120,7 +120,6 @@ position: absolute; top: 2px; right: 4px; - display: block; } #right-panel>ol{ margin-top: 10px; @@ -128,3 +127,31 @@ #right-panel .panel-body img { max-width: 100%; } +#right-panel-heading * { + display: inline; +} +#right-panel-heading h4 img { + margin-right: 5px; +} +#right-panel-heading div.btn-group { + float: right; + margin-top: -5px; + margin-right: -8px; +} +.panel-body dd>input { + width: 100%; +} +.filter-row>select.form-control:first-child { + width: 10%; +} +.filter-row>select.form-control { + width: 15%; +} +.filter-row>select.form-control:last-child { + width: 25%; +} +.filter-row>input.form-control { + width: auto; +} +#filter-button-row>*{ +} diff --git a/src/web/views/views.py b/src/web/views/views.py index 49554cb3..f8549e93 100644 --- a/src/web/views/views.py +++ b/src/web/views/views.py @@ -230,6 +230,7 @@ def signup(): return render_template('signup.html', form=form) +from calendar import timegm from flask import jsonify @@ -250,8 +251,12 @@ def get_menu(): categories[cat_id]['feeds'] = [] feeds = {feed.id: feed.dump() for feed in FeedController(g.user.id).read()} for feed_id, feed in feeds.items(): + feed['created_stamp'] = timegm(feed['created_date'].timetuple()) * 1000 + feed['last_stamp'] = timegm(feed['last_retrieved'].timetuple()) * 1000 feed['category_id'] = feed['category_id'] or 0 feed['unread'] = unread.get(feed['id'], 0) + if not feed['filters']: + feed['filters'] = [] if feed.get('icon_url'): feed['icon_url'] = url_for('icon.icon', url=feed['icon_url']) categories[feed['category_id']]['unread'] += feed['unread'] @@ -284,17 +289,13 @@ def _get_filters(in_dict): return filters -import calendar - - def _articles_to_json(articles, fd_hash=None): return jsonify(**{'articles': [{'title': art.title, 'liked': art.like, 'read': art.readed, 'article_id': art.id, 'selected': False, 'feed_id': art.feed_id, 'category_id': art.category_id or 0, 'feed_title': fd_hash[art.feed_id]['title'] if fd_hash else None, 'icon_url': fd_hash[art.feed_id]['icon_url'] if fd_hash else None, - 'date': art.date, - 'timestamp': calendar.timegm(art.date.timetuple()) * 1000} + 'date': art.date, 'timestamp': timegm(art.date.timetuple()) * 1000} for art in articles.limit(1000)]}) -- cgit