Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 31 additions & 33 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { Component } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Header from './components/Header';
import Globe from './components/Globe';
import GlobeOptimized from './components/GlobeOptimized';
import GroupedButton from './components/GroupedButton';
import Timeline from './components/Timeline';
import Footer from './components/Footer';
import VideoView from './components/VideoView';

import { StorageProvider } from './contexts/storage';
import { NavigationProvider } from './contexts/navigation';
import { NotificationProvider } from './contexts/notification';
import { WorkerProvider } from './contexts/worker';

import { MuiPickersUtilsProvider } from '@material-ui/pickers';
import { MuiThemeProvider } from '@material-ui/core/styles';
Expand Down Expand Up @@ -53,39 +53,37 @@ class App extends Component {
<Router>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<MuiThemeProvider theme={theme}>
<StorageProvider>
<Switch>
<Route path="/video">
<NavigationProvider>
<div className="globe-container">
<div className="main-section">
<VideoView />
</div>
</div>
</NavigationProvider>
</Route>
<Route path="/">
<div className="App">
<NavigationProvider>
<Header />
<div className="globe-container">
<div className="main-section">
<GlobeOptimized />
{/* <Globe
showGlobe={this.state.showGlobe}
showZoom={true}
width={window.innerWidth - 50}
height={window.innerHeight}
/> */}
<NotificationProvider>
<WorkerProvider>
<StorageProvider>
<Switch>
<Route path="/video">
<NavigationProvider>
<div className="globe-container">
<div className="main-section">
<VideoView />
</div>
</div>
{this.renderTimeline()}
</NavigationProvider>
</Route>
<Route path="/">
<div className="App">
<NavigationProvider>
<Header />
<div className="globe-container">
<div className="main-section">
<GlobeOptimized />
</div>
{this.renderTimeline()}
</div>
</NavigationProvider>
<Footer />
</div>
</NavigationProvider>
<Footer />
</div>
</Route>
</Switch>
</StorageProvider>
</Route>
</Switch>
</StorageProvider>
</WorkerProvider>
</NotificationProvider>
</MuiThemeProvider>
</MuiPickersUtilsProvider>
</Router>
Expand Down
33 changes: 33 additions & 0 deletions src/clients/timeline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from 'axios';

const TIMELINE_STATUS_URL = 'https://meteorshowers.seti.org/api/timeline';
const TIMELINE_CREATE_URL =
'https://meteorshowers.seti.org/api/timeline/create';

export async function createTimeline(startDate, endDate, params) {
const response = await axios.post(TIMELINE_CREATE_URL, {
start_date: startDate,
end_date: endDate,
});

if (!response.data.success) {
throw new Error('cannot create timeline');
}

return response.data.token;
}

export async function getTimeline(token) {
const response = await axios.get(TIMELINE_STATUS_URL, {
params: {
token,
},
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
});

return response.data;
}
68 changes: 55 additions & 13 deletions src/components/NavigationCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import React from 'react';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Checkbox from '@material-ui/core/Checkbox';
// import IconButton from '@material-ui/core/IconButton';
import { DatePicker } from '@material-ui/pickers';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Typography from '@material-ui/core/Typography';
import { useNavigationState } from '../contexts/navigation';

import leftIcon from '../images/left-icon.png';
import rightIcon from '../images/right-icon.png';
import { useNotificationState } from '../contexts/notification';
import { useWorkerState } from '../contexts/worker';
import { createTimeline, getTimeline } from '../clients/timeline';

const monthNames = [
'Jan',
Expand Down Expand Up @@ -37,6 +39,12 @@ const NavigationCard = (props) => {
const [maxDate] = React.useState(`${getMaxDate()}`);
const [isOpen, setIsOpen] = React.useState(false);

// push notification test
const notificationState = useNotificationState();

// worker test
const workerState = useWorkerState();

const getFormat = () => {
const date = new Date(navigationState.date);
let month = monthNames[date.getMonth()];
Expand Down Expand Up @@ -83,7 +91,7 @@ const NavigationCard = (props) => {
<div className="disappear">
<DatePicker
autoOk
clearable
clearable="true"
disableFuture
disableToolbar
format="MMM d, yyyy"
Expand All @@ -102,17 +110,6 @@ const NavigationCard = (props) => {
<Typography variant="h2" color="textSecondary" className="text-left p-1">
<div onClick={() => setIsOpen(true)}>{getFormat()}</div>
</Typography>
{/* <Grid container>
<Grid item>
<DatePicker
autoOk
clearable
disableFuture
value={selectedDate}
onChange={(date) => handleDateChange(date)}
/>
</Grid>
</Grid> */}
<Grid container spacing={1} className="p-1">
<Grid item onClick={decrementDate}>
<Button
Expand Down Expand Up @@ -153,6 +150,51 @@ const NavigationCard = (props) => {
}
/>
</Grid>
{/* <button
onClick={async () => {
const token = await createTimeline('2021-07-10', '2021-07-15');

const callbacks = {
onComplete: () => {
workerState.dequeueWork('TimelinePoll');

// notification
notificationState.pushNotification({
title: 'Your timeline is ready',
body:
'Please download your timeline by clicking this notification',
icon:
'https://nationaltoday.com/wp-content/uploads/2021/06/National-Meteor-Watch-Day.jpg',
onClick: () => {
window
.open(
`https://meteorshowers.seti.org/api/timeline/download?token=${token}`,
'_blank'
)
.focus();
},
});
},
onInProgress: (data) => {
// can use data to update UI
console.log(data);
},
};

const fn = async () => {
const timeline = await getTimeline(token);

return {
complete: timeline.status === 'COMPLETE',
...timeline,
};
};

workerState.enqueueWork('TimelinePoll', fn, 1000, callbacks);
}}
>
Click Me
</button> */}
</Grid>
</>
);
Expand Down
55 changes: 55 additions & 0 deletions src/contexts/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useEffect } from 'react';

const NotificationContext = React.createContext();

export function NotificationProvider({ children }) {
useEffect(() => {
// Ask for notification permission when first loaded
if (window.Notification && Notification.permission !== 'granted') {
Notification.requestPermission((status) => {
if (Notification.permission !== status) {
Notification.permission = status;
}
});
}
}, []);

function pushNotification(options = {}) {
const { title, body, icon, onClick } = options;

if (window.Notification && Notification.permission !== 'granted') {
return;
}

const notification = new Notification(title, {
body,
icon,
});

notification.onclick = onClick;

return notification;
}

return (
<NotificationContext.Provider
value={{
pushNotification,
}}
>
{children}
</NotificationContext.Provider>
);
}

export function useNotificationState() {
const context = React.useContext(NotificationContext);

if (!context) {
throw new Error(
'useNotficationState must be used within a NavigationProvider'
);
}

return context;
}
73 changes: 73 additions & 0 deletions src/contexts/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useRef } from 'react';

const WorkerContext = React.createContext();

export function WorkerProvider({ children }) {
const workMapRef = useRef(new Map());

function enqueueWork(id, fn, interval = 500, callbacks) {
if (workMapRef.current.get(id)) {
throw new Error(`Work ${id} already enqueued`);
}

const { onInProgress, onComplete, onError } = callbacks;

const intervalId = setInterval(() => {
fn()
.then((data) => {
const { complete } = data;

if (complete) {
onComplete();
return;
}

onInProgress(data);
})
.catch((error) => {
if (onError) {
onError(error);
}
});
}, interval);

workMapRef.current.set(id, intervalId);

console.log(`Work ${id} enqueued`);
}

function dequeueWork(id) {
if (!workMapRef.current.get(id)) {
throw new Error(`Work ${id} not found`);
}

const intervalId = workMapRef.current.get(id);

clearInterval(intervalId);

workMapRef.current.set(id, null);

console.log(`Work ${id} dequeued`);
}

return (
<WorkerContext.Provider
value={{
enqueueWork,
dequeueWork,
}}
>
{children}
</WorkerContext.Provider>
);
}

export function useWorkerState() {
const context = React.useContext(WorkerContext);

if (!context) {
throw new Error('useWorkerState must be used within a WorkerProvider');
}

return context;
}