From 31007978bea8498b4a72a1b729985f976432ae1a Mon Sep 17 00:00:00 2001 From: NMarcora Date: Thu, 14 Jun 2018 00:32:09 +0100 Subject: [PATCH 1/2] added playlist url sharing and search via routing --- website-react/package.json | 1 + website-react/src/App.js | 12 -- website-react/src/RoutedApp.js | 19 ++++ website-react/src/components/Header/Search.js | 9 +- .../src/components/Main/SearchResults.js | 106 +++++++++++------- .../src/components/Main/SharedPlaylist.js | 73 ++++++++++++ website-react/src/index.js | 10 +- website-react/src/models/SearchModel.js | 3 +- 8 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 website-react/src/RoutedApp.js create mode 100644 website-react/src/components/Main/SharedPlaylist.js 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..abb6148c 100644 --- a/website-react/src/App.js +++ b/website-react/src/App.js @@ -14,7 +14,6 @@ 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' @@ -25,7 +24,6 @@ import Loader from './components/Loader' 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,7 +32,6 @@ export const App = connect(state => ({ playbackRate: state.player.playbackRate }))( ({ - results, searchTerm, currentSearch, loading, @@ -67,15 +64,6 @@ export const App = connect(state => ({ playlist={playlist} blur={currentSearch !== ''} /> - { - currentSearch !== '' && - - } 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 => ( + +
+ + + } + /> +
+
+) 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}) => (
{ event.preventDefault() event.target.querySelector('input').blur() - actions.search.search() + history.push(`/search/${searchTerm}`) }} > ( -
{ - if (event.target.nodeName !== 'DIV') return - actions.search.clearSearch() - }} - > - {`${results.length} results for "${currentSearch}"`} - { - results.length === 0 - ?

No results were found. Please try again.

- : results.map(episode => - actions.player.play(episode)} - key={episode.id} - playing={episode.audioUrl === nowPlaying.audioUrl} - > - {episode.episodeTitle} - {episode.podcastTitle} - item.audioUrl === episode.audioUrl)} - onClick={event => { - event.stopPropagation() - actions.player.addToPlaylist(episode) - }} - /> - - ) - } -
-) +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 + } = this.props + + return
{ + if (event.target.nodeName !== 'DIV') return + actions.search.clearSearch() + }} + > + {`${results.length} results for "${currentSearch}"`} + { + results.length === 0 + ?

No results were found. Please try again.

+ : results.map(episode => + actions.player.play(episode)} + key={episode.id} + playing={episode.audioUrl === nowPlaying.audioUrl} + > + {episode.episodeTitle} + {episode.podcastTitle} + item.audioUrl === episode.audioUrl)} + onClick={event => { + event.stopPropagation() + actions.player.addToPlaylist(episode) + }} + /> + + ) + } +
+ } +} + +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 +}))(SearchResults) -export default styled(SearchResults)` +export default styled(ConnectedSearchResults)` position: absolute; top: 0; left: 0; diff --git a/website-react/src/components/Main/SharedPlaylist.js b/website-react/src/components/Main/SharedPlaylist.js new file mode 100644 index 00000000..afefc275 --- /dev/null +++ b/website-react/src/components/Main/SharedPlaylist.js @@ -0,0 +1,73 @@ +import React from 'react' +import styled from 'styled-components' +import { actions } from 'mirrorx' +import { Link } from 'react-router-dom' + +import Episode from './Episode/' +import EpisodeTitle from './Episode/EpisodeTitle' +import PodcastTitle from './Episode/PodcastTitle' +import AddToPlaylistButton from './Episode/AddToPlaylistButton' + +export const SharedPlaylist = ({className}) => { + 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
+ X +
+

{`${metadata.author} has shared the playlist "${metadata.title}" with you!`}

+ { + episodes.map(episode => ( + actions.player.play(episode)} + key={episode.id} + playing={episode.audioUrl === 'nowPlaying.audioUrl'} + > + {episode.episodeTitle} + {episode.podcastTitle} + item.audioUrl === episode.audioUrl)} + added={false} + onClick={event => { + event.stopPropagation() + actions.player.addToPlaylist(episode) + }} + /> + + )) + } +
+
+} + +export default styled(SharedPlaylist)` + 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: 800px; + margin: 0 auto; + } + + #closeButton { + position: absolute; + right: 20px; + top: 20px; + font-size: 3rem; + color: white; + text-decoration: none; + } +` 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(, document.getElementById('root')) +render( + , + 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: { From 4d1b14e121a4934f9f3cd53d63d288e6070744c4 Mon Sep 17 00:00:00 2001 From: NMarcora Date: Sat, 16 Jun 2018 02:22:16 +0100 Subject: [PATCH 2/2] polished search and sharedplaylist and removed nowplaying --- website-react/src/App.js | 18 ++--- .../src/components/Main/SearchResults.js | 73 +++++++++++++------ .../src/components/Main/SharedPlaylist.js | 40 ++++++---- .../src/components/Player/NowPlaying.js | 25 +------ 4 files changed, 87 insertions(+), 69 deletions(-) diff --git a/website-react/src/App.js b/website-react/src/App.js index abb6148c..83288b2b 100644 --- a/website-react/src/App.js +++ b/website-react/src/App.js @@ -19,9 +19,7 @@ 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 => ({ searchTerm: state.search.searchTerm, @@ -34,18 +32,22 @@ export const App = connect(state => ({ ({ 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> } @@ -80,8 +82,6 @@ export const App = connect(state => ({ </NowPlaying> } - { loading && <Loader /> } - </Container> )) diff --git a/website-react/src/components/Main/SearchResults.js b/website-react/src/components/Main/SearchResults.js index 537a02b5..98a31aa6 100644 --- a/website-react/src/components/Main/SearchResults.js +++ b/website-react/src/components/Main/SearchResults.js @@ -6,6 +6,7 @@ import Episode from './Episode/' import EpisodeTitle from './Episode/EpisodeTitle' import PodcastTitle from './Episode/PodcastTitle' import AddToPlaylistButton from './Episode/AddToPlaylistButton' +import Loader from '../Loader' export class SearchResults extends Component { componentWillMount () { @@ -20,38 +21,47 @@ export class SearchResults extends Component { results, playlist, nowPlaying, - currentSearch + 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('/') }} > - <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 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> } } @@ -67,7 +77,8 @@ export const ConnectedSearchResults = connect(state => ({ nowPlaying: state.player.nowPlaying, results: state.search.results, playlist: state.player.playlist, - currentSearch: state.search.currentSearch + currentSearch: state.search.currentSearch, + loading: state.search.loading }))(SearchResults) export default styled(ConnectedSearchResults)` @@ -79,9 +90,23 @@ export default styled(ConnectedSearchResults)` 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 index afefc275..4b9aa5b1 100644 --- a/website-react/src/components/Main/SharedPlaylist.js +++ b/website-react/src/components/Main/SharedPlaylist.js @@ -1,35 +1,41 @@ import React from 'react' import styled from 'styled-components' -import { actions } from 'mirrorx' -import { Link } from 'react-router-dom' +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}) => { +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}> - <Link to='/' id='closeButton'>X</Link> + 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> + <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'} + playing={episode.audioUrl === nowPlaying.audioUrl} > <EpisodeTitle>{episode.episodeTitle}</EpisodeTitle> <PodcastTitle>{episode.podcastTitle}</PodcastTitle> <AddToPlaylistButton - // added={playlist.some(item => item.audioUrl === episode.audioUrl)} - added={false} + added={playlist.some(item => item.audioUrl === episode.audioUrl)} onClick={event => { event.stopPropagation() actions.player.addToPlaylist(episode) @@ -42,7 +48,16 @@ export const SharedPlaylist = ({className}) => { </div> } -export default styled(SharedPlaylist)` +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; @@ -58,14 +73,11 @@ export default styled(SharedPlaylist)` } .playlist-container { - max-width: 800px; + max-width: 700px; margin: 0 auto; } #closeButton { - position: absolute; - right: 20px; - top: 20px; 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; }