mirror of
https://github.com/nickzuber/meteorite.git
synced 2024-11-25 15:32:12 +03:00
Fix bc, more charts and footer
This commit is contained in:
parent
e88b61e9f8
commit
90a5c0a673
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
// import {VictoryPie, VictoryChart} from "victory";
|
// import {VictoryPie, VictoryChart} from "victory";
|
||||||
import {Link} from "@reach/router";
|
import {Link} from "@reach/router";
|
||||||
import styled from '@emotion/styled';
|
import styled from 'react-emotion';
|
||||||
import Icon from '../../components/Icon';
|
import Icon from '../../components/Icon';
|
||||||
import Logo from '../../components/Logo';
|
import Logo from '../../components/Logo';
|
||||||
import LoadingIcon from '../../components/LoadingIcon';
|
import LoadingIcon from '../../components/LoadingIcon';
|
||||||
@ -676,7 +676,7 @@ function getPRIssueIcon (type, _reasons) {
|
|||||||
|
|
||||||
export default function Scene ({
|
export default function Scene ({
|
||||||
currentTime,
|
currentTime,
|
||||||
readStatistics,
|
stagedStatistics,
|
||||||
isFirstTimeUser,
|
isFirstTimeUser,
|
||||||
notificationsPermission,
|
notificationsPermission,
|
||||||
queuedCount,
|
queuedCount,
|
||||||
@ -707,11 +707,7 @@ export default function Scene ({
|
|||||||
fetchingNotificationsError,
|
fetchingNotificationsError,
|
||||||
activeFilter,
|
activeFilter,
|
||||||
onSetActiveFilter,
|
onSetActiveFilter,
|
||||||
setNotificationsPermission,
|
setNotificationsPermission
|
||||||
highestScore,
|
|
||||||
lowestScore,
|
|
||||||
hasUnread,
|
|
||||||
...props
|
|
||||||
}) {
|
}) {
|
||||||
const loading = isSearching || isFetchingNotifications;
|
const loading = isSearching || isFetchingNotifications;
|
||||||
const isFirstPage = page === 1;
|
const isFirstPage = page === 1;
|
||||||
@ -725,11 +721,11 @@ export default function Scene ({
|
|||||||
// probably prompt to mark all as read to start out since they prob don't use notifs
|
// probably prompt to mark all as read to start out since they prob don't use notifs
|
||||||
}
|
}
|
||||||
|
|
||||||
readStatistics = readStatistics.map(n => parseInt(n, 10));
|
stagedStatistics = stagedStatistics.map(n => parseInt(n, 10));
|
||||||
|
|
||||||
const highestStagedCount = readStatistics.reduce((n, m) => Math.max(n, m), 0);
|
const highestStagedCount = stagedStatistics.reduce((n, m) => Math.max(n, m), 0);
|
||||||
let lastWeekStats = readStatistics.slice(0, 7);
|
let lastWeekStats = stagedStatistics.slice(0, 7);
|
||||||
let thisWeekStats = readStatistics.slice(7);
|
let thisWeekStats = stagedStatistics.slice(7);
|
||||||
|
|
||||||
// Trim off the weekends.
|
// Trim off the weekends.
|
||||||
lastWeekStats = lastWeekStats.slice(1, -1);
|
lastWeekStats = lastWeekStats.slice(1, -1);
|
||||||
@ -748,7 +744,6 @@ export default function Scene ({
|
|||||||
size={36}
|
size={36}
|
||||||
style={{
|
style={{
|
||||||
float: 'left',
|
float: 'left',
|
||||||
filter: 'brightness(2)',
|
|
||||||
marginRight: 48,
|
marginRight: 48,
|
||||||
cursor: 'pointer'
|
cursor: 'pointer'
|
||||||
}}
|
}}
|
||||||
@ -757,7 +752,7 @@ export default function Scene ({
|
|||||||
onSetActiveFilter(Filters.PARTICIPATING);
|
onSetActiveFilter(Filters.PARTICIPATING);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <UnofficialReleaseTag>beta</UnofficialReleaseTag> */}
|
<UnofficialReleaseTag>beta</UnofficialReleaseTag>
|
||||||
<SearchField>
|
<SearchField>
|
||||||
<Icon.Search size={48} opacity={.45} />
|
<Icon.Search size={48} opacity={.45} />
|
||||||
<EnhancedSearchInput
|
<EnhancedSearchInput
|
||||||
@ -777,7 +772,6 @@ export default function Scene ({
|
|||||||
height: 36,
|
height: 36,
|
||||||
padding: '0 12px'
|
padding: '0 12px'
|
||||||
}} to={routes.HOME}>home</Link>
|
}} to={routes.HOME}>home</Link>
|
||||||
</div>
|
|
||||||
<div style={{display: 'inline-block'}} className="button-container-alt">
|
<div style={{display: 'inline-block'}} className="button-container-alt">
|
||||||
<Link style={{
|
<Link style={{
|
||||||
marginRight: 15,
|
marginRight: 15,
|
||||||
@ -787,6 +781,7 @@ export default function Scene ({
|
|||||||
padding: '0 12px'
|
padding: '0 12px'
|
||||||
}} to={routes.REDESIGN_NOTIFICATIONS}>use new redesign</Link>
|
}} to={routes.REDESIGN_NOTIFICATIONS}>use new redesign</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div style={{display: 'inline-block'}} className="button-container-alt">
|
<div style={{display: 'inline-block'}} className="button-container-alt">
|
||||||
<a style={{
|
<a style={{
|
||||||
marginRight: 15,
|
marginRight: 15,
|
||||||
|
@ -14,24 +14,10 @@ import { Reasons, Badges } from '../../constants/reasons';
|
|||||||
import Scene, { getMessageFromReasons } from './Scene';
|
import Scene, { getMessageFromReasons } from './Scene';
|
||||||
import issueIcon from '../../images/issue-bg.png';
|
import issueIcon from '../../images/issue-bg.png';
|
||||||
import prIcon from '../../images/pr-bg.png';
|
import prIcon from '../../images/pr-bg.png';
|
||||||
import tabIcon from '../../images/iconCircle.png';
|
import tabIcon from '../../images/icon.png';
|
||||||
import tabDotIcon from '../../images/iconCircleDotAlt.png';
|
import tabDotIcon from '../../images/iconDot.png';
|
||||||
|
|
||||||
export const PER_PAGE = 10;
|
const PER_PAGE = 10;
|
||||||
|
|
||||||
export const Sort = {
|
|
||||||
TYPE: 1,
|
|
||||||
TITLE: 0,
|
|
||||||
REPOSITORY: 2,
|
|
||||||
SCORE: 3,
|
|
||||||
DATE: 4
|
|
||||||
};
|
|
||||||
|
|
||||||
export const View = {
|
|
||||||
UNREAD: 1,
|
|
||||||
READ: 0,
|
|
||||||
ARCHIVED: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
// @TODO Move these functions.
|
// @TODO Move these functions.
|
||||||
|
|
||||||
@ -146,11 +132,8 @@ class NotificationsPage extends React.Component {
|
|||||||
isSearching: false,
|
isSearching: false,
|
||||||
query: null,
|
query: null,
|
||||||
activeFilter: Filters.PARTICIPATING,
|
activeFilter: Filters.PARTICIPATING,
|
||||||
activeStatus: View.UNREAD,
|
activeStatus: Status.QUEUED,
|
||||||
currentPage: 1,
|
currentPage: 1
|
||||||
sort: Sort.SCORE,
|
|
||||||
descending: false,
|
|
||||||
user: null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@ -162,9 +145,6 @@ class NotificationsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.props.notificationsApi.fetchNotifications();
|
this.props.notificationsApi.fetchNotifications();
|
||||||
this.props.notificationsApi.requestUser().then(user => {
|
|
||||||
this.setState({user});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tabSyncer = setInterval(() => {
|
this.tabSyncer = setInterval(() => {
|
||||||
if (!document.hidden && this.isUnreadTab) {
|
if (!document.hidden && this.isUnreadTab) {
|
||||||
@ -221,7 +201,6 @@ class NotificationsPage extends React.Component {
|
|||||||
|
|
||||||
// Ignore empty queries.
|
// Ignore empty queries.
|
||||||
if (text.length <= 0) {
|
if (text.length <= 0) {
|
||||||
this.onClearQuery();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +210,7 @@ class NotificationsPage extends React.Component {
|
|||||||
query: text,
|
query: text,
|
||||||
isSearching: false
|
isSearching: false
|
||||||
});
|
});
|
||||||
}, 800);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
enhancedOnStageThread = (thread_id, repository) => {
|
enhancedOnStageThread = (thread_id, repository) => {
|
||||||
@ -341,77 +320,33 @@ class NotificationsPage extends React.Component {
|
|||||||
|
|
||||||
const filteredNotifications = notifications.filter(filterMethod);
|
const filteredNotifications = notifications.filter(filterMethod);
|
||||||
|
|
||||||
let notificationsQueued = filteredNotifications.filter(n => n.status === Status.QUEUED);
|
const notificationsQueued = filteredNotifications.filter(n => n.status === Status.QUEUED);
|
||||||
let notificationsStaged = filteredNotifications.filter(n => n.status === Status.STAGED);
|
const notificationsStaged = filteredNotifications.filter(n => n.status === Status.STAGED);
|
||||||
let notificationsClosed = filteredNotifications.filter(n => n.status === Status.CLOSED);
|
const notificationsClosed = filteredNotifications.filter(n => n.status === Status.CLOSED);
|
||||||
|
|
||||||
let notificationsToRender = [];
|
let notificationsToRender = [];
|
||||||
switch (this.state.activeStatus) {
|
switch (this.state.activeStatus) {
|
||||||
case View.ARCHIVED:
|
case Status.CLOSED:
|
||||||
notificationsToRender = notificationsClosed;
|
notificationsToRender = notificationsClosed;
|
||||||
break;
|
break;
|
||||||
case View.READ:
|
case Status.STAGED:
|
||||||
notificationsToRender = notificationsStaged;
|
notificationsToRender = notificationsStaged;
|
||||||
break;
|
break;
|
||||||
case View.UNREAD:
|
case Status.QUEUED:
|
||||||
default:
|
default:
|
||||||
notificationsToRender = notificationsQueued;
|
notificationsToRender = notificationsQueued;
|
||||||
}
|
}
|
||||||
|
|
||||||
let scoredAndSortedNotifications = notificationsToRender
|
let scoredAndSortedNotifications = notificationsToRender
|
||||||
.map(decorateWithScore);
|
.map(decorateWithScore)
|
||||||
|
.sort((a, b) => b.score - a.score);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.state.sort === Sort.DATE) {
|
|
||||||
if (this.state.descending) {
|
|
||||||
scoredAndSortedNotifications.sort((a, b) => moment(a.updated_at).diff(b.updated_at));
|
|
||||||
} else {
|
|
||||||
scoredAndSortedNotifications.sort((a, b) => moment(b.updated_at).diff(a.updated_at));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We gotta make sure to search notifications before we paginate.
|
// We gotta make sure to search notifications before we paginate.
|
||||||
// Otherwise we'd just end up searching on the current page, which is bad.
|
// Otherwise we'd just end up searching on the current page, which is bad.
|
||||||
if (this.state.query) {
|
if (this.state.query) {
|
||||||
scoredAndSortedNotifications = scoredAndSortedNotifications.filter(n => (
|
scoredAndSortedNotifications = scoredAndSortedNotifications.filter(n => (
|
||||||
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
|
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
|
||||||
);
|
)
|
||||||
notificationsQueued = notificationsQueued.filter(n => (
|
|
||||||
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
|
|
||||||
);
|
|
||||||
notificationsStaged = notificationsStaged.filter(n => (
|
|
||||||
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
|
|
||||||
);
|
|
||||||
notificationsClosed = notificationsClosed.filter(n => (
|
|
||||||
n.name.toLowerCase().indexOf(this.state.query.toLowerCase()) > -1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.notificationsApi.newChanges) {
|
if (this.props.notificationsApi.newChanges) {
|
||||||
@ -433,7 +368,7 @@ class NotificationsPage extends React.Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
if (!this.props.authApi.token) {
|
if (!this.props.authApi.token) {
|
||||||
return <Redirect noThrow to={routes.LOGIN} />
|
return <Redirect noThrow to={routes.HOME} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -451,12 +386,6 @@ class NotificationsPage extends React.Component {
|
|||||||
closedCount,
|
closedCount,
|
||||||
} = this.getFilteredNotifications();
|
} = 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 firstIndex = (this.state.currentPage - 1) * PER_PAGE;
|
||||||
let lastIndex = (this.state.currentPage * PER_PAGE);
|
let lastIndex = (this.state.currentPage * PER_PAGE);
|
||||||
let notificationsOnPage = scoredAndSortedNotifications.slice(firstIndex, lastIndex);
|
let notificationsOnPage = scoredAndSortedNotifications.slice(firstIndex, lastIndex);
|
||||||
@ -473,13 +402,7 @@ class NotificationsPage extends React.Component {
|
|||||||
lastNumbered = 0;
|
lastNumbered = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const todayLastWeek = this.state.currentTime.clone().subtract(1, 'week');
|
|
||||||
const stagedTodayCount = this.props.storageApi.getStat('stagedCount')[0];
|
const stagedTodayCount = this.props.storageApi.getStat('stagedCount')[0];
|
||||||
const stagedTodayLastWeekCount = this.props.storageApi.getStat(
|
|
||||||
'stagedCount',
|
|
||||||
todayLastWeek,
|
|
||||||
todayLastWeek.clone().add(1, 'day')
|
|
||||||
)[0];
|
|
||||||
const stagedStatistics = this.props.storageApi.getStat(
|
const stagedStatistics = this.props.storageApi.getStat(
|
||||||
'stagedCount',
|
'stagedCount',
|
||||||
this.state.currentTime.clone().startOf('week').subtract(1, 'week'),
|
this.state.currentTime.clone().startOf('week').subtract(1, 'week'),
|
||||||
@ -489,15 +412,14 @@ class NotificationsPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Scene
|
<Scene
|
||||||
currentTime={this.state.currentTime}
|
currentTime={this.state.currentTime}
|
||||||
readStatistics={stagedStatistics}
|
stagedStatistics={stagedStatistics}
|
||||||
isFirstTimeUser={this.state.isFirstTimeUser}
|
isFirstTimeUser={this.state.isFirstTimeUser}
|
||||||
setNotificationsPermission={this.setNotificationsPermission}
|
setNotificationsPermission={this.setNotificationsPermission}
|
||||||
notificationsPermission={notificationsPermission}
|
notificationsPermission={notificationsPermission}
|
||||||
unreadCount={queuedCount}
|
queuedCount={queuedCount}
|
||||||
readCount={stagedCount}
|
stagedCount={stagedCount}
|
||||||
archivedCount={closedCount}
|
closedCount={closedCount}
|
||||||
readTodayCount={parseInt(stagedTodayCount, 10) || 0}
|
stagedTodayCount={stagedTodayCount || 0}
|
||||||
readTodayLastWeekCount={parseInt(stagedTodayLastWeekCount, 10) || 0}
|
|
||||||
first={firstNumbered}
|
first={firstNumbered}
|
||||||
last={lastNumbered}
|
last={lastNumbered}
|
||||||
lastPage={lastPage}
|
lastPage={lastPage}
|
||||||
@ -523,16 +445,6 @@ class NotificationsPage extends React.Component {
|
|||||||
isFetchingNotifications={isFetchingNotifications}
|
isFetchingNotifications={isFetchingNotifications}
|
||||||
fetchingNotificationsError={fetchingNotificationsError || this.state.error}
|
fetchingNotificationsError={fetchingNotificationsError || this.state.error}
|
||||||
onSetActiveFilter={this.onSetActiveFilter}
|
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}
|
|
||||||
user={this.state.user}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -378,9 +378,19 @@ class NotificationsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
if (this.state.sort === Sort.REPOSITORY) {
|
if (this.state.sort === Sort.REPOSITORY) {
|
||||||
if (this.state.descending) {
|
if (this.state.descending) {
|
||||||
scoredAndSortedNotifications.sort((a, b) => a.repository.localeCompare(b.repository));
|
scoredAndSortedNotifications.sort((a, b) => {
|
||||||
|
const diff = a.repository.localeCompare(b.repository);
|
||||||
|
return diff === 0
|
||||||
|
? b.score - a.score
|
||||||
|
: diff;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
scoredAndSortedNotifications.sort((a, b) => b.repository.localeCompare(a.repository));
|
scoredAndSortedNotifications.sort((a, b) => {
|
||||||
|
const diff = b.repository.localeCompare(a.repository);
|
||||||
|
return diff === 0
|
||||||
|
? b.score - a.score
|
||||||
|
: diff;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.state.sort === Sort.TYPE) {
|
if (this.state.sort === Sort.TYPE) {
|
||||||
|
@ -3,28 +3,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import {navigate} from "@reach/router"
|
||||||
import {css, jsx, keyframes} from '@emotion/core';
|
import {css, jsx, keyframes} from '@emotion/core';
|
||||||
import {useSpring, useTransition, animated} from 'react-spring'
|
import {useSpring, useTransition, animated} from 'react-spring'
|
||||||
import {
|
import {LineChart, Line, XAxis, Tooltip} from 'recharts';
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
Legend
|
|
||||||
} from 'recharts';
|
|
||||||
import Icon from '../../../components/Icon';
|
|
||||||
import Logo from '../../../components/Logo';
|
import Logo from '../../../components/Logo';
|
||||||
import LoadingIcon from '../../../components/LoadingIcon';
|
import LoadingIcon from '../../../components/LoadingIcon'
|
||||||
import {Reasons, Badges} from '../../../constants/reasons';
|
import {routes} from '../../../constants';
|
||||||
|
import {Reasons} from '../../../constants/reasons';
|
||||||
import {withOnEnter} from '../../../enhance';
|
import {withOnEnter} from '../../../enhance';
|
||||||
import {Sort, View} from '../index';
|
import {Sort, View} from '../index';
|
||||||
|
|
||||||
|
const hash = process.env.GIT_HASH ? `#${process.env.GIT_HASH}` : '';
|
||||||
|
const version = require('../../../../package.json').version + hash;
|
||||||
|
|
||||||
const BLUE = '#457cff';
|
const BLUE = '#457cff';
|
||||||
const WHITE = 'rgb(255, 254, 252)';
|
const WHITE = 'rgb(255, 254, 252)';
|
||||||
|
const FOOTER_HEIGHT = '96px';
|
||||||
const COLLAPSED_WIDTH = '72px';
|
const COLLAPSED_WIDTH = '72px';
|
||||||
const EXPANDED_WIDTH = '286px';
|
const EXPANDED_WIDTH = '326px';
|
||||||
const Mode = {
|
const Mode = {
|
||||||
ALL: 0,
|
ALL: 0,
|
||||||
REVIEWS: 1,
|
REVIEWS: 1,
|
||||||
@ -187,8 +184,9 @@ const Item = styled('div')`
|
|||||||
const MenuHeaderItem = styled(Item)`
|
const MenuHeaderItem = styled(Item)`
|
||||||
height: ${COLLAPSED_WIDTH};
|
height: ${COLLAPSED_WIDTH};
|
||||||
width: ${({expand}) => expand ? EXPANDED_WIDTH : COLLAPSED_WIDTH};
|
width: ${({expand}) => expand ? EXPANDED_WIDTH : COLLAPSED_WIDTH};
|
||||||
border-bottom: 1px solid #EDEEF0;
|
border-bottom: 1px solid #292d35;
|
||||||
border-right: 1px solid #EDEEF0;
|
border-right: 1px solid #292d35;
|
||||||
|
background: #2f343e;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -208,11 +206,10 @@ const MenuContainerItem = styled(Item)`
|
|||||||
// Faded gray: #fbfbfb
|
// Faded gray: #fbfbfb
|
||||||
const ContentItem = styled(Item)`
|
const ContentItem = styled(Item)`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: calc(100vh - ${COLLAPSED_WIDTH});
|
min-height: calc(100vh - ${COLLAPSED_WIDTH} - ${FOOTER_HEIGHT});
|
||||||
width: calc(100% - ${COLLAPSED_WIDTH});
|
width: calc(100% - ${COLLAPSED_WIDTH});
|
||||||
background: #f7f6f3;
|
background: #f7f6f3;
|
||||||
padding-bottom: 48px;
|
border-left: 1px solid #292d35;
|
||||||
border-left: 1px solid #E5E6EB;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CardSection = styled('div')`
|
const CardSection = styled('div')`
|
||||||
@ -265,27 +262,27 @@ const IconContainer = styled('div')`
|
|||||||
outline: none;
|
outline: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all 200ms ease;
|
transition: all 200ms ease;
|
||||||
&:after {
|
// &:after {
|
||||||
transition: all 200ms ease;
|
// transition: all 200ms ease;
|
||||||
content: "";
|
// content: "";
|
||||||
position: absolute;
|
// position: absolute;
|
||||||
width: 3px;
|
// width: 3px;
|
||||||
background: ${props => !props.noBorder && props.selected ? props.primary : 'transparent'};
|
// background: ${props => !props.noBorder && props.selected ? '#fff' : 'transparent'};
|
||||||
right: 0;
|
// right: 0;
|
||||||
top: 4px;
|
// top: 4px;
|
||||||
bottom: 4px;
|
// bottom: 4px;
|
||||||
border-top-left-radius: 8px;
|
// border-top-left-radius: 8px;
|
||||||
border-bottom-left-radius: 8px;
|
// border-bottom-left-radius: 8px;
|
||||||
}
|
// }
|
||||||
i {
|
i {
|
||||||
transition: all 200ms ease;
|
transition: all 200ms ease;
|
||||||
color: ${props => props.selected ? props.primary : '#BFC5D1'}
|
color: ${props => props.selected ? WHITE : '#bfc5d15e'}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${props => props.selected ? WHITE : 'rgba(233, 233, 233, .25)'};
|
background: ${props => props.selected ? 'transparent' : 'rgba(233, 233, 233, .1)'};
|
||||||
i {
|
// i {
|
||||||
color: ${props => props.selected ? props.primary : '#616671'}
|
// color: ${props => props.selected ? props.primary : '#616671'}
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -431,7 +428,7 @@ const PageItemComponent = styled('li')`
|
|||||||
|
|
||||||
const InteractionSection = styled('ul')`
|
const InteractionSection = styled('ul')`
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 50px;
|
width: 100px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -456,7 +453,7 @@ const InteractionMenu = styled('div')`
|
|||||||
height: ${props => props.show ? 345 : 0}px;
|
height: ${props => props.show ? 345 : 0}px;
|
||||||
opacity: ${props => props.show ? 1 : 0};
|
opacity: ${props => props.show ? 1 : 0};
|
||||||
top: 32px;
|
top: 32px;
|
||||||
left: 32px;
|
left: 82px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: initial;
|
cursor: initial;
|
||||||
@ -633,6 +630,8 @@ const ProfileContainer = styled('div')`
|
|||||||
padding: 0 22px;
|
padding: 0 22px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
z-index: 3;
|
||||||
|
background: #fffefc;
|
||||||
transition: all 200ms ease;
|
transition: all 200ms ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -660,6 +659,89 @@ const ProfilePicture = styled('img')`
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
function ProfileSection ({user, onLogout}) {
|
||||||
|
const [menuShow, setMenuShow] = React.useState(false);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const body = window.document.querySelector('body');
|
||||||
|
const hideMenu = () => setMenuShow(false);
|
||||||
|
body.addEventListener('click', hideMenu);
|
||||||
|
return () => body.removeEventListener('click', hideMenu);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProfileContainer onClick={() => setMenuShow(true)}>
|
||||||
|
<ProfilePicture src={user.avatar_url} />
|
||||||
|
<ProfileName>{user.name}</ProfileName>
|
||||||
|
<i className="fas fa-caret-down" css={css`
|
||||||
|
transform: ${menuShow ? 'rotate(180deg)' : 'rotate(0deg)'};
|
||||||
|
`}></i>
|
||||||
|
</ProfileContainer>
|
||||||
|
<InteractionMenu show={menuShow} css={css`
|
||||||
|
top: ${COLLAPSED_WIDTH};
|
||||||
|
height: ${menuShow ? 'auto' : '0px'};
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
background: ${WHITE};
|
||||||
|
border: 1px solid #edeef0;
|
||||||
|
width: ${22 + 142 + 22}px;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
user-select: none;
|
||||||
|
box-shadow: rgba(84,70,35,0) 0px 2px 8px, rgba(84,70,35,0.15) 0px 1px 3px;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`}>
|
||||||
|
<div onClick={event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
navigate(routes.HOME);
|
||||||
|
setMenuShow(false);
|
||||||
|
}}>
|
||||||
|
<h2>Go home</h2>
|
||||||
|
<p>Head over back to the home page</p>
|
||||||
|
</div>
|
||||||
|
<div onClick={event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
navigate(routes.NOTIFICATIONS);
|
||||||
|
setMenuShow(false);
|
||||||
|
}}>
|
||||||
|
<h2>Use old design</h2>
|
||||||
|
<p>Switch back to the original Meteorite design</p>
|
||||||
|
</div>
|
||||||
|
<div onClick={event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
onLogout();
|
||||||
|
setMenuShow(false);
|
||||||
|
}}>
|
||||||
|
<h2>Sign out</h2>
|
||||||
|
<p>Log off your account and return to home page</p>
|
||||||
|
</div>
|
||||||
|
</InteractionMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const NotificationIconWrapper = styled('div')`
|
const NotificationIconWrapper = styled('div')`
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
@ -717,16 +799,40 @@ const Divider = styled('div')`
|
|||||||
const RepoBarContainer = styled('div')`
|
const RepoBarContainer = styled('div')`
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 22px;
|
margin-bottom: 28px;
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
margin: 10px 0;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
margin: 2px 0 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
color: #bfc5d1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LinkText = styled('div')`
|
||||||
|
text-decoration: underline;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #37352f59;
|
||||||
|
font-weight: 500;
|
||||||
|
text-underline-position: under;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
&:hover {
|
||||||
|
color: #37352faa;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -817,13 +923,52 @@ function CustomTick ({x, y, payload}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RepoBarGroup ({reposReadCounts, highestRepoReadCount, colorOfRepoCount}) {
|
||||||
|
const [show, setShow] = React.useState(false);
|
||||||
|
const repos = Object.keys(reposReadCounts).sort((a, b) => reposReadCounts[b] - reposReadCounts[a]);
|
||||||
|
|
||||||
|
const shownRepos = repos.slice(0, 4);
|
||||||
|
const hiddenRepos = repos.slice(4);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{shownRepos.map(repo => (
|
||||||
|
<RepoBar
|
||||||
|
name={repo}
|
||||||
|
value={reposReadCounts[repo]}
|
||||||
|
max={highestRepoReadCount}
|
||||||
|
colorOfValue={colorOfRepoCount}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{hiddenRepos.length > 0 && (
|
||||||
|
show ? (
|
||||||
|
<>
|
||||||
|
{hiddenRepos.map(repo => (
|
||||||
|
<RepoBar
|
||||||
|
name={repo}
|
||||||
|
value={reposReadCounts[repo]}
|
||||||
|
max={highestRepoReadCount}
|
||||||
|
colorOfValue={colorOfRepoCount}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<LinkText onClick={() => setShow(false)}>Show less</LinkText>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<LinkText onClick={() => setShow(true)}>Show more</LinkText>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RepoBar ({name, value, max, colorOfValue}) {
|
function RepoBar ({name, value, max, colorOfValue}) {
|
||||||
return (
|
return (
|
||||||
<RepoBarContainer>
|
<RepoBarContainer>
|
||||||
<p>@{name}</p>
|
<p>{name.split('/')[1]}</p>
|
||||||
|
<span>{name.split('/')[0]}</span>
|
||||||
<Bar
|
<Bar
|
||||||
color={BLUE}
|
title={value}
|
||||||
// color={colorOfValue(value)}
|
color={'#4880ffd1'}
|
||||||
value={value / max}
|
value={value / max}
|
||||||
/>
|
/>
|
||||||
</RepoBarContainer>
|
</RepoBarContainer>
|
||||||
@ -948,6 +1093,7 @@ export default function Scene ({
|
|||||||
reposReadCounts,
|
reposReadCounts,
|
||||||
readTodayLastWeekCount,
|
readTodayLastWeekCount,
|
||||||
onRestoreThread,
|
onRestoreThread,
|
||||||
|
onLogout,
|
||||||
}) {
|
}) {
|
||||||
const hasNotificationsOn = notificationsPermission === 'granted';
|
const hasNotificationsOn = notificationsPermission === 'granted';
|
||||||
const [menuOpen, setMenuOpen] = React.useState(false);
|
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||||
@ -980,6 +1126,7 @@ export default function Scene ({
|
|||||||
|
|
||||||
// Global event listeners for things like the dropdowns & popups.
|
// Global event listeners for things like the dropdowns & popups.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
const body = window.document.querySelector('body');
|
const body = window.document.querySelector('body');
|
||||||
const hideDropdownMenu = () => setDropdownOpen(false);
|
const hideDropdownMenu = () => setDropdownOpen(false);
|
||||||
body.addEventListener('click', hideDropdownMenu);
|
body.addEventListener('click', hideDropdownMenu);
|
||||||
@ -1049,18 +1196,13 @@ export default function Scene ({
|
|||||||
onClick={() => window.scrollTo(0, 0)}
|
onClick={() => window.scrollTo(0, 0)}
|
||||||
size={32}
|
size={32}
|
||||||
/>
|
/>
|
||||||
{user && (
|
{user && <ProfileSection user={user} onLogout={onLogout} />}
|
||||||
<ProfileContainer>
|
|
||||||
<ProfilePicture src={user.avatar_url} />
|
|
||||||
<ProfileName>{user.name}</ProfileName>
|
|
||||||
<i className="fas fa-caret-down"></i>
|
|
||||||
</ProfileContainer>
|
|
||||||
)}
|
|
||||||
</ContentHeaderItem>
|
</ContentHeaderItem>
|
||||||
</Row>
|
</Row>
|
||||||
<Row css={css`
|
<Row css={css`
|
||||||
height: calc(100% - ${COLLAPSED_WIDTH});
|
height: calc(100% - ${COLLAPSED_WIDTH});
|
||||||
margin-top: ${COLLAPSED_WIDTH};
|
margin-top: ${COLLAPSED_WIDTH};
|
||||||
|
background: #2f343e;
|
||||||
`}>
|
`}>
|
||||||
<MenuContainerItem expand={menuOpen}>
|
<MenuContainerItem expand={menuOpen}>
|
||||||
<MenuIconItem
|
<MenuIconItem
|
||||||
@ -1124,21 +1266,40 @@ export default function Scene ({
|
|||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle>{'Activity'}</CardTitle>
|
<CardTitle>{'Activity'}</CardTitle>
|
||||||
<CardSubTitle css={css`margin-bottom: 22px;`}>{'Your interactions'}</CardSubTitle>
|
<CardSubTitle css={css`margin-bottom: 22px;`}>{'Interactions by repository'}</CardSubTitle>
|
||||||
{Object.keys(reposReadCounts).sort((a, b) => a.localeCompare(b)).map(repo => (
|
<RepoBarGroup
|
||||||
<RepoBar
|
reposReadCounts={reposReadCounts}
|
||||||
name={repo}
|
highestRepoReadCount={highestRepoReadCount}
|
||||||
value={reposReadCounts[repo]}
|
colorOfRepoCount={colorOfRepoCount}
|
||||||
max={highestRepoReadCount}
|
|
||||||
colorOfValue={colorOfRepoCount}
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</Card>
|
</Card>
|
||||||
</CardSection>
|
</CardSection>
|
||||||
<NotificationsSection>
|
<NotificationsSection>
|
||||||
<TitleSection>
|
<TitleSection>
|
||||||
<Title>{'Updates'}</Title>
|
<Title>{'Updates'}</Title>
|
||||||
<InteractionSection>
|
<InteractionSection>
|
||||||
|
<li onClick={event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
switch(notificationsPermission) {
|
||||||
|
case 'granted':
|
||||||
|
return setNotificationsPermission('denied');
|
||||||
|
case 'denied':
|
||||||
|
case 'default':
|
||||||
|
default:
|
||||||
|
Notification.requestPermission().then(result => {
|
||||||
|
return setNotificationsPermission(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setDropdownOpen(false);
|
||||||
|
}}>
|
||||||
|
<IconLink>
|
||||||
|
{hasNotificationsOn ? (
|
||||||
|
<i class="fas fa-bell"></i>
|
||||||
|
) : (
|
||||||
|
<i class="fas fa-bell-slash"></i>
|
||||||
|
)}
|
||||||
|
</IconLink>
|
||||||
|
</li>
|
||||||
<li onClick={() => setDropdownOpen(true)}>
|
<li onClick={() => setDropdownOpen(true)}>
|
||||||
<IconLink>
|
<IconLink>
|
||||||
<i className="fas fa-ellipsis-v"></i>
|
<i className="fas fa-ellipsis-v"></i>
|
||||||
@ -1407,6 +1568,48 @@ export default function Scene ({
|
|||||||
</NotificationsSection>
|
</NotificationsSection>
|
||||||
</ContentItem>
|
</ContentItem>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row css={css`
|
||||||
|
height: calc(100% - ${COLLAPSED_WIDTH});
|
||||||
|
background: #2f343e;
|
||||||
|
`}>
|
||||||
|
<MenuContainerItem expand={menuOpen}>
|
||||||
|
</MenuContainerItem>
|
||||||
|
<ContentItem css={css`
|
||||||
|
min-height: ${FOOTER_HEIGHT};
|
||||||
|
height: ${FOOTER_HEIGHT};
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #37352f52;
|
||||||
|
margin: 0 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: underline;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #37352f52;
|
||||||
|
margin: 0 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
text-underline-position: under;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
&:hover {
|
||||||
|
color: #37352faa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}>
|
||||||
|
<a target="_blank" href="https://github.com/nickzuber/meteorite/issues">Submit bugs</a>
|
||||||
|
<a target="_blank" href="https://github.com/nickzuber/meteorite/pulls">Make changes</a>
|
||||||
|
<a target="_blank" href="https://github.com/nickzuber/meteorite/issues">Leave feedback</a>
|
||||||
|
<a target="_blank" href="https://github.com/nickzuber/meteorite">See source code</a>
|
||||||
|
<a target="_blank" href="https://twitter.com/nick_zuber">Follow me on twitter</a>
|
||||||
|
<span css={css`margin-right: 76px !important;`}>v{version}</span>
|
||||||
|
</ContentItem>
|
||||||
|
</Row>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user