Add toasts, condense repository column

This commit is contained in:
Nicholas Zuber 2020-02-16 15:08:12 -05:00
parent 7ef2aab9ce
commit 2c7440e2e3
4 changed files with 429 additions and 68 deletions

149
package-lock.json generated
View File

@ -14948,6 +14948,155 @@
"prop-types": "^15.5.8"
}
},
"react-toast-notifications": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/react-toast-notifications/-/react-toast-notifications-2.4.0.tgz",
"integrity": "sha512-8tkrbNh7LxkiFmtqAL/AiI55efIeI+fBk3c6ImsiZ0VObb4yvOq0cqYuJHyUiv9fuD2aBxvXGVH+n4Snt8qoWA==",
"requires": {
"@emotion/core": "^10.0.14",
"react-transition-group": "^4.3.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz",
"integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==",
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"@emotion/cache": {
"version": "10.0.27",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.27.tgz",
"integrity": "sha512-Zp8BEpbMunFsTcqAK4D7YTm3MvCp1SekflSLJH8lze2fCcSZ/yMkXHo8kb3t1/1Tdd3hAqf3Fb7z9VZ+FMiC9w==",
"requires": {
"@emotion/sheet": "0.9.4",
"@emotion/stylis": "0.8.5",
"@emotion/utils": "0.11.3",
"@emotion/weak-memoize": "0.2.5"
}
},
"@emotion/core": {
"version": "10.0.27",
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.27.tgz",
"integrity": "sha512-XbD5R36pVbohQMnKfajHv43g8EbN4NHdF6Zh9zg/C0nr0jqwOw3gYnC07Xj3yG43OYSRyrGsoQ5qPwc8ycvLZw==",
"requires": {
"@babel/runtime": "^7.5.5",
"@emotion/cache": "^10.0.27",
"@emotion/css": "^10.0.27",
"@emotion/serialize": "^0.11.15",
"@emotion/sheet": "0.9.4",
"@emotion/utils": "0.11.3"
}
},
"@emotion/css": {
"version": "10.0.27",
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz",
"integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==",
"requires": {
"@emotion/serialize": "^0.11.15",
"@emotion/utils": "0.11.3",
"babel-plugin-emotion": "^10.0.27"
}
},
"@emotion/hash": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz",
"integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A=="
},
"@emotion/memoize": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
},
"@emotion/serialize": {
"version": "0.11.15",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.15.tgz",
"integrity": "sha512-YE+qnrmGwyR+XB5j7Bi+0GT1JWsdcjM/d4POu+TXkcnrRs4RFCCsi3d/Ebf+wSStHqAlTT2+dfd+b9N9EO2KBg==",
"requires": {
"@emotion/hash": "0.7.4",
"@emotion/memoize": "0.7.4",
"@emotion/unitless": "0.7.5",
"@emotion/utils": "0.11.3",
"csstype": "^2.5.7"
}
},
"@emotion/sheet": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz",
"integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA=="
},
"@emotion/stylis": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
"integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
},
"@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"@emotion/utils": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz",
"integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw=="
},
"@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"babel-plugin-emotion": {
"version": "10.0.27",
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.27.tgz",
"integrity": "sha512-SUNYcT4FqhOqvwv0z1oeYhqgheU8qrceLojuHyX17ngo7WtWqN5I9l3IGHzf21Xraj465CVzF4IvOlAF+3ed0A==",
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"@emotion/hash": "0.7.4",
"@emotion/memoize": "0.7.4",
"@emotion/serialize": "^0.11.15",
"babel-plugin-macros": "^2.0.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^1.0.5",
"find-root": "^1.1.0",
"source-map": "^0.5.7"
}
},
"dom-helpers": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz",
"integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==",
"requires": {
"@babel/runtime": "^7.6.3",
"csstype": "^2.6.7"
},
"dependencies": {
"csstype": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz",
"integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA=="
}
}
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
"integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
}
}
},
"react-transition-group": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.7.1.tgz",

View File

@ -63,6 +63,7 @@
"react-ga": "^2.7.0",
"react-spring": "^8.0.18",
"react-svg-inline": "^2.1.1",
"react-toast-notifications": "^2.4.0",
"recharts": "^1.5.0",
"recompose": "^0.30.0",
"resolve": "1.8.1",

View File

@ -1,8 +1,10 @@
/** @jsx jsx */
import React from 'react';
import styled from '@emotion/styled';
import {animated} from 'react-spring'
import {css, jsx} from '@emotion/core';
import { compose } from 'recompose';
import {useSpring} from 'react-spring'
import {AreaChart, Area, XAxis, Tooltip} from 'recharts';
import {ReactComponent as BlankCanvasSvg} from '../../../images/svg/blank.svg'
@ -76,11 +78,135 @@ import {
ThemeContext,
optimized
} from './ui';
import {ToastProvider, useToasts} from 'react-toast-notifications';
export const AnimatedNotificationRow = animated(NotificationRow);
const hash = process.localEnv.GIT_HASH ? `#${process.localEnv.GIT_HASH}` : '';
const version = require('../../../../package.json').version + hash;
const snackStates = {
entering: 'transform: translateX(-120%); opacity: 0',
entered: 'transform: translateX(0%); opacity: 1',
exiting: 'transform: scale(0.9); opacity: 0',
exited: 'transform: scale(0.9); opacity: 0'
};
const Snack = ({
children,
transitionDuration,
transitionState,
onDismiss,
action,
dark,
onUndo
}) => {
return (
<div css={css`
display: flex;
align-items: center;
justify-content: space-between;
background: ${dark ? DarkTheme.SecondaryAlt : WHITE};
border: 1px solid ${dark ? DarkTheme.Secondary : '#ebecee'};
box-shadow: rgba(0,0,0,0) 0px 2px 8px, rgba(0,0,0,0.25) 0px 2px 6px;
border-radius: 6px;
margin: 8px;
overflow: hidden;
height: 40px;
max-width: 560px;
min-width: 400px;
padding: 8px 16px;
transition: all 200ms ease;
transform: translateX(-120%);
${snackStates[transitionState]};
`}>
<div css={css`
background: ${ThemeColor(dark)}29;
border-radius: 100%;
height: 30px;
width: 30px;
display: flex;
justify-content: center;
align-items: center;
i {
color: ${ThemeColor(dark)};
}
`}>
{action === 'read' ? (
<i className="fas fa-check"></i>
) : (
<i className="fas fa-times"></i>
)}
</div>
<div css={css`
margin: 8px 20px;
display: flex;
flex-direction: column;
align-items: end;
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 400px;
}
`}>
{children}
</div>
<div css={css`
user-select: none;
cursor: pointer;
height: 100%;
position: relative;
flex: 1;
text-align: right;
min-width: 40px;
`}>
<span css={css`
font-size: 13px;
font-weight: 500;
color: ${dark ? WHITE : 'inherit'};
transition: all 150ms ease;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 0;
&:hover {
opacity: 0.6;
}
&:active {
opacity: 0.4;
}
`} onClick={() => {
onDismiss();
onUndo();
}}>
{'Undo'}
</span>
</div>
</div>
);
}
const withToastProvider = WrappedComponent => props => (
<ToastProvider
// autoDismiss
autoDismissTimeout={5000}
components={{Toast: Snack}}
placement="bottom-left"
>
<WrappedComponent {...props} />
</ToastProvider>
);
const withToasts = WrappedComponent => props => {
const {addToast} = useToasts();
return (
<WrappedComponent addToast={addToast} {...props} />
);
};
function BasePageItem ({children, onChange, ...props}) {
return (
<PageItemComponent
@ -94,6 +220,20 @@ function BasePageItem ({children, onChange, ...props}) {
const PageItem = withTooltip(BasePageItem);
const ToastTitle = styled('div')(p => `
font-size: 13px;
font-weight: 500;
margin: 2px 0;
color: ${p.dark ? WHITE : 'inherit'};
`);
const ToastByline = styled('div')`
font-size: 12px;
font-weight: 500;
color: #8893a7cc;
margin: 2px 0;
`;
function MenuIconItem ({children, onChange, selected, alwaysActive, noBorder, ...props}) {
return (
<IconContainer
@ -301,7 +441,7 @@ function ReadCountGraph ({data, onHover, onExit, dark}) {
);
}
export default function Scene ({
function Scene ({
notifications,
notificationsPermission,
currentTime,
@ -343,7 +483,8 @@ export default function Scene ({
mode,
setMode,
getUserItem,
setUserItem
setUserItem,
addToast
}) {
const hasNotificationsOn = notificationsPermission === 'granted';
const [darkMode, setDarkMode] = React.useState(getUserItem('dark-mode-enabled'));
@ -355,6 +496,58 @@ export default function Scene ({
prev: readTodayLastWeekCount
});
const onStageThreadWithToast = (thread_id, repository) => {
const notification = notifications.find(({id}) => id === thread_id);
const {title, tags} = extractJiraTags(notification.name);
addToast((
<React.Fragment>
<ToastTitle dark={darkMode}>
{tags.map(tag => (
<JiraTag key={tag} css={css`vertical-align: middle;`} color={colorOfTag(tag)}>
{tag}
</JiraTag>
))}
{title}
</ToastTitle>
<ToastByline>
{'Notification was marked as read'}
</ToastByline>
</React.Fragment>
), {
dark: darkMode,
action: 'read',
onUndo: () => onRestoreThread(thread_id)
});
onStageThread(thread_id, repository);
}
const onArchiveThreadWithToast = (thread_id, repository) => {
const notification = notifications.find(({id}) => id === thread_id);
const {title, tags} = extractJiraTags(notification.name);
addToast((
<React.Fragment>
<ToastTitle dark={darkMode}>
{tags.map(tag => (
<JiraTag key={tag} css={css`vertical-align: middle;`} color={colorOfTag(tag)}>
{tag}
</JiraTag>
))}
{title}
</ToastTitle>
<ToastByline>
{'Notification was marked as archived'}
</ToastByline>
</React.Fragment>
), {
dark: darkMode,
action: 'archive',
onUndo: () => onRestoreThread(thread_id)
});
onArchiveThread(thread_id, repository);
}
readStatistics = readStatistics.map(n => parseInt(n, 10));
const lastWeekStats = readStatistics.slice(0, 7);
const thisWeekStats = readStatistics.slice(7);
@ -926,8 +1119,8 @@ export default function Scene ({
fact={fact}
notifications={notifications}
colorOfScore={createColorOfScore(lowestScore, highestScore)}
markAsRead={onStageThread}
markAsArchived={onArchiveThread}
markAsRead={onStageThreadWithToast}
markAsArchived={onArchiveThreadWithToast}
markAsUnread={onRestoreThread}
user={user}
/>
@ -1156,6 +1349,7 @@ function ActionItems ({item, view, markAsRead, markAsArchived, markAsUnread}) {
tooltip="Mark as archived"
onClick={() => markAsArchived(item.id, item.repository)}
>
{/* <i className="fas fa-thumbtack"></i> */}
<i className="fas fa-times"></i>
</IconLink>
</>
@ -1198,3 +1392,10 @@ function ActionItems ({item, view, markAsRead, markAsArchived, markAsUnread}) {
return null
}
}
const enhance = compose(
withToastProvider,
withToasts
)
export default enhance(Scene);

View File

@ -1,4 +1,4 @@
@import url('https://rsms.me/inter/inter-ui.css');
@import url("https://rsms.me/inter/inter-ui.css");
/* ::selection {
color: rgb(255, 254, 252);
@ -6,98 +6,108 @@
} */
* {
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}
html, body {
/* height: 100%; */
width: 100%;
scroll-behavior: smooth;
html,
body {
/* height: 100%; */
width: 100%;
scroll-behavior: smooth;
}
html, body, * {
font-family: 'Inter UI', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 14px;
/* color: #202124; */
/* color: #19233C; */
color: rgb(20, 19, 19);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
html,
body,
* {
font-family: "Inter UI", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
font-size: 14px;
/* color: #202124; */
/* color: #19233C; */
color: rgb(20, 19, 19);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
background: rgb(255, 254, 253);
margin: 0;
padding: 0;
background: rgb(255, 254, 253);
margin: 0;
padding: 0;
}
h1 {
font-weight: 600;
font-size: 48px;
letter-spacing: -0.05px;
font-size: 52px;
letter-spacing: -0.75px;
font-weight: 600;
font-size: 48px;
letter-spacing: -0.05px;
font-size: 52px;
letter-spacing: -0.75px;
}
h2 {
font-weight: 500;
font-size: 38px;
letter-spacing: -0.05px;
font-weight: 500;
font-size: 38px;
letter-spacing: -0.05px;
}
h3 {
font-weight: 500;
font-size: 28px;
letter-spacing: -0.05px;
margin: 10px 0 20px;
font-weight: 500;
font-size: 28px;
letter-spacing: -0.05px;
margin: 10px 0 20px;
}
p {
font: 15px/22px 'Inter UI', system-ui, sans-serif;
color: #37352f;;
line-height: 1.5;
letter-spacing: -0.002em;
font-weight: 400;
margin: 20px 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
font-kerning: normal;
-moz-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
-ms-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
-o-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
-webkit-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
font: 15px/22px "Inter UI", system-ui, sans-serif;
color: #37352f;
line-height: 1.5;
letter-spacing: -0.002em;
font-weight: 400;
margin: 20px 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
font-kerning: normal;
-moz-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
-ms-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
-o-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
-webkit-font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
font-feature-settings: "calt" 1, "kern" 1, "liga" 1;
}
div {
vertical-align: top;
vertical-align: top;
}
svg { overflow: visible; }
svg {
overflow: visible;
}
line[stroke="#666"] {
stroke-width: 2;
stroke: #BFC5D166;
stroke-width: 2;
stroke: #bfc5d166;
}
a {
text-underline-position: under;
transition: all 200ms ease;
text-underline-position: under;
transition: all 200ms ease;
}
.react-tooltip {
z-index: 999999;
pointer-events: none;
position: absolute;
background: #10293c;
color: #fff;
padding: 4px 8px;
font-weight: 600;
font-size: 11px;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
transition: all 75ms ease-in;
z-index: 999999;
pointer-events: none;
position: absolute;
background: #10293c;
color: #fff;
padding: 4px 8px;
font-weight: 600;
font-size: 11px;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
transition: all 75ms ease-in;
}
.react-toast-notifications__container {
overflow: hidden !important;
}