Skip to content

Instantly share code, notes, and snippets.

@codesnakers
Created February 16, 2019 21:37
Show Gist options
  • Save codesnakers/83e732153566e29f7982b109cad6b0cd to your computer and use it in GitHub Desktop.
Save codesnakers/83e732153566e29f7982b109cad6b0cd to your computer and use it in GitHub Desktop.
/* eslint-disable */
import React, { Component } from 'react';
import Axios from 'axios';
import MovieIcon from '@material-ui/icons/Movie';
import AvTimerIcon from '@material-ui/icons/AvTimer';
import VisibilityIcon from '@material-ui/icons/Visibility';
import FavoriteIcon from '@material-ui/icons/Favorite';
import WarningIcon from '@material-ui/icons/Warning';
import PropTypes from 'prop-types';
import {VideoClass} from '../../models/video.class';
import { YoutubeService } from '../../services/youtube/Youtube';
import './Youtube.scss';
import { appConfig } from '../../config';
const service = new YoutubeService();
export const splitNumToArray = (n, limit) => {
var result = [];
while(n>0){
if(n>limit){ result.push(limit); }
else{ result.push(n); }
n = n - limit;
}
return result;
}
class Youtube extends Component {
overWriteTrends = false;
constructor(props) {
super(props);
this.state = {
trends: [],
isError: false,
isLoading: false,
totalResults: null
};
this.handlePromise = this.handlePromise.bind(this);
var _self = this;
// Binds our scroll event handler
window.onscroll = () => {
const {isError, isLoading} = this.state;
// Bails early if:
// * there's an error
// * it's already loading
// * there's nothing left to load
/*if (isError || isLoading || !hasMore) return;*/
if (isError || isLoading) return;
// Checks that the page has scrolled to the bottom
/*console.log(window.innerHeight , document.documentElement.scrollTop , document.documentElement.scrollHeight, document.documentElement.offsetHeight);
*/
/*if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight){*/
/*console.log(window.innerHeight + document.documentElement.scrollTop, document.documentElement.scrollHeight);*/
if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.scrollHeight){
/*console.log("loading more videos...");*/
if(_self.state.trends.length>0 && _self.state.totalResults!==null && _self.state.trends.length<_self.state.totalResults){
_self.setState({ isLoading: true }, _self.loadVideos);
}
}
};
}
resetState(cb){
if(this.state.isLoading===false){
this.setState({isLoading:true, totalResults: null, trends:[]}, cb);
}
service.nextPageTokenClassVar = null;
this.overWriteTrends = true;
}
componentWillMount() {
var _self = this;
this.props.setTitle('YOUTUBE');
this.loadCategories();
this.props.onChanges(() => {
if(_self.state.isLoading===false){
_self.setState({ isLoading: true }, this.loadVideos);
}
});
}
componentDidUpdate(prevProps){
const config = appConfig;
var prev = prevProps.filterObj, cur = this.props.filterObj;
/*
If user increases the slider value we reset the nextPageToken
so if previously 25 was slider value and user slides to 30
30 videos are requested from youtube API and overwritten in trends state
and thanks to React only the new 5 videos are added to the dom
*/
if(prev[config.CATEGORY_FILTER]===cur[config.CATEGORY_FILTER] &&
prev[config.COUNTRY_FILTER]===cur[config.COUNTRY_FILTER] &&
prev[config.MAXVIDEO_FILTER]!=cur[config.MAXVIDEO_FILTER] ){
this.overWriteTrends = true;
service.nextPageTokenClassVar = null;
}
/* if user changes category or country reset trends, total results, nextPageToken and loadVideos */
else if(prev[config.CATEGORY_FILTER]!==cur[config.CATEGORY_FILTER] ||
prev[config.COUNTRY_FILTER]!==cur[config.COUNTRY_FILTER] ||
prev[config.MAXVIDEO_FILTER]!==cur[config.MAXVIDEO_FILTER] ){
/*this.resetState(this.loadVideos);*/
this.resetState();
}
}
async loadCategories(){
const _self = this;
Axios.all(await service.getCategories(_self.props.filterObj) )
.then((data) => {
data = data.flat();
/*console.log(data);*/
_self.props.setCategories(data);
})
.catch((err) => {
_self.setState({isError: true});
console.log(err);
});
}
/*
Initially totalResults = null , trends.length = 0
so remaining = 0
so videosToLoad = filterObj[appConfig.MAXVIDEO_FILTER] i.e. slider value
after initial load we get totalResults value in youtube api response
so lets say slider value was 25, so initially 25 videos loaded
and lets say totalResults = 200
so remaining = totalResults - trends.length = 200 - 25 = 175
this will be used when next load happens like when user scrolls
*/
getVideosToLoad(totalResults, trendsLen, maxVideosToLoad, overWriteTrends){
var remaining = totalResults - trendsLen;
var videosToLoad;
if(remaining>0 && remaining < maxVideosToLoad && overWriteTrends===false){
videosToLoad = remaining;
}else{
videosToLoad = maxVideosToLoad;
}
return videosToLoad;
}
getTrendingVideosPromise(videosToLoad, maxYTVideoLimit, filterObj){
/*
youtube api only returns max 50 videos in 1 request so we split requests
splitNumToArray( 150, 50 ) => returns [50,50,50]
splitNumToArray( 161, 50 ) => returns [50,50,50,11]
*/
var splitBy50 = splitNumToArray(videosToLoad, maxYTVideoLimit);
/*console.log(splitBy50, videosToLoad, maxYTVideoLimit);*/
var prom;
if(splitBy50.length > 1){
/* https://decembersoft.com/posts/promises-in-serial-with-array-reduce/ */
/*
this technique found in url above will work only if we have >=2 values in array
*/
prom = splitBy50.reduce((accumulator, currentVal)=>{
var prom = typeof accumulator === "number"?service.getTrendingVideos(accumulator, filterObj):accumulator;
return prom.then((result)=>{
result = [result];
result = result.flat();
service.nextPageTokenClassVar = result[result.length-1].data.nextPageToken;
/*
if resultant nextPageToken is undefined means all videos are loaded and we dont have next page to load
so we return result
*/
if(result[result.length-1].data.nextPageToken===undefined){
return result;
}
return service.getTrendingVideos(currentVal, filterObj).then((result2)=>{
service.nextPageTokenClassVar = result2.data.nextPageToken;
result.push(result2);
return result;
});
});
});
}else{
/*if slider value has <=50 we just need a single request */
prom = service.getTrendingVideos(splitBy50[0], filterObj);
}
return prom;
}
handlePromise(allResults){
const _self = this;
/** if slider value is <=50 we get an object, */
if(allResults.length === undefined){
allResults = [allResults];
}
var totalResults = allResults[allResults.length-1].data.pageInfo.totalResults;
service.nextPageTokenClassVar = allResults[allResults.length-1].data.nextPageToken;
allResults = allResults.map((res)=>{
return res.data.items.map((item) => new VideoClass(item)).filter((item) => item.id !== '');
});
/** if only slder was change we overwrite trends otherwise we add to the trends */
_self.setState({
trends: _self.overWriteTrends?[...allResults.flat()]:[..._self.state.trends, ...allResults.flat()],
totalResults: totalResults,
isError: false,
isLoading: false
});
}
async loadVideos() {
/*Axios.all(await service.getTrendingVideos(this.props.config.maxVideosToLoad))*/
const _self = this;
const { trends, totalResults } = _self.state;
/*const {filterObj, nextPageToken} = _self.props;*/
const {filterObj} = _self.props;
const config = appConfig;
/*console.log(trends.length, totalResults);*/
/* 0>=null is true #JavascriptTheWeirdParts :D */
/*if(nextPageToken!==null && trends.length > 0 && totalResults!==null && trends.length >= totalResults){*/
var videosToLoad = this.getVideosToLoad(totalResults, trends.length, filterObj[config.MAXVIDEO_FILTER], _self.overWriteTrends);
if(trends.length > 0 && totalResults!==null && trends.length >= totalResults && filterObj[config.MAXVIDEO_FILTER]>=totalResults){
/*console.log('all videos loaded');*/
_self.setState({
isError: false,
isLoading: false
});
return;
}
var prom = this.getTrendingVideosPromise(videosToLoad, config.maxYTVideoLimit, filterObj);
prom.then(this.handlePromise).catch((err) => {
_self.setState({isError: true,isLoading: false});
console.log(err);
}).then(()=>{
/** reset overWriteTrends value back to false */
_self.overWriteTrends = false;
});
}
openVideo() {
return window.location.href = '/youtube/' + this;
}
youtubeCard() {
if(this.state.trends.length===0 && this.state.isLoading===false && this.state.totalResults!==null){
return <div className="noVideos">No Trending Videos</div>;
}
return this.state.trends.map((videos, index) =>
<div key={index} className="card-container">
<div className="card" onClick={this.openVideo.bind(videos.id)}>
<div className="img-container">
<img src={videos.thumbnail} alt={videos.title}/>
<MovieIcon/>
</div>
<div className="video-statistic">
<div className="publishedAt">
<AvTimerIcon/>
<span>{videos.publishedAt}</span>
</div>
<div className="viewCount">
<VisibilityIcon/>
<span>{videos.viewCount}</span>
</div>
<div className="likeCount">
<FavoriteIcon/>
<span>{videos.likeCount}</span>
</div>
</div>
<p className="video-title text-ellipsis">
{videos.title}
</p>
</div>
</div>
);
}
errorOnPage() {
return <div className="error-plate">
<WarningIcon/>
<span>Error loading. Please try again later.</span>
</div>;
}
render() {
const { trends, totalResults, isLoading } = this.state;
return !this.state.isError ? ( <div id="youtube">
<div className="row">
{this.youtubeCard()}
</div>
{(trends.length > 0 && totalResults!==null && trends.length >= totalResults)?
<div className="loadingVideos"></div>:
(isLoading?<div className="loadingVideos">Loading...</div>:'')
}
</div>) : (this.errorOnPage());
}
}
Youtube.propTypes = {
setTitle : PropTypes.func,
onChanges: PropTypes.func,
setCategories: PropTypes.func,
/*setNextPageToken: PropTypes.func,
nextPageToken: PropTypes.string*/
};
export default Youtube;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment