mirror of
https://github.com/nickzuber/meteorite.git
synced 2024-08-16 23:10:38 +03:00
storage
This commit is contained in:
parent
698d9d44a2
commit
4be84327ce
@ -3,7 +3,7 @@
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(1 1)" stroke-width="2.6">
|
||||
<circle stroke-opacity=".5" cx="9" cy="9" r="9"/>
|
||||
<path stroke="#9065ff" d="M18,9 C18,4.03 13.97,0 9,0">
|
||||
<path stroke="#6772e5" d="M18,9 C18,4.03 13.97,0 9,0">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
|
Before Width: | Height: | Size: 713 B After Width: | Height: | Size: 713 B |
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import meteoriteSvg from './meteorite.svg';
|
||||
import meteoritePng from './meteorite-v2-2.png';
|
||||
// import meteoritePng from './meteorite-v2-2.png';
|
||||
|
||||
export default function LoadingIcon ({ style, size, ...props }) {
|
||||
return (
|
||||
|
@ -1,18 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 415.481 415.481" style="enable-background:new 0 0 415.481 415.481;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M263.768,332.171c14.135,14.135,32.696,21.202,51.263,21.2c18.563-0.001,37.131-7.068,51.263-21.2 c13.693-13.692,21.234-31.898,21.234-51.263s-7.541-37.57-21.234-51.263c-28.266-28.266-74.258-28.267-102.526,0 C235.502,257.911,235.502,303.904,263.768,332.171z M315.031,221.439c15.235,0,30.471,5.799,42.07,17.398 c11.237,11.237,17.426,26.178,17.426,42.07s-6.189,30.833-17.426,42.07c-23.198,23.199-60.942,23.198-84.141,0 c-23.198-23.198-23.198-60.943,0-84.141C284.56,227.238,299.796,221.439,315.031,221.439z" style="fill: #9065ff"></path>
|
||||
<path d="M386.093,209.846l-80.602-80.602c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l80.602,80.602 c34.115,34.115,34.115,89.624,0,123.739c-34.115,34.115-89.624,34.115-123.739,0L106.798,196.414c-2.538-2.538-6.654-2.538-9.192,0 c-2.539,2.539-2.539,6.654,0,9.192L243.969,351.97c19.592,19.592,45.327,29.388,71.062,29.388c25.735,0,51.47-9.796,71.062-29.388 C425.277,312.786,425.277,249.029,386.093,209.846z" style="fill: #9065ff"></path>
|
||||
<path d="M271.905,114.043c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 L239.73,63.482c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L271.905,114.043z" style="fill: #9065ff"></path>
|
||||
<path d="M11.096,47.326c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l39.2,39.2 c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192L11.096,47.326z" style="fill: #9065ff"></path>
|
||||
<path d="M215.869,267.791c1.664,0,3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192l-155.37-155.37 c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l155.37,155.37 C212.542,267.156,214.206,267.791,215.869,267.791z" style="fill: #9065ff"></path>
|
||||
<path d="M203.319,178.388c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 l-82.728-82.728c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L203.319,178.388z" style="fill: #9065ff"></path>
|
||||
<path d="M241.5,216.569c1.269,1.269,2.933,1.904,4.596,1.904c1.664,0,3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 l-18.03-18.03c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L241.5,216.569z" style="fill: #9065ff"></path>
|
||||
<path d="M210.213,105.736c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 l-60.517-60.517c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L210.213,105.736z" style="fill: #9065ff"></path>
|
||||
<path d="M240.617,117.756c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l59.394,59.394 c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192L240.617,117.756z" style="fill: #9065ff"></path>
|
||||
<path d="M291.916,282.231c10.614,0,19.25-8.635,19.25-19.249c0-10.614-8.635-19.25-19.25-19.25 c-10.614,0-19.249,8.635-19.249,19.25C272.667,273.595,281.302,282.231,291.916,282.231z M291.916,256.732 c3.446,0,6.25,2.804,6.25,6.25s-2.804,6.249-6.25,6.249s-6.249-2.803-6.249-6.249S288.47,256.732,291.916,256.732z" style="fill: #9065ff"></path>
|
||||
<path d="M343.67,320.479c14.288,0,25.912-11.624,25.912-25.912c0-14.288-11.624-25.912-25.912-25.912 c-14.288,0-25.912,11.624-25.912,25.912C317.758,308.855,329.382,320.479,343.67,320.479z M343.67,281.655 c7.12,0,12.912,5.792,12.912,12.912s-5.792,12.912-12.912,12.912s-12.912-5.792-12.912-12.912S336.55,281.655,343.67,281.655z" style="fill: #9065ff"></path>
|
||||
<path d="M294.915,303.991c-1.905-4.641-8.646-5.429-11.4-1.12c-2.745,4.001,0.019,9.587,4.765,10.079 C293.023,313.44,296.841,308.413,294.915,303.991C294.755,303.601,295.085,304.381,294.915,303.991z" style="fill: #9065ff"></path>
|
||||
<path d="M329.415,250.471c1.902,4.647,8.646,5.429,11.4,1.12c1.765-2.572,1.324-6.174-0.92-8.305 c-2.156-2.047-5.615-2.402-8.09-0.705C329.243,244.216,328.236,247.696,329.415,250.471 C329.575,250.861,329.245,250.071,329.415,250.471z" style="fill: #9065ff"></path>
|
||||
<path d="M304.915,321.481c0,3.536,2.962,6.5,6.5,6.5c3.536,0,6.5-2.966,6.5-6.5c0-3.544-2.956-6.5-6.5-6.5 C307.871,314.981,304.915,317.937,304.915,321.481z" style="fill: #9065ff"></path>
|
||||
<path d="M263.768,332.171c14.135,14.135,32.696,21.202,51.263,21.2c18.563-0.001,37.131-7.068,51.263-21.2 c13.693-13.692,21.234-31.898,21.234-51.263s-7.541-37.57-21.234-51.263c-28.266-28.266-74.258-28.267-102.526,0 C235.502,257.911,235.502,303.904,263.768,332.171z M315.031,221.439c15.235,0,30.471,5.799,42.07,17.398 c11.237,11.237,17.426,26.178,17.426,42.07s-6.189,30.833-17.426,42.07c-23.198,23.199-60.942,23.198-84.141,0 c-23.198-23.198-23.198-60.943,0-84.141C284.56,227.238,299.796,221.439,315.031,221.439z" style="fill: #6772e5"></path>
|
||||
<path d="M386.093,209.846l-80.602-80.602c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l80.602,80.602 c34.115,34.115,34.115,89.624,0,123.739c-34.115,34.115-89.624,34.115-123.739,0L106.798,196.414c-2.538-2.538-6.654-2.538-9.192,0 c-2.539,2.539-2.539,6.654,0,9.192L243.969,351.97c19.592,19.592,45.327,29.388,71.062,29.388c25.735,0,51.47-9.796,71.062-29.388 C425.277,312.786,425.277,249.029,386.093,209.846z" style="fill: #6772e5"></path>
|
||||
<path d="M271.905,114.043c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 L239.73,63.482c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L271.905,114.043z" style="fill: #6772e5"></path>
|
||||
<path d="M11.096,47.326c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l39.2,39.2 c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192L11.096,47.326z" style="fill: #6772e5"></path>
|
||||
<path d="M215.869,267.791c1.664,0,3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192l-155.37-155.37 c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l155.37,155.37 C212.542,267.156,214.206,267.791,215.869,267.791z" style="fill: #6772e5"></path>
|
||||
<path d="M203.319,178.388c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 l-82.728-82.728c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L203.319,178.388z" style="fill: #6772e5"></path>
|
||||
<path d="M241.5,216.569c1.269,1.269,2.933,1.904,4.596,1.904c1.664,0,3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 l-18.03-18.03c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L241.5,216.569z" style="fill: #6772e5"></path>
|
||||
<path d="M210.213,105.736c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192 l-60.517-60.517c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192L210.213,105.736z" style="fill: #6772e5"></path>
|
||||
<path d="M240.617,117.756c-2.538-2.538-6.654-2.538-9.192,0c-2.539,2.539-2.539,6.654,0,9.192l59.394,59.394 c1.269,1.269,2.933,1.904,4.596,1.904s3.327-0.635,4.596-1.904c2.539-2.539,2.539-6.654,0-9.192L240.617,117.756z" style="fill: #6772e5"></path>
|
||||
<path d="M291.916,282.231c10.614,0,19.25-8.635,19.25-19.249c0-10.614-8.635-19.25-19.25-19.25 c-10.614,0-19.249,8.635-19.249,19.25C272.667,273.595,281.302,282.231,291.916,282.231z M291.916,256.732 c3.446,0,6.25,2.804,6.25,6.25s-2.804,6.249-6.25,6.249s-6.249-2.803-6.249-6.249S288.47,256.732,291.916,256.732z" style="fill: #6772e5"></path>
|
||||
<path d="M343.67,320.479c14.288,0,25.912-11.624,25.912-25.912c0-14.288-11.624-25.912-25.912-25.912 c-14.288,0-25.912,11.624-25.912,25.912C317.758,308.855,329.382,320.479,343.67,320.479z M343.67,281.655 c7.12,0,12.912,5.792,12.912,12.912s-5.792,12.912-12.912,12.912s-12.912-5.792-12.912-12.912S336.55,281.655,343.67,281.655z" style="fill: #6772e5"></path>
|
||||
<path d="M294.915,303.991c-1.905-4.641-8.646-5.429-11.4-1.12c-2.745,4.001,0.019,9.587,4.765,10.079 C293.023,313.44,296.841,308.413,294.915,303.991C294.755,303.601,295.085,304.381,294.915,303.991z" style="fill: #6772e5"></path>
|
||||
<path d="M329.415,250.471c1.902,4.647,8.646,5.429,11.4,1.12c1.765-2.572,1.324-6.174-0.92-8.305 c-2.156-2.047-5.615-2.402-8.09-0.705C329.243,244.216,328.236,247.696,329.415,250.471 C329.575,250.861,329.245,250.071,329.415,250.471z" style="fill: #6772e5"></path>
|
||||
<path d="M304.915,321.481c0,3.536,2.962,6.5,6.5,6.5c3.536,0,6.5-2.966,6.5-6.5c0-3.544-2.956-6.5-6.5-6.5 C307.871,314.981,304.915,317.937,304.915,321.481z" style="fill: #6772e5"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@ -13,7 +13,8 @@ const Container = styled('div')({
|
||||
borderRadius: 4,
|
||||
margin: '0 auto',
|
||||
padding: '24px 48px 76px',
|
||||
width: 300
|
||||
width: 300,
|
||||
boxShadow: '0 50px 100px rgba(50,50,93,.1), 0 15px 35px rgba(50,50,93,.15), 0 5px 15px rgba(0,0,0,.1)'
|
||||
});
|
||||
|
||||
const ButtonsContainer = styled('div')({
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Link } from "@reach/router";
|
||||
import moment from 'moment';
|
||||
import styled from 'react-emotion';
|
||||
import Icon from '../../components/Icon';
|
||||
import Logo from '../../components/Logo';
|
||||
@ -10,9 +9,12 @@ import { Filters } from '../../constants/filters';
|
||||
import { withOnEnter } from '../../enhance';
|
||||
import '../../styles/gradient.css';
|
||||
|
||||
const FixedContainer = styled('div')({
|
||||
position: 'fixed'
|
||||
});
|
||||
|
||||
const NotificationsContainer = styled('div')({
|
||||
position: 'relative',
|
||||
boxSizing: 'border-box',
|
||||
background: '#fff',
|
||||
margin: '0 auto',
|
||||
padding: 0,
|
||||
@ -36,41 +38,43 @@ const NavigationContainer = styled('div')({
|
||||
|
||||
const GeneralOptionsContainer = styled(NavigationContainer)({
|
||||
background: '#fff',
|
||||
padding: '8px 80px',
|
||||
flex: '0 0 75px',
|
||||
padding: '8px 16px',
|
||||
paddingLeft: 260,
|
||||
flex: '0 0 50px',
|
||||
'button': {
|
||||
display: 'inline-flex',
|
||||
margin: 0,
|
||||
marginTop: 12
|
||||
margin: 0
|
||||
}
|
||||
});
|
||||
|
||||
const Sidebar = styled('div')({
|
||||
flex: '0 0 180px',
|
||||
flex: '0 0 200px',
|
||||
padding: '0 20px 20px',
|
||||
marginTop: 15,
|
||||
'a': {
|
||||
textAlign: 'left',
|
||||
margin: '0 auto',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '8px',
|
||||
alignItems: 'center',
|
||||
padding: '0 16px',
|
||||
height: '48px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '700',
|
||||
textTransform: 'uppercase',
|
||||
textDecoration: 'none',
|
||||
transition: 'background 0.12s ease-in-out',
|
||||
display: 'flex'
|
||||
}
|
||||
marginTop: 15
|
||||
});
|
||||
|
||||
const SidebarLink = styled('a')({}, ({active, color}) => ({
|
||||
textAlign: 'left',
|
||||
margin: '0 auto',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '8px',
|
||||
alignItems: 'center',
|
||||
padding: '0 14px',
|
||||
height: 40,
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.5,
|
||||
textTransform: 'uppercase',
|
||||
textDecoration: 'none',
|
||||
transition: 'background 0.12s ease-in-out',
|
||||
display: 'flex',
|
||||
background: active ? color : 'none',
|
||||
color: active ? '#fff' : '#1a1a1a',
|
||||
':hover': {
|
||||
background: active ? color: 'rgba(200, 200, 200, .25)'
|
||||
},
|
||||
'div': {
|
||||
marginRight: 5
|
||||
}
|
||||
}));
|
||||
|
||||
@ -83,7 +87,6 @@ const Tab = styled('button')({
|
||||
border: 0,
|
||||
outline: 'none',
|
||||
background: 'none',
|
||||
display: 'block',
|
||||
height: 40,
|
||||
width: 40,
|
||||
borderRadius: '100%',
|
||||
@ -108,7 +111,7 @@ const SearchField = styled('div')({
|
||||
float: 'left',
|
||||
textAlign: 'left',
|
||||
width: '50%',
|
||||
boxShadow: '0 1px 3px #4a4a4a5c',
|
||||
boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
|
||||
margin: '0 auto',
|
||||
background: '#fff',
|
||||
borderRadius: '4px',
|
||||
@ -147,23 +150,30 @@ const SearchInput = styled('input')({
|
||||
});
|
||||
const EnhancedSearchInput = withOnEnter(SearchInput);
|
||||
|
||||
const NotificationRow = styled('div')({
|
||||
const NotificationRow = styled('tr')({
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
borderBottom: '1px solid #f2f2f2',
|
||||
display: 'block',
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
margin: '0 auto',
|
||||
background: '#fff',
|
||||
padding: '16px 24px',
|
||||
padding: '8px 16px',
|
||||
transition: 'all 0.12s ease-in-out',
|
||||
borderRadius: 8,
|
||||
boxSizing: 'border-box',
|
||||
':hover': {
|
||||
background: '#f9f9f9'
|
||||
// background: '#f9f9f9',
|
||||
boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
|
||||
zIndex: 10
|
||||
}
|
||||
});
|
||||
|
||||
const NotificationTitle = styled('div')({
|
||||
const NotificationTab = styled(Tab)({
|
||||
margin: 0,
|
||||
});
|
||||
|
||||
const NotificationTitle = styled('span')({
|
||||
position: 'relative',
|
||||
}, ({img}) => img && ({
|
||||
paddingLeft: 20,
|
||||
@ -180,15 +190,30 @@ const NotificationTitle = styled('div')({
|
||||
}));
|
||||
|
||||
const Repository = styled('span')({
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
marginLeft: 10,
|
||||
fontSize: 16
|
||||
fontSize: 14
|
||||
});
|
||||
|
||||
const PRIssue = styled(Repository)({
|
||||
fontWeight: 400,
|
||||
});
|
||||
|
||||
const Table = styled('table')({
|
||||
display: 'block',
|
||||
'td': {
|
||||
display: 'inline-block'
|
||||
}
|
||||
});
|
||||
|
||||
const TableItem = styled('td')({
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}, ({width}) => ({
|
||||
width
|
||||
}));
|
||||
|
||||
function getPRIssueIcon (type, reason) {
|
||||
const grow = 1.2;
|
||||
|
||||
@ -231,14 +256,17 @@ export default function Scene ({
|
||||
n.reason !== 'manual' &&
|
||||
n.reason !== 'invitation'
|
||||
);
|
||||
break;
|
||||
break;
|
||||
case Filters.SUBSCRIBED:
|
||||
filterMethod = n => n.reason === 'subscribed';
|
||||
break;
|
||||
case Filters.ALL:
|
||||
default:
|
||||
filterMethod = () => true;
|
||||
}
|
||||
|
||||
notifications = notifications
|
||||
.sort((a, b) => b.repository.name.localeCompare(a.repository.name))
|
||||
.sort((a, b) => a.repository.localeCompare(b.repository))
|
||||
.filter(filterMethod);
|
||||
|
||||
return (
|
||||
@ -276,13 +304,13 @@ export default function Scene ({
|
||||
<Tab disabled={isLoading}>
|
||||
<Icon.Refresh
|
||||
opacity={0.9}
|
||||
onClick={!isLoading && onFetchNotifications}
|
||||
onClick={!isLoading ? (() => onFetchNotifications()) : undefined}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab disabled={isLoading}>
|
||||
<Icon.DoneAll
|
||||
opacity={0.9}
|
||||
onClick={!isLoading && (() => onMarkAsRead('402658026'))}
|
||||
onClick={!isLoading ? (() => onMarkAsRead('402658026')) : undefined}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab>
|
||||
@ -301,58 +329,60 @@ export default function Scene ({
|
||||
</GeneralOptionsContainer>
|
||||
<NotificationsContainer>
|
||||
<Sidebar>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.ALL}
|
||||
color="#9065ff"
|
||||
onClick={() => onSetActiveFilter(Filters.ALL)}
|
||||
>
|
||||
{activeFilter === Filters.ALL ? (
|
||||
<Icon.InboxWhite shrink={.6} />
|
||||
) : (
|
||||
<Icon.Inbox shrink={.6} />
|
||||
)}
|
||||
all notifications
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.REVIEW_REQUESTED}
|
||||
color="#2cbf73"
|
||||
onClick={() => onSetActiveFilter(Filters.REVIEW_REQUESTED)}
|
||||
>
|
||||
{activeFilter === Filters.REVIEW_REQUESTED ? (
|
||||
<Icon.BoltWhite shrink={.6} />
|
||||
) : (
|
||||
<Icon.Bolt shrink={.6} />
|
||||
)}
|
||||
review requested
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.PARTICIPATING}
|
||||
color="#00A0F5"
|
||||
onClick={() => onSetActiveFilter(Filters.PARTICIPATING)}
|
||||
>
|
||||
{activeFilter === Filters.PARTICIPATING ? (
|
||||
<Icon.PeopleWhite shrink={.6} />
|
||||
) : (
|
||||
<Icon.People shrink={.6} />
|
||||
)}
|
||||
participating
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.SUBSCRIBED}
|
||||
color="#f68700"
|
||||
onClick={() => onSetActiveFilter(Filters.SUBSCRIBED)}
|
||||
>
|
||||
<Icon.Bookmark shrink={.6} />
|
||||
subscribed
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.HOT}
|
||||
color="#ee3044"
|
||||
onClick={() => onSetActiveFilter(Filters.HOT)}
|
||||
>
|
||||
<Icon.Hot shrink={.6} />
|
||||
hot
|
||||
</SidebarLink>
|
||||
<FixedContainer>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.ALL}
|
||||
color="#6772e5"
|
||||
onClick={() => onSetActiveFilter(Filters.ALL)}
|
||||
>
|
||||
{activeFilter === Filters.ALL ? (
|
||||
<Icon.InboxWhite shrink={.6} />
|
||||
) : (
|
||||
<Icon.Inbox shrink={.6} />
|
||||
)}
|
||||
all notifications
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.REVIEW_REQUESTED}
|
||||
color="#2cbf73"
|
||||
onClick={() => onSetActiveFilter(Filters.REVIEW_REQUESTED)}
|
||||
>
|
||||
{activeFilter === Filters.REVIEW_REQUESTED ? (
|
||||
<Icon.BoltWhite shrink={.6} />
|
||||
) : (
|
||||
<Icon.Bolt shrink={.6} />
|
||||
)}
|
||||
review requested
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.PARTICIPATING}
|
||||
color="#00A0F5"
|
||||
onClick={() => onSetActiveFilter(Filters.PARTICIPATING)}
|
||||
>
|
||||
{activeFilter === Filters.PARTICIPATING ? (
|
||||
<Icon.PeopleWhite shrink={.6} />
|
||||
) : (
|
||||
<Icon.People shrink={.6} />
|
||||
)}
|
||||
participating
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.SUBSCRIBED}
|
||||
color="#f68700"
|
||||
onClick={() => onSetActiveFilter(Filters.SUBSCRIBED)}
|
||||
>
|
||||
<Icon.Bookmark shrink={.6} />
|
||||
subscribed
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
active={activeFilter === Filters.HOT}
|
||||
color="#ee3044"
|
||||
onClick={() => onSetActiveFilter(Filters.HOT)}
|
||||
>
|
||||
<Icon.Hot shrink={.6} />
|
||||
hot
|
||||
</SidebarLink>
|
||||
</FixedContainer>
|
||||
</Sidebar>
|
||||
<Notifications>
|
||||
{isFetchingNotifications ? (
|
||||
@ -364,24 +394,37 @@ export default function Scene ({
|
||||
<p>no notifications</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{notifications.map(n => (
|
||||
<NotificationRow key={n.id}>
|
||||
<NotificationTitle>
|
||||
<div style={{
|
||||
float: 'left',
|
||||
marginTop: -3
|
||||
}}>
|
||||
{getPRIssueIcon(n.subject.type, n.reason)}
|
||||
</div>
|
||||
<PRIssue>{n.subject.title} ({n.reason})</PRIssue>
|
||||
{/* <Repository>{n.repository.name}</Repository> */}
|
||||
</NotificationTitle>
|
||||
{/* <p>Last read at {n.last_read_at ? moment(n.last_read_at).format('dddd h:mma') : 'never'}</p>
|
||||
<p>Last updated at {moment(n.last_updated).format('dddd h:mma')}</p> */}
|
||||
</NotificationRow>
|
||||
))}
|
||||
</div>
|
||||
<Table>
|
||||
<tbody>
|
||||
{notifications.map(n => (
|
||||
<NotificationRow key={n.id}>
|
||||
<TableItem>
|
||||
<div style={{ float: 'left', marginTop: -3 }}>
|
||||
{getPRIssueIcon(n.type, n.reason)}
|
||||
</div>
|
||||
</TableItem>
|
||||
<TableItem width={500} onClick={() => window.open(n.url)}>
|
||||
<NotificationTitle>
|
||||
<PRIssue>{n.name} ({n.reason})</PRIssue>
|
||||
</NotificationTitle>
|
||||
</TableItem>
|
||||
<TableItem width={200}>
|
||||
<Repository>{n.repository}</Repository>
|
||||
</TableItem>
|
||||
<TableItem>
|
||||
<NotificationTab>
|
||||
<Icon.Done
|
||||
opacity={0.9}
|
||||
onClick={!isLoading ? (() => onMarkAsRead(n.id)) : undefined}
|
||||
/>
|
||||
</NotificationTab>
|
||||
</TableItem>
|
||||
{/* <p>Last read at {n.last_read_at ? moment(n.last_read_at).format('dddd h:mma') : 'never'}</p>
|
||||
<p>Last updated at {moment(n.last_updated).format('dddd h:mma')}</p> */}
|
||||
</NotificationRow>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)}
|
||||
</Notifications>
|
||||
<Sidebar>
|
||||
|
@ -1,9 +1,17 @@
|
||||
import React from 'react';
|
||||
import {AuthConsumer} from './Auth';
|
||||
import {StorageProvider} from './Storage';
|
||||
import {MockNotifications} from '../utils/mocks';
|
||||
|
||||
const BASE_GITHUB_API_URL = 'https://api.github.com';
|
||||
|
||||
function subjectUrlToIssue (url) {
|
||||
return url
|
||||
.replace('api.github.com', 'github.com')
|
||||
.replace('/repos/', '/')
|
||||
.replace('/pulls/', '/pull/');
|
||||
}
|
||||
|
||||
function processHeadersAndBodyJson (response) {
|
||||
const entries = response.headers.entries();
|
||||
const headers = {};
|
||||
@ -29,7 +37,19 @@ function processHeadersAndBodyJson (response) {
|
||||
// links.next.page
|
||||
headers['link'] = links;
|
||||
|
||||
return [headers, response.json()];
|
||||
// 304 will usually mean nothing has changed from our last fetch.
|
||||
if (response.status === 304) {
|
||||
return Promise.resolve({
|
||||
headers,
|
||||
json: null
|
||||
});
|
||||
}
|
||||
|
||||
// I can't get marking a notification as read to get past here??
|
||||
return response.json().then(json => ({
|
||||
headers,
|
||||
json
|
||||
}));
|
||||
}
|
||||
|
||||
class NotificationsProvider extends React.Component {
|
||||
@ -41,17 +61,16 @@ class NotificationsProvider extends React.Component {
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
error: null,
|
||||
notifications: MockNotifications
|
||||
error: null
|
||||
}
|
||||
|
||||
requestPage = (page = 1) => {
|
||||
requestPage = (page = 1, optimizePolling = true) => {
|
||||
const headers = {
|
||||
'Authorization': `token ${this.props.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (this.last_modified) {
|
||||
if (optimizePolling && this.last_modified) {
|
||||
headers['If-Modified-Since'] = this.last_modified;
|
||||
}
|
||||
|
||||
@ -60,13 +79,16 @@ class NotificationsProvider extends React.Component {
|
||||
headers: headers
|
||||
})
|
||||
.then(processHeadersAndBodyJson)
|
||||
.then(([headers, body]) => {
|
||||
.then(({headers, json}) => {
|
||||
// If there were updates, make sure we get the newest last-modified.
|
||||
if (headers['last-modified']) {
|
||||
this.last_modified = headers['last-modified'];
|
||||
}
|
||||
|
||||
return body;
|
||||
return {
|
||||
headers,
|
||||
json
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -77,24 +99,65 @@ class NotificationsProvider extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotifications = () => {
|
||||
fetchNotifications = (page = 1, optimizePolling = true) => {
|
||||
if (!this.props.token) {
|
||||
console.error('Unauthenitcated, aborting request.')
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({ loading: true });
|
||||
return this.mockRequestPage(1)
|
||||
.then(notifications => this.processNotificationsChunk(notifications))
|
||||
.catch(error => this.setState({ error }))
|
||||
return this.requestPage(page, optimizePolling)
|
||||
.then(({headers, json}) => {
|
||||
if (json === null) return;
|
||||
let nextPage = null;
|
||||
const links = headers['link'];
|
||||
if (links && links.next && links.next.page) {
|
||||
nextPage = links.next.page;
|
||||
}
|
||||
return this.processNotificationsChunk(nextPage, json);
|
||||
})
|
||||
.catch(error => console.error(error) || this.setState({ error }))
|
||||
.finally(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
processNotificationsChunk = notificationsChunk => {
|
||||
console.warn(notificationsChunk);
|
||||
this.setState({
|
||||
notifications: notificationsChunk
|
||||
processNotificationsChunk = (nextPage, notificationsChunk) => {
|
||||
console.warn('chunk', notificationsChunk)
|
||||
let everythingUpdated = true;
|
||||
|
||||
notificationsChunk.forEach(n => {
|
||||
const cached_n = this.props.getItemFromStorage(n.id);
|
||||
// If we've seen this notification before and it hasn't updated, skip it.
|
||||
if (cached_n && (cached_n.updated_at === n.updated_at)) {
|
||||
// This means that something didn't update, which means the page we're
|
||||
// currently processing has stale data so we don't need to fetch the
|
||||
// next page.
|
||||
everythingUpdated = false;
|
||||
return;
|
||||
}
|
||||
// Else, update the cache.
|
||||
this.updateNotification(n);
|
||||
});
|
||||
|
||||
if (nextPage && everythingUpdated) {
|
||||
// Still need to fetch more updates.
|
||||
this.fetchNotifications(nextPage, false);
|
||||
} else {
|
||||
// All done fetching updates, let's trigger a sync.
|
||||
this.props.refreshNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
updateNotification = n => {
|
||||
const value = {
|
||||
id: n.id, // @TODO can prob remove this id since its the key
|
||||
updated_at: n.updated_at,
|
||||
reason: n.reason,
|
||||
type: n.subject.type,
|
||||
name: n.subject.title,
|
||||
url: subjectUrlToIssue(n.subject.url),
|
||||
repository: n.repository.name,
|
||||
};
|
||||
this.props.setItemInStorage(n.id, value);
|
||||
}
|
||||
|
||||
requestMarkAsRead = thread_id => {
|
||||
@ -108,9 +171,12 @@ class NotificationsProvider extends React.Component {
|
||||
headers: headers
|
||||
})
|
||||
.then(processHeadersAndBodyJson)
|
||||
.then(([headers, body]) => {
|
||||
console.warn(body)
|
||||
return body;
|
||||
.then(({headers, json}) => {
|
||||
console.warn(headers, json);
|
||||
console.warn('removing', thread_id);
|
||||
this.props.removeItemFromStorage(thread_id);
|
||||
this.props.refreshNotifications();
|
||||
return Promise.resolve(json);
|
||||
});
|
||||
}
|
||||
|
||||
@ -130,6 +196,7 @@ class NotificationsProvider extends React.Component {
|
||||
render () {
|
||||
return this.props.children({
|
||||
...this.state,
|
||||
notifications: this.props.notifications,
|
||||
fetchNotifications: this.fetchNotifications,
|
||||
markAsRead: this.markAsRead
|
||||
});
|
||||
@ -139,11 +206,22 @@ class NotificationsProvider extends React.Component {
|
||||
const withNotificationsProvider = WrappedComponent => props => (
|
||||
<AuthConsumer>
|
||||
{({ token }) => (
|
||||
<NotificationsProvider token={token}>
|
||||
{(notificationsApi) => (
|
||||
<WrappedComponent {...props} notificationsApi={notificationsApi} />
|
||||
<StorageProvider>
|
||||
{({ refreshNotifications, notifications, getItem, setItem, removeItem }) => (
|
||||
<NotificationsProvider
|
||||
refreshNotifications={refreshNotifications}
|
||||
notifications={notifications}
|
||||
getItemFromStorage={getItem}
|
||||
setItemInStorage={setItem}
|
||||
removeItemFromStorage={removeItem}
|
||||
token={token}
|
||||
>
|
||||
{(notificationsApi) => (
|
||||
<WrappedComponent {...props} notificationsApi={notificationsApi} />
|
||||
)}
|
||||
</NotificationsProvider>
|
||||
)}
|
||||
</NotificationsProvider>
|
||||
</StorageProvider>
|
||||
)}
|
||||
</AuthConsumer>
|
||||
);
|
||||
|
79
src/providers/Storage.js
Normal file
79
src/providers/Storage.js
Normal file
@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
const LOCAL_STORAGE_PREFIX = '__meteorite_noti_cache__';
|
||||
|
||||
class StorageProvider extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.last_modified = null;
|
||||
}
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
error: null,
|
||||
notifications: {}
|
||||
}
|
||||
|
||||
// @TODO move all this storage stuff to its own provider
|
||||
// this guy is concerned about updating the cache and syncing
|
||||
// the storage provider will be concerned about providing the notifications.
|
||||
componentWillMount () {
|
||||
this.refreshNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up the notifications state with the cache.
|
||||
*/
|
||||
refreshNotifications = () => {
|
||||
const notifications = [];
|
||||
Object.keys(localStorage).forEach(key => {
|
||||
if (key.indexOf(LOCAL_STORAGE_PREFIX) > -1) {
|
||||
const n = JSON.parse(localStorage.getItem(key));
|
||||
notifications.push(n);
|
||||
}
|
||||
});
|
||||
this.setState({
|
||||
notifications
|
||||
});
|
||||
}
|
||||
|
||||
setItem = (id, value) => {
|
||||
localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${id}`, JSON.stringify(value));
|
||||
}
|
||||
|
||||
removeItem = id => {
|
||||
localStorage.removeItem(`${LOCAL_STORAGE_PREFIX}${id}`);
|
||||
}
|
||||
|
||||
getItem = id => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${id}`));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return this.props.children({
|
||||
...this.state,
|
||||
setItem: this.setItem,
|
||||
getItem: this.getItem,
|
||||
removeItem: this.removeItem,
|
||||
refreshNotifications: this.refreshNotifications
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const withStorageProvider = WrappedComponent => props => (
|
||||
<StorageProvider>
|
||||
{(storageApi) => (
|
||||
<WrappedComponent {...props} storageApi={storageApi} />
|
||||
)}
|
||||
</StorageProvider>
|
||||
);
|
||||
|
||||
export {
|
||||
StorageProvider,
|
||||
withStorageProvider
|
||||
};
|
BIN
src/styles/fonts/Camphor-Heavy.ttf
Normal file
BIN
src/styles/fonts/Camphor-Heavy.ttf
Normal file
Binary file not shown.
BIN
src/styles/fonts/Camphor-Light.ttf
Normal file
BIN
src/styles/fonts/Camphor-Light.ttf
Normal file
Binary file not shown.
BIN
src/styles/fonts/Camphor-Regular.ttf
Normal file
BIN
src/styles/fonts/Camphor-Regular.ttf
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
|
||||
.container-gradient {
|
||||
/* background: radial-gradient(farthest-corner at -0% 100%, #7247ff 30%, #00ffbe 95%); */
|
||||
background: radial-gradient(farthest-corner at -0% 100%, #9065ff 30%, #58fff7 95%);
|
||||
background: radial-gradient(farthest-corner at -0% 100%, #6772e5 30%, #00cfff 95%);
|
||||
background-size: 400% 400%;
|
||||
|
||||
-webkit-animation: gradientTransition 20s ease infinite;
|
||||
@ -11,10 +11,10 @@
|
||||
|
||||
.button-container a {
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 3px #4a4a4a5c;
|
||||
box-shadow: 0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08);
|
||||
margin: 0 auto;
|
||||
cursor: pointer;
|
||||
color: #9065ff;
|
||||
color: #6772e5;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
-webkit-align-items: center;
|
||||
|
@ -1,14 +1,31 @@
|
||||
@import url('https://rsms.me/inter/inter-ui.css');
|
||||
|
||||
@font-face {
|
||||
font-family: 'Camphor';
|
||||
font-weight: 500;
|
||||
src: url('./fonts/Camphor-Regular.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Camphor';
|
||||
font-weight: 300;
|
||||
src: url('./fonts/Camphor-Light.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Camphor';
|
||||
font-weight: 700;
|
||||
src: url('./fonts/Camphor-Heavy.ttf');
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: #fff;
|
||||
background: #9065ff;
|
||||
background: #6772e5;
|
||||
}
|
||||
|
||||
html, body, * {
|
||||
font-family: 'Inter UI', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
font-family: 'Camphor', 'Inter UI', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
color: #1a1a1a;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user