Many tickets and new readme
BIN
.github/icon.png
vendored
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
.github/template.png
vendored
Normal file
After Width: | Height: | Size: 57 KiB |
@ -1,7 +1,11 @@
|
|||||||
# meteorite
|
# <img width="650" src=".github/icon.png" /> meteorite
|
||||||
|
|
||||||
> Smarter GitHub notifications.
|
> Smarter GitHub notifications.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img width="650" src=".github/template.png" />
|
||||||
|
</div>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is free to use under the MIT License. See [this reference](https://opensource.org/licenses/MIT) for license text and copyright information.
|
This software is free to use under the MIT License. See [this reference](https://opensource.org/licenses/MIT) for license text and copyright information.
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
"start": "node scripts/start.js",
|
"start": "node scripts/start.js",
|
||||||
"build": "node scripts/build.js && cp build/index.html build/200.html",
|
"build": "node scripts/build.js && cp build/index.html build/200.html",
|
||||||
"test": "node scripts/test.js",
|
"test": "node scripts/test.js",
|
||||||
"deploy": "surge"
|
"deploy": "npm run build && surge ./build"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
|
@ -5,6 +5,7 @@ import alarm from './svg/alarm.svg';
|
|||||||
import allInbox from './svg/all_inbox.svg';
|
import allInbox from './svg/all_inbox.svg';
|
||||||
import back from './svg/back.svg';
|
import back from './svg/back.svg';
|
||||||
import bolt from './svg/bolt.svg';
|
import bolt from './svg/bolt.svg';
|
||||||
|
import boltAlt from './svg/bolt-alt.svg';
|
||||||
import boltWhite from './svg/bolt-white.svg';
|
import boltWhite from './svg/bolt-white.svg';
|
||||||
import bookmarkAlt from './svg/bookmark-alt.svg';
|
import bookmarkAlt from './svg/bookmark-alt.svg';
|
||||||
import bookmarkAltWhite from './svg/bookmark-alt-white.svg';
|
import bookmarkAltWhite from './svg/bookmark-alt-white.svg';
|
||||||
@ -43,6 +44,14 @@ import x from './svg/x.svg';
|
|||||||
import zap from './svg/zap.svg';
|
import zap from './svg/zap.svg';
|
||||||
import ear from './svg/ear.svg';
|
import ear from './svg/ear.svg';
|
||||||
import ring from './svg/ring.svg';
|
import ring from './svg/ring.svg';
|
||||||
|
import eye from './svg/eye.svg';
|
||||||
|
import eyeWhite from './svg/eye-white.svg';
|
||||||
|
import warn from './svg/warn.svg';
|
||||||
|
import bubbles from './svg/bubbles.svg';
|
||||||
|
import cloudOff from './svg/cloudoff.svg';
|
||||||
|
import tag from './svg/tag.svg';
|
||||||
|
import tagWhite from './svg/tag-white.svg';
|
||||||
|
import sync from './svg/sync.svg';
|
||||||
|
|
||||||
import issue_closed from './svg/github/issue-closed.svg';
|
import issue_closed from './svg/github/issue-closed.svg';
|
||||||
import issue_open from './svg/github/issue-open.svg';
|
import issue_open from './svg/github/issue-open.svg';
|
||||||
@ -76,6 +85,7 @@ Icon.AllInbox = createIcon(allInbox);
|
|||||||
Icon.Back = createIcon(back);
|
Icon.Back = createIcon(back);
|
||||||
Icon.Bolt = createIcon(bolt);
|
Icon.Bolt = createIcon(bolt);
|
||||||
Icon.BoltWhite = createIcon(boltWhite);
|
Icon.BoltWhite = createIcon(boltWhite);
|
||||||
|
Icon.BoltAlt = createIcon(boltAlt);
|
||||||
Icon.BookmarkAlt = createIcon(bookmarkAlt);
|
Icon.BookmarkAlt = createIcon(bookmarkAlt);
|
||||||
Icon.BookmarkAltWhite = createIcon(bookmarkAltWhite);
|
Icon.BookmarkAltWhite = createIcon(bookmarkAltWhite);
|
||||||
Icon.Bookmark = createIcon(bookmark);
|
Icon.Bookmark = createIcon(bookmark);
|
||||||
@ -113,6 +123,14 @@ Icon.X = createIcon(x);
|
|||||||
Icon.Zap = createIcon(zap);
|
Icon.Zap = createIcon(zap);
|
||||||
Icon.Ear = createIcon(ear);
|
Icon.Ear = createIcon(ear);
|
||||||
Icon.Ring = createIcon(ring);
|
Icon.Ring = createIcon(ring);
|
||||||
|
Icon.Eye = createIcon(eye);
|
||||||
|
Icon.EyeWhite = createIcon(eyeWhite);
|
||||||
|
Icon.Warn = createIcon(warn);
|
||||||
|
Icon.Bubbles = createIcon(bubbles);
|
||||||
|
Icon.CloudOffWhite = createIcon(cloudOff);
|
||||||
|
Icon.Tag = createIcon(tag);
|
||||||
|
Icon.TagWhite = createIcon(tagWhite);
|
||||||
|
Icon.Sync = createIcon(sync);
|
||||||
|
|
||||||
Icon.IssueClosed = createIcon(issue_closed);
|
Icon.IssueClosed = createIcon(issue_closed);
|
||||||
Icon.IssueOpen = createIcon(issue_open);
|
Icon.IssueOpen = createIcon(issue_open);
|
||||||
|
4
src/components/Icon/svg/bolt-alt.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path fill="red" d="M12 2.02c-5.51 0-9.98 4.47-9.98 9.98s4.47 9.98 9.98 9.98 9.98-4.47 9.98-9.98S17.51 2.02 12 2.02zM11.48 20v-6.26H8L13 4v6.26h3.35L11.48 20z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 297 B |
1
src/components/Icon/svg/bubbles.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><circle cx="7.2" cy="14.4" r="3.2"/><circle cx="14.8" cy="18" r="2"/><circle cx="15.2" cy="8.8" r="4.8"/></svg>
|
After Width: | Height: | Size: 231 B |
1
src/components/Icon/svg/cloudoff.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#fff" d="M19.35 10.04C18.67 6.59 15.64 4 12 4c-1.48 0-2.85.43-4.01 1.17l1.46 1.46C10.21 6.23 11.08 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3 0 1.13-.64 2.11-1.56 2.62l1.45 1.45C23.16 18.16 24 16.68 24 15c0-2.64-2.05-4.78-4.65-4.96zM3 5.27l2.75 2.74C2.56 8.15 0 10.77 0 14c0 3.31 2.69 6 6 6h11.73l2 2L21 20.73 4.27 4 3 5.27zM7.73 10l8 8H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h1.73z"/></svg>
|
After Width: | Height: | Size: 528 B |
1
src/components/Icon/svg/eye-white.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#fff" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
After Width: | Height: | Size: 360 B |
1
src/components/Icon/svg/eye.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
After Width: | Height: | Size: 347 B |
1
src/components/Icon/svg/sync.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
After Width: | Height: | Size: 361 B |
1
src/components/Icon/svg/tag-white.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#fff" d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/></svg>
|
After Width: | Height: | Size: 400 B |
1
src/components/Icon/svg/tag.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/></svg>
|
After Width: | Height: | Size: 387 B |
1
src/components/Icon/svg/warn.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>
|
After Width: | Height: | Size: 188 B |
@ -1,5 +1,6 @@
|
|||||||
export const Filters = {
|
export const Filters = {
|
||||||
ALL: 'all',
|
ALL: 'all',
|
||||||
|
ASSIGNED: 'assign',
|
||||||
REVIEW_REQUESTED: 'review_requested',
|
REVIEW_REQUESTED: 'review_requested',
|
||||||
PARTICIPATING: 'participating',
|
PARTICIPATING: 'participating',
|
||||||
SUBSCRIBED: 'subscribed',
|
SUBSCRIBED: 'subscribed',
|
||||||
|
@ -31,7 +31,7 @@ class Tooltip extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {tooltipOffsetX, tooltipOffsetY} = this.props;
|
const {tooltipOffsetX, tooltipOffsetY} = this.props;
|
||||||
const {x, y, height} = event.target.getBoundingClientRect();
|
const {x, y} = event.target.getBoundingClientRect();
|
||||||
const text = document.createTextNode(this.props.message);
|
const text = document.createTextNode(this.props.message);
|
||||||
const tooltipElement = document.createElement('div');
|
const tooltipElement = document.createElement('div');
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import Icon from '../../components/Icon';
|
|||||||
import Logo from '../../components/Logo';
|
import Logo from '../../components/Logo';
|
||||||
import '../../styles/gradient.css';
|
import '../../styles/gradient.css';
|
||||||
|
|
||||||
function createImagePlaceholder () {
|
function createImagePlaceholder (highlight) {
|
||||||
return (
|
return (
|
||||||
<ImagePlaceholder>
|
<ImagePlaceholder>
|
||||||
{/* navigation backdrop */}
|
{/* navigation backdrop */}
|
||||||
@ -136,19 +136,21 @@ function createImagePlaceholder () {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
top: 190,
|
top: highlight === 'badges' ? 190 : 170,
|
||||||
left: -20,
|
left: highlight === 'badges' ? -20 : 0,
|
||||||
right: -20,
|
right: highlight === 'badges' ? -20 : 0,
|
||||||
height: 50,
|
height: 50,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 'rgba(130, 126, 126, 0.27) 0px 3px 8px',
|
boxShadow: highlight === 'badges'
|
||||||
|
? 'rgba(130, 126, 126, 0.27) 0px 3px 8px'
|
||||||
|
: '0 0 0',
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#dee1e6',
|
background: '#dee1e6',
|
||||||
top: 15,
|
top: 15,
|
||||||
left: 30,
|
left: highlight === 'badges' ? 30 : 100,
|
||||||
width: 200,
|
width: highlight === 'badges' ? 160 : 120,
|
||||||
height: 10,
|
height: 10,
|
||||||
borderRadius: 50
|
borderRadius: 50
|
||||||
}} />
|
}} />
|
||||||
@ -156,7 +158,7 @@ function createImagePlaceholder () {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#dee1e6',
|
background: '#dee1e6',
|
||||||
top: 30,
|
top: 30,
|
||||||
left: 30,
|
left: highlight === 'badges' ? 30 : 100,
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 7,
|
height: 7,
|
||||||
borderRadius: 50
|
borderRadius: 50
|
||||||
@ -164,20 +166,44 @@ function createImagePlaceholder () {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 12,
|
top: 12,
|
||||||
left: 300,
|
left: 315,
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30
|
height: 30
|
||||||
}}>
|
}}>
|
||||||
<Icon.Hot shrink={1.1} />
|
{highlight === 'badges' ? (
|
||||||
|
<Icon.Hot shrink={1.1} />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#f42839',
|
||||||
|
height: 15,
|
||||||
|
width: 15,
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: '100%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 13,
|
top: 12,
|
||||||
left: 330,
|
left: 345,
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30
|
height: 30
|
||||||
}}>
|
}}>
|
||||||
<Icon.Convo shrink={1.1} />
|
{highlight === 'badges' ? (
|
||||||
|
<Icon.Convo shrink={1.1} />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#009ef8',
|
||||||
|
height: 15,
|
||||||
|
width: 15,
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: '100%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -211,19 +237,21 @@ function createImagePlaceholder () {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
top: 280,
|
top: highlight === 'badges' ? 280 : 220,
|
||||||
left: -20,
|
left: highlight === 'badges' ? -20 : 0,
|
||||||
right: -20,
|
right: highlight === 'badges' ? -20 : 0,
|
||||||
height: 50,
|
height: 50,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 'rgba(130, 126, 126, 0.27) 0px 3px 8px',
|
boxShadow: highlight === 'badges'
|
||||||
|
? 'rgba(130, 126, 126, 0.27) 0px 3px 8px'
|
||||||
|
: '0 0 0',
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#dee1e6',
|
background: '#dee1e6',
|
||||||
top: 15,
|
top: 15,
|
||||||
left: 30,
|
left: highlight === 'badges' ? 30 : 100,
|
||||||
width: 220,
|
width: highlight === 'badges' ? 220 : 140,
|
||||||
height: 10,
|
height: 10,
|
||||||
borderRadius: 50
|
borderRadius: 50
|
||||||
}} />
|
}} />
|
||||||
@ -231,7 +259,7 @@ function createImagePlaceholder () {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#dee1e6',
|
background: '#dee1e6',
|
||||||
top: 30,
|
top: 30,
|
||||||
left: 30,
|
left: highlight === 'badges' ? 30 : 100,
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 7,
|
height: 7,
|
||||||
borderRadius: 50
|
borderRadius: 50
|
||||||
@ -240,7 +268,7 @@ function createImagePlaceholder () {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
background: '#dee1e6',
|
background: '#dee1e6',
|
||||||
top: 30,
|
top: 30,
|
||||||
left: 64,
|
left: highlight === 'badges' ? 64 : 134,
|
||||||
width: 7,
|
width: 7,
|
||||||
height: 7,
|
height: 7,
|
||||||
borderRadius: '100%'
|
borderRadius: '100%'
|
||||||
@ -248,11 +276,23 @@ function createImagePlaceholder () {
|
|||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 12,
|
top: 12,
|
||||||
left: 315,
|
left: 330,
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30
|
height: 30
|
||||||
}}>
|
}}>
|
||||||
<Icon.Timer shrink={1.1} />
|
{highlight === 'badges' ? (
|
||||||
|
<Icon.Timer shrink={1.1} />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#00d299',
|
||||||
|
height: 15,
|
||||||
|
width: 15,
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: '100%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -293,8 +333,8 @@ const Section = styled('div')({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
margin: '40px auto',
|
margin: '28px auto 0',
|
||||||
padding: '80px 0'
|
padding: '60px 0'
|
||||||
}, ({alt}) => alt && ({
|
}, ({alt}) => alt && ({
|
||||||
background: '#24292e',
|
background: '#24292e',
|
||||||
'p': {
|
'p': {
|
||||||
@ -303,6 +343,9 @@ const Section = styled('div')({
|
|||||||
'h2': {
|
'h2': {
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
},
|
},
|
||||||
|
'@media (max-width: 1000px)': {
|
||||||
|
flexDirection: 'column'
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const Item = styled('div')({
|
const Item = styled('div')({
|
||||||
@ -311,9 +354,10 @@ const Item = styled('div')({
|
|||||||
padding: '24px 72px',
|
padding: '24px 72px',
|
||||||
'h2': {
|
'h2': {
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
fontSize: 36,
|
marginLeft: 15,
|
||||||
|
fontSize: 42,
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
fontWeight: 700
|
fontWeight: 600
|
||||||
},
|
},
|
||||||
'p': {
|
'p': {
|
||||||
fontSize: 18
|
fontSize: 18
|
||||||
@ -339,6 +383,7 @@ const ImagePlaceholder = styled('div')({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
height: 400,
|
height: 400,
|
||||||
|
width: 600,
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
boxShadow: '0 2px 8px rgba(179, 179, 179, 0.25)'
|
boxShadow: '0 2px 8px rgba(179, 179, 179, 0.25)'
|
||||||
@ -348,7 +393,7 @@ const ImagePlaceholder = styled('div')({
|
|||||||
const Header = styled('h1')({
|
const Header = styled('h1')({
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
margin: '0 auto 20px',
|
margin: '0 auto 48px',
|
||||||
letterSpacing: '-1.0px'
|
letterSpacing: '-1.0px'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -364,7 +409,7 @@ const SubHeader = styled(Header)({
|
|||||||
const LandingHeader = styled('div')({
|
const LandingHeader = styled('div')({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
margin: '54px 20px',
|
margin: '54px 20px 78px',
|
||||||
maxWidth: 1000,
|
maxWidth: 1000,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
@ -421,7 +466,7 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="container-gradient" style={{
|
<div className="container-gradient" style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: 600,
|
minHeight: 600,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@ -443,7 +488,7 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
|
|||||||
)}
|
)}
|
||||||
</LandingHeader>
|
</LandingHeader>
|
||||||
<LandingMessage>
|
<LandingMessage>
|
||||||
<Header>Control your notifications</Header>
|
<Header>Control your GitHub notifications</Header>
|
||||||
<SubHeader>Prioritize the tasks that keep you and your team most productive</SubHeader>
|
<SubHeader>Prioritize the tasks that keep you and your team most productive</SubHeader>
|
||||||
<div className="button-container">
|
<div className="button-container">
|
||||||
<RouterLink to={routes.LOGIN}>let's get started</RouterLink>
|
<RouterLink to={routes.LOGIN}>let's get started</RouterLink>
|
||||||
@ -465,50 +510,54 @@ export default function Scene ({loggedIn, onLogout, ...props}) {
|
|||||||
</LandingMessage>
|
</LandingMessage>
|
||||||
<Curve />
|
<Curve />
|
||||||
</div>
|
</div>
|
||||||
<Section>
|
<Section id="section">
|
||||||
|
<Item style={{flex: '0 0 2.5%', padding: 0}} />
|
||||||
<Item>
|
<Item>
|
||||||
{createImagePlaceholder()}
|
{createImagePlaceholder('badges')}
|
||||||
</Item>
|
</Item>
|
||||||
<Item>
|
<Item id="item-text">
|
||||||
<h2>Surface the most important tasks to tackle as they happen</h2>
|
<h2>Surface the things that matter the most.</h2>
|
||||||
<ItemText>
|
<ItemText>
|
||||||
<Icon.Ring />
|
<Icon.Ring />
|
||||||
<p>The issues and pull requests that require your attention the most are called out for you.</p>
|
<p>The most important issues and pull requests that require your presence are called out and brought to your attention.</p>
|
||||||
</ItemText>
|
</ItemText>
|
||||||
<ItemText>
|
<ItemText>
|
||||||
<Icon.Ear />
|
<Icon.Ear />
|
||||||
<p>We listen for updates with your notifications and let you know as soon as things change.</p>
|
<p>We listen for updates with your notifications and let you know <i>why</i> and <i>when</i> things change.</p>
|
||||||
</ItemText>
|
</ItemText>
|
||||||
<ItemText>
|
<ItemText>
|
||||||
<Icon.Zap />
|
<Icon.Zap />
|
||||||
<p>Super charge your day by focusing on getting things done, rather than sifting through notifications.</p>
|
<p>Super charge your day by focusing on getting things done, rather than sifting through notifications or emails.</p>
|
||||||
</ItemText>
|
</ItemText>
|
||||||
</Item>
|
</Item>
|
||||||
|
<Item style={{flex: '0 0 2.5%', padding: 0}} />
|
||||||
</Section>
|
</Section>
|
||||||
<Section alt={true} style={{paddingTop: 140}}>
|
<Section id="section" alt={true} style={{paddingTop: 140, overflowX: 'hidden'}}>
|
||||||
<Curve style={{
|
<Curve style={{
|
||||||
bottom: 'auto',
|
bottom: 'auto',
|
||||||
|
marginBottom: 0,
|
||||||
|
marginTop: -1,
|
||||||
top: 0,
|
top: 0,
|
||||||
transform: 'translateX(-50%) rotate(180deg)'
|
transform: 'translateX(-50%) rotate(180deg)'
|
||||||
}} />
|
}} />
|
||||||
<Item style={{flex: '0 0 2.5%', padding: 0}} />
|
<Item style={{flex: '0 0 2.5%', padding: 0}} />
|
||||||
<Item>
|
<Item id="item-text">
|
||||||
<h2>Surface the most important tasks to tackle as they happen</h2>
|
<h2>Your privacy matters, so<br />we keep things offline.</h2>
|
||||||
<ItemText>
|
<ItemText>
|
||||||
<Icon.PeopleWhite />
|
<Icon.CloudOffWhite />
|
||||||
<p>The issues and pull requests that require your attention the most are called out for you.</p>
|
<p>a</p>
|
||||||
</ItemText>
|
</ItemText>
|
||||||
<ItemText>
|
<ItemText>
|
||||||
<Icon.BoltWhite />
|
<Icon.CloudOffWhite />
|
||||||
<p>We listen for updates with your notifications and let you know as soon as things change.</p>
|
<p>a</p>
|
||||||
</ItemText>
|
</ItemText>
|
||||||
<ItemText>
|
<ItemText>
|
||||||
<Icon.BookmarkAltWhite />
|
<Icon.CloudOffWhite />
|
||||||
<p>Super charge your day by focusing on getting things done, rather than sifting through notifications.</p>
|
<p>a</p>
|
||||||
</ItemText>
|
</ItemText>
|
||||||
</Item>
|
</Item>
|
||||||
<Item>
|
<Item>
|
||||||
{createImagePlaceholder()}
|
{createImagePlaceholder('reason')}
|
||||||
</Item>
|
</Item>
|
||||||
<Item style={{flex: '0 0 2.5%', padding: 0}} />
|
<Item style={{flex: '0 0 2.5%', padding: 0}} />
|
||||||
</Section>
|
</Section>
|
||||||
|
@ -1,898 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import moment from 'moment';
|
|
||||||
import {VictoryPie, VictoryChart} from "victory";
|
|
||||||
import {Link} from "@reach/router";
|
|
||||||
import styled from 'react-emotion';
|
|
||||||
import Icon from '../../components/Icon';
|
|
||||||
import Logo from '../../components/Logo';
|
|
||||||
import LoadingIcon from '../../components/LoadingIcon';
|
|
||||||
import {routes} from '../../constants';
|
|
||||||
import {Filters} from '../../constants/filters';
|
|
||||||
import {withOnEnter, withTooltip} from '../../enhance';
|
|
||||||
import {Status} from '../../constants/status';
|
|
||||||
import {Badges} from '../../constants/reasons';
|
|
||||||
import '../../styles/gradient.css';
|
|
||||||
|
|
||||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
||||||
|
|
||||||
function getRelativeTime (time) {
|
|
||||||
const currentTime = moment();
|
|
||||||
const targetTime = moment(time);
|
|
||||||
const diffMinutes = currentTime.diff(targetTime, 'minutes');
|
|
||||||
if (diffMinutes < 1)
|
|
||||||
return 'Just now';
|
|
||||||
if (diffMinutes < 5)
|
|
||||||
return 'Few minutes ago';
|
|
||||||
if (diffMinutes < 60)
|
|
||||||
return diffMinutes + ' minutes ago';
|
|
||||||
if (diffMinutes < 60 * 24)
|
|
||||||
return Math.floor(diffMinutes / 60) + ' hours ago';
|
|
||||||
|
|
||||||
const diffDays = currentTime.diff(targetTime, 'days');
|
|
||||||
if (diffDays === 1)
|
|
||||||
return 'Yesterday';
|
|
||||||
if (diffDays <= 7)
|
|
||||||
return 'Last ' + targetTime.format('dddd');
|
|
||||||
// @TODO implement longer diffs
|
|
||||||
return 'Long time ago';
|
|
||||||
}
|
|
||||||
|
|
||||||
const UnofficialReleaseTag = styled('span')({
|
|
||||||
color: 'white',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '21px',
|
|
||||||
bottom: '0px',
|
|
||||||
fontSize: '9px',
|
|
||||||
background: '#f42839',
|
|
||||||
fontWeight: '800',
|
|
||||||
padding: '2px 4px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
});
|
|
||||||
|
|
||||||
const FixedContainer = styled('div')({
|
|
||||||
height: '80%',
|
|
||||||
maxWidth: 270,
|
|
||||||
display: 'block',
|
|
||||||
position: 'fixed'
|
|
||||||
});
|
|
||||||
|
|
||||||
const InlineBlockContainer = styled('div')({
|
|
||||||
'div': {
|
|
||||||
display: 'inline-block'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const NotificationsContainer = styled('div')({
|
|
||||||
position: 'relative',
|
|
||||||
background: '#fff',
|
|
||||||
margin: '0 auto',
|
|
||||||
padding: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
overflowX: 'hidden',
|
|
||||||
boxSizing: 'border-box'
|
|
||||||
});
|
|
||||||
|
|
||||||
const NavigationContainer = styled('div')({
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
margin: '0 auto',
|
|
||||||
width: '100%',
|
|
||||||
height: 60,
|
|
||||||
color: 'hsla(0,0%,100%,.75)',
|
|
||||||
paddingBottom: '12px',
|
|
||||||
paddingTop: '12px',
|
|
||||||
zIndex: '100',
|
|
||||||
});
|
|
||||||
|
|
||||||
const GeneralOptionsContainer = styled(NavigationContainer)({
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: '1',
|
|
||||||
height: 'initial',
|
|
||||||
minHeight: 60,
|
|
||||||
width: '95%',
|
|
||||||
margin: 0,
|
|
||||||
background: '#fff',
|
|
||||||
padding: '8px 16px',
|
|
||||||
paddingTop: 18,
|
|
||||||
flex: '0 0 50px',
|
|
||||||
'button': {
|
|
||||||
display: 'inline-flex',
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const Sidebar = styled('div')({
|
|
||||||
flex: '0 0 300px',
|
|
||||||
padding: '32px 20px',
|
|
||||||
paddingRight: 0,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
const SidebarLink = styled('a')({}, ({active, color}) => ({
|
|
||||||
textAlign: 'left',
|
|
||||||
userSelect: 'none',
|
|
||||||
margin: '0 auto 5px',
|
|
||||||
position: 'relative',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: 4,
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '0 14px',
|
|
||||||
height: 40,
|
|
||||||
width: 200,
|
|
||||||
fontSize: '12px',
|
|
||||||
fontWeight: 600,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
textTransform: 'capitalize',
|
|
||||||
textDecoration: 'none',
|
|
||||||
transition: 'background 0.12s ease-in-out',
|
|
||||||
display: 'flex',
|
|
||||||
background: active ? color : 'none',
|
|
||||||
color: active ? '#fff' : '#202124',
|
|
||||||
':before': {
|
|
||||||
content: '""',
|
|
||||||
transition: 'all 150ms ease',
|
|
||||||
background: 'rgba(190, 197, 208, 0.25)',
|
|
||||||
borderRadius: 4,
|
|
||||||
display: 'block',
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
left: 0,
|
|
||||||
position: 'absolute',
|
|
||||||
transform: 'scale(0)'
|
|
||||||
},
|
|
||||||
':hover:before': {
|
|
||||||
transform: active ? 'scale(0)' : 'scale(1)',
|
|
||||||
},
|
|
||||||
':active:before': {
|
|
||||||
background: 'rgba(190, 197, 208, 0.5)'
|
|
||||||
},
|
|
||||||
'div': {
|
|
||||||
marginRight: 5
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Notifications = styled('div')({
|
|
||||||
flex: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const NavTab = styled('a')({
|
|
||||||
position: 'relative',
|
|
||||||
textTransform: 'capitalize',
|
|
||||||
userSelect: 'none',
|
|
||||||
borderRadius: 4,
|
|
||||||
textDecoration: 'none',
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: '14px',
|
|
||||||
textAlign: 'left',
|
|
||||||
opacity: 0.6,
|
|
||||||
padding: '20px 32px',
|
|
||||||
paddingLeft: '16px',
|
|
||||||
width: '150px',
|
|
||||||
display: 'inline-block',
|
|
||||||
margin: 0,
|
|
||||||
transition: 'all 150ms ease',
|
|
||||||
':hover': {
|
|
||||||
background: 'rgba(190, 197, 208, 0.25)',
|
|
||||||
},
|
|
||||||
}, ({ number }) => ({
|
|
||||||
':after': number > 0 && {
|
|
||||||
content: `"${number}"`,
|
|
||||||
color: '#ffffff',
|
|
||||||
background: '#a8a8a9',
|
|
||||||
fontSize: '10px',
|
|
||||||
verticalAlign: 'text-top',
|
|
||||||
padding: '1px 8px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
marginLeft: '6px',
|
|
||||||
display: 'inline-block',
|
|
||||||
}
|
|
||||||
}), ({ active, color, number }) => active && ({
|
|
||||||
color,
|
|
||||||
opacity: 1,
|
|
||||||
':before': {
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
background: color,
|
|
||||||
height: '3px',
|
|
||||||
width: '90%',
|
|
||||||
bottom: '0',
|
|
||||||
left: '5%',
|
|
||||||
borderTopLeftRadius: '4px',
|
|
||||||
borderTopRightRadius: '4px',
|
|
||||||
},
|
|
||||||
':after': number > 0 && {
|
|
||||||
content: `"${number}"`,
|
|
||||||
color: '#ffffff',
|
|
||||||
background: color,
|
|
||||||
fontSize: '10px',
|
|
||||||
verticalAlign: 'text-top',
|
|
||||||
padding: '1px 8px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
marginLeft: '6px',
|
|
||||||
display: 'inline-block',
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Tab = styled('button')({
|
|
||||||
position: 'relative',
|
|
||||||
userSelect: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
border: 0,
|
|
||||||
outline: 'none',
|
|
||||||
background: 'none',
|
|
||||||
height: 40,
|
|
||||||
width: 40,
|
|
||||||
borderRadius: '100%',
|
|
||||||
margin: '0 auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
':before': {
|
|
||||||
content: "''",
|
|
||||||
transition: 'all 150ms ease',
|
|
||||||
background: 'rgba(190, 197, 208, 0.25)',
|
|
||||||
borderRadius: '100%',
|
|
||||||
display: 'block',
|
|
||||||
height: 40,
|
|
||||||
width: 40,
|
|
||||||
position: 'absolute',
|
|
||||||
transform: 'scale(0)'
|
|
||||||
},
|
|
||||||
':hover:before': {
|
|
||||||
transform: 'scale(1)',
|
|
||||||
},
|
|
||||||
':active:before': {
|
|
||||||
background: 'rgba(190, 197, 208, 0.5)'
|
|
||||||
}
|
|
||||||
}, ({disabled}) => disabled && ({
|
|
||||||
background: 'none !important',
|
|
||||||
opacity: 0.35,
|
|
||||||
cursor: 'default',
|
|
||||||
':hover:before': {
|
|
||||||
transform: 'scale(0) !important',
|
|
||||||
},
|
|
||||||
':active:before': {
|
|
||||||
background: 'none !important'
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const SearchField = styled('div')({
|
|
||||||
float: 'left',
|
|
||||||
textAlign: 'left',
|
|
||||||
width: '50%',
|
|
||||||
boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
|
|
||||||
margin: '0 auto',
|
|
||||||
background: 'hsla(0,0%,100%,.125)',
|
|
||||||
borderRadius: '4px',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 0,
|
|
||||||
height: '36px',
|
|
||||||
fontSize: '13px',
|
|
||||||
textDecoration: 'none',
|
|
||||||
transition: 'all 0.06s ease-in-out',
|
|
||||||
display: 'inline-flex',
|
|
||||||
':focus-within': {
|
|
||||||
background: '#fff'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const Message = styled('div')({
|
|
||||||
display: 'block',
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 24 * 5,
|
|
||||||
'p': {
|
|
||||||
paddingTop: 24,
|
|
||||||
userSelect: 'none',
|
|
||||||
display: 'block',
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const LoaderContainer = styled('div')({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '100%'
|
|
||||||
});
|
|
||||||
|
|
||||||
const SearchInput = styled('input')({
|
|
||||||
flex: 1,
|
|
||||||
textAlign: 'left',
|
|
||||||
margin: '0 auto',
|
|
||||||
background: 'none',
|
|
||||||
padding: 0,
|
|
||||||
height: '36px',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '13px',
|
|
||||||
textDecoration: 'none',
|
|
||||||
display: 'inline-flex',
|
|
||||||
border: '0',
|
|
||||||
outline: 'none',
|
|
||||||
':focus': {
|
|
||||||
color: '#202124'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const EnhancedSearchInput = withOnEnter(SearchInput);
|
|
||||||
|
|
||||||
const NotificationRow = styled('tr')({
|
|
||||||
position: 'relative',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
textAlign: 'left',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: 4,
|
|
||||||
margin: '0 auto',
|
|
||||||
background: '#fff',
|
|
||||||
padding: '8px 16px',
|
|
||||||
transition: 'all 0.1s ease-in-out',
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
':hover': {
|
|
||||||
background: '#f9f9f9',
|
|
||||||
// boxShadow: '0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08)',
|
|
||||||
zIndex: 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const NotificationTab = styled(Tab)({
|
|
||||||
display: 'inline-flex',
|
|
||||||
margin: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Timestamp = styled('span')({
|
|
||||||
position: 'relative',
|
|
||||||
margin: 0,
|
|
||||||
marginLeft: 10,
|
|
||||||
fontSize: 11,
|
|
||||||
opacity: 0.5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const NotificationTitle = styled('span')({
|
|
||||||
position: 'relative',
|
|
||||||
display: 'block'
|
|
||||||
}, ({img}) => img && ({
|
|
||||||
paddingLeft: 20,
|
|
||||||
'::before': {
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'block',
|
|
||||||
background: `url(${img}) center center no-repeat`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
left: 0,
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Repository = styled('span')({
|
|
||||||
fontWeight: 500,
|
|
||||||
marginLeft: 10,
|
|
||||||
fontSize: 14
|
|
||||||
});
|
|
||||||
|
|
||||||
const PRIssue = styled(Repository)({
|
|
||||||
fontWeight: 400,
|
|
||||||
}, ({after}) => ({
|
|
||||||
':after': {
|
|
||||||
content: `"#${after}"`,
|
|
||||||
fontSize: 13,
|
|
||||||
opacity: .3,
|
|
||||||
marginLeft: 5
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Table = styled('table')({
|
|
||||||
width: '96%',
|
|
||||||
minWidth: 970,
|
|
||||||
'td': {
|
|
||||||
display: 'inline-block'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const TableItem = styled('td')({
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
}, ({width, flex}) => ({
|
|
||||||
width,
|
|
||||||
flex
|
|
||||||
}));
|
|
||||||
|
|
||||||
const SmallLink = styled('a')({
|
|
||||||
display: 'block',
|
|
||||||
marginRight: 10,
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: 10,
|
|
||||||
lineHeight: '20px',
|
|
||||||
fontWeight: 400,
|
|
||||||
textDecoration: 'underline',
|
|
||||||
transition: 'all 0.12s ease-in-out',
|
|
||||||
':hover': {
|
|
||||||
opacity: 0.75
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const EnhancedTab = withTooltip(Tab);
|
|
||||||
const EnhancedNavTab = withTooltip(NavTab);
|
|
||||||
const EnhancedNotificationTab = withTooltip(NotificationTab);
|
|
||||||
const EnhancedSidebarLink = withTooltip(SidebarLink);
|
|
||||||
const EnhancedIconHot = withTooltip(Icon.Hot);
|
|
||||||
const EnhancedIconTimer = withTooltip(Icon.Timer);
|
|
||||||
const EnhancedIconConvo = withTooltip(Icon.Convo);
|
|
||||||
|
|
||||||
function getPRIssueIcon (type, reasons) {
|
|
||||||
const grow = 1.0;
|
|
||||||
switch (type) {
|
|
||||||
case 'PullRequest':
|
|
||||||
return (
|
|
||||||
<Icon.PrMerged shrink={grow} />
|
|
||||||
);
|
|
||||||
case 'Issue':
|
|
||||||
return (
|
|
||||||
<Icon.IssueOpen shrink={grow} />
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Scene ({
|
|
||||||
queuedCount,
|
|
||||||
stagedCount,
|
|
||||||
closedCount,
|
|
||||||
first,
|
|
||||||
last,
|
|
||||||
lastPage,
|
|
||||||
page,
|
|
||||||
notifications,
|
|
||||||
query,
|
|
||||||
activeStatus,
|
|
||||||
allNotificationsCount,
|
|
||||||
stagedTodayCount,
|
|
||||||
onChangePage,
|
|
||||||
onSetActiveStatus,
|
|
||||||
onClearQuery,
|
|
||||||
onLogout,
|
|
||||||
onSearch,
|
|
||||||
onMarkAsRead,
|
|
||||||
onFetchNotifications,
|
|
||||||
onRefreshNotifications,
|
|
||||||
onStageThread,
|
|
||||||
onRestoreThread,
|
|
||||||
isSearching,
|
|
||||||
isFetchingNotifications,
|
|
||||||
onClearCache,
|
|
||||||
fetchingNotificationsError,
|
|
||||||
activeFilter,
|
|
||||||
onSetActiveFilter,
|
|
||||||
}) {
|
|
||||||
const loading = isSearching || isFetchingNotifications;
|
|
||||||
const isFirstPage = page === 1;
|
|
||||||
const isLastPage = page === lastPage;
|
|
||||||
|
|
||||||
console.warn('before render in scene', notifications)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{marginTop: 60}}>
|
|
||||||
<NavigationContainer className="container-gradient">
|
|
||||||
<div style={{
|
|
||||||
position: 'relative',
|
|
||||||
textAlign: 'right',
|
|
||||||
margin: '0 auto',
|
|
||||||
width: '92%'
|
|
||||||
}}>
|
|
||||||
<Logo
|
|
||||||
size={36}
|
|
||||||
style={{
|
|
||||||
float: 'left',
|
|
||||||
marginRight: 48,
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
onSetActiveStatus(Status.QUEUED);
|
|
||||||
onSetActiveFilter(Filters.PARTICIPATING);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<UnofficialReleaseTag>beta</UnofficialReleaseTag>
|
|
||||||
<SearchField>
|
|
||||||
<Icon.Search size={48} opacity={.45} />
|
|
||||||
<EnhancedSearchInput
|
|
||||||
disabled={loading}
|
|
||||||
type="text"
|
|
||||||
placeholder="Search for notifications"
|
|
||||||
onEnter={onSearch}
|
|
||||||
/>
|
|
||||||
{isSearching && <LoadingIcon white={true} size={48} />}
|
|
||||||
</SearchField>
|
|
||||||
<div style={{display: 'inline-block'}} className="button-container-alt">
|
|
||||||
<Link style={{
|
|
||||||
marginRight: 15,
|
|
||||||
background: 'none',
|
|
||||||
color: '#fff',
|
|
||||||
height: 36,
|
|
||||||
padding: '0 12px'
|
|
||||||
}} to={routes.HOME}>home</Link>
|
|
||||||
</div>
|
|
||||||
<div style={{display: 'inline-block'}} className="button-container-alt">
|
|
||||||
<a style={{
|
|
||||||
marginRight: 15,
|
|
||||||
background: 'none',
|
|
||||||
color: '#fff',
|
|
||||||
height: 36,
|
|
||||||
padding: '0 12px'
|
|
||||||
}} href="#" onClick={onLogout}>sign out</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NavigationContainer>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row'
|
|
||||||
}}>
|
|
||||||
<div style={{
|
|
||||||
flex: '0 0 300px'
|
|
||||||
}}>
|
|
||||||
<Sidebar>
|
|
||||||
<FixedContainer>
|
|
||||||
<div style={{
|
|
||||||
width: 220,
|
|
||||||
padding: '0 14px',
|
|
||||||
margin: '0 11px 12px',
|
|
||||||
}}>
|
|
||||||
<h3 style={{
|
|
||||||
margin: 0
|
|
||||||
}}>
|
|
||||||
<Icon.Clock style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
verticalAlign: 'middle',
|
|
||||||
marginRight: '5px',
|
|
||||||
top: '-3px',
|
|
||||||
}} />
|
|
||||||
{moment().format('h:mma')}
|
|
||||||
</h3>
|
|
||||||
<span style={{
|
|
||||||
display: 'block',
|
|
||||||
padding: '6px 0px',
|
|
||||||
fontSize: 15,
|
|
||||||
opacity: 0.7,
|
|
||||||
}}>{moment().format('dddd, MMMM Do')}</span>
|
|
||||||
<span style={{
|
|
||||||
display: 'block',
|
|
||||||
padding: '6px 0 8px',
|
|
||||||
fontSize: 12,
|
|
||||||
opacity: 0.5,
|
|
||||||
}}>You've triaged {stagedTodayCount} notifications today</span>
|
|
||||||
</div>
|
|
||||||
{/*
|
|
||||||
We shouldn't show all the notificaitons. Pointless and creates more noise.
|
|
||||||
|
|
||||||
<SidebarLink
|
|
||||||
active={activeFilter === Filters.ALL}
|
|
||||||
color="#00A0F5"
|
|
||||||
onClick={() => onSetActiveFilter(Filters.ALL)}>
|
|
||||||
{activeFilter === Filters.ALL ? (
|
|
||||||
<Icon.InboxWhite shrink={.6} />
|
|
||||||
) : (
|
|
||||||
<Icon.Inbox shrink={.6} />
|
|
||||||
)}
|
|
||||||
all notifications
|
|
||||||
</SidebarLink>
|
|
||||||
*/}
|
|
||||||
<EnhancedSidebarLink
|
|
||||||
tooltip="All the updates for issues and pull requests that are your responsibility to deal with"
|
|
||||||
tooltipOffsetX={130}
|
|
||||||
active={activeFilter === Filters.PARTICIPATING}
|
|
||||||
color="#00d19a"
|
|
||||||
onClick={() => onSetActiveFilter(Filters.PARTICIPATING)}>
|
|
||||||
{activeFilter === Filters.PARTICIPATING ? (
|
|
||||||
<Icon.BoltWhite shrink={.6} />
|
|
||||||
) : (
|
|
||||||
<Icon.Bolt shrink={.6} />
|
|
||||||
)}
|
|
||||||
your updates
|
|
||||||
</EnhancedSidebarLink>
|
|
||||||
<EnhancedSidebarLink
|
|
||||||
tooltip="Updates for issues and pull requests that you have commented on"
|
|
||||||
tooltipOffsetX={100}
|
|
||||||
active={activeFilter === Filters.COMMENT}
|
|
||||||
color="#00A0F5"
|
|
||||||
onClick={() => onSetActiveFilter(Filters.COMMENT)}>
|
|
||||||
{activeFilter === Filters.COMMENT ? (
|
|
||||||
<Icon.PeopleAltWhite shrink={.6} />
|
|
||||||
) : (
|
|
||||||
<Icon.PeopleAlt shrink={.6} />
|
|
||||||
)}
|
|
||||||
participating
|
|
||||||
</EnhancedSidebarLink>
|
|
||||||
<div style={{
|
|
||||||
padding: 14,
|
|
||||||
margin: 21,
|
|
||||||
background: '#f5f5f5',
|
|
||||||
borderRadius: 4,
|
|
||||||
height: 100,
|
|
||||||
fontSize: 12
|
|
||||||
}}>
|
|
||||||
bar chart, statistics, etc. will be here eventually
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
padding: 14,
|
|
||||||
margin: 21,
|
|
||||||
}}>
|
|
||||||
<SmallLink target="_blank" href="https://github.com/nickzuber/meteorite/issues">Report bugs</SmallLink>
|
|
||||||
<SmallLink target="_blank" href="https://github.com/nickzuber/meteorite/issues">Submit feedback</SmallLink>
|
|
||||||
<SmallLink target="_blank" href="https://github.com/nickzuber/meteorite">See source code</SmallLink>
|
|
||||||
</div>
|
|
||||||
</FixedContainer>
|
|
||||||
</Sidebar>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
flex: 1
|
|
||||||
}}>
|
|
||||||
<GeneralOptionsContainer>
|
|
||||||
<EnhancedTab tooltip={!loading ? "Refresh your notifications" : null} disabled={loading}>
|
|
||||||
<Icon.Refresh
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => onFetchNotifications()) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedTab>
|
|
||||||
<EnhancedTab tooltip={!loading ? "Delete all of your notifications from the cache" : null} disabled={loading}>
|
|
||||||
<Icon.Trash
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => {
|
|
||||||
const response = window.confirm('Are you sure you want to clear the cache?');
|
|
||||||
if (response) {
|
|
||||||
onClearCache();
|
|
||||||
}
|
|
||||||
}) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedTab>
|
|
||||||
{query ? (
|
|
||||||
<React.Fragment>
|
|
||||||
<div style={{display: 'inline-block'}} className="button-container-alt">
|
|
||||||
<a style={{
|
|
||||||
background: 'none',
|
|
||||||
color: '#202124',
|
|
||||||
textTransform: 'inherit',
|
|
||||||
boxShadow: '0 0 0',
|
|
||||||
fontWeight: 400,
|
|
||||||
height: 36,
|
|
||||||
padding: '0 12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Showing results for '{query}'
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<EnhancedTab disabled={loading}>
|
|
||||||
<Icon.X
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => onClearQuery()) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedTab>
|
|
||||||
</React.Fragment>
|
|
||||||
) : null}
|
|
||||||
<div style={{float: 'right'}}>
|
|
||||||
<div style={{display: 'inline-block'}} className="button-container-alt">
|
|
||||||
<a style={{
|
|
||||||
marginRight: 15,
|
|
||||||
background: 'none',
|
|
||||||
color: '#202124',
|
|
||||||
textTransform: 'inherit',
|
|
||||||
boxShadow: '0 0 0',
|
|
||||||
fontWeight: 400,
|
|
||||||
height: 36,
|
|
||||||
padding: '0 12px',
|
|
||||||
}}>
|
|
||||||
{first}-{last} of about {allNotificationsCount}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<EnhancedTab disabled={loading || isFirstPage}>
|
|
||||||
<Icon.Prev
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading && !isFirstPage ? (() => onChangePage(page - 1)) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedTab>
|
|
||||||
<EnhancedTab disabled={loading || isLastPage}>
|
|
||||||
<Icon.Next
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading && !isLastPage ? (() => onChangePage(page + 1)) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedTab>
|
|
||||||
</div>
|
|
||||||
</GeneralOptionsContainer>
|
|
||||||
<GeneralOptionsContainer style={{paddingTop: 4}}>
|
|
||||||
<EnhancedNavTab
|
|
||||||
tooltip="New updates that you haven't dealt with yet"
|
|
||||||
tooltipOffsetX={55}
|
|
||||||
number={queuedCount}
|
|
||||||
color="#00d19a"
|
|
||||||
active={activeStatus === Status.QUEUED}
|
|
||||||
onClick={() => onSetActiveStatus(Status.QUEUED)}
|
|
||||||
href="javascript:void(0);">
|
|
||||||
Unread
|
|
||||||
</EnhancedNavTab>
|
|
||||||
<EnhancedNavTab
|
|
||||||
tooltip="Notifications that you've seen, clicked on, or otherwise have handled"
|
|
||||||
tooltipOffsetX={55}
|
|
||||||
number={stagedCount}
|
|
||||||
color="#009ef8"
|
|
||||||
active={activeStatus === Status.STAGED}
|
|
||||||
onClick={() => onSetActiveStatus(Status.STAGED)}
|
|
||||||
href="javascript:void(0);">
|
|
||||||
Read
|
|
||||||
</EnhancedNavTab>
|
|
||||||
<EnhancedNavTab
|
|
||||||
tooltip="Stale and old notifications that are considered closed out and finished"
|
|
||||||
tooltipOffsetX={55}
|
|
||||||
number={closedCount}
|
|
||||||
color="#f12c3f"
|
|
||||||
active={activeStatus === Status.CLOSED}
|
|
||||||
onClick={() => onSetActiveStatus(Status.CLOSED)}
|
|
||||||
href="javascript:void(0);">
|
|
||||||
Resolved
|
|
||||||
</EnhancedNavTab>
|
|
||||||
</GeneralOptionsContainer>
|
|
||||||
<NotificationsContainer>
|
|
||||||
<Notifications>
|
|
||||||
{isFetchingNotifications ? (
|
|
||||||
<LoaderContainer>
|
|
||||||
<LoadingIcon />
|
|
||||||
</LoaderContainer>
|
|
||||||
) : notifications.length <= 0 ? (
|
|
||||||
<Message>
|
|
||||||
<p style={{
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 400,
|
|
||||||
}}>
|
|
||||||
No {activeStatus.toLowerCase()} notifications</p>
|
|
||||||
<p style={{
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 400,
|
|
||||||
color: '#5f6368'
|
|
||||||
}}>
|
|
||||||
<span role="img" aria-label="hooray">🎉</span> You're all set here for the moment</p>
|
|
||||||
</Message>
|
|
||||||
) : (
|
|
||||||
<Table>
|
|
||||||
<tbody style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
}}>
|
|
||||||
{notifications.map(n => (
|
|
||||||
<NotificationRow key={n.id}>
|
|
||||||
<TableItem>
|
|
||||||
<div style={{ float: 'left', marginTop: 2 }}>
|
|
||||||
{getPRIssueIcon(n.type, n.reasons)}
|
|
||||||
</div>
|
|
||||||
</TableItem>
|
|
||||||
<TableItem
|
|
||||||
style={{height: 36, cursor: 'pointer', userSelect: 'none'}}
|
|
||||||
width={400}
|
|
||||||
flex={.65}
|
|
||||||
onClick={() => {
|
|
||||||
window.open(n.url);
|
|
||||||
onStageThread(n.id, n.repository)
|
|
||||||
}}>
|
|
||||||
<NotificationTitle>
|
|
||||||
<PRIssue after={n.number}>{n.name}</PRIssue>
|
|
||||||
</NotificationTitle>
|
|
||||||
<Timestamp>
|
|
||||||
{getRelativeTime(n.updated_at)}
|
|
||||||
{n.isAuthor && (
|
|
||||||
<Icon.User
|
|
||||||
shrink={0.5}
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
top: -3
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Timestamp>
|
|
||||||
</TableItem>
|
|
||||||
<TableItem width={100}>
|
|
||||||
<InlineBlockContainer>
|
|
||||||
{n.badges.map(badge => {
|
|
||||||
switch (badge) {
|
|
||||||
case Badges.HOT:
|
|
||||||
// lots of `reasons` within short time frame
|
|
||||||
return (
|
|
||||||
<EnhancedIconHot
|
|
||||||
tooltip="Lots of recent activity"
|
|
||||||
tooltipOffsetX={-15}
|
|
||||||
tooltipOffsetY={-10}
|
|
||||||
shrink={0.75}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case Badges.OLD:
|
|
||||||
// old
|
|
||||||
return (
|
|
||||||
<EnhancedIconTimer
|
|
||||||
tooltip="Old pull request that needs your review"
|
|
||||||
tooltipOffsetX={-15}
|
|
||||||
tooltipOffsetY={-10}
|
|
||||||
shrink={0.75}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case Badges.COMMENTS:
|
|
||||||
// lots of `reasons`
|
|
||||||
return (
|
|
||||||
<EnhancedIconConvo
|
|
||||||
tooltip="Very talkative thread"
|
|
||||||
tooltipOffsetX={-15}
|
|
||||||
tooltipOffsetY={-10}
|
|
||||||
shrink={0.75}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</InlineBlockContainer>
|
|
||||||
</TableItem>
|
|
||||||
<TableItem width={250} flex={.35}>
|
|
||||||
<Repository
|
|
||||||
onClick={() => window.open(n.repositoryUrl)}
|
|
||||||
style={{cursor: 'pointer', userSelect: 'none'}}>
|
|
||||||
{n.repository}</Repository>
|
|
||||||
</TableItem>
|
|
||||||
<TableItem width={150} style={{textAlign: 'right'}}>
|
|
||||||
<EnhancedNotificationTab
|
|
||||||
tooltip={!loading ? "Score representing this notification's importance" : null}
|
|
||||||
tooltipOffsetX={-20}
|
|
||||||
>
|
|
||||||
{n.score}
|
|
||||||
</EnhancedNotificationTab>
|
|
||||||
{activeStatus === Status.QUEUED ? (
|
|
||||||
<EnhancedNotificationTab
|
|
||||||
tooltip={!loading ? "Mark as read" : null}
|
|
||||||
tooltipOffsetX={-10}
|
|
||||||
>
|
|
||||||
<Icon.Check
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => onStageThread(n.id, n.repository)) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedNotificationTab>
|
|
||||||
) : (
|
|
||||||
<EnhancedNotificationTab
|
|
||||||
tooltip={!loading ? "Revert back to unread" : null}
|
|
||||||
tooltipOffsetX={-10}
|
|
||||||
>
|
|
||||||
<Icon.Undo
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => onRestoreThread(n.id)) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedNotificationTab>
|
|
||||||
)}
|
|
||||||
{activeStatus === Status.CLOSED ? (
|
|
||||||
<EnhancedNotificationTab>
|
|
||||||
<Icon.Help
|
|
||||||
shrink={0.8}
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => {}) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedNotificationTab>
|
|
||||||
) : (
|
|
||||||
<EnhancedNotificationTab tooltip={!loading ? "Mark as resolved" : null}>
|
|
||||||
<Icon.X
|
|
||||||
opacity={0.9}
|
|
||||||
onClick={!loading ? (() => onMarkAsRead(n.id)) : undefined}
|
|
||||||
/>
|
|
||||||
</EnhancedNotificationTab>
|
|
||||||
)}
|
|
||||||
</TableItem>
|
|
||||||
</NotificationRow>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</Notifications>
|
|
||||||
</NotificationsContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -11,7 +11,7 @@ import { routes } from '../../constants';
|
|||||||
import { Filters } from '../../constants/filters';
|
import { Filters } from '../../constants/filters';
|
||||||
import { Status } from '../../constants/status';
|
import { Status } from '../../constants/status';
|
||||||
import { Reasons, Badges } from '../../constants/reasons';
|
import { Reasons, Badges } from '../../constants/reasons';
|
||||||
import Scene from './SceneAlt';
|
import Scene from './Scene';
|
||||||
|
|
||||||
const PER_PAGE = 15;
|
const PER_PAGE = 15;
|
||||||
|
|
||||||
@ -53,7 +53,6 @@ function scoreOf (notification) {
|
|||||||
let prevReason = null;
|
let prevReason = null;
|
||||||
for (let i = 0; i < reasons.length; i++) {
|
for (let i = 0; i < reasons.length; i++) {
|
||||||
const reason = reasons[i].reason;
|
const reason = reasons[i].reason;
|
||||||
console.log(reason)
|
|
||||||
if (prevReason && reason === prevReason) {
|
if (prevReason && reason === prevReason) {
|
||||||
const degradedScore = Math.ceil(scoreOfReason[reason] / 3);
|
const degradedScore = Math.ceil(scoreOfReason[reason] / 3);
|
||||||
score += Math.max(degradedScore, 2);
|
score += Math.max(degradedScore, 2);
|
||||||
@ -122,8 +121,9 @@ class NotificationsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
this.props.notificationsApi.fetchNotifications();
|
||||||
|
|
||||||
this.syncer = setInterval(() => {
|
this.syncer = setInterval(() => {
|
||||||
console.warn('sync');
|
|
||||||
this.props.notificationsApi.fetchNotificationsSync();
|
this.props.notificationsApi.fetchNotificationsSync();
|
||||||
}, 8 * 1000);
|
}, 8 * 1000);
|
||||||
}
|
}
|
||||||
@ -172,14 +172,12 @@ class NotificationsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enhancedOnStageThread = (thread_id, repository) => {
|
enhancedOnStageThread = (thread_id, repository) => {
|
||||||
console.warn('staging thread', thread_id, 'in repo', repository);
|
|
||||||
this.props.storageApi.incrStat('stagedCount');
|
this.props.storageApi.incrStat('stagedCount');
|
||||||
this.props.storageApi.incrStat(repository + '-stagedCount');
|
this.props.storageApi.incrStat(repository + '-stagedCount');
|
||||||
this.props.notificationsApi.stageThread(thread_id);
|
this.props.notificationsApi.stageThread(thread_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreThread = thread_id => {
|
restoreThread = thread_id => {
|
||||||
console.warn('restoring thread');
|
|
||||||
this.props.notificationsApi.restoreThread(thread_id);
|
this.props.notificationsApi.restoreThread(thread_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +189,7 @@ class NotificationsPage extends React.Component {
|
|||||||
const {
|
const {
|
||||||
fetchNotifications,
|
fetchNotifications,
|
||||||
markAsRead,
|
markAsRead,
|
||||||
|
markAllAsStaged,
|
||||||
clearCache,
|
clearCache,
|
||||||
notifications,
|
notifications,
|
||||||
loading: isFetchingNotifications,
|
loading: isFetchingNotifications,
|
||||||
@ -210,6 +209,16 @@ class NotificationsPage extends React.Component {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case Filters.ASSIGNED:
|
||||||
|
filterMethod = n => (
|
||||||
|
n.reasons.some(({ reason }) => reason === 'assign')
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Filters.REVIEW_REQUESTED:
|
||||||
|
filterMethod = n => (
|
||||||
|
n.reasons.some(({ reason }) => reason === 'review_requested')
|
||||||
|
);
|
||||||
|
break;
|
||||||
case Filters.COMMENT:
|
case Filters.COMMENT:
|
||||||
filterMethod = n => (
|
filterMethod = n => (
|
||||||
n.reasons.some(({ reason }) => reason === 'comment')
|
n.reasons.some(({ reason }) => reason === 'comment')
|
||||||
@ -290,6 +299,7 @@ class NotificationsPage extends React.Component {
|
|||||||
onClearQuery={this.onClearQuery}
|
onClearQuery={this.onClearQuery}
|
||||||
onFetchNotifications={fetchNotifications}
|
onFetchNotifications={fetchNotifications}
|
||||||
onMarkAsRead={markAsRead}
|
onMarkAsRead={markAsRead}
|
||||||
|
onMarkAllAsStaged={markAllAsStaged}
|
||||||
onClearCache={clearCache}
|
onClearCache={clearCache}
|
||||||
onStageThread={this.enhancedOnStageThread}
|
onStageThread={this.enhancedOnStageThread}
|
||||||
onRestoreThread={this.restoreThread}
|
onRestoreThread={this.restoreThread}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {AuthConsumer} from './Auth';
|
import {AuthConsumer} from './Auth';
|
||||||
import {StorageProvider} from './Storage';
|
import {StorageProvider, LOCAL_STORAGE_PREFIX} from './Storage';
|
||||||
import {Status} from '../constants/status';
|
import {Status} from '../constants/status';
|
||||||
|
|
||||||
const BASE_GITHUB_API_URL = 'https://api.github.com';
|
const BASE_GITHUB_API_URL = 'https://api.github.com';
|
||||||
@ -121,7 +121,7 @@ class NotificationsProvider extends React.Component {
|
|||||||
|
|
||||||
fetchNotifications = (page = 1, optimizePolling = true) => {
|
fetchNotifications = (page = 1, optimizePolling = true) => {
|
||||||
if (!this.props.token) {
|
if (!this.props.token) {
|
||||||
console.error('Unauthenitcated, aborting request.')
|
console.error('Unauthenitcated, aborting request.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ class NotificationsProvider extends React.Component {
|
|||||||
|
|
||||||
processNotificationsChunk = (nextPage, notificationsChunk) => {
|
processNotificationsChunk = (nextPage, notificationsChunk) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log('chunk', notificationsChunk)
|
console.log('chunk', notificationsChunk);
|
||||||
let everythingUpdated = true;
|
let everythingUpdated = true;
|
||||||
|
|
||||||
if (notificationsChunk.length === 0) {
|
if (notificationsChunk.length === 0) {
|
||||||
@ -187,7 +187,6 @@ class NotificationsProvider extends React.Component {
|
|||||||
: Promise.reject();
|
: Promise.reject();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.warn('removing', thread_id);
|
|
||||||
this.props.removeItemFromStorage(thread_id);
|
this.props.removeItemFromStorage(thread_id);
|
||||||
this.props.refreshNotifications();
|
this.props.refreshNotifications();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@ -208,7 +207,6 @@ class NotificationsProvider extends React.Component {
|
|||||||
|
|
||||||
requestClearCache = () => {
|
requestClearCache = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.warn('clearing cache');
|
|
||||||
this.props.clearStorageCache();
|
this.props.clearStorageCache();
|
||||||
this.props.refreshNotifications();
|
this.props.refreshNotifications();
|
||||||
this.last_modified = null;
|
this.last_modified = null;
|
||||||
@ -225,7 +223,6 @@ class NotificationsProvider extends React.Component {
|
|||||||
|
|
||||||
requestStageThread = thread_id => {
|
requestStageThread = thread_id => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.warn('staging thread', thread_id);
|
|
||||||
const cached_n = this.props.getItemFromStorage(thread_id);
|
const cached_n = this.props.getItemFromStorage(thread_id);
|
||||||
if (cached_n) {
|
if (cached_n) {
|
||||||
const newValue = {
|
const newValue = {
|
||||||
@ -241,9 +238,28 @@ class NotificationsProvider extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestStageAll = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Object.keys(localStorage).forEach(nKey => {
|
||||||
|
// Only update the notification items in the cache.
|
||||||
|
// Don't get the statistics or anything else caught in there.
|
||||||
|
if (nKey.includes(LOCAL_STORAGE_PREFIX)) {
|
||||||
|
let cached_n = null;
|
||||||
|
cached_n = JSON.parse(window.localStorage.getItem(nKey));
|
||||||
|
const newValue = {
|
||||||
|
...cached_n,
|
||||||
|
status: Status.STAGED
|
||||||
|
};
|
||||||
|
window.localStorage.setItem(nKey, JSON.stringify(newValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.props.refreshNotifications();
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
requestRestoreThread = thread_id => {
|
requestRestoreThread = thread_id => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.warn('restoring thread', thread_id);
|
|
||||||
const cached_n = this.props.getItemFromStorage(thread_id);
|
const cached_n = this.props.getItemFromStorage(thread_id);
|
||||||
if (cached_n) {
|
if (cached_n) {
|
||||||
const newValue = {
|
const newValue = {
|
||||||
@ -259,6 +275,13 @@ class NotificationsProvider extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAllAsStaged = () => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
return this.requestStageAll()
|
||||||
|
.catch(error => this.setState({ error }))
|
||||||
|
.finally(() => this.setState({ loading: false }));
|
||||||
|
}
|
||||||
|
|
||||||
stageThread = thread_id => {
|
stageThread = thread_id => {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
return this.requestStageThread(thread_id)
|
return this.requestStageThread(thread_id)
|
||||||
@ -282,7 +305,6 @@ class NotificationsProvider extends React.Component {
|
|||||||
|
|
||||||
if (prevReason) {
|
if (prevReason) {
|
||||||
reasons = prevReason.concat(newReason);
|
reasons = prevReason.concat(newReason);
|
||||||
console.warn('MULTIPLE REASONS', reasons)
|
|
||||||
} else {
|
} else {
|
||||||
reasons = [newReason];
|
reasons = [newReason];
|
||||||
}
|
}
|
||||||
@ -311,6 +333,7 @@ class NotificationsProvider extends React.Component {
|
|||||||
fetchNotifications: this.fetchNotifications,
|
fetchNotifications: this.fetchNotifications,
|
||||||
fetchNotificationsSync: this.requestFetchNotifications,
|
fetchNotificationsSync: this.requestFetchNotifications,
|
||||||
markAsRead: this.markAsRead,
|
markAsRead: this.markAsRead,
|
||||||
|
markAllAsStaged: this.markAllAsStaged,
|
||||||
clearCache: this.clearCache,
|
clearCache: this.clearCache,
|
||||||
stageThread: this.stageThread,
|
stageThread: this.stageThread,
|
||||||
restoreThread: this.restoreThread,
|
restoreThread: this.restoreThread,
|
||||||
|
@ -3,8 +3,8 @@ import moment from 'moment';
|
|||||||
import {Status} from '../constants/status';
|
import {Status} from '../constants/status';
|
||||||
import {Reasons} from '../constants/reasons';
|
import {Reasons} from '../constants/reasons';
|
||||||
|
|
||||||
const LOCAL_STORAGE_PREFIX = '__meteorite_noti_cache__';
|
export const LOCAL_STORAGE_PREFIX = '__meteorite_noti_cache__';
|
||||||
const LOCAL_STORAGE_STATISTIC_PREFIX = '__meteorite_statistic_cache__';
|
export const LOCAL_STORAGE_STATISTIC_PREFIX = '__meteorite_statistic_cache__';
|
||||||
|
|
||||||
const getMockReasons = n => {
|
const getMockReasons = n => {
|
||||||
const reasons = Object.values(Reasons);
|
const reasons = Object.values(Reasons);
|
||||||
|
@ -112,3 +112,12 @@
|
|||||||
50% {background-position: 60% 50%}
|
50% {background-position: 60% 50%}
|
||||||
100% {background-position: 0% 50%}
|
100% {background-position: 0% 50%}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1400px) {
|
||||||
|
#section {
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
#item-text {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|