diff --git a/package.json b/package.json index d4a67e6..4f10589 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "moment": "^2.17.1", "qrcode.react": "^0.6.1", "react": "^0.13.0", - "react-router": "^0.13.3", + "react-router": "^2.0.0", "shortid": "^2.2.6", "socket.io": "^1.7.2", "socket.io-client": "^1.7.2" diff --git a/public/css/style.css b/public/css/style.css index 5d62183..51ebf1c 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -6,7 +6,7 @@ body { background: #84DCCF; text-align: center; box-shadow: 0 0 7px #555; - margin-bottom: 2.0rem; + margin-bottom: 2.5rem; } .title { @@ -40,6 +40,11 @@ body { color: #cc0000; } +.storSupport p { + background: rgba(132, 220, 207, 0.2); + padding: 1rem; +} + code { white-space: normal; } diff --git a/src/App.js b/src/App.js deleted file mode 100644 index c793b37..0000000 --- a/src/App.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import Router, { RouteHandler } from 'react-router'; - -class App extends React.Component { - constructor( props, context ) { - super(); - this.publishRouter( context.router ); - } - render() { - let params = this.context.router.getCurrentParams(); - return ( - - ); - } - publishRouter( router ){ - var routes = {}; - - // Use route names as constants - router.routes[0].childRoutes.forEach( function( r ){ - routes[ r.name ] = r.path; - }); - - // Render the router accessible without contexts - Router.currentRouter = router; - Router.routes = routes; - } -} - -App.contextTypes = { - router: React.PropTypes.func -}; - -export default App; diff --git a/src/index.js b/src/index.js index 3e97e96..4048058 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,9 @@ -import React from 'react'; -import Router from 'react-router'; -import routes from './routes'; +import React from 'react' +import { Router, Route, browserHistory } from 'react-router' +import Site from './ui/Site'; -Router.run( routes, Router.HistoryLocation, function( Handler ){ - React.render( - React.createElement( Handler ), - document.getElementById('root') - ); -}); +React.render(( + + + +), document.getElementById('root')) diff --git a/src/routes.js b/src/routes.js deleted file mode 100644 index c6c4c63..0000000 --- a/src/routes.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; -import React from 'react'; -import Router, {Route, NotFoundRoute, DefaultRoute} from 'react-router'; -import App from './App'; -import Site from './ui/Site'; - -var routes = ( - - - -); - -export default routes; diff --git a/src/ui/Home.js b/src/ui/Home.js index d669445..e33c9f2 100644 --- a/src/ui/Home.js +++ b/src/ui/Home.js @@ -1,14 +1,130 @@ 'use strict'; import React from 'react'; -import Shortid from 'shortid'; -import { Router, Route, Link } from 'react-router'; +import io from 'socket.io-client'; +import QRCode from 'qrcode.react'; export default class Home extends React.Component { + constructor(props) { + super(props); + + this.state = { + supported: false, + registration: null, + haveperm: false, + connected: false, + socket: io.connect() + } + } + + componentDidMount() { + this.connect(); + this.checksupport(); + } + + componentWillUnmount() { + this.setState({socket: this.state.socket.removeAllListeners()}); + } + + connect() { + let socket = this.state.socket; + + let room = this.props.id; + + socket.on('connect', () => { + socket.emit('room', room); + this.setState({connected: true}); + + socket.on('disconnect', () => { + this.setState({connected: false}); + }); + }); + + socket.on('message', (data) => { + console.log("Notification: " + data); + this.sendNotification(data); + }); + } + + sendNotification(data) { + let title = data || 'Received a notification!'; + + let options = { + body: 'Notification from Notica', + icon: 'img/icon.png', + iconUrl: 'img/icon.png', + vibrate: [200, 100, 200] + }; + + if (this.state.registration) { + console.log(this.state.registration.showNotification(title, options)); + } else { + console.log(new Notification(title, options)); + } + } + + checksupport() { + let supported = ('Notification' in window); + this.setState({supported: supported}); + + if (supported) { + Notification.requestPermission(permission => { + this.checkperm(permission); + + try { + navigator.serviceWorker.register('/js/sw.js').then((reg) => { + this.setState({registration: reg}); + }); + } catch (e) { // If we are on a browser without serviceWorker + this.setState({registration: false}); + } + }.bind(this)); + } + } + + checkperm(permission) { + if (permission === 'granted') { + this.setState({haveperm: true}); + } + else { + this.setState({haveperm: false}); + } + } + render(){ - let id = this.props.urlid || Shortid.generate(); + let id = this.props.id; + let storSupport = this.props.storSupport; + let supported = this.state.supported; + let haveperm = this.state.haveperm; + let connected = this.state.connected; return (
+
+
+ { supported ||

+ This browser does not support desktop notifications. +

} + { !haveperm && supported &&
+

+ Please give this site permission to display notifications. +
+ this.checkperm(Notification.permission)}> + Check Again + +

+
} + { !connected && supported &&
+

+ Unable to connect to the Notica server. +
+ Attempting to reconnect... +

+
} + { haveperm && connected && supported &&

+ This page is monitoring for notifications. +

} +
+

Usage

@@ -21,7 +137,7 @@ export default class Home extends React.Component {

$ long-running-command; notica Finished!

- This will wait until the first command completes before running Notica. That way you can go do other things while your long task runs. Then you will recieve a notification on any devices that have the Notica website open. + This will wait until the first command completes before running Notica. That way you can go do other things while your long task runs. Then you will recieve a notification on any devices that have the Notica website open with your unique ID.

@@ -37,7 +153,7 @@ export default class Home extends React.Component { $ echo 'notica() { curl --data "d:$*" https://notica.us/{id} ; }' >> ~/.bashrc && source ~/.bashrc

-

Go to this link to receive your notifications (bookmark it since it's yours): https://notica.us/{id}

+

Now open this page on any devices you want to receive the notifications on: {'https://notica.us/' + id}

Setup

Curl is required to use Notica.

@@ -52,10 +168,66 @@ export default class Home extends React.Component { $ source .bashrc

- All done! Now go to this link (bookmark it since it's yours):
- https://notica.us/{id} + All done! Now open this page on any devices you want to receive the notifications on: {'https://notica.us/' + id}

- + { storSupport &&

+ Notica uses Local Storage to keep track of your unique ID. If you would like to generate a new random ID, click here. +

} +
+
+
+
+

Examples

+

Here are some different ways to use Notica:

+

+ Just run it from your terminal:
+ + $ notica + +

+

+ Add a custom message:
+ + $ notica Hello world! + +

+

+ Get an alert when a command finishes:
+ + $ sudo apt-get update; notica Done! + +

+

+ Get an alert when a command succeeds:
+ + $ make all && notica Success! + +

+

+ Get an alert when a command fails:
+ + $ make all || notica Failed! + +

+
+
+

Tips

+

Bookmark this page! It is unique to the function in your .bashrc file. + Notifications will be sent to all open pages with the same ID code in the URL.

+

+ Use quotes on messages with special characters:
+ + $ notica "This is awesome :)" + +

+

+ Open this page on your phone: +

+

+
+
+
+

About

Notica was written by Tanner Collin after he got tired of checking if his commands were done running. diff --git a/src/ui/NotifPage.js b/src/ui/NotifPage.js deleted file mode 100644 index 800ee70..0000000 --- a/src/ui/NotifPage.js +++ /dev/null @@ -1,189 +0,0 @@ -'use strict'; -import React from 'react'; -import io from 'socket.io-client'; -import { Router, Route, Link } from 'react-router'; -import QRCode from 'qrcode.react'; - -export default class NotifPage extends React.Component { - constructor(props) { - super(props); - - this.state = { - supported: false, - registration: null, - haveperm: false, - connected: false, - socket: io.connect() - } - } - - componentDidMount() { - this.connect(); - this.checksupport(); - } - - componentWillUnmount() { - this.setState({socket: this.state.socket.removeAllListeners()}); - } - - connect() { - let socket = this.state.socket; - - let room = this.props.urlid; - - socket.on('connect', () => { - socket.emit('room', room); - this.setState({connected: true}); - - socket.on('disconnect', () => { - this.setState({connected: false}); - }); - }); - - socket.on('message', (data) => { - console.log("Notification: " + data); - this.sendNotification(data); - }); - } - - sendNotification(data) { - let title = data || 'Received a notification!'; - - let options = { - body: 'Notification from Notica', - icon: 'img/icon.png', - iconUrl: 'img/icon.png', - vibrate: [200, 100, 200] - }; - - if (this.state.registration) { - console.log(this.state.registration.showNotification(title, options)); - } else { - console.log(new Notification(title, options)); - } - } - - checksupport() { - let supported = ('Notification' in window); - this.setState({supported: supported}); - - if (supported) { - Notification.requestPermission(permission => { - this.checkperm(permission); - - try { - navigator.serviceWorker.register('/js/sw.js').then((reg) => { - this.setState({registration: reg}); - }); - } catch (e) { // If we are on a browser without serviceWorker - this.setState({registration: false}); - } - }.bind(this)); - } - } - - checkperm(permission) { - if (permission === 'granted') { - this.setState({haveperm: true}); - } - else { - this.setState({haveperm: false}); - } - } - - render() { - let supported = this.state.supported; - let haveperm = this.state.haveperm; - let connected = this.state.connected; - let urlid = this.props.urlid; - - return ( -

-
-
-

Notification Page

- { supported ||

- This browser does not support desktop notifications. -

} - { !haveperm && supported &&
-

- Please give this site permission to display notifications. -
- this.checkperm(Notification.permission)}> - Check Again - -

-
} - { !connected && supported &&
-

- Unable to connect to the Notica server. -
- Attempting to reconnect... -

-
} - { haveperm && connected && supported &&

- This page is monitoring for notifications. -

} -
-
-
-
-

Examples

-

Here are some different ways to use Notica:

-

- Just run it from your terminal:
- - $ notica - -

-

- Add a custom message:
- - $ notica Hello world! - -

-

- Get an alert when a command finishes:
- - $ sudo apt-get update; notica Done! - -

-

- Get an alert when a command succeeds:
- - $ make all && notica Success! - -

-

- Get an alert when a command fails:
- - $ make all || notica Failed! - -

-
-
-

Tips

-

Bookmark this page! It is unique to the function in your .bashrc file. - Notifications will be sent to all open pages with the same ID code in the URL.

-

- Use quotes on messages with special characters:
- - $ notica "This is awesome :)" - -

-

- Need to set Notica up again?
- - Click here to go back to the instructions. - -

-

- Open this page on your phone: -

-

-
-
-
- ); - } -} diff --git a/src/ui/Site.js b/src/ui/Site.js index 2f79d9a..63e41c9 100644 --- a/src/ui/Site.js +++ b/src/ui/Site.js @@ -1,37 +1,72 @@ 'use strict'; import React from 'react'; import Home from './Home'; -import NotifPage from './NotifPage'; import Error from './Error'; import Shortid from 'shortid'; -import { Router, Route, Link } from 'react-router'; +import { Link, browserHistory } from 'react-router'; export default class Site extends React.Component { - render(){ - let url = this.props.splat; - let page = null; - let id = ''; + constructor(props) { + super(props); - if (url == '') { - page = ; + this.state = { + page: null, + id: '', + storSupport: (typeof localStorage !== 'undefined') } - else if (url.substring(0, 4) == 'home') { - id = url.substring(5); - page = ; + } + + componentWillMount() { + this.setPage(); + } + + componentDidUpdate(prevProps) { + let oldUrl = prevProps.params.splat; + let newUrl = this.props.params.splat; + if (newUrl !== oldUrl) this.setPage(); + } + + setId(url) { + if (this.state.storSupport) { + if (localStorage.getItem('id')) { + this.state.id = url || localStorage.getItem('id'); + } else { + this.state.id = url || Shortid.generate(); + } + localStorage.setItem('id', this.state.id); + } else { + this.state.id = url || Shortid.generate(); + } + } + + setPage() { + let url = this.props.params.splat; + + if (url == 'clear') { + localStorage.clear(); + url = ''; + } + + if (url == '') { + this.setId(); + browserHistory.push('/' + this.state.id); + this.state.page = ; } else if (Shortid.isValid(url)) { - id = url; - page = ; + this.setId(url); + this.state.page = ; } else { - page = ; + this.state.page = ; } + } + render(){ return (
- + Notica @@ -40,7 +75,7 @@ export default class Site extends React.Component { Send browser notifications from your terminal. No installation. No registration.
- {page} + {this.state.page}
); }