@@ -156,7 +158,7 @@ function createImagePlaceholder () {
position: 'absolute',
background: '#dee1e6',
top: 30,
- left: 30,
+ left: highlight === 'badges' ? 30 : 100,
width: 50,
height: 7,
borderRadius: 50
@@ -164,20 +166,44 @@ function createImagePlaceholder () {
@@ -231,7 +259,7 @@ function createImagePlaceholder () {
position: 'absolute',
background: '#dee1e6',
top: 30,
- left: 30,
+ left: highlight === 'badges' ? 30 : 100,
width: 30,
height: 7,
borderRadius: 50
@@ -240,7 +268,7 @@ function createImagePlaceholder () {
position: 'absolute',
background: '#dee1e6',
top: 30,
- left: 64,
+ left: highlight === 'badges' ? 64 : 134,
width: 7,
height: 7,
borderRadius: '100%'
@@ -248,11 +276,23 @@ function createImagePlaceholder () {
-
+ {highlight === 'badges' ? (
+
+ ) : (
+
+ )}
alt && ({
background: '#24292e',
'p': {
@@ -303,6 +343,9 @@ const Section = styled('div')({
'h2': {
color: '#fff'
},
+ '@media (max-width: 1000px)': {
+ flexDirection: 'column'
+ }
}));
const Item = styled('div')({
@@ -311,9 +354,10 @@ const Item = styled('div')({
padding: '24px 72px',
'h2': {
marginTop: 0,
- fontSize: 36,
+ marginLeft: 15,
+ fontSize: 42,
textAlign: 'left',
- fontWeight: 700
+ fontWeight: 600
},
'p': {
fontSize: 18
@@ -339,6 +383,7 @@ const ImagePlaceholder = styled('div')({
position: 'relative',
display: 'block',
height: 400,
+ width: 600,
background: '#fff',
borderRadius: 8,
boxShadow: '0 2px 8px rgba(179, 179, 179, 0.25)'
@@ -348,7 +393,7 @@ const ImagePlaceholder = styled('div')({
const Header = styled('h1')({
color: '#fff',
padding: '0 20px',
- margin: '0 auto 20px',
+ margin: '0 auto 48px',
letterSpacing: '-1.0px'
});
@@ -364,7 +409,7 @@ const SubHeader = styled(Header)({
const LandingHeader = styled('div')({
position: 'relative',
width: '100%',
- margin: '54px 20px',
+ margin: '54px 20px 78px',
maxWidth: 1000,
display: 'flex',
justifyContent: 'space-between',
@@ -421,7 +466,7 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
- Control your notifications
+ Control your GitHub notifications
Prioritize the tasks that keep you and your team most productive
let's get started
@@ -465,50 +510,54 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
-
+
+
-
- {createImagePlaceholder()}
+ {createImagePlaceholder('badges')}
- -
-
Surface the most important tasks to tackle as they happen
+ -
+
Surface the things that matter the most.
- The issues and pull requests that require your attention the most are called out for you.
+ The most important issues and pull requests that require your presence are called out and brought to your attention.
- We listen for updates with your notifications and let you know as soon as things change.
+ We listen for updates with your notifications and let you know why and when things change.
- Super charge your day by focusing on getting things done, rather than sifting through notifications.
+ Super charge your day by focusing on getting things done, rather than sifting through notifications or emails.
+
-
+
- -
-
Surface the most important tasks to tackle as they happen
+ -
+
Your privacy matters, so
we keep things offline.
-
- The issues and pull requests that require your attention the most are called out for you.
+
+ a
-
- We listen for updates with your notifications and let you know as soon as things change.
+
+ a
-
- Super charge your day by focusing on getting things done, rather than sifting through notifications.
+
+ a
-
- {createImagePlaceholder()}
+ {createImagePlaceholder('reason')}
diff --git a/src/pages/Notifications/Scene.js b/src/pages/Notifications/Scene.js
index 3576beb..27811ed 100644
--- a/src/pages/Notifications/Scene.js
+++ b/src/pages/Notifications/Scene.js
@@ -1,4 +1,6 @@
import React from 'react';
+import moment from 'moment';
+// import {VictoryPie, VictoryChart} from "victory";
import {Link} from "@reach/router";
import styled from 'react-emotion';
import Icon from '../../components/Icon';
@@ -6,49 +8,73 @@ import Logo from '../../components/Logo';
import LoadingIcon from '../../components/LoadingIcon';
import {routes} from '../../constants';
import {Filters} from '../../constants/filters';
-import {withOnEnter} from '../../enhance';
+import {withOnEnter, withTooltip} from '../../enhance';
import {Status} from '../../constants/status';
+import {Reasons, Badges} from '../../constants/reasons';
import '../../styles/gradient.css';
-/**
- * Given a notification, give it a score based on its importance.
- *
- * There are some interesting workarounds that go into this algorithm to account
- * for GitHub's broken notifications API -- but we will get to that later. First,
- * let's start off with the basics of scoring.
- *
- * There are a few "reasons" that we can be getting a notification, each having
- * an initial weight of importance:
- *
- * - MENTION -> 8
- * - ASSIGN -> 14
- * - REVIEW_REQUESTED -> 20
- * - SUBSCRIBED -> 3
- * - AUTHOR -> 8
- * - OTHER -> 2
- *
- * There are some rules that go to giving out these scores, primarily being the
- * first time we see one of these unique reasons, we award the notification with
- * the respective score, but a reason that transitions into itself will be awarded
- * a degraded score of min(ceil(n/3), 2). For example:
- *
- * - null, MENTION, MENTION -> 0, 8, 3
- * - null, ASSIGN, ASSIGN, REVIEW_REQUESTED, -> 0, 14, 5, 20
- * - null, SUBSCRIBED, SUBSCRIBED, SUBSCRIBED -> 0, 3, 2, 2
- *
- * @param {Object} notification Some notification to score.
- * @return {number} The score.
- */
-function scoreOf (notification) {
- return notification.reasons.length
+/* eslint-disable jsx-a11y/anchor-is-valid */
+/* eslint-disable no-script-url */
+
+function getMessageFromReasons (reasons) {
+ switch (reasons[reasons.length - 1].reason) {
+ case Reasons.ASSIGN:
+ return 'You were just assigned a task';
+ case Reasons.AUTHOR:
+ return 'There was activity on a thread you created';
+ case Reasons.COMMENT:
+ return 'Somebody just left a comment';
+ case Reasons.MENTION:
+ return 'You were just @mentioned';
+ case Reasons.REVIEW_REQUESTED:
+ return 'Your review was just requested';
+ case Reasons.SUBSCRIBED:
+ return 'There was an update and you\'re subscribed';
+ case Reasons.OTHER:
+ default:
+ return 'Something was updated';
+ }
}
-const decorateWithScore = notification => ({
- ...notification,
- score: scoreOf(notification)
+function getRelativeTime (time) {
+ const currentTime = moment();
+ const targetTime = moment(time);
+ const diffMinutes = currentTime.diff(targetTime, 'minutes');
+ if (diffMinutes < 1)
+ return 'Just now';
+ if (diffMinutes < 5)
+ return 'Few minutes ago';
+ if (diffMinutes < 60)
+ return diffMinutes + ' minutes ago';
+ if (diffMinutes < 60 * 24)
+ return Math.floor(diffMinutes / 60) + ' hours ago';
+
+ const diffDays = currentTime.diff(targetTime, 'days');
+ if (diffDays === 1)
+ return 'Yesterday';
+ if (diffDays <= 7)
+ return 'Last ' + targetTime.format('dddd');
+ // @TODO implement longer diffs
+ return 'Over a week ago';
+}
+
+const UnofficialReleaseTag = styled('span')({
+ color: 'white',
+ position: 'absolute',
+ left: '21px',
+ bottom: '0px',
+ fontSize: '9px',
+ background: '#f42839',
+ fontWeight: '800',
+ padding: '2px 4px',
+ borderRadius: '4px',
+ textTransform: 'uppercase',
});
const FixedContainer = styled('div')({
+ height: '80%',
+ maxWidth: 270,
+ display: 'block',
position: 'fixed'
});
@@ -64,7 +90,7 @@ const NotificationsContainer = styled('div')({
margin: '0 auto',
padding: 0,
width: '100%',
- height: '100vh',
+ height: '100%',
display: 'flex',
flexDirection: 'row',
overflowX: 'hidden',
@@ -72,19 +98,28 @@ const NotificationsContainer = styled('div')({
});
const NavigationContainer = styled('div')({
- position: 'relative',
+ position: 'fixed',
+ top: 0,
boxSizing: 'border-box',
margin: '0 auto',
- padding: '24px 48px',
width: '100%',
- background: 'none',
- height: 'initial'
+ height: 60,
+ color: 'hsla(0,0%,100%,.75)',
+ paddingBottom: '12px',
+ paddingTop: '12px',
+ zIndex: '100',
});
const GeneralOptionsContainer = styled(NavigationContainer)({
+ position: 'relative',
+ zIndex: '1',
+ height: 'initial',
+ minHeight: 60,
+ width: '95%',
+ margin: 0,
background: '#fff',
padding: '8px 16px',
- paddingLeft: 260,
+ paddingTop: 18,
flex: '0 0 50px',
'button': {
display: 'inline-flex',
@@ -93,30 +128,51 @@ const GeneralOptionsContainer = styled(NavigationContainer)({
});
const Sidebar = styled('div')({
- flex: '0 0 200px',
- padding: '0 20px 20px',
- marginTop: 15
+ flex: '0 0 300px',
+ padding: '32px 20px',
+ paddingRight: 0,
+ display: 'flex',
+ justifyContent: 'center',
});
const SidebarLink = styled('a')({}, ({active, color}) => ({
textAlign: 'left',
- margin: '0 auto',
+ userSelect: 'none',
+ margin: '0 auto 5px',
+ position: 'relative',
cursor: 'pointer',
- borderRadius: '8px',
+ borderRadius: 4,
alignItems: 'center',
padding: '0 14px',
height: 40,
+ width: 200,
fontSize: '12px',
fontWeight: 600,
letterSpacing: 0.5,
- textTransform: 'uppercase',
+ textTransform: 'capitalize',
textDecoration: 'none',
transition: 'background 0.12s ease-in-out',
display: 'flex',
background: active ? color : 'none',
color: active ? '#fff' : '#202124',
- ':hover': {
- background: active ? color: 'rgba(200, 200, 200, .25)'
+ ':before': {
+ content: '""',
+ transition: 'all 150ms ease',
+ background: 'rgba(190, 197, 208, 0.25)',
+ borderRadius: 4,
+ display: 'block',
+ top: 0,
+ bottom: 0,
+ right: 0,
+ left: 0,
+ position: 'absolute',
+ transform: 'scale(0)'
+ },
+ ':hover:before': {
+ transform: active ? 'scale(0)' : 'scale(1)',
+ },
+ ':active:before': {
+ background: 'rgba(190, 197, 208, 0.5)'
},
'div': {
marginRight: 5
@@ -127,8 +183,67 @@ const Notifications = styled('div')({
flex: 1,
});
+const NavTab = styled('a')({
+ position: 'relative',
+ textTransform: 'capitalize',
+ userSelect: 'none',
+ borderRadius: 4,
+ textDecoration: 'none',
+ fontWeight: '500',
+ fontSize: '14px',
+ textAlign: 'left',
+ opacity: 0.6,
+ padding: '20px 32px',
+ paddingLeft: '16px',
+ width: '150px',
+ display: 'inline-block',
+ margin: 0,
+ transition: 'all 150ms ease',
+ ':hover': {
+ background: 'rgba(190, 197, 208, 0.25)',
+ },
+}, ({ number }) => ({
+ ':after': number > 0 && {
+ content: `"${number}"`,
+ color: '#ffffff',
+ background: '#a8a8a9',
+ fontSize: '10px',
+ verticalAlign: 'text-top',
+ padding: '1px 8px',
+ borderRadius: '4px',
+ marginLeft: '6px',
+ display: 'inline-block',
+ }
+}), ({ active, color, number }) => active && ({
+ color,
+ opacity: 1,
+ ':before': {
+ content: '""',
+ position: 'absolute',
+ background: color,
+ height: '3px',
+ width: '90%',
+ bottom: '0',
+ left: '5%',
+ borderTopLeftRadius: '4px',
+ borderTopRightRadius: '4px',
+ },
+ ':after': number > 0 && {
+ content: `"${number}"`,
+ color: '#ffffff',
+ background: color,
+ fontSize: '10px',
+ verticalAlign: 'text-top',
+ padding: '1px 8px',
+ borderRadius: '4px',
+ marginLeft: '6px',
+ display: 'inline-block',
+ }
+}));
+
const Tab = styled('button')({
position: 'relative',
+ userSelect: 'none',
cursor: 'pointer',
border: 0,
outline: 'none',
@@ -159,8 +274,14 @@ const Tab = styled('button')({
}
}, ({disabled}) => disabled && ({
background: 'none !important',
- opacity: 0.5,
+ opacity: 0.35,
cursor: 'default',
+ ':hover:before': {
+ transform: 'scale(0) !important',
+ },
+ ':active:before': {
+ background: 'none !important'
+ }
}));
const SearchField = styled('div')({
@@ -169,17 +290,29 @@ const SearchField = styled('div')({
width: '50%',
boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
margin: '0 auto',
- background: '#fff',
+ background: 'hsla(0,0%,100%,.125)',
borderRadius: '4px',
alignItems: 'center',
padding: 0,
- height: '48px',
- fontSize: '14px',
+ height: '36px',
+ fontSize: '13px',
textDecoration: 'none',
transition: 'all 0.06s ease-in-out',
display: 'inline-flex',
':focus-within': {
- boxShadow: '0 3px 9px #4a4a4a5c',
+ background: '#fff'
+ }
+});
+
+const Message = styled('div')({
+ display: 'block',
+ textAlign: 'center',
+ marginTop: 24 * 5,
+ 'p': {
+ paddingTop: 24,
+ userSelect: 'none',
+ display: 'block',
+ margin: 0
}
});
@@ -196,23 +329,26 @@ const SearchInput = styled('input')({
margin: '0 auto',
background: 'none',
padding: 0,
- height: '48px',
- fontSize: '14px',
+ height: '36px',
+ color: '#fff',
+ fontSize: '13px',
textDecoration: 'none',
- transition: 'all 0.12s ease-in-out',
display: 'inline-flex',
border: '0',
- outline: 'none'
+ outline: 'none',
+ ':focus': {
+ color: '#202124'
+ }
});
const EnhancedSearchInput = withOnEnter(SearchInput);
const NotificationRow = styled('tr')({
position: 'relative',
- cursor: 'pointer',
- borderBottom: '1px solid #f2f2f2',
- display: 'block',
+ display: 'flex',
+ alignItems: 'center',
textAlign: 'left',
width: '100%',
+ borderRadius: 4,
margin: '0 auto',
background: '#fff',
padding: '8px 16px',
@@ -230,12 +366,25 @@ const NotificationTab = styled(Tab)({
margin: 0,
});
+const Timestamp = styled('span')({
+ position: 'relative',
+ margin: 0,
+ marginLeft: 10,
+ fontSize: 11,
+ opacity: 0.5,
+});
+
+const ReasonMessage = styled(Timestamp)({
+
+});
+
const NotificationTitle = styled('span')({
position: 'relative',
+ display: 'block'
}, ({img}) => img && ({
paddingLeft: 20,
'::before': {
- content: "''",
+ content: '""',
position: 'absolute',
display: 'block',
background: `url(${img}) center center no-repeat`,
@@ -254,34 +403,56 @@ const Repository = styled('span')({
const PRIssue = styled(Repository)({
fontWeight: 400,
-});
+}, ({after}) => ({
+ ':after': {
+ content: `"#${after}"`,
+ fontSize: 13,
+ opacity: .3,
+ marginLeft: 5
+ }
+}));
const Table = styled('table')({
- display: 'block',
+ width: '96%',
+ minWidth: 970,
'td': {
display: 'inline-block'
}
});
-const TableHeader = styled('h2')({
- fontWeight: 500,
- fontSize: 34,
- color: 'rgba(0, 0, 0, 0.86)',
- letterSpacing: '-0.05px',
- margin: '20px 15px 0',
-});
-
const TableItem = styled('td')({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
-}, ({width}) => ({
- width
+}, ({width, flex}) => ({
+ width,
+ flex
}));
+const SmallLink = styled('a')({
+ display: 'block',
+ marginRight: 10,
+ cursor: 'pointer',
+ fontSize: 10,
+ lineHeight: '20px',
+ fontWeight: 400,
+ textDecoration: 'underline',
+ transition: 'all 0.12s ease-in-out',
+ ':hover': {
+ opacity: 0.75
+ }
+});
+
+const EnhancedTab = withTooltip(Tab);
+const EnhancedNavTab = withTooltip(NavTab);
+const EnhancedNotificationTab = withTooltip(NotificationTab);
+const EnhancedSidebarLink = withTooltip(SidebarLink);
+const EnhancedIconHot = withTooltip(Icon.Hot);
+const EnhancedIconTimer = withTooltip(Icon.Timer);
+const EnhancedIconConvo = withTooltip(Icon.Convo);
+
function getPRIssueIcon (type, reasons) {
const grow = 1.0;
-
switch (type) {
case 'PullRequest':
return (
@@ -297,13 +468,28 @@ function getPRIssueIcon (type, reasons) {
}
export default function Scene ({
+ queuedCount,
+ stagedCount,
+ closedCount,
+ first,
+ last,
+ lastPage,
+ page,
notifications,
+ query,
+ activeStatus,
+ allNotificationsCount,
+ stagedTodayCount,
+ onChangePage,
+ onSetActiveStatus,
+ onClearQuery,
onLogout,
onSearch,
onMarkAsRead,
+ onMarkAllAsStaged,
onFetchNotifications,
- onRefreshNotifications,
onStageThread,
+ onRestoreThread,
isSearching,
isFetchingNotifications,
onClearCache,
@@ -311,223 +497,471 @@ export default function Scene ({
activeFilter,
onSetActiveFilter,
}) {
- const isLoading = isSearching || isFetchingNotifications;
+ const loading = isSearching || isFetchingNotifications;
+ const isFirstPage = page === 1;
+ const isLastPage = page === lastPage;
- let filterMethod = () => true;
- switch (activeFilter) {
- case Filters.REVIEW_REQUESTED:
- filterMethod = n => n.reasons[0].reason === 'review_requested';
- break;
- case Filters.PARTICIPATING:
- filterMethod = n => (
- n.reasons.some(({ reason }) => (
- reason === 'review_requested' ||
- reason === 'assign' ||
- reason === 'mention' ||
- reason === 'author'
- ))
- );
- break;
- case Filters.SUBSCRIBED:
- filterMethod = n => n.reasons[0].reason === 'subscribed';
- break;
- case Filters.ALL:
- default:
- filterMethod = () => true;
- }
-
- notifications = notifications
- .filter(filterMethod)
- .sort((a, b) => a.repository.localeCompare(b.repository))
- .map(decorateWithScore)
- .sort((a, b) => b.score - a.score);
-
- console.warn(notifications)
-
- const notificationsQueued = notifications.filter(n => n.status === Status.QUEUED);
- const notificationsStaged = notifications.filter(n => n.status === Status.STAGED);
+ console.log('notifications', notifications)
return (
-
-
-
-
+
+
+
+
{
+ onSetActiveStatus(Status.QUEUED);
+ onSetActiveFilter(Filters.PARTICIPATING);
+ }}
+ />
+ beta
- {isSearching && }
+ {isSearching && }
- go home
- sign out
+
+ home
+
+
-
-
- onFetchNotifications()) : undefined}
- />
-
-
- onClearCache()) : undefined}
- />
-
-
-
- {notifications.length}
-
-
-
-
-
-
- onSetActiveFilter(Filters.ALL)}
- >
- {activeFilter === Filters.ALL ? (
-
- ) : (
-
- )}
- all notifications
-
- onSetActiveFilter(Filters.PARTICIPATING)}
- >
- {activeFilter === Filters.PARTICIPATING ? (
-
- ) : (
-
- )}
- participating
-
-
-
-
- {isFetchingNotifications ? (
-
-
-
- ) : notifications.length <= 0 ? (
-
-
no notifications
+
+
+
+
+
+
+
+ {moment().format('h:mma')}
+
+ {moment().format('dddd, MMMM Do')}
+ You've triaged {stagedTodayCount} notifications today
+
+ onSetActiveFilter(Filters.PARTICIPATING)}>
+ {activeFilter === Filters.PARTICIPATING ? (
+
+ ) : (
+
+ )}
+ all your updates
+
+ onSetActiveFilter(Filters.REVIEW_REQUESTED)}>
+ {activeFilter === Filters.REVIEW_REQUESTED ? (
+
+ ) : (
+
+ )}
+ review requested
+
+ onSetActiveFilter(Filters.ASSIGNED)}>
+ {activeFilter === Filters.ASSIGNED ? (
+
+ ) : (
+
+ )}
+ assigned
+
+ onSetActiveFilter(Filters.COMMENT)}>
+ {activeFilter === Filters.COMMENT ? (
+
+ ) : (
+
+ )}
+ commented
+
+
+
+ statistics coming soonโข
+
+
+ Report bugs
+ Submit feedback
+ See source code
+
+
+
+
+
+
+
+ onFetchNotifications()) : undefined}
+ />
+
+
+ {
+ const response = window.confirm('Are you sure you want to mark all your notifications as read?');
+ if (response) {
+ onMarkAllAsStaged();
+ }
+ }) : undefined}
+ />
+
+
+ {
+ const response = window.confirm('Are you sure you want to clear the cache?');
+ if (response) {
+ onClearCache();
+ }
+ }) : undefined}
+ />
+
+ {query ? (
+
+
+
+ onClearQuery()) : undefined}
+ />
+
+
+ ) : null}
+
+
+
+ onChangePage(page - 1)) : undefined}
+ />
+
+
+ onChangePage(page + 1)) : undefined}
+ />
+
- ) : (
-
-
- Queued
- {notificationsQueued.map(n => (
-
-
-
- {getPRIssueIcon(n.type, n.reasons)}
-
-
- {
- window.open(n.url);
- onStageThread(n.id)
- }}>
-
- {n.name}
-
-
- {/*
- {n.reasons.map(r => r.reason).join(', ')}
- */}
-
-
-
-
-
-
-
- {n.repository}
-
-
-
- onStageThread(n.id)) : undefined}
- />
-
-
- onMarkAsRead(n.id)) : undefined}
- />
-
-
- {/* Last read at {n.last_read_at ? moment(n.last_read_at).format('dddd h:mma') : 'never'}
- Last updated at {moment(n.last_updated).format('dddd h:mma')}
*/}
-
- ))}
- Staged
- {notificationsStaged.map(n => (
-
-
-
- {getPRIssueIcon(n.type, n.reasons)}
-
-
- window.open(n.url)}>
-
- {n.name}
-
-
-
- {n.reasons.map(r => r.reason).join(', ')}
-
-
- {n.repository}
-
-
-
- onMarkAsRead(n.id)) : undefined}
- />
-
-
- {/* Last read at {n.last_read_at ? moment(n.last_read_at).format('dddd h:mma') : 'never'}
- Last updated at {moment(n.last_updated).format('dddd h:mma')}
*/}
-
- ))}
-
-
- )}
-
-
+
+
+ onSetActiveStatus(Status.QUEUED)}
+ href="javascript:void(0);">
+ Unread
+
+ onSetActiveStatus(Status.STAGED)}
+ href="javascript:void(0);">
+ Read
+
+ onSetActiveStatus(Status.CLOSED)}
+ href="javascript:void(0);">
+ Resolved
+
+
+
+
+ {isFetchingNotifications ? (
+
+
+
+ ) : fetchingNotificationsError ? (
+
+ OOPSIE WOOPSIE!! Uwu An error occurred when fetching notifications.
+ onFetchNotifications()} href="#">Try again
+
+ ) : notifications.length <= 0 ? (
+
+
+ No {activeStatus.toLowerCase()} notifications
+
+ ๐ You're all set here for the moment
+
+ ) : (
+
+
+ {notifications.map(n => (
+
+
+
+ {getPRIssueIcon(n.type, n.reasons)}
+
+
+ {
+ window.open(n.url);
+ onStageThread(n.id, n.repository)
+ }}>
+
+ {n.name}
+
+
+ {getRelativeTime(n.updated_at)}
+ {n.isAuthor && (
+
+ )}
+
+
+
+ {getMessageFromReasons(n.reasons)}
+
+
+
+
+ {n.badges.map(badge => {
+ switch (badge) {
+ case Badges.HOT:
+ // lots of `reasons` within short time frame
+ return (
+
+ );
+ case Badges.OLD:
+ // old
+ return (
+
+ );
+ case Badges.COMMENTS:
+ // lots of `reasons`
+ return (
+
+ );
+ default:
+ return null;
+ }
+ })}
+
+
+
+ window.open(n.repositoryUrl)}
+ style={{cursor: 'pointer', userSelect: 'none'}}>
+ {n.repository}
+
+
+
+ +{n.score}
+
+ {activeStatus === Status.QUEUED ? (
+
+ onStageThread(n.id, n.repository)) : undefined}
+ />
+
+ ) : (
+
+ onRestoreThread(n.id)) : undefined}
+ />
+
+ )}
+ {activeStatus === Status.CLOSED ? (
+
+
+
+ ) : (
+
+ onMarkAsRead(n.id)) : undefined}
+ />
+
+ )}
+
+
+ ))}
+
+
+ )}
+
+
+
+
);
}
diff --git a/src/pages/Notifications/SceneAlt.js b/src/pages/Notifications/SceneAlt.js
deleted file mode 100644
index 6d2eeaa..0000000
--- a/src/pages/Notifications/SceneAlt.js
+++ /dev/null
@@ -1,898 +0,0 @@
-import React from 'react';
-import moment from 'moment';
-import {VictoryPie, VictoryChart} from "victory";
-import {Link} from "@reach/router";
-import styled from 'react-emotion';
-import Icon from '../../components/Icon';
-import Logo from '../../components/Logo';
-import LoadingIcon from '../../components/LoadingIcon';
-import {routes} from '../../constants';
-import {Filters} from '../../constants/filters';
-import {withOnEnter, withTooltip} from '../../enhance';
-import {Status} from '../../constants/status';
-import {Badges} from '../../constants/reasons';
-import '../../styles/gradient.css';
-
-/* eslint-disable jsx-a11y/anchor-is-valid */
-
-function getRelativeTime (time) {
- const currentTime = moment();
- const targetTime = moment(time);
- const diffMinutes = currentTime.diff(targetTime, 'minutes');
- if (diffMinutes < 1)
- return 'Just now';
- if (diffMinutes < 5)
- return 'Few minutes ago';
- if (diffMinutes < 60)
- return diffMinutes + ' minutes ago';
- if (diffMinutes < 60 * 24)
- return Math.floor(diffMinutes / 60) + ' hours ago';
-
- const diffDays = currentTime.diff(targetTime, 'days');
- if (diffDays === 1)
- return 'Yesterday';
- if (diffDays <= 7)
- return 'Last ' + targetTime.format('dddd');
- // @TODO implement longer diffs
- return 'Long time ago';
-}
-
-const UnofficialReleaseTag = styled('span')({
- color: 'white',
- position: 'absolute',
- left: '21px',
- bottom: '0px',
- fontSize: '9px',
- background: '#f42839',
- fontWeight: '800',
- padding: '2px 4px',
- borderRadius: '4px',
- textTransform: 'uppercase',
-});
-
-const FixedContainer = styled('div')({
- height: '80%',
- maxWidth: 270,
- display: 'block',
- position: 'fixed'
-});
-
-const InlineBlockContainer = styled('div')({
- 'div': {
- display: 'inline-block'
- }
-});
-
-const NotificationsContainer = styled('div')({
- position: 'relative',
- background: '#fff',
- margin: '0 auto',
- padding: 0,
- width: '100%',
- height: '100%',
- display: 'flex',
- flexDirection: 'row',
- overflowX: 'hidden',
- boxSizing: 'border-box'
-});
-
-const NavigationContainer = styled('div')({
- position: 'fixed',
- top: 0,
- boxSizing: 'border-box',
- margin: '0 auto',
- width: '100%',
- height: 60,
- color: 'hsla(0,0%,100%,.75)',
- paddingBottom: '12px',
- paddingTop: '12px',
- zIndex: '100',
-});
-
-const GeneralOptionsContainer = styled(NavigationContainer)({
- position: 'relative',
- zIndex: '1',
- height: 'initial',
- minHeight: 60,
- width: '95%',
- margin: 0,
- background: '#fff',
- padding: '8px 16px',
- paddingTop: 18,
- flex: '0 0 50px',
- 'button': {
- display: 'inline-flex',
- margin: 0
- }
-});
-
-const Sidebar = styled('div')({
- flex: '0 0 300px',
- padding: '32px 20px',
- paddingRight: 0,
- display: 'flex',
- justifyContent: 'center',
-});
-
-const SidebarLink = styled('a')({}, ({active, color}) => ({
- textAlign: 'left',
- userSelect: 'none',
- margin: '0 auto 5px',
- position: 'relative',
- cursor: 'pointer',
- borderRadius: 4,
- alignItems: 'center',
- padding: '0 14px',
- height: 40,
- width: 200,
- fontSize: '12px',
- fontWeight: 600,
- letterSpacing: 0.5,
- textTransform: 'capitalize',
- textDecoration: 'none',
- transition: 'background 0.12s ease-in-out',
- display: 'flex',
- background: active ? color : 'none',
- color: active ? '#fff' : '#202124',
- ':before': {
- content: '""',
- transition: 'all 150ms ease',
- background: 'rgba(190, 197, 208, 0.25)',
- borderRadius: 4,
- display: 'block',
- top: 0,
- bottom: 0,
- right: 0,
- left: 0,
- position: 'absolute',
- transform: 'scale(0)'
- },
- ':hover:before': {
- transform: active ? 'scale(0)' : 'scale(1)',
- },
- ':active:before': {
- background: 'rgba(190, 197, 208, 0.5)'
- },
- 'div': {
- marginRight: 5
- }
-}));
-
-const Notifications = styled('div')({
- flex: 1,
-});
-
-const NavTab = styled('a')({
- position: 'relative',
- textTransform: 'capitalize',
- userSelect: 'none',
- borderRadius: 4,
- textDecoration: 'none',
- fontWeight: '500',
- fontSize: '14px',
- textAlign: 'left',
- opacity: 0.6,
- padding: '20px 32px',
- paddingLeft: '16px',
- width: '150px',
- display: 'inline-block',
- margin: 0,
- transition: 'all 150ms ease',
- ':hover': {
- background: 'rgba(190, 197, 208, 0.25)',
- },
-}, ({ number }) => ({
- ':after': number > 0 && {
- content: `"${number}"`,
- color: '#ffffff',
- background: '#a8a8a9',
- fontSize: '10px',
- verticalAlign: 'text-top',
- padding: '1px 8px',
- borderRadius: '4px',
- marginLeft: '6px',
- display: 'inline-block',
- }
-}), ({ active, color, number }) => active && ({
- color,
- opacity: 1,
- ':before': {
- content: '""',
- position: 'absolute',
- background: color,
- height: '3px',
- width: '90%',
- bottom: '0',
- left: '5%',
- borderTopLeftRadius: '4px',
- borderTopRightRadius: '4px',
- },
- ':after': number > 0 && {
- content: `"${number}"`,
- color: '#ffffff',
- background: color,
- fontSize: '10px',
- verticalAlign: 'text-top',
- padding: '1px 8px',
- borderRadius: '4px',
- marginLeft: '6px',
- display: 'inline-block',
- }
-}));
-
-const Tab = styled('button')({
- position: 'relative',
- userSelect: 'none',
- cursor: 'pointer',
- border: 0,
- outline: 'none',
- background: 'none',
- height: 40,
- width: 40,
- borderRadius: '100%',
- margin: '0 auto',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- ':before': {
- content: "''",
- transition: 'all 150ms ease',
- background: 'rgba(190, 197, 208, 0.25)',
- borderRadius: '100%',
- display: 'block',
- height: 40,
- width: 40,
- position: 'absolute',
- transform: 'scale(0)'
- },
- ':hover:before': {
- transform: 'scale(1)',
- },
- ':active:before': {
- background: 'rgba(190, 197, 208, 0.5)'
- }
-}, ({disabled}) => disabled && ({
- background: 'none !important',
- opacity: 0.35,
- cursor: 'default',
- ':hover:before': {
- transform: 'scale(0) !important',
- },
- ':active:before': {
- background: 'none !important'
- }
-}));
-
-const SearchField = styled('div')({
- float: 'left',
- textAlign: 'left',
- width: '50%',
- boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
- margin: '0 auto',
- background: 'hsla(0,0%,100%,.125)',
- borderRadius: '4px',
- alignItems: 'center',
- padding: 0,
- height: '36px',
- fontSize: '13px',
- textDecoration: 'none',
- transition: 'all 0.06s ease-in-out',
- display: 'inline-flex',
- ':focus-within': {
- background: '#fff'
- }
-});
-
-const Message = styled('div')({
- display: 'block',
- textAlign: 'center',
- marginTop: 24 * 5,
- 'p': {
- paddingTop: 24,
- userSelect: 'none',
- display: 'block',
- margin: 0
- }
-});
-
-const LoaderContainer = styled('div')({
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- height: '100%'
-});
-
-const SearchInput = styled('input')({
- flex: 1,
- textAlign: 'left',
- margin: '0 auto',
- background: 'none',
- padding: 0,
- height: '36px',
- color: '#fff',
- fontSize: '13px',
- textDecoration: 'none',
- display: 'inline-flex',
- border: '0',
- outline: 'none',
- ':focus': {
- color: '#202124'
- }
-});
-const EnhancedSearchInput = withOnEnter(SearchInput);
-
-const NotificationRow = styled('tr')({
- position: 'relative',
- display: 'flex',
- alignItems: 'center',
- textAlign: 'left',
- width: '100%',
- borderRadius: 4,
- margin: '0 auto',
- background: '#fff',
- padding: '8px 16px',
- transition: 'all 0.1s ease-in-out',
- boxSizing: 'border-box',
- ':hover': {
- background: '#f9f9f9',
- // boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
- zIndex: 10
- }
-});
-
-const NotificationTab = styled(Tab)({
- display: 'inline-flex',
- margin: 0,
-});
-
-const Timestamp = styled('span')({
- position: 'relative',
- margin: 0,
- marginLeft: 10,
- fontSize: 11,
- opacity: 0.5,
-});
-
-const NotificationTitle = styled('span')({
- position: 'relative',
- display: 'block'
-}, ({img}) => img && ({
- paddingLeft: 20,
- '::before': {
- content: '""',
- position: 'absolute',
- display: 'block',
- background: `url(${img}) center center no-repeat`,
- backgroundSize: 'cover',
- left: 0,
- height: 20,
- width: 20,
- }
-}));
-
-const Repository = styled('span')({
- fontWeight: 500,
- marginLeft: 10,
- fontSize: 14
-});
-
-const PRIssue = styled(Repository)({
- fontWeight: 400,
-}, ({after}) => ({
- ':after': {
- content: `"#${after}"`,
- fontSize: 13,
- opacity: .3,
- marginLeft: 5
- }
-}));
-
-const Table = styled('table')({
- width: '96%',
- minWidth: 970,
- 'td': {
- display: 'inline-block'
- }
-});
-
-const TableItem = styled('td')({
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
-}, ({width, flex}) => ({
- width,
- flex
-}));
-
-const SmallLink = styled('a')({
- display: 'block',
- marginRight: 10,
- cursor: 'pointer',
- fontSize: 10,
- lineHeight: '20px',
- fontWeight: 400,
- textDecoration: 'underline',
- transition: 'all 0.12s ease-in-out',
- ':hover': {
- opacity: 0.75
- }
-});
-
-const EnhancedTab = withTooltip(Tab);
-const EnhancedNavTab = withTooltip(NavTab);
-const EnhancedNotificationTab = withTooltip(NotificationTab);
-const EnhancedSidebarLink = withTooltip(SidebarLink);
-const EnhancedIconHot = withTooltip(Icon.Hot);
-const EnhancedIconTimer = withTooltip(Icon.Timer);
-const EnhancedIconConvo = withTooltip(Icon.Convo);
-
-function getPRIssueIcon (type, reasons) {
- const grow = 1.0;
- switch (type) {
- case 'PullRequest':
- return (
-
- );
- case 'Issue':
- return (
-
- );
- default:
- return null;
- }
-}
-
-export default function Scene ({
- queuedCount,
- stagedCount,
- closedCount,
- first,
- last,
- lastPage,
- page,
- notifications,
- query,
- activeStatus,
- allNotificationsCount,
- stagedTodayCount,
- onChangePage,
- onSetActiveStatus,
- onClearQuery,
- onLogout,
- onSearch,
- onMarkAsRead,
- onFetchNotifications,
- onRefreshNotifications,
- onStageThread,
- onRestoreThread,
- isSearching,
- isFetchingNotifications,
- onClearCache,
- fetchingNotificationsError,
- activeFilter,
- onSetActiveFilter,
-}) {
- const loading = isSearching || isFetchingNotifications;
- const isFirstPage = page === 1;
- const isLastPage = page === lastPage;
-
- console.warn('before render in scene', notifications)
-
- return (
-
-
-
-
{
- onSetActiveStatus(Status.QUEUED);
- onSetActiveFilter(Filters.PARTICIPATING);
- }}
- />
- beta
-
-
-
- {isSearching && }
-
-
- home
-
-
-
-
-
-
-
-
-
-
-
- {moment().format('h:mma')}
-
- {moment().format('dddd, MMMM Do')}
- You've triaged {stagedTodayCount} notifications today
-
- {/*
- We shouldn't show all the notificaitons. Pointless and creates more noise.
-
- onSetActiveFilter(Filters.ALL)}>
- {activeFilter === Filters.ALL ? (
-
- ) : (
-
- )}
- all notifications
-
- */}
- onSetActiveFilter(Filters.PARTICIPATING)}>
- {activeFilter === Filters.PARTICIPATING ? (
-
- ) : (
-
- )}
- your updates
-
- onSetActiveFilter(Filters.COMMENT)}>
- {activeFilter === Filters.COMMENT ? (
-
- ) : (
-
- )}
- participating
-
-
- bar chart, statistics, etc. will be here eventually
-
-
- Report bugs
- Submit feedback
- See source code
-
-
-
-
-
-
-
- onFetchNotifications()) : undefined}
- />
-
-
- {
- const response = window.confirm('Are you sure you want to clear the cache?');
- if (response) {
- onClearCache();
- }
- }) : undefined}
- />
-
- {query ? (
-
-
-
- onClearQuery()) : undefined}
- />
-
-
- ) : null}
-
-
-
- onChangePage(page - 1)) : undefined}
- />
-
-
- onChangePage(page + 1)) : undefined}
- />
-
-
-
-
- onSetActiveStatus(Status.QUEUED)}
- href="javascript:void(0);">
- Unread
-
- onSetActiveStatus(Status.STAGED)}
- href="javascript:void(0);">
- Read
-
- onSetActiveStatus(Status.CLOSED)}
- href="javascript:void(0);">
- Resolved
-
-
-
-
- {isFetchingNotifications ? (
-
-
-
- ) : notifications.length <= 0 ? (
-
-
- No {activeStatus.toLowerCase()} notifications
-
- ๐ You're all set here for the moment
-
- ) : (
-
-
- {notifications.map(n => (
-
-
-
- {getPRIssueIcon(n.type, n.reasons)}
-
-
- {
- window.open(n.url);
- onStageThread(n.id, n.repository)
- }}>
-
- {n.name}
-
-
- {getRelativeTime(n.updated_at)}
- {n.isAuthor && (
-
- )}
-
-
-
-
- {n.badges.map(badge => {
- switch (badge) {
- case Badges.HOT:
- // lots of `reasons` within short time frame
- return (
-
- );
- case Badges.OLD:
- // old
- return (
-
- );
- case Badges.COMMENTS:
- // lots of `reasons`
- return (
-
- );
- default:
- return null;
- }
- })}
-
-
-
- window.open(n.repositoryUrl)}
- style={{cursor: 'pointer', userSelect: 'none'}}>
- {n.repository}
-
-
-
- {n.score}
-
- {activeStatus === Status.QUEUED ? (
-
- onStageThread(n.id, n.repository)) : undefined}
- />
-
- ) : (
-
- onRestoreThread(n.id)) : undefined}
- />
-
- )}
- {activeStatus === Status.CLOSED ? (
-
- {}) : undefined}
- />
-
- ) : (
-
- onMarkAsRead(n.id)) : undefined}
- />
-
- )}
-
-
- ))}
-
-
- )}
-
-
-
-
-
- );
-}
diff --git a/src/pages/Notifications/index.js b/src/pages/Notifications/index.js
index fd0784d..3e60932 100644
--- a/src/pages/Notifications/index.js
+++ b/src/pages/Notifications/index.js
@@ -11,7 +11,7 @@ import { routes } from '../../constants';
import { Filters } from '../../constants/filters';
import { Status } from '../../constants/status';
import { Reasons, Badges } from '../../constants/reasons';
-import Scene from './SceneAlt';
+import Scene from './Scene';
const PER_PAGE = 15;
@@ -53,7 +53,6 @@ function scoreOf (notification) {
let prevReason = null;
for (let i = 0; i < reasons.length; i++) {
const reason = reasons[i].reason;
- console.log(reason)
if (prevReason && reason === prevReason) {
const degradedScore = Math.ceil(scoreOfReason[reason] / 3);
score += Math.max(degradedScore, 2);
@@ -122,8 +121,9 @@ class NotificationsPage extends React.Component {
}
componentDidMount () {
+ this.props.notificationsApi.fetchNotifications();
+
this.syncer = setInterval(() => {
- console.warn('sync');
this.props.notificationsApi.fetchNotificationsSync();
}, 8 * 1000);
}
@@ -172,14 +172,12 @@ class NotificationsPage extends React.Component {
}
enhancedOnStageThread = (thread_id, repository) => {
- console.warn('staging thread', thread_id, 'in repo', repository);
this.props.storageApi.incrStat('stagedCount');
this.props.storageApi.incrStat(repository + '-stagedCount');
this.props.notificationsApi.stageThread(thread_id);
}
restoreThread = thread_id => {
- console.warn('restoring thread');
this.props.notificationsApi.restoreThread(thread_id);
}
@@ -191,6 +189,7 @@ class NotificationsPage extends React.Component {
const {
fetchNotifications,
markAsRead,
+ markAllAsStaged,
clearCache,
notifications,
loading: isFetchingNotifications,
@@ -210,6 +209,16 @@ class NotificationsPage extends React.Component {
))
);
break;
+ case Filters.ASSIGNED:
+ filterMethod = n => (
+ n.reasons.some(({ reason }) => reason === 'assign')
+ );
+ break;
+ case Filters.REVIEW_REQUESTED:
+ filterMethod = n => (
+ n.reasons.some(({ reason }) => reason === 'review_requested')
+ );
+ break;
case Filters.COMMENT:
filterMethod = n => (
n.reasons.some(({ reason }) => reason === 'comment')
@@ -290,6 +299,7 @@ class NotificationsPage extends React.Component {
onClearQuery={this.onClearQuery}
onFetchNotifications={fetchNotifications}
onMarkAsRead={markAsRead}
+ onMarkAllAsStaged={markAllAsStaged}
onClearCache={clearCache}
onStageThread={this.enhancedOnStageThread}
onRestoreThread={this.restoreThread}
diff --git a/src/providers/Notifications.js b/src/providers/Notifications.js
index 3bdcf06..abdf8e1 100644
--- a/src/providers/Notifications.js
+++ b/src/providers/Notifications.js
@@ -1,6 +1,6 @@
import React from 'react';
import {AuthConsumer} from './Auth';
-import {StorageProvider} from './Storage';
+import {StorageProvider, LOCAL_STORAGE_PREFIX} from './Storage';
import {Status} from '../constants/status';
const BASE_GITHUB_API_URL = 'https://api.github.com';
@@ -121,7 +121,7 @@ class NotificationsProvider extends React.Component {
fetchNotifications = (page = 1, optimizePolling = true) => {
if (!this.props.token) {
- console.error('Unauthenitcated, aborting request.')
+ console.error('Unauthenitcated, aborting request.');
return false;
}
@@ -133,7 +133,7 @@ class NotificationsProvider extends React.Component {
processNotificationsChunk = (nextPage, notificationsChunk) => {
return new Promise((resolve, reject) => {
- console.log('chunk', notificationsChunk)
+ console.log('chunk', notificationsChunk);
let everythingUpdated = true;
if (notificationsChunk.length === 0) {
@@ -187,7 +187,6 @@ class NotificationsProvider extends React.Component {
: Promise.reject();
})
.then(() => {
- console.warn('removing', thread_id);
this.props.removeItemFromStorage(thread_id);
this.props.refreshNotifications();
return Promise.resolve();
@@ -208,7 +207,6 @@ class NotificationsProvider extends React.Component {
requestClearCache = () => {
return new Promise((resolve, reject) => {
- console.warn('clearing cache');
this.props.clearStorageCache();
this.props.refreshNotifications();
this.last_modified = null;
@@ -225,7 +223,6 @@ class NotificationsProvider extends React.Component {
requestStageThread = thread_id => {
return new Promise((resolve, reject) => {
- console.warn('staging thread', thread_id);
const cached_n = this.props.getItemFromStorage(thread_id);
if (cached_n) {
const newValue = {
@@ -241,9 +238,28 @@ class NotificationsProvider extends React.Component {
});
}
+ requestStageAll = () => {
+ return new Promise((resolve, reject) => {
+ Object.keys(localStorage).forEach(nKey => {
+ // Only update the notification items in the cache.
+ // Don't get the statistics or anything else caught in there.
+ if (nKey.includes(LOCAL_STORAGE_PREFIX)) {
+ let cached_n = null;
+ cached_n = JSON.parse(window.localStorage.getItem(nKey));
+ const newValue = {
+ ...cached_n,
+ status: Status.STAGED
+ };
+ window.localStorage.setItem(nKey, JSON.stringify(newValue));
+ }
+ });
+ this.props.refreshNotifications();
+ return resolve();
+ });
+ }
+
requestRestoreThread = thread_id => {
return new Promise((resolve, reject) => {
- console.warn('restoring thread', thread_id);
const cached_n = this.props.getItemFromStorage(thread_id);
if (cached_n) {
const newValue = {
@@ -259,6 +275,13 @@ class NotificationsProvider extends React.Component {
});
}
+ markAllAsStaged = () => {
+ this.setState({ loading: true });
+ return this.requestStageAll()
+ .catch(error => this.setState({ error }))
+ .finally(() => this.setState({ loading: false }));
+ }
+
stageThread = thread_id => {
this.setState({ loading: true });
return this.requestStageThread(thread_id)
@@ -282,7 +305,6 @@ class NotificationsProvider extends React.Component {
if (prevReason) {
reasons = prevReason.concat(newReason);
- console.warn('MULTIPLE REASONS', reasons)
} else {
reasons = [newReason];
}
@@ -311,6 +333,7 @@ class NotificationsProvider extends React.Component {
fetchNotifications: this.fetchNotifications,
fetchNotificationsSync: this.requestFetchNotifications,
markAsRead: this.markAsRead,
+ markAllAsStaged: this.markAllAsStaged,
clearCache: this.clearCache,
stageThread: this.stageThread,
restoreThread: this.restoreThread,
diff --git a/src/providers/Storage.js b/src/providers/Storage.js
index 937e977..f5165a0 100644
--- a/src/providers/Storage.js
+++ b/src/providers/Storage.js
@@ -3,8 +3,8 @@ import moment from 'moment';
import {Status} from '../constants/status';
import {Reasons} from '../constants/reasons';
-const LOCAL_STORAGE_PREFIX = '__meteorite_noti_cache__';
-const LOCAL_STORAGE_STATISTIC_PREFIX = '__meteorite_statistic_cache__';
+export const LOCAL_STORAGE_PREFIX = '__meteorite_noti_cache__';
+export const LOCAL_STORAGE_STATISTIC_PREFIX = '__meteorite_statistic_cache__';
const getMockReasons = n => {
const reasons = Object.values(Reasons);
diff --git a/src/styles/gradient.css b/src/styles/gradient.css
index b29581d..857a75e 100644
--- a/src/styles/gradient.css
+++ b/src/styles/gradient.css
@@ -112,3 +112,12 @@
50% {background-position: 60% 50%}
100% {background-position: 0% 50%}
}
+
+@media only screen and (max-width: 1400px) {
+ #section {
+ flex-direction: column !important;
+ }
+ #item-text {
+ width: 600px;
+ }
+}