diff --git a/.github/icon.png b/.github/icon.png new file mode 100644 index 0000000..f0c89fe Binary files /dev/null and b/.github/icon.png differ diff --git a/.github/template.png b/.github/template.png new file mode 100644 index 0000000..dcbfa2b Binary files /dev/null and b/.github/template.png differ diff --git a/README.md b/README.md index 9ec7549..a54ac7c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# meteorite +# meteorite > Smarter GitHub notifications. +
+ +
+ ## License This software is free to use under the MIT License. See [this reference](https://opensource.org/licenses/MIT) for license text and copyright information. diff --git a/package.json b/package.json index fdf803b..0d2e3e9 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "start": "node scripts/start.js", "build": "node scripts/build.js && cp build/index.html build/200.html", "test": "node scripts/test.js", - "deploy": "surge" + "deploy": "npm run build && surge ./build" }, "eslintConfig": { "extends": "react-app" diff --git a/src/components/Icon/index.js b/src/components/Icon/index.js index 668729c..3699edd 100644 --- a/src/components/Icon/index.js +++ b/src/components/Icon/index.js @@ -5,6 +5,7 @@ import alarm from './svg/alarm.svg'; import allInbox from './svg/all_inbox.svg'; import back from './svg/back.svg'; import bolt from './svg/bolt.svg'; +import boltAlt from './svg/bolt-alt.svg'; import boltWhite from './svg/bolt-white.svg'; import bookmarkAlt from './svg/bookmark-alt.svg'; import bookmarkAltWhite from './svg/bookmark-alt-white.svg'; @@ -43,6 +44,14 @@ import x from './svg/x.svg'; import zap from './svg/zap.svg'; import ear from './svg/ear.svg'; import ring from './svg/ring.svg'; +import eye from './svg/eye.svg'; +import eyeWhite from './svg/eye-white.svg'; +import warn from './svg/warn.svg'; +import bubbles from './svg/bubbles.svg'; +import cloudOff from './svg/cloudoff.svg'; +import tag from './svg/tag.svg'; +import tagWhite from './svg/tag-white.svg'; +import sync from './svg/sync.svg'; import issue_closed from './svg/github/issue-closed.svg'; import issue_open from './svg/github/issue-open.svg'; @@ -76,6 +85,7 @@ Icon.AllInbox = createIcon(allInbox); Icon.Back = createIcon(back); Icon.Bolt = createIcon(bolt); Icon.BoltWhite = createIcon(boltWhite); +Icon.BoltAlt = createIcon(boltAlt); Icon.BookmarkAlt = createIcon(bookmarkAlt); Icon.BookmarkAltWhite = createIcon(bookmarkAltWhite); Icon.Bookmark = createIcon(bookmark); @@ -113,6 +123,14 @@ Icon.X = createIcon(x); Icon.Zap = createIcon(zap); Icon.Ear = createIcon(ear); Icon.Ring = createIcon(ring); +Icon.Eye = createIcon(eye); +Icon.EyeWhite = createIcon(eyeWhite); +Icon.Warn = createIcon(warn); +Icon.Bubbles = createIcon(bubbles); +Icon.CloudOffWhite = createIcon(cloudOff); +Icon.Tag = createIcon(tag); +Icon.TagWhite = createIcon(tagWhite); +Icon.Sync = createIcon(sync); Icon.IssueClosed = createIcon(issue_closed); Icon.IssueOpen = createIcon(issue_open); diff --git a/src/components/Icon/svg/bolt-alt.svg b/src/components/Icon/svg/bolt-alt.svg new file mode 100644 index 0000000..6bbf5fb --- /dev/null +++ b/src/components/Icon/svg/bolt-alt.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Icon/svg/bubbles.svg b/src/components/Icon/svg/bubbles.svg new file mode 100644 index 0000000..150b3f5 --- /dev/null +++ b/src/components/Icon/svg/bubbles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon/svg/cloudoff.svg b/src/components/Icon/svg/cloudoff.svg new file mode 100644 index 0000000..87b080e --- /dev/null +++ b/src/components/Icon/svg/cloudoff.svg @@ -0,0 +1 @@ + diff --git a/src/components/Icon/svg/eye-white.svg b/src/components/Icon/svg/eye-white.svg new file mode 100644 index 0000000..b416030 --- /dev/null +++ b/src/components/Icon/svg/eye-white.svg @@ -0,0 +1 @@ + diff --git a/src/components/Icon/svg/eye.svg b/src/components/Icon/svg/eye.svg new file mode 100644 index 0000000..4beebcf --- /dev/null +++ b/src/components/Icon/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon/svg/sync.svg b/src/components/Icon/svg/sync.svg new file mode 100644 index 0000000..38795a1 --- /dev/null +++ b/src/components/Icon/svg/sync.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon/svg/tag-white.svg b/src/components/Icon/svg/tag-white.svg new file mode 100644 index 0000000..6edc0d8 --- /dev/null +++ b/src/components/Icon/svg/tag-white.svg @@ -0,0 +1 @@ + diff --git a/src/components/Icon/svg/tag.svg b/src/components/Icon/svg/tag.svg new file mode 100644 index 0000000..07d9a06 --- /dev/null +++ b/src/components/Icon/svg/tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon/svg/warn.svg b/src/components/Icon/svg/warn.svg new file mode 100644 index 0000000..8dcb028 --- /dev/null +++ b/src/components/Icon/svg/warn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/constants/filters.js b/src/constants/filters.js index f8af81f..0e1ef05 100644 --- a/src/constants/filters.js +++ b/src/constants/filters.js @@ -1,5 +1,6 @@ export const Filters = { ALL: 'all', + ASSIGNED: 'assign', REVIEW_REQUESTED: 'review_requested', PARTICIPATING: 'participating', SUBSCRIBED: 'subscribed', diff --git a/src/enhance/index.js b/src/enhance/index.js index d933954..a719beb 100644 --- a/src/enhance/index.js +++ b/src/enhance/index.js @@ -31,7 +31,7 @@ class Tooltip extends React.Component { } const {tooltipOffsetX, tooltipOffsetY} = this.props; - const {x, y, height} = event.target.getBoundingClientRect(); + const {x, y} = event.target.getBoundingClientRect(); const text = document.createTextNode(this.props.message); const tooltipElement = document.createElement('div'); diff --git a/src/pages/Home/Scene.js b/src/pages/Home/Scene.js index 90cfe23..f8d6358 100644 --- a/src/pages/Home/Scene.js +++ b/src/pages/Home/Scene.js @@ -7,7 +7,7 @@ import Icon from '../../components/Icon'; import Logo from '../../components/Logo'; import '../../styles/gradient.css'; -function createImagePlaceholder () { +function createImagePlaceholder (highlight) { return ( {/* navigation backdrop */} @@ -136,19 +136,21 @@ function createImagePlaceholder () {
@@ -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 () {
- + {highlight === 'badges' ? ( + + ) : ( +
+ )}
- + {highlight === 'badges' ? ( + + ) : ( +
+ )}
@@ -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 +
+
+ sign out +
- - - 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 -
-
- sign out -
-
-
-
-
- - -
-

- - {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; + } +}