diff --git a/website-react/package.json b/website-react/package.json index 9565f68e..54062e00 100644 --- a/website-react/package.json +++ b/website-react/package.json @@ -8,6 +8,7 @@ "prop-types": "^15.6.1", "react": "^16.4.0", "react-dom": "^16.4.0", + "react-router-dom": "^4.3.1", "react-scripts": "1.1.4", "react-sortable-hoc": "^0.8.3", "styled-components": "^3.3.0" diff --git a/website-react/src/App.js b/website-react/src/App.js index 2ce06e98..83288b2b 100644 --- a/website-react/src/App.js +++ b/website-react/src/App.js @@ -14,18 +14,14 @@ import Subtitle from './components/Header/Subtitle' import Main from './components/Main/' import Card from './components/Main/Card' import EpisodeList from './components/Main/Episode/EpisodeList' -import SearchResults from './components/Main/SearchResults' import Queue from './components/Main/Queue' import NowPlaying from './components/Player/NowPlaying' import AudioPlayer from './components/Player/AudioPlayer' -import Loader from './components/Loader' - -import {proxyUrl, setPlaybackRate} from './helpers' +import { proxyUrl, setPlaybackRate } from './helpers' export const App = connect(state => ({ - results: state.search.results, searchTerm: state.search.searchTerm, currentSearch: state.search.currentSearch, loading: state.search.loading, @@ -34,21 +30,24 @@ export const App = connect(state => ({ playbackRate: state.player.playbackRate }))( ({ - results, searchTerm, currentSearch, - loading, nowPlaying, playlist, - playbackRate + playbackRate, + history, + location }) => (
{ - currentSearch !== '' && - <BackButton onClick={actions.search.clearSearch}> + (currentSearch !== '' || location.pathname.startsWith('/playlist')) && + <BackButton onClick={() => { + history.push('/') + actions.search.clearSearch() + }}> < </BackButton> } @@ -67,15 +66,6 @@ export const App = connect(state => ({ playlist={playlist} blur={currentSearch !== ''} /> - { - currentSearch !== '' && - <SearchResults - nowPlaying={nowPlaying} - results={results} - playlist={playlist} - currentSearch={currentSearch} - /> - } </EpisodeList> </Card> </Main> @@ -92,8 +82,6 @@ export const App = connect(state => ({ </NowPlaying> } - { loading && <Loader /> } - </Container> )) diff --git a/website-react/src/RoutedApp.js b/website-react/src/RoutedApp.js new file mode 100644 index 00000000..a40a77ba --- /dev/null +++ b/website-react/src/RoutedApp.js @@ -0,0 +1,19 @@ +import React from 'react' +import { BrowserRouter as Router, Route } from 'react-router-dom' + +import App from './App' +import SharedPlaylist from './components/Main/SharedPlaylist' +import SearchResults from './components/Main/SearchResults' + +export default props => ( + <Router> + <div> + <Route path='/' component={App} /> + <Route path='/playlist/' component={SharedPlaylist} /> + <Route + path='/search/:query' + render={props => <SearchResults {...props} key={document.URL} />} + /> + </div> + </Router> +) diff --git a/website-react/src/components/Header/Search.js b/website-react/src/components/Header/Search.js index 86297826..2d2af024 100644 --- a/website-react/src/components/Header/Search.js +++ b/website-react/src/components/Header/Search.js @@ -2,14 +2,15 @@ import React from 'react' import {actions} from 'mirrorx' import styled from 'styled-components' import PropTypes from 'prop-types' +import { withRouter } from 'react-router-dom' -export const Search = ({className, searchTerm}) => ( +export const Search = ({className, searchTerm, history}) => ( <form className={className} onSubmit={event => { event.preventDefault() event.target.querySelector('input').blur() - actions.search.search() + history.push(`/search/${searchTerm}`) }} > <input @@ -31,7 +32,9 @@ Search.propTypes = { searchTerm: PropTypes.string } -export default styled(Search)` +export const SearchWithRouter = withRouter(Search) + +export default styled(SearchWithRouter)` input { font-size: 1.5rem; text-align: center; diff --git a/website-react/src/components/Main/SearchResults.js b/website-react/src/components/Main/SearchResults.js index 9d40724c..98a31aa6 100644 --- a/website-react/src/components/Main/SearchResults.js +++ b/website-react/src/components/Main/SearchResults.js @@ -1,52 +1,87 @@ -import React from 'react' -import {actions} from 'mirrorx' +import React, { Component } from 'react' +import { actions, connect } from 'mirrorx' import styled from 'styled-components' import Episode from './Episode/' import EpisodeTitle from './Episode/EpisodeTitle' import PodcastTitle from './Episode/PodcastTitle' import AddToPlaylistButton from './Episode/AddToPlaylistButton' +import Loader from '../Loader' -export const SearchResults = ({ - className, - results, - playlist, - nowPlaying, - currentSearch -}) => ( - <div - className={className} - onClick={event => { - if (event.target.nodeName !== 'DIV') return - actions.search.clearSearch() - }} - > - <b>{`${results.length} results for "${currentSearch}"`}</b> - { - results.length === 0 - ? <p id='noResults'>No results were found. Please try again.</p> - : results.map(episode => - <Episode - onClick={() => actions.player.play(episode)} - key={episode.id} - playing={episode.audioUrl === nowPlaying.audioUrl} - > - <EpisodeTitle>{episode.episodeTitle}</EpisodeTitle> - <PodcastTitle>{episode.podcastTitle}</PodcastTitle> - <AddToPlaylistButton - added={playlist.some(item => item.audioUrl === episode.audioUrl)} - onClick={event => { - event.stopPropagation() - actions.player.addToPlaylist(episode) - }} - /> - </Episode> - ) - } - </div> -) +export class SearchResults extends Component { + componentWillMount () { + const query = this.props.match.params.query + actions.search.updateSearchTerm(query) + actions.search.search(query) + } + + render () { + const { + className, + results, + playlist, + nowPlaying, + currentSearch, + history, + loading + } = this.props + + if (loading) return <Loader /> + + return <div + className={className} + onClick={event => { + if (event.target.nodeName !== 'DIV') return + actions.search.clearSearch() + history.push('/') + }} + > + <div id='searchContainer'> + <div id='resultText'> + {`${results.length} results for "${currentSearch}"`} + </div> + { + results.length === 0 + ? <p id='noResults'>No results were found. Please try again.</p> + : results.map(episode => + <Episode + onClick={() => actions.player.play(episode)} + key={episode.id} + playing={episode.audioUrl === nowPlaying.audioUrl} + > + <EpisodeTitle>{episode.episodeTitle}</EpisodeTitle> + <PodcastTitle>{episode.podcastTitle}</PodcastTitle> + <AddToPlaylistButton + added={playlist.some(item => item.audioUrl === episode.audioUrl)} + onClick={event => { + event.stopPropagation() + actions.player.addToPlaylist(episode) + }} + /> + </Episode> + ) + } + </div> + </div> + } +} + +SearchResults.defaultProps = { + results: [], + playlist: [], + nowPlaying: {}, + currentSearch: '' +} + +export const ConnectedSearchResults = connect(state => ({ + nowPlaying: state.player.nowPlaying, + results: state.search.results, + playlist: state.player.playlist, + currentSearch: state.search.currentSearch, + loading: state.search.loading +}))(SearchResults) -export default styled(SearchResults)` +export default styled(ConnectedSearchResults)` position: absolute; top: 0; left: 0; @@ -55,9 +90,23 @@ export default styled(SearchResults)` overflow: scroll; padding: 80px 10% 130px 10%; + display: flex; + justify-content: center; + flex-wrap: wrap; + background: rgba(255, 255, 255, 0.8); list-style: none; + #searchContainer { + max-width: 700px; + } + + #resultText { + text-align: center; + width: 100%; + font-weight: bold; + } + #noResults { margin-top: 100px; text-align: center; diff --git a/website-react/src/components/Main/SharedPlaylist.js b/website-react/src/components/Main/SharedPlaylist.js new file mode 100644 index 00000000..4b9aa5b1 --- /dev/null +++ b/website-react/src/components/Main/SharedPlaylist.js @@ -0,0 +1,85 @@ +import React from 'react' +import styled from 'styled-components' +import { actions, connect } from 'mirrorx' + +import Episode from './Episode/' +import EpisodeTitle from './Episode/EpisodeTitle' +import PodcastTitle from './Episode/PodcastTitle' +import AddToPlaylistButton from './Episode/AddToPlaylistButton' + +export const SharedPlaylist = ({className, history, playlist, nowPlaying}) => { + const url = new URL(document.URL) + const data = url.pathname.replace('/playlist/', '') + console.log(decodeURI(data).episodes) + const { metadata, episodes } = JSON.parse(decodeURI(data)) + + return <div + className={className} + id='sharedPlaylistBackdrop' + onClick={event => { + event.target.id === 'sharedPlaylistBackdrop' && + history.push('/') + }} + > + <div className='playlist-container'> + <h2> + {`${metadata.author} has shared the playlist "${metadata.title}" with you!`} + </h2> + { + episodes.map(episode => ( + <Episode + onClick={() => actions.player.play(episode)} + key={episode.id} + playing={episode.audioUrl === nowPlaying.audioUrl} + > + <EpisodeTitle>{episode.episodeTitle}</EpisodeTitle> + <PodcastTitle>{episode.podcastTitle}</PodcastTitle> + <AddToPlaylistButton + added={playlist.some(item => item.audioUrl === episode.audioUrl)} + onClick={event => { + event.stopPropagation() + actions.player.addToPlaylist(episode) + }} + /> + </Episode> + )) + } + </div> + </div> +} + +SharedPlaylist.defaultProps = { + playlist: [] +} + +export const ConnectedSharedPlaylist = connect(state => ({ + playlist: state.player.playlist, + nowPlaying: state.player.nowPlaying +}))(SharedPlaylist) + +export default styled(ConnectedSharedPlaylist)` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + list-style: none; + padding-top: 60px; + background: rgba(0,0,0,0.8); + + h2 { + text-align: center; + color: white; + } + + .playlist-container { + max-width: 700px; + margin: 0 auto; + } + + #closeButton { + font-size: 3rem; + color: white; + text-decoration: none; + } +` diff --git a/website-react/src/components/Player/NowPlaying.js b/website-react/src/components/Player/NowPlaying.js index 54374258..4bfb4b87 100644 --- a/website-react/src/components/Player/NowPlaying.js +++ b/website-react/src/components/Player/NowPlaying.js @@ -5,7 +5,6 @@ import PropTypes from 'prop-types' export const NowPlaying = ({className, nowPlaying, children}) => ( <div className={className}> <div id='playerInfo'> - <h2 id='nowPlaying'>Now playing:</h2> <p id='episodeTitle'>{nowPlaying.episodeTitle}</p> <p id='podcastTitle'>{nowPlaying.podcastTitle}</p> </div> @@ -30,6 +29,7 @@ export default styled(NowPlaying)` max-width: 800px; border: solid 2px rgba(0, 0, 0, 0.3); border-radius: 3px; + z-index: 1; background: linear-gradient(to right, rgb(53, 145, 137), #185a9d); color: rgba(255, 255, 255, 0.9); @@ -41,25 +41,11 @@ export default styled(NowPlaying)` align-items: center; } - #nowPlaying { - position: absolute; - background: linear-gradient(to right, #4F8F88, #46828C); - top: -22px; - left: 2px; - border: solid 2px rgba(0, 0, 0, 0.3); - border-radius: 5px; - padding: 3px; - font-size: 1.1rem; - margin-bottom: 10px; - } - - h2, p { + p { text-align: center; margin: 0; - } - - p { width: 100%; + } #episodeTitle { @@ -74,11 +60,6 @@ export default styled(NowPlaying)` } @media screen and (max-width: 500px) { - #nowPlaying { - font-size: 1rem; - top: -19px; - } - #episodeTitle { font-size: 1rem; } diff --git a/website-react/src/index.js b/website-react/src/index.js index 13fb7b27..ea315128 100644 --- a/website-react/src/index.js +++ b/website-react/src/index.js @@ -1,12 +1,16 @@ import React from 'react' -import {render} from 'mirrorx' +import { render } from 'mirrorx' + import './models/SearchModel' import './models/AudioPlayerModel' import './hooks/eventTrackingHook' import './index.css' -import App from './App' +import App from './RoutedApp' import registerServiceWorker from './registerServiceWorker' -render(<App />, document.getElementById('root')) +render( + <App />, + document.getElementById('root') +) registerServiceWorker() diff --git a/website-react/src/models/SearchModel.js b/website-react/src/models/SearchModel.js index dc5bc162..d64aeee2 100644 --- a/website-react/src/models/SearchModel.js +++ b/website-react/src/models/SearchModel.js @@ -35,10 +35,9 @@ export default mirror.model({ } }, effects: { - async search (_, getState) { + async search (searchTerm) { actions.search.startLoading() - const searchTerm = getState().search.searchTerm const url = config.baseUrl + searchTerm const options = { headers: {