Ton of stuff

This commit is contained in:
Nicholas Zuber 2019-03-30 15:55:52 -04:00
parent 77d9c907c3
commit e20690001e
12 changed files with 2608 additions and 348 deletions

1854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,11 @@
"version": "1.0.0",
"private": true,
"dependencies": {
"@emotion/core": "^10.0.10",
"@emotion/styled": "^10.0.10",
"@reach/router": "^1.2.1",
"@svgr/webpack": "2.4.1",
"ajv": "^6.10.0",
"axios": "^0.18.0",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "9.0.0",
@ -33,6 +36,8 @@
"jest": "23.6.0",
"jest-pnp-resolver": "1.0.1",
"jest-resolve": "23.6.0",
"lodash-es": "^4.17.11",
"lodash-move": "^1.1.1",
"mini-css-extract-plugin": "0.4.3",
"moment": "^2.22.2",
"optimize-css-assets-webpack-plugin": "5.0.1",
@ -42,11 +47,12 @@
"postcss-preset-env": "6.0.6",
"postcss-safe-parser": "4.0.1",
"query-string": "^6.2.0",
"react": "^16.6.0",
"react": "^16.8.5",
"react-app-polyfill": "^0.1.3",
"react-dev-utils": "^6.0.5",
"react-dom": "^16.6.0",
"react-dom": "^16.8.5",
"react-emotion": "^9.2.12",
"react-spring": "^8.0.18",
"react-svg-inline": "^2.1.1",
"recompose": "^0.30.0",
"resolve": "1.8.1",

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<link rel="shortcut icon" href="%PUBLIC_URL%/icon.png">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<meta name="theme-color" content="#24292e">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<title>Meteorite — Smarter GitHub notifications</title>

View File

@ -1,5 +1,5 @@
import React from 'react';
import styled from 'react-emotion';
import styled from '@emotion/styled';
const Message = styled('p')({
color: '#eb3349',

View File

@ -1,5 +1,5 @@
import React from 'react';
import styled from 'react-emotion';
import styled from '@emotion/styled';
import alarm from './svg/alarm.svg';
import allInbox from './svg/all_inbox.svg';

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link as RouterLink } from "@reach/router";
import styled from 'react-emotion';
import styled from '@emotion/styled';
import { routes } from '../../constants';
import Curve from '../../components/Curve';
import Icon from '../../components/Icon';

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link } from "@reach/router";
import styled from 'react-emotion';
import styled from '@emotion/styled';
import { routes } from '../../constants';
import { AuthenticationButton } from '../../components/buttons';
import LoadingIcon from '../../components/LoadingIcon';

View File

@ -2,7 +2,7 @@ import React from 'react';
import moment from 'moment';
// import {VictoryPie, VictoryChart} from "victory";
import {Link} from "@reach/router";
import styled from 'react-emotion';
import styled from '@emotion/styled';
import Icon from '../../components/Icon';
import Logo from '../../components/Logo';
import LoadingIcon from '../../components/LoadingIcon';
@ -14,6 +14,8 @@ import {Status} from '../../constants/status';
import {Reasons, Badges} from '../../constants/reasons';
import '../../styles/gradient.css';
import {default as RedesignScene} from './redesign/Scene';
/* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable no-script-url */
@ -707,7 +709,11 @@ export default function Scene ({
fetchingNotificationsError,
activeFilter,
onSetActiveFilter,
setNotificationsPermission
setNotificationsPermission,
highestScore,
lowestScore,
hasUnread,
...props
}) {
const loading = isSearching || isFetchingNotifications;
const isFirstPage = page === 1;
@ -731,6 +737,31 @@ export default function Scene ({
lastWeekStats = lastWeekStats.slice(1, -1);
thisWeekStats = thisWeekStats.slice(1, -1);
return (
<RedesignScene
notifications={notifications}
notificationsPermission={notificationsPermission}
highestScore={highestScore}
lowestScore={lowestScore}
hasUnread={hasUnread}
unreadCount={queuedCount}
readCount={stagedCount}
archivedCount={closedCount}
loading={loading}
isLastPage={isLastPage}
isFirstPage={isFirstPage}
page={page}
onChangePage={onChangePage}
view={activeFilter}
setView={onSetActiveFilter}
query={query}
onClearQuery={onClearQuery}
onSearch={onSearch}
isSearching={isSearching}
{...props}
/>
);
return (
<div style={{marginTop: 60}}>
<NavigationContainer className="container-gradient">

View File

@ -17,7 +17,20 @@ import prIcon from '../../images/pr-bg.png';
import tabIcon from '../../images/icon.png';
import tabDotIcon from '../../images/iconDot.png';
const PER_PAGE = 10;
export const PER_PAGE = 10;
export const Sort = {
TYPE: 1,
TITLE: 0,
REPOSITORY: 2,
SCORE: 3
};
export const View = {
UNREAD: 1,
READ: 0,
ARCHIVED: 2
};
// @TODO Move these functions.
@ -132,8 +145,10 @@ class NotificationsPage extends React.Component {
isSearching: false,
query: null,
activeFilter: Filters.PARTICIPATING,
activeStatus: Status.QUEUED,
currentPage: 1
activeStatus: View.UNREAD,
currentPage: 1,
sort: Sort.SCORE,
descending: false
}
componentDidMount () {
@ -210,7 +225,7 @@ class NotificationsPage extends React.Component {
query: text,
isSearching: false
});
}, 500);
}, 800);
}
enhancedOnStageThread = (thread_id, repository) => {
@ -326,20 +341,48 @@ class NotificationsPage extends React.Component {
let notificationsToRender = [];
switch (this.state.activeStatus) {
case Status.CLOSED:
case View.ARCHIVED:
notificationsToRender = notificationsClosed;
break;
case Status.STAGED:
case View.READ:
notificationsToRender = notificationsStaged;
break;
case Status.QUEUED:
case View.UNREAD:
default:
notificationsToRender = notificationsQueued;
}
let scoredAndSortedNotifications = notificationsToRender
.map(decorateWithScore)
.sort((a, b) => b.score - a.score);
.map(decorateWithScore);
if (this.state.sort === Sort.TITLE) {
if (this.state.descending) {
scoredAndSortedNotifications.sort((a, b) => a.name.localeCompare(b.name));
} else {
scoredAndSortedNotifications.sort((a, b) => b.name.localeCompare(a.name));
}
}
if (this.state.sort === Sort.SCORE) {
if (this.state.descending) {
scoredAndSortedNotifications.sort((a, b) => a.score - b.score);
} else {
scoredAndSortedNotifications.sort((a, b) => b.score - a.score);
}
}
if (this.state.sort === Sort.REPOSITORY) {
if (this.state.descending) {
scoredAndSortedNotifications.sort((a, b) => a.repository.localeCompare(b.repository));
} else {
scoredAndSortedNotifications.sort((a, b) => b.repository.localeCompare(a.repository));
}
}
if (this.state.sort === Sort.TYPE) {
if (this.state.descending) {
scoredAndSortedNotifications.sort((a, b) => a.type.localeCompare(b.type));
} else {
scoredAndSortedNotifications.sort((a, b) => b.type.localeCompare(a.type));
}
}
// We gotta make sure to search notifications before we paginate.
// Otherwise we'd just end up searching on the current page, which is bad.
@ -386,6 +429,12 @@ class NotificationsPage extends React.Component {
closedCount,
} = this.getFilteredNotifications();
const [highestScore, lowestScore] = scoredAndSortedNotifications.reduce(([h, l], notification) => {
h = Math.max(notification.score, h);
l = Math.min(notification.score, l);
return [h, l];
}, [0, Infinity]);
let firstIndex = (this.state.currentPage - 1) * PER_PAGE;
let lastIndex = (this.state.currentPage * PER_PAGE);
let notificationsOnPage = scoredAndSortedNotifications.slice(firstIndex, lastIndex);
@ -445,6 +494,15 @@ class NotificationsPage extends React.Component {
isFetchingNotifications={isFetchingNotifications}
fetchingNotificationsError={fetchingNotificationsError || this.state.error}
onSetActiveFilter={this.onSetActiveFilter}
highestScore={highestScore}
lowestScore={lowestScore}
hasUnread={this.isUnreadTab}
sort={this.state.sort}
setSort={sort => this.setState({sort})}
descending={this.state.descending}
setDescending={descending => this.setState({descending})}
view={this.state.activeStatus}
setView={this.onSetActiveStatus}
/>
);
}

View File

@ -0,0 +1,955 @@
/** @jsx jsx */
import React from 'react';
import moment from 'moment';
import styled from '@emotion/styled';
import {css, jsx} from '@emotion/core';
import {useSpring, useTransition, animated} from 'react-spring'
import Icon from '../../../components/Icon';
import LoadingIcon from '../../../components/LoadingIcon';
import {withOnEnter} from '../../../enhance';
import {Sort, View} from '../index';
const WHITE = 'rgb(255, 254, 252)';
const COLLAPSED_WIDTH = '72px';
const EXPANDED_WIDTH = '286px';
const Mode = {
ALL: 0,
REVIEWS: 1,
COMMENTED: 2
};
// @TODO if GitHub ever fixes their API, we can use `reasons` to know when
// a PR/Issue merges/closes/etc.
function getPRIssueIcon (type, _reasons) {
const scale = 1.5;
switch (type) {
case 'PullRequest':
return (
<NotificationIconWrapper>
<i className="fas fa-code-branch" css={css`
color: #4C84FF;
font-size: 18px;
`}></i>
</NotificationIconWrapper>
);
case 'Issue':
return (
<NotificationIconWrapper>
<Icon.IssueOpen shrink={scale} />
</NotificationIconWrapper>
);
default:
return null;
}
}
function colorOfScore (score, min, max) {
const ratio = (score - min) / (max - min);
if (ratio > .9) return '#ec1461';
if (ratio > .8) return '#ec5314';
if (ratio > .7) return '#ec5314';
if (ratio > .6) return '#ec7b14';
if (ratio > .5) return '#ec7b14';
if (ratio > .4) return '#ec9914';
if (ratio > .3) return '#ec9914';
if (ratio > .2) return '#ecad14';
if (ratio > .1) return '#ecad14';
return '#ecc114';
}
const Container = styled('div')`
position: relative;
display: block;
background: ${WHITE};
height: calc(100% - ${COLLAPSED_WIDTH});
`;
const Row = styled('div')`
position: relative;
display: block;
display: flex;
flex-direction: row;
`;
const Item = styled('div')`
position: relative;
display: inline-block;
transition: all 200ms ease;
`;
const MenuHeaderItem = styled(Item)`
height: ${COLLAPSED_WIDTH};
width: ${({expand}) => expand ? EXPANDED_WIDTH : COLLAPSED_WIDTH};
border-bottom: 1px solid #EDEEF0;
border-right: 1px solid #EDEEF0;
z-index: 1;
`;
const ContentHeaderItem = styled(Item)`
height: ${COLLAPSED_WIDTH};
width: calc(100% - ${COLLAPSED_WIDTH});
border-bottom: 1px solid #E5E6EB;
z-index: 1;
`;
const MenuContainerItem = styled(Item)`
width: ${({expand}) => expand ? EXPANDED_WIDTH : COLLAPSED_WIDTH};
height: 100%;
`;
// Faded blue: #F5F6FA
// Faded gray: #fbfbfb
const ContentItem = styled(Item)`
height: 100%;
min-height: calc(100vh - ${COLLAPSED_WIDTH});
width: calc(100% - ${COLLAPSED_WIDTH});
background: #f7f6f3;
border-left: 1px solid #E5E6EB;
`;
const CardSection = styled('div')`
float: left;
display: inline-block;
width: 330px;
padding: 0 16px;
`;
const Card = styled('div')`
width: 298px;
height: 100px;
margin: 32px auto 0;
background: ${WHITE};
border: 1px solid #E5E6EB;
box-shadow: rgba(84, 70, 35, 0) 0px 2px 8px, rgba(84,70,35,0.15) 0px 1px 3px;
border-radius: 6px;
`;
const IconContainer = styled('div')`
position: relative;
height: 72px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
user-select: none;
transition: all 200ms ease;
&:after {
transition: all 200ms ease;
content: "";
position: absolute;
width: 3px;
background: ${props => !props.noBorder && props.selected ? props.primary : WHITE};
right: 0;
top: 4px;
bottom: 4px;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
i {
transition: all 200ms ease;
color: ${props => props.selected ? props.primary : '#BFC5D1'}
}
&:hover {
background: ${props => props.selected ? WHITE : 'rgba(233, 233, 233, .25)'};
i {
color: ${props => props.selected ? props.primary : '#616671'}
}
}
`;
const NotificationsSection = styled('div')`
display: inline-block;
width: calc(100% - 362px - 68px);
padding-top: 36px;
padding-right: 68px;
padding-left: 0;
padding-bottom: 0;
height: auto;
`;
const TitleSection = styled('div')`
display: flex;
width: 100%;
padding: 0;
margin: 0;
height: auto;
`;
const SubTitleSection = styled('div')`
display: flex;
width: 100%;
padding: 0;
margin: 0;
height: auto;
h4 {
margin: 8px 0;
font-weight: 500;
font-size: 1rem;
color: #19223dab;
}
`;
const Title = styled('h1')`
margin: 0;
font-size: 2rem;
line-height: 3rem;
font-weight: 500;
letter-spacing: -1.05px;
`;
const UnorderedList = styled('ul')`
position: relative;
width: 100%;
margin-top: 0;
padding: 0;
height: 50px;
display: flex;
align-items: center;
justify-content: flex-start;
list-style: none;
`;
const PageSelection = styled(UnorderedList)`
border-bottom: 2px solid #e5e6eb;
`;
const SearchField = styled('div')`
position: relative;
float: left;
text-align: left;
// box-shadow: rgba(84,70,35,0.01) 0px 2px 19px 8px, rgba(84, 70, 35, 0.11) 0px 2px 12px;
align-items: center;
height: 36px;
font-size: 13px;
display: inline-flex;
margin: 0 24px;
background: rgba(255, 255, 255, 0.125);
border-radius: 4px;
padding: 0px;
text-decoration: none;
transition: all 0.06s ease-in-out 0s;
border: 1px solid #bfc5d161;
&:hover {
border: 1px solid #bfc5d1aa;
}
&:focus-within {
border: 1px solid #457cff;
}
i {
color: #bfc5d1;
height: 36px;
width: 48px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
`;
const SearchInput = styled('input')`
position: relative;
text-align: left;
transition: all 200ms ease;
height: 36px;
font-size: 13px;
display: inline-flex;
flex: 1 1 0%;
font-weight: 500;
margin: 0px auto;
background: none;
width: 184px;
padding: 0px;
text-decoration: none;
border-width: 0px;
border-style: initial;
border-color: initial;
border-image: initial;
outline: none;
opacity: 0.5;
&:focus {
opacity: 1;
color: rgb(55, 53, 47);
width: 500px;
}
`;
const EnhancedSearchInput = withOnEnter(SearchInput);
const PageItemComponent = styled('li')`
font-size: 14px;
padding: 0 32px 0 16px;
width: 112px;
position: relative;
height: 100%;
line-height: 52px;
cursor: pointer;
outline: none;
user-select: none;
transition: all 200ms ease;
font-weight: ${props => props.selected ? 600 : 400};
&:hover {
background: rgba(233, 233, 233, .5);
}
&:active {
background: rgba(233, 233, 233, .75);
}
&:after {
transition: all 200ms ease;
content: "";
position: absolute;
width: 100%;
background: ${props => props.selected ? props.primary : 'none'};
right: 0;
bottom: -2px;
left: 0;
height: 3px;
}
`;
const InteractionSection = styled('ul')`
position: relative;
width: 50px;
height: 50px;
padding: 0;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
list-style: none;
li {
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
display: flex;
font-size: 16px;
cursor: pointer;
outline: none;
user-select: none;
}
`;
const InteractionMenu = styled('div')`
height: ${props => props.show ? 338 : 0}px;
opacity: ${props => props.show ? 1 : 0};
top: 32px;
left: 32px;
margin-left: 2px;
overflow: hidden;
cursor: initial;
position: absolute;
z-index: 2;
transition: all 200ms ease;
div {
margin: 0;
height: auto;
div {
margin: 0;
padding: 12px 16px;
cursor: pointer;
outline: none;
user-select: none;
transition: all 200ms ease;
&:hover {
background: rgba(233, 233, 233, .25);
}
h2 {
margin: 0 0 4px;
font-size: 15px;
}
p {
margin: 0;
font-size: 13px;
opacity: 0.7;
}
}
}
`;
const SortingItemComponent = styled('div')`
display: flex;
height: auto;
margin-right: 12px;
cursor: pointer;
outline: none;
user-select: none;
span {
text-transform: uppercase;
font-size: 11px;
margin-right: 8px;
font-weight: 600;
color: ${props => props.selected ? '#8c93a5' : '#8c93a5a1'};
}
span:hover {
color: #8c93a5dd;
}
span:active {
color: #8c93a5;
}
i {
font-size: 13px;
line-height: 15px;
color: ${props => props.selected ? '#8c93a5' : '#8c93a5a1'};
}
`;
const NotificationsTable = styled('table')`
position: relative;
display: flex;
flex-direction: column;
`;
const NotificationRowHeader = styled('tr')`
position: relative;
width: 100%;
display: flex;
align-items: center;
text-align: left;
margin: 0 auto;
padding: 8px 24px 8px;
box-sizing: border-box;
`;
const NotificationRow = styled(NotificationRowHeader)`
position: relative;
background: ${WHITE};
border-radius: 4px;
padding: 2px 18px;
font-size: 14px;
margin-bottom: 12px;
border-radius: 6px;
cursor: pointer;
user-select: none;
transition: all 200ms ease;
&:hover {
box-shadow: rgba(84,70,35,0.01) 0px 2px 19px 8px, rgba(84, 70, 35, 0.11) 0px 2px 12px;
// background: rgb(252, 250, 248);
};
`;
const NotificationBlock = styled('tbody')`
`;
const AnimatedNotificationRow = animated(NotificationRow);
const AnimatedNotificationsBlock = animated(NotificationBlock);
const NotificationCell = styled('td')`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: ${props => {
if (props.width) {
return `0 0 ${props.width}px`;
} else {
return props.flex || 1;
}
}};
`;
const NotificationIconWrapper = styled('div')`
background: #DBE7FF;
width: 48px;
height: 48px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
transform: scale(.65);
`;
const IconLink = styled('span')`
position: relative;
cursor: ${props => props.disabled ? 'default' : 'pointer'};
display: inline-flex;
justify-content: center;
align-items: center;
z-index: 1;
user-select: none;
height: 40px;
width: 40px;
transition: all 150ms ease;
i {
color: ${props => props.disabled ? '#bfc5d1' : 'inherit'};
}
&:before {
content: "";
transition: all 150ms ease;
background: #BFC5D122;
border-radius: 100%;
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
transform: scale(0);
}
&:hover:before {
transform: ${props => props.disabled ? 'scale(0)' : 'scale(1)'};
}
&:active:before {
background: ${props => props.disabled ? '#BFC5D122' : '#BFC5D144'};;
}
`;
const Divider = styled('div')`
position: relative;
display: inline-block;
background: #e5e6eb;
height: 28px;
width: 2px;
margin: 0 8px;
`;
function PageItem ({children, onChange, ...props}) {
return (
<PageItemComponent
onClick={() => onChange(props.view)}
{...props}
>
{children}
</PageItemComponent>
);
}
function MenuIconItem ({children, onChange, selected, alwaysActive, noBorder, ...props}) {
return (
<IconContainer
onClick={() => onChange(props.mode)}
selected={alwaysActive || selected}
noBorder={noBorder}
{...props}
>
{children}
</IconContainer>
);
}
function SortingItem ({children, selected, onChange, descending, setDescending, ...props}) {
return (
<SortingItemComponent
selected={selected}
onClick={() => {
if (selected) {
setDescending(!descending);
} else {
setDescending(true);
}
onChange(props.sort)
}}
>
<span {...props}>
{children}
</span>
<i css={css`
transition: all 200ms ease;
opacity: ${selected ? 1 : 0};
transform: ${descending ? 'rotate(180deg)' : 'rotate(0deg)'};
`} className="fas fa-caret-up"></i>
</SortingItemComponent>
);
}
export default function Scene ({
notifications,
notificationsPermission,
highestScore,
lowestScore,
hasUnread,
unreadCount,
readCount,
archivedCount,
loading,
isLastPage,
isFirstPage,
page,
onChangePage,
sort,
descending,
setSort,
setDescending,
view,
setView,
query,
onClearQuery,
onSearch,
isSearching,
}) {
const [menuOpen, setMenuOpen] = React.useState(false);
const [dropdownOpen, setDropdownOpen] = React.useState(false);
const [mode, setMode] = React.useState(Mode.ALL);
const hasNotificationsOn = notificationsPermission === 'granted';
// @TODO move to index file
if (sort === Sort.TITLE) {
if (descending) {
notifications.sort((a, b) => a.name.localeCompare(b.name));
} else {
notifications.sort((a, b) => b.name.localeCompare(a.name));
}
}
// const notificationTransitions = useTransition(notifications, item => item.id, {
// from: {background: 'green', opacity: 0, transform: 'translate3d(50px, 0, 0)'},
// enter: {background: 'yellow', opacity: 1, transform: 'translate3d(0, 0, 0)'},
// leave: {opacity: 0, transform: 'translate3d(-50px, 0, 0)', display: 'none'},
// unique: true,
// trail: 50,
// config: {
// tension: 300,
// duration: 1000,
// }
// });
const props = useSpring({
from: {opacity: 0, transform: 'translate3d(50px, 0, 0)'},
to: {opacity: 1, transform: 'translate3d(0, 0, 0)'},
config: {
tension: 300,
duration: 200,
}
});
React.useEffect(() => {
const body = window.document.querySelector('body');
const hideDropdownMenu = () => setDropdownOpen(false);
body.addEventListener('click', hideDropdownMenu);
return () => body.removeEventListener('click', hideDropdownMenu);
}, []);
return (
<Container>
<Row style={{height: COLLAPSED_WIDTH}}>
<MenuHeaderItem expand={menuOpen}>
<MenuIconItem
alwaysActive
noBorder
primary="#BFC5D1"
onClick={() => setMenuOpen(!menuOpen)}
>
<i className="fas fa-bars"></i>
</MenuIconItem>
</MenuHeaderItem>
<ContentHeaderItem css={css`
display: inline-flex;
align-items: center;
`}>
<SearchField>
<i className="fas fa-search"></i>
<EnhancedSearchInput
disabled={loading}
onClick={event => event.target.select()}
type="text"
placeholder="Search for notifications"
onEnter={onSearch}
/>
{isSearching && <LoadingIcon size={36} style={{
transition: 'all 100ms ease',
position: 'absolute',
right: 0,
transform: 'scale(0.8)',
backgroundColor: 'white'
}} />}
</SearchField>
</ContentHeaderItem>
</Row>
<Row style={{height: `calc(100% - ${COLLAPSED_WIDTH})`}}>
<MenuContainerItem expand={menuOpen}>
<MenuIconItem
mode={Mode.ALL}
primary="#4caf50"
selected={mode === Mode.ALL}
onChange={setMode}
style={{margin: '8px 0'}}
>
<i className="fas fa-seedling"></i>
</MenuIconItem>
<MenuIconItem
mode={Mode.REVIEWS}
primary="#e91e63"
selected={mode === Mode.REVIEWS}
onChange={setMode}
style={{margin: '8px 0'}}
>
<i className="fas fa-fire"></i>
</MenuIconItem>
<MenuIconItem
mode={Mode.COMMENTED}
primary="#4C84FF"
selected={mode === Mode.COMMENTED}
onChange={setMode}
style={{margin: '8px 0'}}
>
<i className="fas fa-splotch"></i>
</MenuIconItem>
</MenuContainerItem>
<ContentItem>
<CardSection>
<Card />
<Card />
</CardSection>
<NotificationsSection>
<TitleSection>
<Title>{'Updates'}</Title>
<InteractionSection>
<li onClick={() => setDropdownOpen(true)}>
<IconLink>
<i className="fas fa-ellipsis-v"></i>
</IconLink>
<InteractionMenu show={dropdownOpen}>
<Card>
<div>
<h2>Reload notifications</h2>
<p>Manually fetch new notifications instead of waiting for the sync</p>
</div>
<div>
<h2>Mark all as read</h2>
<p>Move all your unread notifications to the read tab</p>
</div>
<div>
<h2>Empty cache</h2>
<p>Clear all the notifications that are being tracked in your local storage</p>
</div>
<div>
<h2>Turn {hasNotificationsOn ? 'off' : 'on'} notifications</h2>
<p>
{hasNotificationsOn
? 'Stop receiving web notifications when you get a new update'
: 'Receive web notifications whenever you get a new update'
}
</p>
</div>
</Card>
</InteractionMenu>
</li>
</InteractionSection>
</TitleSection>
<SubTitleSection>
<h4>See all of the notifications that matter to you</h4>
</SubTitleSection>
<PageSelection>
<PageItem
view={View.UNREAD}
selected={view === View.UNREAD}
primary="#4C84FF"
onChange={setView}
mark={hasUnread}
>
{'Unread'}
{unreadCount > 0 && (
<span css={css`
background: ${view === View.UNREAD ? '#4880ff' : '#bfc5d1'};
color: ${WHITE};
font-size: 9px;
margin: 0 6px;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
vertical-align: middle;
`}>
{unreadCount}
</span>
)}
</PageItem>
<PageItem
view={View.READ}
selected={view === View.READ}
primary="#4C84FF"
onChange={setView}
>
{'Read'}
{readCount > 0 && (
<span css={css`
background: ${view === View.READ ? '#4880ff' : '#bfc5d1'};
color: ${WHITE};
font-size: 9px;
margin: 0 6px;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
vertical-align: middle;
`}>
{readCount}
</span>
)}
</PageItem>
<PageItem
view={View.ARCHIVED}
selected={view === View.ARCHIVED}
primary="#4C84FF"
onChange={setView}
>
{'Archived'}
{archivedCount > 0 && (
<span css={css`
background: ${view === View.ARCHIVED ? '#4880ff' : '#bfc5d1'};
color: ${WHITE};
font-size: 9px;
margin: 0 6px;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
vertical-align: middle;
`}>
{archivedCount}
</span>
)}
</PageItem>
<div css={css`
height: auto;
position: absolute;
display: flex;
right: 0;
justify-content: center;
align-items: center;
`}>
{query && (
<>
<span css={css`
font-size: 13px;
color: #797d8c;
font-weight: 500;
vertical-align: text-top;
margin-right: 8px;
span {
font-size: 13px;
color: #797d8c;
font-weight: 600;
vertical-align: text-top;
}
`}>
{'Showing results for '}
<span>{query}</span>
</span>
<IconLink
onClick={!loading ? (() => onClearQuery()) : undefined}
>
<i className="fas fa-times"></i>
</IconLink>
<Divider />
</>
)}
<IconLink
disabled={loading || isFirstPage}
onClick={!loading && !isFirstPage ? (() => onChangePage(page - 1)) : undefined}
>
<i className="fas fa-chevron-left"></i>
</IconLink>
<IconLink
disabled={loading || isLastPage}
onClick={!loading && !isLastPage ? (() => onChangePage(page + 1)) : undefined}
>
<i className="fas fa-chevron-right"></i>
</IconLink>
</div>
</PageSelection>
<NotificationsTable>
<NotificationRowHeader>
{/* Type */}
<NotificationCell width={70}>
<SortingItem
sort={Sort.TYPE}
descending={descending}
setDescending={setDescending}
selected={sort === Sort.TYPE}
onChange={setSort}
>
{'Type'}
</SortingItem>
</NotificationCell>
{/* Title */}
<NotificationCell flex={4}>
<SortingItem
sort={Sort.TITLE}
descending={descending}
setDescending={setDescending}
selected={sort === Sort.TITLE}
onChange={setSort}
>
{'Title'}
</SortingItem>
</NotificationCell>
{/* Repository */}
<NotificationCell flex={2}>
<SortingItem
sort={Sort.REPOSITORY}
descending={descending}
setDescending={setDescending}
selected={sort === Sort.REPOSITORY}
onChange={setSort}
>
{'Repository'}
</SortingItem>
</NotificationCell>
{/* Score */}
<NotificationCell width={60}>
<SortingItem
sort={Sort.SCORE}
descending={descending}
setDescending={setDescending}
selected={sort === Sort.SCORE}
onChange={setSort}
>
{'Score'}
</SortingItem>
</NotificationCell>
{/* Actions */}
<NotificationCell width={70}>
&nbsp;
</NotificationCell>
</NotificationRowHeader>
<AnimatedNotificationsBlock style={props} page={page}>
{notifications.map(item => (
<AnimatedNotificationRow key={notifications.id}>
{/* Type */}
<NotificationCell width={80}>
{getPRIssueIcon(item.type, item.reasons)}
</NotificationCell>
{/* Title */}
<NotificationCell flex={4} css={css`
font-weight: 500;
`}>
{item.name}
</NotificationCell>
{/* Repository */}
<NotificationCell flex={2} css={css`
font-weight: 500;
color: #8994A6;
`}>
{'@' + item.repository}
</NotificationCell>
{/* Score */}
<NotificationCell width={60} css={css`
font-weight: 600;
color: ${colorOfScore(item.score, lowestScore, highestScore)};
font-size: 12px;
text-align: center;
`}>
{'+' + item.score}
</NotificationCell>
<NotificationCell width={80} css={css`
i {
padding: 13px 0;
text-align: center;
width: 40px;
}
`}>
<IconLink>
<i className="fas fa-check"></i>
</IconLink>
<IconLink>
<i className="fas fa-times"></i>
</IconLink>
</NotificationCell>
</AnimatedNotificationRow>
))}
</AnimatedNotificationsBlock>
</NotificationsTable>
</NotificationsSection>
</ContentItem>
</Row>
</Container>
);
}

View File

@ -174,7 +174,6 @@ class NotificationsProvider extends React.Component {
processNotificationsChunk = (nextPage, notificationsChunk) => {
return new Promise((resolve, reject) => {
console.log('chunk', notificationsChunk);
let everythingUpdated = true;
if (notificationsChunk.length === 0) {
@ -208,8 +207,6 @@ class NotificationsProvider extends React.Component {
this.setState({newChanges: processedNotifications});
}
console.log('everythingUpdated', everythingUpdated);
if (nextPage && everythingUpdated) {
// Still need to fetch more updates.
return this.requestPage(nextPage, false).then(resolve);

View File

@ -21,11 +21,18 @@
background: #24292e;
}
html, body {
/* height: 100%; */
width: 100%;
}
html, body, * {
font-family: 'Inter UI', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
color: #202124;
/* color: #202124; */
/* color: #19233C; */
color: rgb(55, 53, 47);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@ -78,6 +85,11 @@ p {
font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
}
div {
height: 100%;
vertical-align: top;
}
.react-tooltip {
z-index: 999999;
pointer-events: none;