mirror of
https://github.com/nickzuber/meteorite.git
synced 2024-11-29 09:31:15 +03:00
Ton of stuff
This commit is contained in:
parent
77d9c907c3
commit
e20690001e
1854
package-lock.json
generated
1854
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import styled from 'react-emotion';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const Message = styled('p')({
|
||||
color: '#eb3349',
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
955
src/pages/Notifications/redesign/Scene.js
Normal file
955
src/pages/Notifications/redesign/Scene.js
Normal 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}>
|
||||
|
||||
</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>
|
||||
);
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user