Skip to content

Commit b077631

Browse files
Merge pull request #78 from ekonstantinidis/animated-hide
Animation for marking notification as read
2 parents db7f677 + 15787f5 commit b077631

File tree

12 files changed

+140
-37
lines changed

12 files changed

+140
-37
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ You can download Gitify from the [releases](https://github.com/ekonstantinidis/g
2626

2727

2828
### Installation
29-
If you encounter any issues with `npm install`, then run `ulimit -n 512`.
3029

3130
npm install
3231

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "main.js",
66
"scripts": {
77
"build-js": "mkdir -p build/js && browserify -t reactify src/js/app.js -o build/js/app.js",
8-
"build": "ulimit -n 512 && npm install && ulimit -n 512 && grunt build && npm run build-js",
8+
"build": "ulimit -n 512 && npm install && grunt build && npm run build-js",
99
"watch-js": "watchify -t reactify src/js/app.js -o build/js/app.js -v",
1010
"watch": "grunt build && npm build && npm run watch-js | grunt watch",
1111
"start": "electron .",

src/js/__tests__/components/notification.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ describe('Test for Notification Component', function () {
157157

158158
instance.markAsRead();
159159

160+
jest.runAllTimers();
161+
160162
});
161163

162164
it('Should fail to mark a notification as read succesfully', function () {
@@ -181,9 +183,10 @@ describe('Test for Notification Component', function () {
181183
key={notification.id} />);
182184

183185
var superagent = require('superagent');
184-
superagent.__setResponse(400, 'notOk', {}, false);
186+
superagent.__setResponse(400, false, {}, false);
185187

186188
instance.markAsRead();
189+
expect(instance.isRead).toBeFalsy();
187190

188191
});
189192

src/js/__tests__/components/notifications.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('Test for Notifications Component', function () {
5555
var instance = TestUtils.renderIntoDocument(<Notifications />);
5656
expect(instance.state.loading).toBeTruthy();
5757

58-
var response = [[{
58+
var response = [{
5959
'repository': {
6060
'full_name': 'ekonstantinidis/gitify',
6161
'owner': {
@@ -65,7 +65,7 @@ describe('Test for Notifications Component', function () {
6565
'subject': {
6666
'type': 'Issue'
6767
}
68-
}]];
68+
}];
6969

7070
NotificationsStore.trigger(response);
7171
expect(instance.state.notifications.length).toBe(1);
@@ -104,7 +104,7 @@ describe('Test for Notifications Component', function () {
104104

105105
var instance = TestUtils.renderIntoDocument(<Notifications />);
106106

107-
var response = [[{
107+
var response = [{
108108
'repository': {
109109
'full_name': 'ekonstantinidis/gitify',
110110
'owner': {
@@ -114,7 +114,7 @@ describe('Test for Notifications Component', function () {
114114
'subject': {
115115
'type': 'Issue'
116116
}
117-
}]];
117+
}];
118118

119119
NotificationsStore.trigger(response);
120120

src/js/__tests__/components/repository.js

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('Test for Repository Component', function () {
4545
it('Should render the Repository component', function () {
4646

4747
var repoDetails = [{
48+
'id': '123',
4849
'repository': {
4950
'full_name': 'ekonstantinidis/gitify',
5051
'owner': {
@@ -81,6 +82,7 @@ describe('Test for Repository Component', function () {
8182
it('Should mark a repo as read - successfully', function () {
8283

8384
var repoDetails = [{
85+
'id': '123',
8486
'repository': {
8587
'name': 'gitify',
8688
'full_name': 'ekonstantinidis/gitify',
@@ -118,6 +120,7 @@ describe('Test for Repository Component', function () {
118120
it('Should mark a repo as read - fail', function () {
119121

120122
var repoDetails = [{
123+
'id': '123',
121124
'repository': {
122125
'name': 'gitify',
123126
'full_name': 'ekonstantinidis/gitify',

src/js/__tests__/stores/notifications.js

+53-4
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ describe('Tests for NotificationsStore', function () {
107107

108108
jest.runAllTimers();
109109

110-
var repository = NotificationsStore._notifications[0][0].repository;
111-
var subject = NotificationsStore._notifications[0][0].subject;
110+
var repository = NotificationsStore._notifications[0].repository;
111+
var subjectTitle = NotificationsStore._notifications[0].subject.title;
112112
expect(repository.full_name).toBe('octocat/Hello-World');
113-
expect(subject.title).toBe('Greetings');
113+
expect(subjectTitle).toBe('Greetings');
114114
expect(NotificationsStore.trigger).toHaveBeenCalled();
115115

116116
});
@@ -133,7 +133,7 @@ describe('Tests for NotificationsStore', function () {
133133

134134
});
135135

136-
it('should FAIL to create a booking via the API', function () {
136+
it('should FAIL to get the notifications from the GitHub API', function () {
137137

138138
spyOn(NotificationsStore, 'trigger');
139139
spyOn(NotificationsStore, 'onGetNotificationsFailed');
@@ -149,4 +149,53 @@ describe('Tests for NotificationsStore', function () {
149149

150150
});
151151

152+
it('should mark a notification as read - remove single notification from store', function () {
153+
154+
spyOn(NotificationsStore, 'trigger');
155+
156+
NotificationsStore._notifications = ['abc', 'def'];
157+
158+
expect(NotificationsStore._notifications.length).toBe(2);
159+
160+
Actions.removeNotification('abc');
161+
162+
jest.runAllTimers();
163+
164+
expect(NotificationsStore._notifications.length).toBe(1);
165+
expect(NotificationsStore.trigger).toHaveBeenCalled();
166+
167+
});
168+
169+
it('should mark a repo as read - remove notifications from store', function () {
170+
171+
spyOn(NotificationsStore, 'trigger');
172+
173+
NotificationsStore._notifications = [
174+
{
175+
'id': '1',
176+
'repository': {
177+
'full_name': 'ekonstantinidis/gitify'
178+
},
179+
'unread': true
180+
},
181+
{
182+
'id': '2',
183+
'repository': {
184+
'full_name': 'ekonstantinidis/gitify'
185+
},
186+
'reason': 'subscribed'
187+
}
188+
];
189+
190+
expect(NotificationsStore._notifications.length).toBe(2);
191+
192+
Actions.removeRepoNotifications('ekonstantinidis/gitify');
193+
194+
jest.runAllTimers();
195+
196+
expect(NotificationsStore._notifications.length).toBe(0);
197+
expect(NotificationsStore.trigger).toHaveBeenCalled();
198+
199+
});
200+
152201
});

src/js/actions/actions.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ var Actions = Reflux.createActions({
55
'login': {},
66
'logout': {},
77
'getNotifications': {asyncResult: true},
8+
'removeNotification': {},
9+
'removeRepoNotifications': {},
810
'isNewNotification': {},
911
'updateSearchTerm': {},
1012
'clearSearchTerm': {},

src/js/components/notification.js

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var React = require('react');
22
var remote = window.require('remote');
33
var shell = remote.require('shell');
44

5+
var Actions = require('../actions/actions');
56
var apiRequests = require('../utils/api-requests');
67

78
var NotificationItem = React.createClass({
@@ -28,7 +29,9 @@ var NotificationItem = React.createClass({
2829

2930
markAsRead: function () {
3031
var self = this;
32+
3133
if (this.state.read) { return; }
34+
3235
apiRequests
3336
.patchAuth('https://api.github.com/notifications/threads/' + this.props.notification.id)
3437
.end(function (err, response) {
@@ -37,9 +40,13 @@ var NotificationItem = React.createClass({
3740
self.setState({
3841
isRead: true
3942
});
43+
Actions.removeNotification(self.props.notification);
4044
} else {
4145
// Error - Show messages.
4246
// Show appropriate message
47+
self.setState({
48+
isRead: false
49+
});
4350
}
4451
});
4552
},

src/js/components/notifications.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ var Notifications = React.createClass({
1414
},
1515

1616
matchesSearchTerm: function (obj) {
17-
var repoFullName = obj[0].repository.full_name;
1817
var searchTerm = this.state.searchTerm.replace(/^\s+/, '').replace(/\s+$/, '');
1918
var searchTerms = searchTerm.split(/\s+/);
20-
21-
return _.all(searchTerms, this.areIn.bind(null, repoFullName));
19+
return _.all(searchTerms, this.areIn.bind(null, obj.repository.full_name));
2220
},
2321

2422
mixins: [
@@ -84,8 +82,13 @@ var Notifications = React.createClass({
8482
}
8583

8684
if (notifications.length) {
85+
86+
var groupedNotifications = _.groupBy(notifications, function (object) {
87+
return object.repository.full_name;
88+
});
89+
8790
notifications = (
88-
notifications.map(function (obj) {
91+
_.map(groupedNotifications, function (obj) {
8992
var repoFullName = obj[0].repository.full_name;
9093
return <Repository repo={obj} repoName={repoFullName} key={repoFullName} />;
9194
})

src/js/components/repository.js

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var remote = window.require('remote');
33
var shell = remote.require('shell');
44

55
var SingleNotification = require('../components/notification');
6+
var Actions = require('../actions/actions');
67
var apiRequests = require('../utils/api-requests');
78

89
var Repository = React.createClass({
@@ -27,6 +28,7 @@ var Repository = React.createClass({
2728
var self = this;
2829
var loginId = this.props.repo[0].repository.owner.login;
2930
var repoId = this.props.repo[0].repository.name;
31+
var fullName = this.props.repo[0].repository.full_name;
3032

3133
apiRequests
3234
.putAuth('https://api.github.com/repos/' + loginId + '/' + repoId + '/notifications', {})
@@ -37,6 +39,8 @@ var Repository = React.createClass({
3739
isRead: true,
3840
errors: false
3941
});
42+
43+
Actions.removeRepoNotifications(fullName);
4044
} else {
4145
self.setState({
4246
isRead: false,

src/js/stores/notifications.js

+23-10
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,35 @@ var NotificationsStore = Reflux.createStore({
4444
},
4545

4646
onGetNotificationsCompleted: function (notifications) {
47-
var groupedNotifications = _.groupBy(notifications, function (object) {
48-
return object.repository.full_name;
49-
});
50-
51-
var array = [];
52-
_.map(groupedNotifications, function (obj) {
53-
array.push(obj);
54-
});
55-
56-
this._notifications = array;
47+
this._notifications = notifications;
5748
this.trigger(this._notifications);
5849
},
5950

6051
onGetNotificationsFailed: function () {
6152
this._notifications = [];
6253
this.trigger(this._notifications);
54+
},
55+
56+
onRemoveNotification: function (notification) {
57+
var self = this;
58+
59+
this._notifications = _.without(this._notifications, notification);
60+
61+
setTimeout(function () {
62+
self.trigger(self._notifications);
63+
}, 800);
64+
},
65+
66+
onRemoveRepoNotifications: function (repoFullName) {
67+
var self = this;
68+
69+
this._notifications = _.reject(this._notifications, function (obj) {
70+
return obj.repository.full_name == repoFullName;
71+
});
72+
73+
setTimeout(function () {
74+
self.trigger(self._notifications);
75+
}, 800);
6376
}
6477

6578
});

src/less/style.less

+32-12
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,36 @@
114114
}
115115
}
116116

117+
.ReadMixin() {
118+
&.read {
119+
// Amazing CSS3 tutorial by Sara Soueidan
120+
// http://sarasoueidan.com/blog/creative-list-effects/
121+
animation: removed-item-animation .8s cubic-bezier(.65,-0.02,.72,.29);
122+
}
123+
124+
@keyframes removed-item-animation {
125+
0% {
126+
opacity: 1;
127+
transform: translateX(0);
128+
}
129+
130+
30% {
131+
opacity: 1;
132+
transform: translateX(50px);
133+
}
134+
 
135+
80% {
136+
opacity: 1;
137+
transform: translateX(-800px);
138+
}
139+
140+
100% {
141+
opacity: 0;
142+
transform: translateX(-800px);
143+
}
144+
}
145+
}
146+
117147
/* @end Mixins */
118148

119149

@@ -314,10 +344,7 @@ input {
314344
padding: 10px 20px;
315345
margin: 0;
316346
background-color: @LightGray;
317-
318-
&.read {
319-
.Opacity(0.4);
320-
}
347+
.ReadMixin();
321348

322349
.col-xs-2,
323350
.col-xs-10 {
@@ -391,10 +418,7 @@ input {
391418
margin: 0;
392419
padding: 3px 20px;
393420
border-bottom: 1px solid darken(@LightGray, 10%);
394-
395-
transition: opacity .25s ease-in-out;
396-
-moz-transition: opacity .25s ease-in-out;
397-
-webkit-transition: opacity .25s ease-in-out;
421+
.ReadMixin();
398422

399423
.col-xs-1,
400424
.col-xs-10 {
@@ -421,10 +445,6 @@ input {
421445
.CheckOcticon();
422446
}
423447

424-
&.read {
425-
.Opacity(0.4);
426-
}
427-
428448
&:hover {
429449
background-color: @LightGray;
430450
}

0 commit comments

Comments
 (0)