This commit is contained in:
Nicholas Zuber 2019-04-01 19:38:34 -04:00
parent 88bb99ac0a
commit 5344bf8f46
5 changed files with 326 additions and 14 deletions

149
package-lock.json generated
View File

@ -3939,6 +3939,11 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decimal.js-light": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.0.tgz",
"integrity": "sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg=="
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@ -4170,6 +4175,29 @@
"utila": "~0.4"
}
},
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"requires": {
"@babel/runtime": "^7.1.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.2.tgz",
"integrity": "sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==",
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
}
}
},
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@ -8177,6 +8205,11 @@
"lodash._reinterpolate": "~3.0.0"
}
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -8258,6 +8291,11 @@
"object-visit": "^1.0.0"
}
},
"math-expression-evaluator": {
"version": "1.2.17",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
"integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw="
},
"math-random": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
@ -12181,6 +12219,28 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-resize-detector": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-2.3.0.tgz",
"integrity": "sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ==",
"requires": {
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"prop-types": "^15.6.0",
"resize-observer-polyfill": "^1.5.0"
}
},
"react-smooth": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-1.0.2.tgz",
"integrity": "sha512-pIGzL1g9VGAsRsdZQokIK0vrCkcdKtnOnS1gyB2rrowdLy69lNSWoIjCTWAfgbiYvria8tm5hEZqj+jwXMkV4A==",
"requires": {
"lodash": "~4.17.4",
"prop-types": "^15.6.0",
"raf": "^3.4.0",
"react-transition-group": "^2.5.0"
}
},
"react-spring": {
"version": "8.0.18",
"resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.18.tgz",
@ -12213,6 +12273,17 @@
"prop-types": "^15.5.8"
}
},
"react-transition-group": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.7.1.tgz",
"integrity": "sha512-b0VJTzNRnXxRpCuxng6QJbAzmmrhBn1BZJfPPnHbH2PIo8msdkajqwtfdyGm/OypPXZNfAHKEqeN15wjMXrRJQ==",
"requires": {
"dom-helpers": "^3.3.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@ -12567,6 +12638,47 @@
"util.promisify": "^1.0.0"
}
},
"recharts": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-1.5.0.tgz",
"integrity": "sha512-bsKJRvh4NepPJlXoI4L9oRKhrv59W7fjs6qTqk6le9MFAywe8EYiW/t07RDQHBVIj6icb+UdSFsHxEo8c5AcTg==",
"requires": {
"classnames": "~2.2.5",
"core-js": "~2.5.1",
"d3-interpolate": "~1.3.0",
"d3-scale": "~2.1.0",
"d3-shape": "~1.2.0",
"lodash": "~4.17.5",
"prop-types": "~15.6.0",
"react-resize-detector": "~2.3.0",
"react-smooth": "~1.0.0",
"recharts-scale": "^0.4.2",
"reduce-css-calc": "~1.3.0"
},
"dependencies": {
"d3-scale": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.1.2.tgz",
"integrity": "sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==",
"requires": {
"d3-array": "^1.2.0",
"d3-collection": "1",
"d3-format": "1",
"d3-interpolate": "1",
"d3-time": "1",
"d3-time-format": "2"
}
}
}
},
"recharts-scale": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.2.tgz",
"integrity": "sha512-p/cKt7j17D1CImLgX2f5+6IXLbRHGUQkogIp06VUoci/XkhOQiGSzUrsD1uRmiI7jha4u8XNFOjkHkzzBPivMg==",
"requires": {
"decimal.js-light": "^2.4.1"
}
},
"recompose": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
@ -12588,6 +12700,38 @@
"minimatch": "3.0.4"
}
},
"reduce-css-calc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
"integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=",
"requires": {
"balanced-match": "^0.4.2",
"math-expression-evaluator": "^1.2.14",
"reduce-function-call": "^1.0.1"
},
"dependencies": {
"balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
}
}
},
"reduce-function-call": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz",
"integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=",
"requires": {
"balanced-match": "^0.4.2"
},
"dependencies": {
"balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
}
}
},
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
@ -12805,6 +12949,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",

View File

@ -54,6 +54,7 @@
"react-emotion": "^9.2.12",
"react-spring": "^8.0.18",
"react-svg-inline": "^2.1.1",
"recharts": "^1.5.0",
"recompose": "^0.30.0",
"resolve": "1.8.1",
"sass-loader": "7.1.0",

View File

@ -473,7 +473,14 @@ class NotificationsPage extends React.Component {
lastNumbered = 0;
}
const todayLastWeek = this.state.currentTime.clone().subtract(1, 'week');
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(
'stagedCount',
this.state.currentTime.clone().startOf('week').subtract(1, 'week'),
@ -483,14 +490,15 @@ class NotificationsPage extends React.Component {
return (
<Scene
currentTime={this.state.currentTime}
stagedStatistics={stagedStatistics}
readStatistics={stagedStatistics}
isFirstTimeUser={this.state.isFirstTimeUser}
setNotificationsPermission={this.setNotificationsPermission}
notificationsPermission={notificationsPermission}
unreadCount={queuedCount}
readCount={stagedCount}
archivedCount={closedCount}
stagedTodayCount={stagedTodayCount || 0}
readTodayCount={stagedTodayCount || 0}
readTodayLastWeekCount={stagedTodayLastWeekCount || 0}
first={firstNumbered}
last={lastNumbered}
lastPage={lastPage}

View File

@ -5,6 +5,15 @@ import moment from 'moment';
import styled from '@emotion/styled';
import {css, jsx, keyframes} from '@emotion/core';
import {useSpring, useTransition, animated} from 'react-spring'
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend
} from 'recharts';
import Icon from '../../../components/Icon';
import Logo from '../../../components/Logo';
import LoadingIcon from '../../../components/LoadingIcon';
@ -118,6 +127,24 @@ function createColorOfScore (min, max) {
}
}
function getPercentageDelta (n, o) {
if (n === o) {
return 0;
} else if (n > o) {
return (n - o) / o * 100;
} else {
return (o - n) / o * 100;
}
}
function prettify (n) {
if (Math.floor(n) === n) {
return n.toString();
} else {
return n.toFixed(1);
}
}
// ========================================================================
// END OF 'MOVE TO A UTILS FILE'
// ========================================================================
@ -194,9 +221,10 @@ const CardSection = styled('div')`
`;
const Card = styled('div')`
position: relative;
width: 250px;
padding: 20px 24px;
height: 100px;
min-height: 100px;
margin: 32px auto 0;
background: ${WHITE};
border: 1px solid #E5E6EB;
@ -205,9 +233,22 @@ const Card = styled('div')`
`;
const CardTitle = styled(Title)`
line-height: 1.5em;
font-size: 1.5em;
`;
const CardSubTitle = styled(CardTitle)`
font-size: 1.2em;
color: #9d9b97;
`;
const ScoreDiff = styled(CardTitle)`
position: absolute;
top: 30px;
right: 24px;
color: ${props => props.under ? '#ef055f' : '#457cff'};
`;
const IconContainer = styled('div')`
position: relative;
height: 72px;
@ -223,7 +264,7 @@ const IconContainer = styled('div')`
content: "";
position: absolute;
width: 3px;
background: ${props => !props.noBorder && props.selected ? props.primary : WHITE};
background: ${props => !props.noBorder && props.selected ? props.primary : 'transparent'};
right: 0;
top: 4px;
bottom: 4px;
@ -270,7 +311,7 @@ const SubTitleSection = styled('div')`
margin: 8px 0;
font-weight: 500;
font-size: 1rem;
color: #19223dab;
color: #9d9b97;
}
`;
@ -715,6 +756,97 @@ function SortingItem ({children, selected, onChange, descending, setDescending,
);
}
function CustomTick ({x, y, stroke, payload}) {
if (!payload) return null;
return (
<g transform={`translate(${x},${y})`}>
<text
x={0}
y={0}
dy={16}
textAnchor="middle"
fill="#BFC5D1"
fontWeight="600"
transform="scale(0.65)"
>
{payload.value.substring(0, 2)}
</text>
</g>
);
}
function ReadCountGraph ({data}) {
return (
<LineChart width={250} height={200} data={data}>
<XAxis
dataKey="name"
interval={0}
tickSize={5}
tick={<CustomTick />}
tickFormatter={tick => tick.substring(0, 2)}
/>
<Tooltip
wrapperStyle={{
opacity: 0.9
}}
contentStyle={{
background: 'rgb(255,254,252)',
border: '1px solid #E5E6EB',
borderRadius: 6,
boxShadow: 'rgba(84,70,35,0) 0px 2px 8px, rgba(84,70,35,0.15) 0px 1px 3px',
minWidth: 100,
padding: '6px 12px 8px'
}}
itemStyle={{
fontSize: 12,
fontWeight: '500',
padding: 0,
opacity: .75
}}
labelStyle={{
color: 'rgb(55, 53, 47)',
fontSize: 14,
fontWeight: '600',
padding: 0
}}
cursor={{
stroke: '#BFC5D144',
strokeWidth: 2
}}
animationDuration={400}
formatter={(value, name) => {
switch (name) {
case 'cur':
return [value, 'This week'];
case 'prev':
return [value, 'Last week'];
default:
return [value, name];
}
}}
/>
<Line
type="monotone"
dataKey="prev"
stroke="#BFC5D166"
strokeWidth="2"
animationDuration={0}
dot={{ stroke: '#00000000', fill: '#00000000', r: 0 }}
activeDot={{ stroke: '#BFC5D166', fill: '#BFC5D166', r: 2 }}
/>
<Line
type="monotone"
dataKey="cur"
stroke="#4880ff"
strokeWidth="2"
animationDuration={0}
dot={{ stroke: '#00000000', fill: '#00000000', r: 0 }}
activeDot={{ stroke: '#4880ff', fill: '#4880ff', r: 2 }}
/>
</LineChart>
);
}
export default function Scene ({
notifications,
notificationsPermission,
@ -747,20 +879,30 @@ export default function Scene ({
setNotificationsPermission,
onStageThread,
onArchiveThread,
readStatistics,
readTodayCount,
readTodayLastWeekCount,
}) {
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));
}
}
readStatistics = readStatistics.map(n => parseInt(n, 10));
const lastWeekStats = readStatistics.slice(0, 7).map(n => n || null);
const thisWeekStats = readStatistics.slice(7).map(n => n || null);
const percentageDeltaToday = getPercentageDelta(readTodayCount, readTodayLastWeekCount);
const data = [
{name: 'Sunday', cur: thisWeekStats[0], prev: lastWeekStats[0]},
{name: 'Monday', cur: thisWeekStats[1], prev: lastWeekStats[1]},
{name: 'Tuesday', cur: thisWeekStats[2], prev: lastWeekStats[2]},
{name: 'Wednesday', cur: thisWeekStats[3], prev: lastWeekStats[3]},
{name: 'Thursday', cur: thisWeekStats[4], prev: lastWeekStats[4]},
{name: 'Friday', cur: thisWeekStats[5], prev: lastWeekStats[5]},
{name: 'Saturday', cur: thisWeekStats[6], prev: lastWeekStats[6]},
];
// const notificationTransitions = useTransition(notifications, item => item.id, {
// from: {background: 'green', opacity: 0, transform: 'translate3d(50px, 0, 0)'},
@ -881,7 +1023,14 @@ export default function Scene ({
<ContentItem>
<CardSection>
<Card>
<CardTitle>{currentTime.format('dddd, MMMM Do')}</CardTitle>
<CardTitle>{currentTime.format('dddd')}</CardTitle>
<CardSubTitle>{currentTime.format('MMMM Do, YYYY')}</CardSubTitle>
<ScoreDiff under={readTodayLastWeekCount > readTodayCount}>
{readTodayLastWeekCount > readTodayCount ? '-' : '+'}
{prettify(percentageDeltaToday)}
{'%'}
</ScoreDiff>
<ReadCountGraph data={data} />
</Card>
<Card />
</CardSection>

View File

@ -90,6 +90,11 @@ div {
vertical-align: top;
}
line[stroke="#666"] {
stroke-width: 2;
stroke: #e5e7ea;
}
.react-tooltip {
z-index: 999999;
pointer-events: none;