From a2148dc00a7344466b8aee5b104d76c71969fdb6 Mon Sep 17 00:00:00 2001 From: Jonathan Cremin Date: Mon, 22 Dec 2014 22:38:08 +0000 Subject: [PATCH] React home and share pages working isomorphically --- app.js | 34 +++++--- client/index.jsx | 155 ----------------------------------- client/share.jsx | 153 ---------------------------------- lib/services/xbox/index.js | 4 +- package.json | 3 +- public/stylesheets/style.css | 2 +- routes/index.js | 19 +++-- routes/search.js | 14 ++-- routes/share.js | 56 +++++-------- views/album.ejs | 88 -------------------- views/app.jsx | 62 ++++++++++++++ views/error.ejs | 20 ----- views/error.jsx | 39 +++++++++ views/foot.jsx | 20 +++++ views/footer.ejs | 21 ----- views/head.jsx | 28 +++++++ views/header.ejs | 23 ------ views/home.jsx | 114 ++++++++++++++++++++++++++ views/index.ejs | 43 ---------- views/share.jsx | 154 ++++++++++++++++++++++++++++++++++ views/track.ejs | 88 -------------------- 21 files changed, 482 insertions(+), 658 deletions(-) delete mode 100644 client/index.jsx delete mode 100644 client/share.jsx delete mode 100644 views/album.ejs create mode 100644 views/app.jsx delete mode 100644 views/error.ejs create mode 100644 views/error.jsx create mode 100644 views/foot.jsx delete mode 100644 views/footer.ejs create mode 100644 views/head.jsx delete mode 100644 views/header.ejs create mode 100644 views/home.jsx delete mode 100644 views/index.ejs create mode 100644 views/share.jsx delete mode 100644 views/track.ejs diff --git a/app.js b/app.js index bf5eb6d..58d69ec 100644 --- a/app.js +++ b/app.js @@ -17,6 +17,10 @@ var search = require('./routes/search'); var share = require('./routes/share'); var itunesProxy = require('./routes/itunes-proxy'); +var React = require('react'); +var nodejsx = require('node-jsx').install({extension: '.jsx'}); +var ErrorView = React.createFactory(require('./views/error.jsx')); + var browserify = require('connect-browserify'); var app = express(); @@ -50,7 +54,7 @@ app.use(function(req, res, next) { if (development) { app.get('/javascript/bundle.js', - browserify('./client/index.jsx', { + browserify('./views/app.jsx', { debug: true, watch: true })); @@ -75,9 +79,17 @@ app.get('*', function(req,res,next) { app.get('/', index); app.post('/search', search); -app.get('/:service/:type/:id.json', share.json); -app.get('/:service/:type/:id', share.html); +app.get('/:service/:type/:id', share); app.get('/itunes/*', itunesProxy); +app.get('/recent', function(req, res, next) { + req.db.matches.find().sort({created_at:-1}).limit(6).toArray().then(function(docs){ + var recents = []; + docs.forEach(function(doc) { + recents.push(doc.services[doc._id.split("$$")[0]]); + }); + res.json({recents:recents}); + }); +}); // catch 404 and forward to error handler app.use(function(req, res, next) { @@ -93,11 +105,9 @@ app.use(function(req, res, next) { if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); - res.render('error', { - page: "error", - message: err.message, - error: err - }); + + var content = React.renderToString(ErrorView({status: err.status || 500, message: err.message, error: err})); + res.send('\n' + content); }); } @@ -105,11 +115,9 @@ if (app.get('env') === 'development') { // no stacktraces leaked to user app.use(function(err, req, res, next) { res.status(err.status || 500); - res.render('error', { - page: "error", - message: err.message, - error: {status: err.status || 500} - }); + + var content = React.renderToString(ErrorView({status: err.status || 500, message: err.message, error: {status: err.status || 500}})); + res.send('\n' + content); }); diff --git a/client/index.jsx b/client/index.jsx deleted file mode 100644 index d40318b..0000000 --- a/client/index.jsx +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @jsx React.DOM - */ -'use strict'; - -var React = require('react'); -var Router = require('react-router'); -var request = require('superagent'); -var ga = require('react-google-analytics'); -var GAInitiailizer = ga.Initializer; - -var Head = React.createClass({ - - render: function() { - return ( - - - - Match Audio - - - - - - - - - - - - - - ); - } -}); - -var Foot = React.createClass({ - - render: function() { - return ( - - ); - } -}); - - -var Recent = React.createClass({ - - render: function() { - return (
-
-

Recently Shared

-
- {this.props.items.map(function(item, i){ - return (); - })} -
-
-
); - } - -}); - -var RecentItem = React.createClass({ - - render: function() { - return ( -
- -
- ); - } - -}); - -var SearchForm = React.createClass({ - - handleSubmit: function(e) { - e.preventDefault(); - var url = this.refs.url.getDOMNode().value.trim(); - if (!url) { - return; - } - request.post('/search').send({url:url}).end(function(res) { - window.location = "/" + res.body.service + "/" + res.body.type + "/" + res.body.id - }); - }, - - render: function() { - return ( -
-
- - - - -
-
- ); - } -}); - -var Home = React.createClass({ - - render: function() { - return ( - - - -
-
-

match.audio

-
-
-
-
- -
-
-
-
-

Make sharing from music services better. - We match album and track links from Rdio, Spotify, Deezer, Beats Music, Google Music and iTunes and give you back a link with all of them. -

-
-
- -
-
- - - ")); - //res.render('index', { page: "home", recent: docs, error: req.flash('search-error') }); + Router.run(routes, req.url, function (Handler) { + var App = React.createFactory(Handler); + var content = React.renderToString(App({recents: recents})); + res.send('\n' + content.replace("", "")); + }); + }).catch(function(err) { + console.log(err) }); } \ No newline at end of file diff --git a/routes/search.js b/routes/search.js index 381a0a6..da19e5e 100644 --- a/routes/search.js +++ b/routes/search.js @@ -29,14 +29,14 @@ module.exports = function(req, res, next) { searching = true; services[id].parseUrl(req.body.url).timeout(10000).then(function(result) { if (!result.id) { - req.flash('search-error', 'No match found for this link'); - res.redirect('/'); + res.json({error:{message:"No match found for url"}}); } else { services[id].lookupId(result.id, result.type).then(function(item) { items[id] = item; req.db.matches.save({_id:id + "$$" + result.id, created_at: new Date(), services:items}).then(function() { - res.json(item); - //res.redirect("/" + id + "/" + result.type + "/" + result.id); + setTimeout(function() { + res.json(item); + }, 1000) }); }); } @@ -50,8 +50,7 @@ module.exports = function(req, res, next) { error.status = 500; next(error); } else if (error.status == 404){ - req.flash('search-error', 'No match found for this link'); - res.redirect('/'); + res.json({error:{message:"No match found for url"}}); } }); break; @@ -59,7 +58,6 @@ module.exports = function(req, res, next) { } } if (url.host && !searching) { - req.flash('search-error', 'No match found for this link'); - res.redirect('/'); + res.json({error:{message:"No match found for url"}}); } }; diff --git a/routes/share.js b/routes/share.js index 815b2a4..8b4ac6b 100644 --- a/routes/share.js +++ b/routes/share.js @@ -5,8 +5,9 @@ var util = require('util'); var browserify = require('connect-browserify'); var React = require('react'); +var Router = require('react-router'); var nodejsx = require('node-jsx').install(); -var Share = React.createFactory(require('../client/share').Share); +var routes = require('../views/app.jsx').routes; var services = {}; @@ -18,7 +19,7 @@ require("fs").readdirSync(path.join(__dirname, "..", "lib", "services")).forEach }); -module.exports.html = function(req, res, next) { +module.exports = function(req, res, next) { var serviceId = req.params.service; var type = req.params.type; var itemId = req.params.id; @@ -32,11 +33,13 @@ module.exports.html = function(req, res, next) { req.db.matches.findOne({_id:serviceId + "$$" + itemId}, function(err, doc) { if (err) { return next(new Error()); + } else if (!doc) { + return next(); } - var items = []; + var shares = []; for (var docService in Object.keys(services)) { var loopServiceId = Object.keys(services)[docService]; - items.push(doc.services[loopServiceId]); + shares.push(doc.services[loopServiceId]); if (doc.services[loopServiceId].id === undefined) { services[loopServiceId].search(doc.services[serviceId]).timeout(15000).then(function(item) { if (!item.id) { @@ -52,11 +55,11 @@ module.exports.html = function(req, res, next) { } } - var items = items.filter(function(item) { + var shares = shares.filter(function(item) { return item.service != serviceId; }); - items.sort(function(a, b) { + shares.sort(function(a, b) { return !a.id || !b.id; }).sort(function(a, b) { return !a.streamUrl || b.streamUrl; @@ -64,33 +67,18 @@ module.exports.html = function(req, res, next) { return a.type == "video" && b.type != "video"; }); - items.unshift(doc.services[serviceId]); - - var share = Share({items: items}); - res.send('\n' + React.renderToString(share).replace("", "")); - - // res.render(type, { - // page: type, - // title: doc.services[serviceId].name + " by " + doc.services[serviceId].artist.name, - // matching: doc.services[serviceId], - // matches: items, - // thisUrl: req.userProtocol + '://' + req.get('host') + req.originalUrl - // }); + shares.unshift(doc.services[serviceId]); + if (req.accepts(['html', 'json']) === 'json') { + req.db.matches.findOne({_id:serviceId + "$$" + itemId}, function(err, doc) { + res.json({shares:shares}); + }); + } else { + Router.run(routes, req.url, function (Handler) { + var App = React.createFactory(Handler); + var content = React.renderToString(App({shares: shares})); + res.send('\n' + content.replace("", "")); + }); + } + }); }; - -module.exports.json = function(req, res, next) { - var serviceId = req.params.service; - var type = req.params.type; - var itemId = req.params.id; - var promises = []; - - if (!services[serviceId] || (type != "album" && type != "track")) { - next(); - return; - } - - req.db.matches.findOne({_id:serviceId + "$$" + itemId}, function(err, doc) { - res.json(doc); - }); -}; \ No newline at end of file diff --git a/views/album.ejs b/views/album.ejs deleted file mode 100644 index a887df4..0000000 --- a/views/album.ejs +++ /dev/null @@ -1,88 +0,0 @@ -<% include header.ejs %> -
-
-
- -
-
-
-
-
-
-

Matched albums for

-

<%= matching.name %> - <%= matching.artist.name %>

-
- -
-
- <% for (var i=0;i < matches.length;i++) { var album = matches[i]; %> - <% if (album.type != "video") { %>
<% } else { %>
<% } %> -
"> - - <% if (album.type == "video") { %> -
- -
- More Youtube matches - <% } else if (album.streamUrl) { %> - - - <% } else if (album.purchaseUrl) { %> - - - <% } else { %> - - - <% } %> -
-
<% if((i+1)%4 == 0) { %>
<% } %> - <% } %> -
- -
- -<% include footer.ejs %> diff --git a/views/app.jsx b/views/app.jsx new file mode 100644 index 0000000..eaf34b9 --- /dev/null +++ b/views/app.jsx @@ -0,0 +1,62 @@ +'use strict'; + +var request = require('superagent'); +var React = require('react'); +var Router = require('react-router'); +var Route = require('react-router').Route; +var DefaultRoute = require('react-router').DefaultRoute; +var NotFoundRoute = require('react-router').NotFoundRoute; +var RouteHandler = require('react-router').RouteHandler; +var Home = require('./home.jsx'); +var Share = require('./share.jsx'); +var Error = require('./error.jsx'); +var Head = require('./head.jsx'); +var ga = require('react-google-analytics'); +var GAInitiailizer = ga.Initializer; + +var App = React.createClass({ + + render: function () { + + return ( + + + + + + - - diff --git a/views/head.jsx b/views/head.jsx new file mode 100644 index 0000000..9b37e6c --- /dev/null +++ b/views/head.jsx @@ -0,0 +1,28 @@ +'use strict'; + +var React = require('react'); + +module.exports = React.createClass({ + + render: function() { + return ( + + + + Match Audio + + + + + + + + + + + + + + ); + } +}); \ No newline at end of file diff --git a/views/header.ejs b/views/header.ejs deleted file mode 100644 index 7729b4c..0000000 --- a/views/header.ejs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - <% if (locals.title) { %><%= title %> - <% } %>Match Audio - - - <% if (locals.items) { var item = items[0] %> - - - "> - - - - <% } %> - - - - - - -
diff --git a/views/home.jsx b/views/home.jsx new file mode 100644 index 0000000..e91bef9 --- /dev/null +++ b/views/home.jsx @@ -0,0 +1,114 @@ +'use strict'; + +var React = require('react'); +var request = require('superagent'); +var Router = require('react-router'); +var Link = require('react-router').Link; +var Foot = require('./foot.jsx'); + +var Recent = React.createClass({ + + render: function() { + return (
+
+

Recently Shared

+
+ {this.props.recents.map(function(item, i){ + return (); + })} +
+
+
); + } + +}); + +var RecentItem = React.createClass({ + + render: function() { + return ( +
+ +
+ ); + } + +}); + +var SearchForm = React.createClass({ + + mixins: [ Router.Navigation, Router.State ], + + handleSubmit: function(e) { + var that = this; + e.preventDefault(); + var url = this.refs.url.getDOMNode().value.trim(); + if (!url) { + return; + } + request.post('/search').send({url:url}).end(function(res) { + console.log(res) + that.transitionTo("/" + res.body.service + "/" + res.body.type + "/" + res.body.id); + }); + }, + + render: function() { + return ( +
+
+ + + + +
+
+ ); + } +}); + +module.exports = React.createClass({ + + getInitialState: function () { + return { + recents: this.props.recents + }; + }, + + componentDidMount: function () { + if (!this.props.recents) { + request.get('/recent').set('Accept', 'application/json').end(function(res) { + this.setState({ + recents: res.body.recents + }); + }.bind(this)); + } + }, + + render: function() { + return ( +
+
+
+

match.audio

+
+
+
+
+ +
+
+
+
+

Make sharing from music services better. + We match album and track links from Rdio, Spotify, Deezer, Beats Music, Google Music and iTunes and give you back a link with all of them. +

+
+
+ +
+
+ +
+ ); + } +}); diff --git a/views/index.ejs b/views/index.ejs deleted file mode 100644 index e94f9d1..0000000 --- a/views/index.ejs +++ /dev/null @@ -1,43 +0,0 @@ -<% include header.ejs %> -
-

match.audio

-
-
- - - -
-
-

Make sharing from music services better. - We match album and track links from Rdio, Spotify, Deezer, Beats Music, Google Music and iTunes and give you back a link with all of them.

-
-
- -
-
- <% if (recent.length) { %>

Recently Shared

<% } %> -
- <% for (var i=0;i < recent.length;i++) { var item = recent[i].services[recent[i]._id.split('$$')[0]]; %> - - <% } %> -
-
-
-
-<% include footer.ejs %> diff --git a/views/share.jsx b/views/share.jsx new file mode 100644 index 0000000..11a5cc9 --- /dev/null +++ b/views/share.jsx @@ -0,0 +1,154 @@ +'use strict'; + +var React = require('react'); +var request = require('superagent'); +var Router = require('react-router'); +var Link = require('react-router').Link; +var Foot = require('./foot.jsx'); + +var MusicItem = React.createClass({ + + render: function() { + if (!this.props.item.name) { + return ( +
+
+
+ +
+ +
+
+
+ ); + } else { + return ( +
+
+
+ + +
+ + + +
+
+
+ ); + } + } + +}); + +var VideoItem = React.createClass({ + + render: function() { + if (this.props.item.id) { + return ( +
+ +
+ ); + } else { + return (
); + } + } + +}); + +module.exports = React.createClass({ + + mixins: [ Router.State ], + + getInitialState: function () { + return { + name: this.props.shares[0].name || "", + artist: this.props.shares[0].artist.name || "", + shares: this.props.shares || [] + }; + }, + + componentDidMount: function () { + if (!this.props.shares) { + request.get(this.getPathname()).set('Accept', 'application/json').end(function(res) { + var shares = res.body.shares; + this.setState({ + name: shares[0].name, + artist: shares[0].artist.name, + shares: shares + }); + }.bind(this)) + } + + // Some hacks to pop open the Twitter/Facebook/Google Plus sharing dialogs without using their code. + Array.prototype.forEach.call(document.querySelectorAll(".share-dialog"), function(dialog){ + dialog.addEventListener("click", function(event) { + event.preventDefault(); + var w = 845; + var h = 670; + var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left; + var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top; + + var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; + var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; + + var left = ((width / 2) - (w / 2)) + dualScreenLeft; + var top = ((height / 2) - (h / 2)) + dualScreenTop; + var newWindow = window.open(dialog.href, "Share Music", 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); + if (window.focus) { + newWindow.focus(); + } + }); + }); + }, + + render: function() { + return ( +
+
+
+
+
+
+

match.audio

+
+
+
+
+
+
+
+

Matched {this.state.shares[0] ? this.state.shares[0].type + "s" : ""} for

+

{this.state.name} - {this.state.artist}

+
+
+
    +
  • Share this
  • +
  • Twitter
  • +
  • Facebook
  • +
  • Google+
  • +
+
+
+
+ {this.state.shares.map(function(item, i){ + if (item.service == "youtube") { + return (); + } else { + return (); + } + }.bind(this))} +
+
+
+ +
+ ); + } +}); diff --git a/views/track.ejs b/views/track.ejs deleted file mode 100644 index eb04790..0000000 --- a/views/track.ejs +++ /dev/null @@ -1,88 +0,0 @@ -<% include header.ejs %> -
-
-
- -
-
-
-
-
-
-

Matched tracks for

-

<%= items[0].name %> - <%= items[0].artist.name %>

-
- -
-
- <% for (var i=0;i < items.length;i++) { var track = items[i]; %> - <% if (track.type != "video") { %>
<% } else { %>
<% } %> -
"> - - <% if (track.type == "video") { %> -
- -
- More Youtube matches - <% } else if (track.streamUrl) { %> - - - <% } else if (track.purchaseUrl) { %> - - - <% } else { %> - - - <% } %> -
-
<% if((i+1)%4 == 0) { %>
<% } %> - <% } %> -
- -
- -<% include footer.ejs %>