added needed fields in models and needed libraries for frontend (#67)

This commit is contained in:
Athou
2013-06-01 21:00:10 +02:00
parent 33eb99b8c6
commit 7ffd044a86
11 changed files with 195 additions and 67 deletions

View File

@@ -35,6 +35,8 @@ public class FeedCategory extends AbstractModel {
private boolean collapsed; private boolean collapsed;
private Integer position;
public String getName() { public String getName() {
return name; return name;
} }
@@ -86,4 +88,12 @@ public class FeedCategory extends AbstractModel {
this.collapsed = collapsed; this.collapsed = collapsed;
} }
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -33,6 +33,8 @@ public class FeedSubscription extends AbstractModel {
@OneToMany(mappedBy = "subscription", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "subscription", cascade = CascadeType.REMOVE)
private Set<FeedEntryStatus> statuses; private Set<FeedEntryStatus> statuses;
private Integer position;
public Feed getFeed() { public Feed getFeed() {
return feed; return feed;
} }
@@ -73,4 +75,12 @@ public class FeedSubscription extends AbstractModel {
this.statuses = statuses; this.statuses = statuses;
} }
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -5,6 +5,8 @@ import java.util.List;
import javax.ejb.ApplicationException; import javax.ejb.ApplicationException;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
@@ -50,7 +52,7 @@ public class FeedSubscriptionService {
FeedCategory category) { FeedCategory category) {
final String pubUrl = applicationSettingsService.get().getPublicUrl(); final String pubUrl = applicationSettingsService.get().getPublicUrl();
if (pubUrl == null) { if (StringUtils.isBlank(pubUrl)) {
throw new FeedSubscriptionException( throw new FeedSubscriptionException(
"Public URL of this CommaFeed instance is not set"); "Public URL of this CommaFeed instance is not set");
} }

View File

@@ -8,19 +8,36 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.wordnik.swagger.annotations.ApiClass;
import com.wordnik.swagger.annotations.ApiProperty;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@XmlRootElement @XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@ApiClass("Entry details")
public class Category implements Serializable { public class Category implements Serializable {
@ApiProperty("category id")
private String id; private String id;
@ApiProperty("parent category id")
private String parentId; private String parentId;
@ApiProperty("category id")
private String name; private String name;
@ApiProperty("category children categories")
private List<Category> children = Lists.newArrayList(); private List<Category> children = Lists.newArrayList();
@ApiProperty("category feeds")
private List<Subscription> feeds = Lists.newArrayList(); private List<Subscription> feeds = Lists.newArrayList();
@ApiProperty("wether the category is expanded or collapsed")
private boolean expanded; private boolean expanded;
@ApiProperty("position of the category in the list")
private Integer position;
public String getId() { public String getId() {
return id; return id;
} }
@@ -68,4 +85,13 @@ public class Category implements Serializable {
public void setExpanded(boolean expanded) { public void setExpanded(boolean expanded) {
this.expanded = expanded; this.expanded = expanded;
} }
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -29,6 +29,7 @@ public class Subscription implements Serializable {
Subscription sub = new Subscription(); Subscription sub = new Subscription();
sub.setId(subscription.getId()); sub.setId(subscription.getId());
sub.setName(subscription.getTitle()); sub.setName(subscription.getTitle());
sub.setPosition(subscription.getPosition());
sub.setMessage(feed.getMessage()); sub.setMessage(feed.getMessage());
sub.setErrorCount(feed.getErrorCount()); sub.setErrorCount(feed.getErrorCount());
sub.setFeedUrl(feed.getUrl()); sub.setFeedUrl(feed.getUrl());
@@ -77,6 +78,9 @@ public class Subscription implements Serializable {
@ApiProperty(value = "category id") @ApiProperty(value = "category id")
private String categoryId; private String categoryId;
@ApiProperty("position of the subscription's in the list")
private Integer position;
public Long getId() { public Long getId() {
return id; return id;
} }
@@ -165,4 +169,12 @@ public class Subscription implements Serializable {
this.iconUrl = iconUrl; this.iconUrl = iconUrl;
} }
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
} }

View File

@@ -289,6 +289,19 @@ public class CategoryREST extends AbstractResourceREST {
return Response.ok(Status.OK).build(); return Response.ok(Status.OK).build();
} }
@GET
@Path("/unreadCount")
@ApiOperation(value = "Get unread count for feed subscriptions", responseClass = "List[com.commafeed.frontend.model.UnreadCount]")
public Response getUnreadCount() {
List<UnreadCount> list = Lists.newArrayList();
Map<Long, Long> unreadCount = feedEntryStatusDAO
.getUnreadCount(getUser());
for (Map.Entry<Long, Long> e : unreadCount.entrySet()) {
list.add(new UnreadCount(e.getKey(), e.getValue()));
}
return Response.ok(list).build();
}
@GET @GET
@Path("/get") @Path("/get")
@ApiOperation(value = "Get feed categories", notes = "Get all categories and subscriptions of the user", responseClass = "com.commafeed.frontend.model.Category") @ApiOperation(value = "Get feed categories", notes = "Get all categories and subscriptions of the user", responseClass = "com.commafeed.frontend.model.Category")
@@ -308,19 +321,6 @@ public class CategoryREST extends AbstractResourceREST {
return Response.ok(root).build(); return Response.ok(root).build();
} }
@GET
@Path("/unreadCount")
@ApiOperation(value = "Get unread count for feed subscriptions", responseClass = "List[com.commafeed.frontend.model.UnreadCount]")
public Response getUnreadCount() {
List<UnreadCount> list = Lists.newArrayList();
Map<Long, Long> unreadCount = feedEntryStatusDAO
.getUnreadCount(getUser());
for (Map.Entry<Long, Long> e : unreadCount.entrySet()) {
list.add(new UnreadCount(e.getKey(), e.getValue()));
}
return Response.ok(list).build();
}
private Category buildCategory(Long id, List<FeedCategory> categories, private Category buildCategory(Long id, List<FeedCategory> categories,
List<FeedSubscription> subscriptions, Map<Long, Long> unreadCount) { List<FeedSubscription> subscriptions, Map<Long, Long> unreadCount) {
Category category = new Category(); Category category = new Category();
@@ -335,6 +335,7 @@ public class CategoryREST extends AbstractResourceREST {
subscriptions, unreadCount); subscriptions, unreadCount);
child.setId(String.valueOf(c.getId())); child.setId(String.valueOf(c.getId()));
child.setName(c.getName()); child.setName(c.getName());
child.setPosition(c.getPosition());
if (c.getParent() != null && c.getParent().getId() != null) { if (c.getParent() != null && c.getParent().getId() != null) {
child.setParentId(String.valueOf(c.getParent().getId())); child.setParentId(String.valueOf(c.getParent().getId()));
} }
@@ -345,7 +346,7 @@ public class CategoryREST extends AbstractResourceREST {
Collections.sort(category.getChildren(), new Comparator<Category>() { Collections.sort(category.getChildren(), new Comparator<Category>() {
@Override @Override
public int compare(Category o1, Category o2) { public int compare(Category o1, Category o2) {
return ObjectUtils.compare(o1.getName(), o2.getName()); return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
} }
}); });
@@ -364,7 +365,7 @@ public class CategoryREST extends AbstractResourceREST {
Collections.sort(category.getFeeds(), new Comparator<Subscription>() { Collections.sort(category.getFeeds(), new Comparator<Subscription>() {
@Override @Override
public int compare(Subscription o1, Subscription o2) { public int compare(Subscription o1, Subscription o2) {
return ObjectUtils.compare(o1.getName(), o2.getName()); return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
} }
}); });
return category; return category;

View File

@@ -3,6 +3,7 @@
<group name="lib"> <group name="lib">
<js minimize="false">/vendor/jquery/*.js</js> <js minimize="false">/vendor/jquery/*.js</js>
<js minimize="false">/vendor/jqueryui/*.js</js>
<js minimize="false">/vendor/jquery-mousewheel/*.js</js> <js minimize="false">/vendor/jquery-mousewheel/*.js</js>
<js minimize="false">/vendor/bootstrap/*.js</js> <js minimize="false">/vendor/bootstrap/*.js</js>
<js minimize="false">/vendor/angularjs/*.js</js> <js minimize="false">/vendor/angularjs/*.js</js>

View File

@@ -145,9 +145,9 @@ function($scope, $timeout, $stateParams, $window, $location, $state, $route, Cat
$timeout(function refreshTree() { $timeout(function refreshTree() {
AnalyticsService.track(); AnalyticsService.track();
CategoryService.init(function() { CategoryService.init(function() {
$timeout(refreshTree, 15000); $timeout(refreshTree, 30000);
}, function() { }, function() {
$timeout(refreshTree, 15000); $timeout(refreshTree, 30000);
}); });
}, 15000); }, 15000);

View File

@@ -15,34 +15,44 @@ module.directive('focus', [ '$timeout', function($timeout) {
}; };
} ]); } ]);
/** /**
* Open a popup window pointing to the url in the href attribute * Open a popup window pointing to the url in the href attribute
*/ */
module.directive('popup', function() { module
return { .directive(
link : function(scope, elm, attrs) { 'popup',
elm.bind('click', function(event) { function() {
window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=800'); return {
event.preventDefault(); link : function(scope, elm, attrs) {
}); elm
} .bind(
}; 'click',
}); function(event) {
window
.open(this.href, '',
'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=800');
event.preventDefault();
});
}
};
});
/** /**
* Reusable favicon component * Reusable favicon component
*/ */
module.directive('favicon', function() { module
return { .directive(
restrict : 'E', 'favicon',
scope : { function() {
url : '=' return {
}, restrict : 'E',
replace : true, scope : {
template : '<img ng-src="{{url}}" class="favicon" onError="this.src=\'images/default_favicon.gif\'"></img>' url : '='
}; },
}); replace : true,
template : '<img ng-src="{{url}}" class="favicon" onError="this.src=\'images/default_favicon.gif\'"></img>'
};
});
/** /**
* Support for the blur event * Support for the blur event
@@ -61,11 +71,11 @@ module.directive('ngBlur', function() {
/** /**
* Fired when the top of the element is not visible anymore * Fired when the top of the element is not visible anymore
*/ */
module.directive('onScrollMiddle', function () { module.directive('onScrollMiddle', function() {
return { return {
restrict : 'A', restrict : 'A',
link : function(scope, element, attrs) { link : function(scope, element, attrs) {
var w = $(window); var w = $(window);
var e = $(element); var e = $(element);
var d = $(document); var d = $(document);
@@ -75,7 +85,8 @@ module.directive('onScrollMiddle', function () {
return; return;
var docTop = w.scrollTop(); var docTop = w.scrollTop();
var elemTop = e.offset().top; var elemTop = e.offset().top;
var threshold = docTop === 0 ? elemTop - 1 : docTop + w.height() / 3; var threshold = docTop === 0 ? elemTop - 1 : docTop
+ w.height() / 3;
return (elemTop > threshold) ? 'below' : 'above'; return (elemTop > threshold) ? 'below' : 'above';
}; };
var up = function() { var up = function() {
@@ -84,27 +95,31 @@ module.directive('onScrollMiddle', function () {
var docTop = w.scrollTop(); var docTop = w.scrollTop();
var elemTop = e.offset().top; var elemTop = e.offset().top;
var elemBottom = elemTop + e.height(); var elemBottom = elemTop + e.height();
var threshold = docTop === 0 ? elemBottom - 1 : docTop + w.height() / 3; var threshold = docTop === 0 ? elemBottom - 1 : docTop
+ w.height() / 3;
return (elemBottom > threshold) ? 'below' : 'above'; return (elemBottom > threshold) ? 'below' : 'above';
}; };
w.data.scrollPosition = d.scrollTop(); w.data.scrollPosition = d.scrollTop();
w.data.scrollDirection = 'down'; w.data.scrollDirection = 'down';
if(!w.data.scrollInit){ if (!w.data.scrollInit) {
w.bind('scroll', function(e) { w.bind('scroll',
var scroll = d.scrollTop(); function(e) {
w.data.scrollDirection = (scroll - w.data.scrollPosition > 0) ? 'down' : 'up'; var scroll = d.scrollTop();
w.data.scrollPosition = scroll; w.data.scrollDirection = (scroll
scope.$apply(); - w.data.scrollPosition > 0) ? 'down'
}); : 'up';
w.data.scrollPosition = scroll;
scope.$apply();
});
w.data.scrollInit = true; w.data.scrollInit = true;
} }
scope.$watch(down, function downCallback(value, oldValue) { scope.$watch(down, function downCallback(value, oldValue) {
if(value && value != oldValue && value == 'above') if (value && value != oldValue && value == 'above')
scope.$eval(attrs.onScrollMiddle); scope.$eval(attrs.onScrollMiddle);
}); });
scope.$watch(up, function upCallback(value, oldValue) { scope.$watch(up, function upCallback(value, oldValue) {
if(value && value != oldValue && value == 'below') if (value && value != oldValue && value == 'below')
scope.$eval(attrs.onScrollMiddle); scope.$eval(attrs.onScrollMiddle);
}); });
} }
@@ -145,7 +160,8 @@ module.directive('scrollTo', [ '$timeout', function($timeout) {
} ]); } ]);
/** /**
* Prevent mousewheel scrolling from propagating to the parent when scrollbar reaches top or bottom * Prevent mousewheel scrolling from propagating to the parent when scrollbar
* reaches top or bottom
*/ */
module.directive('mousewheelScrolling', function() { module.directive('mousewheelScrolling', function() {
return { return {
@@ -168,7 +184,8 @@ module.directive('mousewheelScrolling', function() {
}); });
/** /**
* Needed to use recursive directives. Wrap a recursive element with a <recursive> tag * Needed to use recursive directives. Wrap a recursive element with a
* <recursive> tag
*/ */
module.directive('recursive', [ '$compile', function($compile) { module.directive('recursive', [ '$compile', function($compile) {
return { return {
@@ -196,7 +213,7 @@ module.directive('category', [ function() {
return { return {
scope : { scope : {
node : '=', node : '=',
level: '=', level : '=',
selectedType : '=', selectedType : '=',
selectedId : '=', selectedId : '=',
showLabel : '=', showLabel : '=',
@@ -217,15 +234,16 @@ module.directive('category', [ function() {
function($scope, $state, $dialog, FeedService, CategoryService, function($scope, $state, $dialog, FeedService, CategoryService,
SettingsService, MobileService) { SettingsService, MobileService) {
$scope.settingsService = SettingsService; $scope.settingsService = SettingsService;
$scope.getClass = function(level) { $scope.getClass = function(level) {
if ($scope.showLabel){ if ($scope.showLabel) {
return 'indent' + level; return 'indent' + level;
} }
}; };
$scope.categoryLabel = function(category) { $scope.categoryLabel = function(category) {
return $scope.showLabel !== true ? $scope.showLabel : category.name; return $scope.showLabel !== true ? $scope.showLabel
: category.name;
}; };
$scope.categoryCountLabel = function(category) { $scope.categoryCountLabel = function(category) {
@@ -272,16 +290,16 @@ module.directive('category', [ function() {
}); });
} }
}; };
$scope.showFeedDetails = function(feed) { $scope.showFeedDetails = function(feed) {
$state.transitionTo('feeds.feed_details', { $state.transitionTo('feeds.feed_details', {
_id: feed.id _id : feed.id
}); });
}; };
$scope.showCategoryDetails = function(category) { $scope.showCategoryDetails = function(category) {
$state.transitionTo('feeds.category_details', { $state.transitionTo('feeds.category_details', {
_id: category.id _id : category.id
}); });
}; };
@@ -340,3 +358,39 @@ module.directive('spinner', function() {
} }
}; };
}); });
module.directive('draggable', function() {
return {
restrict : 'A',
link : function(scope, element, attrs) {
element.draggable({
revert: true
}).data('source', scope.$eval(attrs.draggable));
}
};
});
module.directive('droppable', function($compile) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
element.droppable({
tolerance: 'pointer',
over: function(event, ui) {
console.log(scope.$eval(attrs.droppable));
},
drop : function(event, ui) {
var draggable = angular.element(ui.draggable);
var index = draggable.data('index');
var source = draggable.data('source');
console.log(source)
scope.$apply();
}
});
}
};
});

View File

@@ -1,5 +1,5 @@
<li> <li>
<div class="pointer tree-item" ui-if="showLabel" ng-class="getClass(level - 1)"> <div class="pointer tree-item" ui-if="showLabel" ng-class="getClass(level - 1)" draggable="node" droppable="node">
<div class="dropdown pull-right"> <div class="dropdown pull-right">
<div class="pull-right" ng-click="showCategoryDetails(node)"> <div class="pull-right" ng-click="showCategoryDetails(node)">
<i class="icon-wrench config pointer"></i> <i class="icon-wrench config pointer"></i>
@@ -11,7 +11,7 @@
<i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all'}" ng-show="!showChildren"></i> <i ng-class="{'icon-star' : node.id == 'starred', 'icon-inbox': node.id == 'all'}" ng-show="!showChildren"></i>
</span> </span>
<span ng-class="{selected: (node.id == selectedId && selectedType == 'category'), unread: unreadCount({category:node})}"> <span ng-class="{selected: (node.id == selectedId && selectedType == 'category'), unread: unreadCount({category:node})}">
{{categoryLabel(node)}} {{categoryCountLabel(node)}} {{categoryLabel(node)}} {{categoryCountLabel(node)}}
</span> </span>
</div> </div>
</div> </div>
@@ -23,7 +23,7 @@
unread-count="unreadCount({category:category})"> unread-count="unreadCount({category:category})">
</category> </category>
</recursive> </recursive>
<li ng-repeat="feed in node.feeds" ng-class="getClass(level)" class="tree-item" <li ng-repeat="feed in node.feeds" ng-class="getClass(level)" class="tree-item" draggable="feed" droppable="feed"
ng-show="settingsService.settings.showRead == true || feed.unread > 0"> ng-show="settingsService.settings.showRead == true || feed.unread > 0">
<div class="pull-right" ng-click="showFeedDetails(feed)"> <div class="pull-right" ng-click="showFeedDetails(feed)">
<i class="icon-wrench config pointer"></i> <i class="icon-wrench config pointer"></i>

File diff suppressed because one or more lines are too long