Merge branch 'feature/video-player' of 858/frontend into develop
This commit is contained in:
commit
7d8778b51c
|
@ -19,7 +19,7 @@
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>858</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
|
|
||||||
|
// Topics Action Types
|
||||||
export const START_LOADING_ALL_TOPICS = 'START_LOADING_ALL_TOPICS';
|
export const START_LOADING_ALL_TOPICS = 'START_LOADING_ALL_TOPICS';
|
||||||
export const LOADED_ALL_TOPICS = 'LOADED_ALL_TOPICS';
|
export const LOADED_ALL_TOPICS = 'LOADED_ALL_TOPICS';
|
||||||
export const START_LOADING_RANDOM_TOPIC = 'START_LOADING_RANDOM_TOPIC';
|
export const START_LOADING_RANDOM_TOPIC = 'START_LOADING_RANDOM_TOPIC';
|
||||||
export const LOADED_RANDOM_TOPIC = 'LOADED_RANDOM_TOPIC';
|
export const LOADED_RANDOM_TOPIC = 'LOADED_RANDOM_TOPIC';
|
||||||
|
|
||||||
|
// Videos Action Types
|
||||||
|
export const ADD_VIDEOS_TO_STATE = 'ADD_VIDEOS_TO_STATE';
|
||||||
|
export const START_LOADING_VIDEO = 'START_LOADING_VIDEO';
|
||||||
|
export const LOADED_VIDEO = 'LOADED_VIDEO';
|
|
@ -2,6 +2,7 @@ import { fetchAllTopics, fetchVideosByTopic } from '../utils/api';
|
||||||
import { APIError } from './errors';
|
import { APIError } from './errors';
|
||||||
import { getItem, setItem } from '../utils/safe-storage';
|
import { getItem, setItem } from '../utils/safe-storage';
|
||||||
import getRandomTopic from '../utils/get-random-topic';
|
import getRandomTopic from '../utils/get-random-topic';
|
||||||
|
import { addVideosToState } from './videos';
|
||||||
import {
|
import {
|
||||||
START_LOADING_ALL_TOPICS,
|
START_LOADING_ALL_TOPICS,
|
||||||
LOADED_ALL_TOPICS,
|
LOADED_ALL_TOPICS,
|
||||||
|
@ -42,6 +43,7 @@ export function getRandomTopicVideos(allTopics, numVideos=4) {
|
||||||
dispatch(loadingRandomTopicVideos());
|
dispatch(loadingRandomTopicVideos());
|
||||||
fetchVideosByTopic(randomTopic.name, 0, 4)
|
fetchVideosByTopic(randomTopic.name, 0, 4)
|
||||||
.then(videos => {
|
.then(videos => {
|
||||||
|
dispatch(addVideosToState(videos));
|
||||||
dispatch(loadedRandomTopicVideos(randomTopic, videos));
|
dispatch(loadedRandomTopicVideos(randomTopic, videos));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
8
src/actions/videos.js
Normal file
8
src/actions/videos.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { ADD_VIDEOS_TO_STATE } from './action_types';
|
||||||
|
|
||||||
|
export function addVideosToState(videos) {
|
||||||
|
return {
|
||||||
|
type: ADD_VIDEOS_TO_STATE,
|
||||||
|
payload: videos
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import VideoItem from "./VideoItem";
|
import VideoItem from "./VideoItem";
|
||||||
import SectionHeading from "./SectionHeading";
|
import SectionHeading from "./SectionHeading";
|
||||||
import {Link} from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
class RandomTopic extends React.Component {
|
class RandomTopic extends React.Component {
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class RandomTopic extends React.Component {
|
||||||
width={450}
|
width={450}
|
||||||
gap={16}>
|
gap={16}>
|
||||||
{this.props.videos.map(video =>
|
{this.props.videos.map(video =>
|
||||||
<VideoItem id={video.id} title={video.title}/>
|
<VideoItem id={video.id} key={video.id} title={video.title}/>
|
||||||
)}
|
)}
|
||||||
<Link to="/topics">
|
<Link to="/topics">
|
||||||
See All Topics
|
See All Topics
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { getThumbnail } from '../utils/video';
|
import { getThumbnail } from '../utils/video';
|
||||||
class VideoItem extends React.Component {
|
class VideoItem extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const videoLink = `/videos/${this.props.id}`;
|
||||||
return (
|
return (
|
||||||
|
<Link to={ videoLink }>
|
||||||
<section className="video-item">
|
<section className="video-item">
|
||||||
<section className="video-thumbnail-container">
|
<section className="video-thumbnail-container">
|
||||||
<img className="video-thumbnail" src={ getThumbnail(this.props.id) } alt=""/>
|
<img className="video-thumbnail" src={ getThumbnail(this.props.id) } alt=""/>
|
||||||
</section>
|
</section>
|
||||||
<h3 className="video-title">{ this.props.title }</h3>
|
<h3 className="video-title">{ this.props.title }</h3>
|
||||||
</section>
|
</section>
|
||||||
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Grid from 'react-css-grid';
|
import Grid from 'react-css-grid';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { getVideo } from '../utils/video';
|
||||||
|
|
||||||
class VideoPlayer extends React.Component {
|
class VideoPlayer extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
|
@ -8,19 +10,13 @@ class VideoPlayer extends React.Component {
|
||||||
<Grid width={450}
|
<Grid width={450}
|
||||||
gap={16}>
|
gap={16}>
|
||||||
<section>
|
<section>
|
||||||
<h2>Title</h2>
|
<h2>{ this.props.title }</h2>
|
||||||
<video src="https://video22.858.ma/BUR/240p3.mp4" controls>
|
<video src={ getVideo(this.props.id) } controls>
|
||||||
</video>
|
</video>
|
||||||
<h3>date mm/dd/yy </h3>
|
<h3>date { this.props.date } </h3>
|
||||||
</section>
|
</section>
|
||||||
<section className="text-left">
|
<section className="text-left">
|
||||||
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the
|
<p>{ this.props.description }</p>
|
||||||
industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and
|
|
||||||
scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into
|
|
||||||
electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of
|
|
||||||
Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like
|
|
||||||
Aldus
|
|
||||||
PageMaker including versions of Lorem Ipsum.</p>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -28,4 +24,10 @@ class VideoPlayer extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (VideoPlayer);
|
VideoPlayer.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
date: PropTypes.string,
|
||||||
|
description: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoPlayer;
|
|
@ -1,14 +1,56 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
import VideoPlayer from "../components/VideoPlayer";
|
import VideoPlayer from "../components/VideoPlayer";
|
||||||
|
import { fetchVideoById } from '../utils/api';
|
||||||
|
|
||||||
class Video extends React.Component {
|
class Video extends React.Component {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
currentVideo: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// get videoId from the URL
|
||||||
|
const videoId = this.props.match.params.videoId;
|
||||||
|
|
||||||
|
// if data for videoId is already part of allVideos, setState to currentVideo
|
||||||
|
if (this.props.allVideos && this.props.allVideos.hasOwnProperty(videoId)) {
|
||||||
|
const currentVideo = this.props.allVideos[videoId];
|
||||||
|
this.setState({currentVideo: currentVideo});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// video is not part of allVideos, fetch it from the API
|
||||||
|
fetchVideoById(videoId)
|
||||||
|
.then(video => {
|
||||||
|
this.setState({
|
||||||
|
currentVideo: video
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const video = this.state.currentVideo;
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<VideoPlayer/>
|
{ video &&
|
||||||
|
<VideoPlayer
|
||||||
|
id={ video.id }
|
||||||
|
title={ video.title }
|
||||||
|
date = { video.date }
|
||||||
|
description={ video.description }
|
||||||
|
/>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default (Video);
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
allVideos: state.videos.allVideos
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Video);
|
|
@ -1,9 +1,11 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
import TopicsReducer from './topics'
|
import TopicsReducer from './topics';
|
||||||
|
import VideosReducer from './videos';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
topics: TopicsReducer
|
topics: TopicsReducer,
|
||||||
|
videos: VideosReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
22
src/reducers/videos.js
Normal file
22
src/reducers/videos.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
ADD_VIDEOS_TO_STATE
|
||||||
|
} from '../actions/action_types';
|
||||||
|
|
||||||
|
export default function(state={}, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_VIDEOS_TO_STATE:
|
||||||
|
let clonedState = Object.assign({}, state);
|
||||||
|
if (!clonedState.allVideos) {
|
||||||
|
clonedState.allVideos = {};
|
||||||
|
}
|
||||||
|
const videosToAdd = action.payload;
|
||||||
|
videosToAdd.forEach(video => {
|
||||||
|
if (!clonedState.allVideos.hasOwnProperty(video.id)) {
|
||||||
|
clonedState.allVideos[video.id] = video;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return clonedState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,16 @@
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
|
const VIDEO_KEYS = [
|
||||||
|
"title",
|
||||||
|
"source",
|
||||||
|
"project",
|
||||||
|
"topic",
|
||||||
|
"language",
|
||||||
|
"duration",
|
||||||
|
"id",
|
||||||
|
"date"
|
||||||
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Base function to make API calls
|
Base function to make API calls
|
||||||
|
@ -98,7 +108,7 @@ export function fetchAllDates(startRange=0, endRange=1000) {
|
||||||
*/
|
*/
|
||||||
export function fetchVideosByX(key, value, startRange, endRange, sortKey='random') {
|
export function fetchVideosByX(key, value, startRange, endRange, sortKey='random') {
|
||||||
const data = {
|
const data = {
|
||||||
"keys": ["editable", "modified", "title", "source", "project", "topic", "language", "duration", "id"],
|
"keys": VIDEO_KEYS,
|
||||||
"query": {
|
"query": {
|
||||||
"conditions": [{
|
"conditions": [{
|
||||||
"key": key,
|
"key": key,
|
||||||
|
@ -205,3 +215,7 @@ export function fetchRandomPlace() {
|
||||||
export function fetchVideosByPlace(placeId, startRange=0, endRange=100, sortKey='random') {
|
export function fetchVideosByPlace(placeId, startRange=0, endRange=100, sortKey='random') {
|
||||||
return fetchVideosByX('place', placeId, startRange, endRange, sortKey);
|
return fetchVideosByX('place', placeId, startRange, endRange, sortKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchVideoById(id) {
|
||||||
|
return callApi('get', {'id': id, keys: VIDEO_KEYS});
|
||||||
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ export function getThumbnail(id, size='256') {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVideo(id, size='480') {
|
export function getVideo(id, size='480') {
|
||||||
return `${config.videoBase}/${id}/${size}.mp4`;
|
return `${config.videoBase}/${id}/${size}p.mp4`;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user