├── db └── pgdb.js ├── Procfile ├── .babelrc ├── .gitignore ├── src ├── index.js ├── components │ ├── SongPlayTile.jsx │ ├── SongQueueTile.jsx │ ├── SongResultTile.jsx │ ├── SongQueue.jsx │ ├── SongPlayer.jsx │ └── SongSearch.jsx └── slick.jsx ├── .travis.yml ├── .eslintrc ├── test ├── dom-mock.js ├── client_test │ └── test.js └── test.js ├── index.html ├── privateKeys.js ├── LICENSE ├── Gulpfile.js ├── server ├── test │ ├── mock-server.js │ └── server-test.js ├── server.js └── controllers │ ├── songsController.js │ └── parseHelpers.js ├── package.json ├── README.md └── styles └── home.css /db/pgdb.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node server/server.js 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | npm-debug.log 4 | privateKeys.js 5 | .DS_Store 6 | /*.env 7 | bundle.js 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import Slick from './slick.jsx'; 4 | 5 | render(, document.getElementById('content')); 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | - "5.1" 5 | - "4" 6 | - "4.2" 7 | - "4.1" 8 | - "4.0" 9 | - "0.12" 10 | - "0.11" 11 | - "0.10" 12 | - "iojs" 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "no-use-before-define": 0, 5 | "no-param-reassign": 0, 6 | "no-console": 0, 7 | "react/sort-comp": 0, 8 | "max-len": 0 9 | }, 10 | "globals": { 11 | "io": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/dom-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = function(markup) { 2 | if (typeof document !== 'undefined') return; 3 | 4 | var jsdom = require('jsdom').jsdom; 5 | 6 | global.document = jsdom(markup || ''); 7 | global.window = document.defaultView; 8 | global.navigator = { 9 | userAgent: 'node.js' 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slick 6 | 7 | 8 | 9 |

slick

10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /privateKeys.js: -------------------------------------------------------------------------------- 1 | const privateKeys = { 2 | googleApiKey: 'AIzaSyBCv_kW7-ggyzKlfVHYfDVzAF5V3M2uwRM', 3 | // Gracenote 4 | clientId: '1064631179', 5 | clientTag: 'D53C77DB2157936F00C4BE8D2AADAF12', 6 | userId: '87071795693237026-C14D11FA6E840FCCE3D1129E34AC8208', 7 | 8 | ClientIdSpotify: "a22cb34e6ab84becac3a72b819ec4f67", 9 | ClientSecretSpotify: "b2644472d210462c816ea397060c99c6", 10 | // Database 11 | postgresURI: 'postgres://usewxxncwyaejx:tZJCuwYOCU0QArl3JUv9_gyE-n@ec2-23-21-164-237.compute-1.amazonaws.com:5432/d4imhd4libvku4' 12 | }; 13 | 14 | module.exports = privateKeys; 15 | -------------------------------------------------------------------------------- /src/components/SongPlayTile.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const SongPlayTile = ({ currSong: { albumImg, artist, title, album } }) => ( 4 |
5 | 6 | 11 |
12 | ); 13 | 14 | SongPlayTile.propTypes = { 15 | currSong: PropTypes.object, 16 | albumImg: PropTypes.string, 17 | artist: PropTypes.string, 18 | title: PropTypes.string, 19 | album: PropTypes.string, 20 | }; 21 | 22 | export default SongPlayTile; 23 | -------------------------------------------------------------------------------- /test/client_test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mocha = require('mocha'); 3 | const expect = require('chai').expect 4 | const sd = require('skin-deep'); 5 | 6 | describe('Slick' , function() { 7 | let vdom, instance, tree; 8 | //'put functions/stuff here that we need' 9 | 10 | 11 | describe('stuff we are doing with slick component or something', function() { 12 | beforeEach(function() { 13 | tree = sd.shallowRender( 14 | 15 | ); 16 | instance = tree.getMountedInstance(); 17 | vdom = tree.getRenderOutput(); 18 | }); 19 | 20 | 21 | it('should do something', function() { 22 | expect(vdom.state.songInfo).to.not.eql([]) 23 | }) 24 | 25 | 26 | }) 27 | 28 | 29 | }) 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/SongQueueTile.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const SongQueueTile = ({ handleNewSongClick, itemNum, albumImg, artist, title, album, numberOfSongs }) => { 4 | const newSongClick = () => { 5 | handleNewSongClick(itemNum); 6 | }; 7 | 8 | return ( 9 |
10 |

{artist} - {title} - {album} - {Number(itemNum) + 1} of {numberOfSongs}

11 |
12 | ); 13 | }; 14 | 15 | SongQueueTile.propTypes = { 16 | handleNewSongClick: PropTypes.func, 17 | itemNum: PropTypes.number, 18 | artist: PropTypes.string, 19 | title: PropTypes.string, 20 | album: PropTypes.string, 21 | albumImg: PropTypes.string, 22 | numberOfSongs: PropTypes.number, 23 | }; 24 | 25 | export default SongQueueTile; 26 | -------------------------------------------------------------------------------- /src/components/SongResultTile.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const SongResultTile = ({ addSongToQueue, itemNum, albumImg, artist, title, album }) => { 4 | const addSong = () => { 5 | addSongToQueue(itemNum); 6 | }; 7 | 8 | return ( 9 |
10 | 11 |
    12 |
  • {artist}
  • 13 |
  • {title}
  • 14 |
  • {album}
  • 15 |
16 |
17 | ); 18 | }; 19 | 20 | SongResultTile.propTypes = { 21 | addSongToQueue: PropTypes.func, 22 | itemNum: PropTypes.number, 23 | artist: PropTypes.string, 24 | title: PropTypes.string, 25 | album: PropTypes.string, 26 | albumImg: PropTypes.string, 27 | }; 28 | 29 | 30 | export default SongResultTile; 31 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./dom-mock')(''); 4 | 5 | const jsdom = require('mocha-jsdom'); 6 | const assert = require('assert'); 7 | const React = require('react'); 8 | const TestUtils = require('react-addons-test-utils'); 9 | 10 | describe('Testing my div', function() { 11 | jsdom({ skipWindowCheck: true }); 12 | 13 | beforeEach(function() { 14 | const testing = require('..src/bundle.js') 15 | }) 16 | 17 | it('should contain text: Lovely! Here it is - my very first React component!', function() { 18 | 19 | const asdf = TestUtils.renderIntoDocument( 20 | 21 | ); 22 | 23 | var divText = TestUtils.findRenderedDOMComponentWithTag( 24 | myDiv, 'span'); 25 | 26 | assert.equal(divText.textContent, 'Lovely! Here it is - my very first React component!'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/components/SongQueue.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import SongQueueTile from './SongQueueTile.jsx'; 3 | 4 | const SongQueue = ({ songInfo, handleNewSongClick }) => { 5 | // iterating over json to make song divs 6 | const songList = songInfo.map((song, i) => 7 | () 19 | ); 20 | 21 | return ( 22 |
23 | {songList} 24 |
25 | ); 26 | }; 27 | 28 | SongQueue.propTypes = { 29 | songInfo: PropTypes.array, 30 | handleNewSongClick: PropTypes.func, 31 | }; 32 | 33 | export default SongQueue; 34 | -------------------------------------------------------------------------------- /src/components/SongPlayer.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import YouTube from 'react-youtube'; 3 | import SongPlayTile from './SongPlayTile.jsx'; 4 | 5 | const opts = { 6 | height: '390', 7 | width: '640', 8 | playerVars: { 9 | autoplay: 1, 10 | fs: 0, 11 | enablejsapi: 1, 12 | }, 13 | }; 14 | 15 | const SongPlayer = ({ currSong, onReady, onPlay, onPause, onEnded }) => ( 16 |
17 | 25 | 26 |
27 | ); 28 | 29 | SongPlayer.propTypes = { 30 | currSong: PropTypes.object, 31 | onReady: PropTypes.func, 32 | onPlay: PropTypes.func, 33 | onPause: PropTypes.func, 34 | onEnded: PropTypes.func, 35 | }; 36 | 37 | export default SongPlayer; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 cs-slick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const browserify = require('browserify'); 3 | const source = require('vinyl-source-stream'); 4 | const watchify = require('watchify'); 5 | const gutil = require('gulp-util'); 6 | const nodemon = require('gulp-nodemon'); 7 | const uglify = require('gulp-uglify'); 8 | const streamify = require('gulp-streamify'); 9 | const notify = require('gulp-notify'); 10 | // const gzip = require('gulp-gzip'); 11 | 12 | const b = watchify(browserify({ 13 | entries: './src/index.js', 14 | cache: {}, 15 | packageCache: {}, 16 | debug: true, 17 | require: ['react', 'react-dom'], 18 | })); 19 | 20 | gulp.task('watch:js', bundle); 21 | b.on('update', bundle); 22 | 23 | function bundle() { 24 | return b 25 | .transform('babelify', { presets: ['es2015', 'react'] }) 26 | .bundle() 27 | .on('error', gutil.log) 28 | .pipe(source('bundle.js')) 29 | .pipe(streamify(uglify('./dist/'))) 30 | // .pipe(streamify(gzip({ append: true }))) 31 | .pipe(gulp.dest('./')) 32 | .pipe(notify('Built Bundle')); 33 | } 34 | 35 | gulp.task('nodemon', serve); 36 | 37 | function serve() { 38 | nodemon({ 39 | script: 'server/server.js', 40 | ignore: ['client/', 'build/'], 41 | }); 42 | } 43 | 44 | 45 | gulp.task('default', ['watch:js', 'nodemon']); 46 | -------------------------------------------------------------------------------- /src/components/SongSearch.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import SongResultTile from './SongResultTile.jsx'; 3 | 4 | const SongSearch = ({ addSongToQueue, searchResults, handleSearchEvent }) => { 5 | const searchTileArray = searchResults.map((result, i) => 6 | 17 | ); 18 | return ( 19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 | 28 |
29 |
30 | {(searchTileArray.length > 0) ? searchTileArray : null} 31 |
32 |
33 | ); 34 | }; 35 | 36 | SongSearch.propTypes = { 37 | addSongToQueue: PropTypes.func, 38 | searchResults: PropTypes.array, 39 | handleSearchEvent: PropTypes.func, 40 | }; 41 | 42 | export default SongSearch; 43 | -------------------------------------------------------------------------------- /server/test/mock-server.js: -------------------------------------------------------------------------------- 1 | var io = require('socket.io').listen(5000); 2 | var clients = {}; 3 | 4 | var verboseServer = false; //Good for debugging the server 5 | 6 | verboseServer && console.log("ChatServer Started"); //If Verbose Debug 7 | io.sockets.on('connection', function (socket) { 8 | socket.on('client connect', user => { 9 | io.sockets.emit('playSong', {user: 'Mike', action: 'play', data: 'kanyewest.mp3'}); 10 | }); 11 | // verboseServer && console.log("New Connection"); //If Verbose Debug 12 | // var userName; 13 | // socket.on('connection name',function(user){ 14 | // verboseServer && console.log("Connection Name"); //If Verbose Debug 15 | // userName = user.name; 16 | // clients[user.name] = socket; 17 | // io.sockets.emit('new user', user.name + " has joined."); 18 | // }); 19 | // 20 | // socket.on('message', function(msg){ 21 | // verboseServer && console.log("New msg"); //If Verbose Debug 22 | // io.sockets.emit('message', msg); 23 | // }); 24 | // 25 | // socket.on('private message', function(msg){ 26 | // verboseServer && console.log("New PM"); //If Verbose Debug 27 | // fromMsg = {from:userName, txt:msg.txt} 28 | // clients[msg.to].emit('private message', fromMsg); 29 | // }); 30 | // 31 | // socket.on('disconnect', function(){ 32 | // verboseServer && console.log("disconnect"); //If Verbose Debug 33 | // delete clients[userName]; 34 | // }); 35 | }); 36 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const server = app.listen(process.env.PORT || 3000); 4 | const io = require('socket.io')(server); 5 | const songsController = require('./controllers/songsController'); 6 | const cors = require('cors'); 7 | const bodyParser = require('body-parser'); 8 | 9 | app.use(cors()); 10 | // body parser middleware 11 | app.use(bodyParser.urlencoded({ extended: true })); 12 | // setting up path directory and going up one level 13 | app.use(express.static(`${__dirname}/..`)); 14 | // sending the html file 15 | app.get('/', (req, res) => { 16 | res.sendFile('index.html'); 17 | }); 18 | // client logic will request this API once index.html loads 19 | // to retrieve stock song queue to generate list of songs 20 | // to render on page 21 | app.get('/songQueue', (req, res) => { 22 | res.json(songsController.playerState); 23 | }); 24 | app.get('/favicon.ico', (req, res) => { 25 | res.send(); 26 | }); 27 | app.get('/untitled', (req, res) => { 28 | res.send(); 29 | }); 30 | app.post('/search', songsController.getSpotifyData, songsController.getYouTubeData, (req, res) => { 31 | // res.writeHeader(200, {'Content-Type': 'application/json'}); 32 | res.send(req.body.final); 33 | }); 34 | // listen for song being clicked and added to the queue, then update everyone's state 35 | io.on('connection', socket => { 36 | // console.log('new client connected'); 37 | socket.on('playSong', (newSongState) => { 38 | // console.log('received updated Song State: ', newSongState); 39 | songsController.playerState = newSongState; 40 | io.emit('playSong', newSongState); 41 | }); 42 | // listen for queue update and then emit the new song state for all clients to update their state 43 | socket.on('updateQueue', (newSongState) => { 44 | songsController.playerState = newSongState; 45 | io.emit('updateQueue', newSongState); 46 | }); 47 | // add playCurrent event handler 48 | socket.on('playCurrent', () => io.emit('playCurrent')); 49 | // add pauseCurrent event handler 50 | socket.on('pauseCurrent', () => io.emit('pauseCurrent')); 51 | socket.on('songEnded', (newSongState) => { 52 | // console.log('song has ended!'); 53 | songsController.playerState = newSongState; 54 | io.emit('songEnded', newSongState); 55 | }); 56 | }); 57 | 58 | module.exports = app; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slick", 3 | "version": "1.0.0", 4 | "description": "Slack for Music: provide real time event driven collaboration between users for synchronized music playback and DJ delegation", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "node_modules/.bin/gulp", 8 | "test": "mocha ./server/test/*.js || exit 0", 9 | "start": "NODE_ENV=production gulp" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/midMEETdle/slick" 14 | }, 15 | "engines": { 16 | "Node": "5.8.0" 17 | }, 18 | "author": "Michael Blanchard, Sandra Winkler, Bryan Kitchener, Matt McLaughlin", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/midMEETdle/slick" 22 | }, 23 | "homepage": "https://github.com/midMEETdle/slick", 24 | "dependencies": { 25 | "body-parser": "^1.15.1", 26 | "cors": "^2.7.1", 27 | "express": "^4.13.4", 28 | "lodash": "^4.13.1", 29 | "node": "0.0.0", 30 | "node-gracenote": "github:ddanninger/node-gracenote", 31 | "node-jsdom": "^3.1.5", 32 | "request": "^2.72.0", 33 | "querystring": "^0.2.0", 34 | "socket.io": "^1.4.6", 35 | "socket.io-client": "^1.4.6" 36 | }, 37 | "devDependencies": { 38 | "babel-core": "6.10.4", 39 | "babel-loader": "6.2.4", 40 | "babel-preset-es2015": "6.9.0", 41 | "babel-preset-react": "6.11.1", 42 | "babelify": "^7.3.0", 43 | "browserify": "^13.0.1", 44 | "chai": "^3.5.0", 45 | "chai-enzyme": "^0.4.2", 46 | "enzyme": "^2.3.0", 47 | "eslint": "2.13.1", 48 | "eslint-config-airbnb": "9.0.1", 49 | "eslint-plugin-import": "1.9.2", 50 | "eslint-plugin-jsx-a11y": "1.5.3", 51 | "eslint-plugin-react": "5.2.2", 52 | "gulp": "^3.9.1", 53 | "gulp-gzip": "1.4.0", 54 | "gulp-nodemon": "^2.0.7", 55 | "gulp-notify": "2.2.0", 56 | "gulp-uglify": "1.5.4", 57 | "gulp-util": "^3.0.7", 58 | "jquery": "^2.2.4", 59 | "mocha": "^2.5.3", 60 | "mocha-jsdom": "^1.1.0", 61 | "react": "^15.1.0", 62 | "react-addons-test-utils": "^15.1.0", 63 | "react-dom": "^15.1.0", 64 | "react-youtube": "^6.1.0", 65 | "reactify": "^1.1.1", 66 | "skin-deep": "^0.16.0", 67 | "snazzy": "4.0.0", 68 | "standard": "7.1.0", 69 | "supertest": "^1.2.0", 70 | "vinyl-source-stream": "^1.1.0", 71 | "watchify": "^3.7.0", 72 | "whatwg-fetch": "^1.0.0", 73 | "youtube-player": "^3.0.4" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slick [![Build Status](https://travis-ci.org/cs-slick/slick.svg?branch=master)](https://travis-ci.org/cs-slick/slick) [![Coverage Status](https://coveralls.io/repos/github/cs-slick/slick/badge.svg?branch=master)](https://coveralls.io/github/cs-slick/slick?branch=master) 2 | Slack for Music: provide real time event driven collaboration between users for synchronized music playback and DJ delegation 3 | 4 | ##Overview 5 | ###Tech Stack 6 | - React 7 | - rollup (bundling) 8 | - Socket.io 9 | - Mocha 10 | - Chai 11 | - Supertest 12 | - socket.io-client 13 | - Node + Express 14 | 15 | ###Front-end 16 | - built in React 17 | - 4 different components 18 | ``
19 | 20 | 21 | 22 | `` 23 | - ``
`` is component where DOM event and socket event handling happens (ex: play, pause, onPlay socket emit events, etc..) 24 | - ```` is component where ``