tvapps-epg-web
v1.0.29
Published
EPG Component for React Js applications. ===
Downloads
16
Readme
EPG Component for React Js applications.
React Js TV Guide for web
- [x] EPG component
- Material design similar to the website
- Supports model (channel, program)
- Renderer support:
- [x] Support React Js
- [x] Support custom UI at component (date, time, channel, program)
- [x] Support callback function when selecting (program, channel, favorite)
- [x] Support change icons (next, prev, calendar, recorded, record, loading, catchup, purchase, favorite, favoriteActive, scheduled record, place holder channel)
- [x] Support change text (today, no program info, loading),
- [x] Support past and future date display
- [x] Support slient load more programs by date
- [x] Hooks for loading additional data when scrolling to the end of the loaded data
- [x] Shows the current and next programme and it's start time
Data
Data is provided in formats defined.
channel = {
imageSrc: string,
id: number,
externalChannelId: string,
name: string,
url: string,
description: string,
category: string,
extrafields: array,
number: number,
isNpvrActivated: bool,
isCatchupActivated: bool,
isFavouriteActivated: bool,
isPurchaseActivated: bool,
flags: number;
};
//default value
channel = {
imageSrc: null,
id: -1,
externalChannelId: '',
name: '',
url: '',
description: '',
category: '',
extrafields: [],
number: -1,
isNpvrActivated: false,
isCatchupActivated: false,
isFavouriteActivated: false,
isPurchaseActivated: false,
flags: 0;
}
};
//example value
channel = {
imageSrc: 'https://votvapps-ng-test.tvaas.com/RTEFacade/images/attachments/TV2.png',
id: 1895201,
externalChannelId: 'LuxeTV',
name: 'Luxe TV',
url: '',
description: '',
category: '',
extrafields: [
{
responseElementType: "Extrafield",
name: "static-playback",
value: "false"
}
],
number: 12,
isNpvrActivated: false,
isCatchupActivated: false,
isFavouriteActivated: false,
isPurchaseActivated: false,
flags: 128,
}
};
const channeList = [
{
...channel
},
{
...channel
},
...
];
//PROGRAM DATA FORMART
program = {
id: number,
name: string,
shortName: string,
serisName: string,
description: string,
prName: string,
startDate: number,//(timestamp)
endDate: number,//(timestamp)
startDateAdjusted: number,// default equal to startDate (timestamp), adjusted to fix start of day (00:00:00)
endDateAdjusted: number,// default equal to endDate (timestamp), adjusted to fix end of day (23:59:59)
referenceProgramId: string,
flags: number,
responseElementType: string,
price: number,
imageSrc: string,
genres: array,
prLevel: number,
}
// default value
program = {
id: -1,
name: '',
shortName: '',
serisName: '',
description: '',
prName: '',
startDate: 0,//(timestamp)
endDate: 0,//(timestamp)
startDateAdjusted: 0,// default equal to startDate (timestamp), adjusted to fix start of day (00:00:00)
endDateAdjusted: 0,// default equal to endDate (timestamp), adjusted to fix end of day (23:59:59)
referenceProgramId: '',
flags: 0,
responseElementType: '',
price: 0,
imageSrc: '',
genres: [],
prLevel: -1,
}
//example data
program = {
id: 12966715,
name: 'Los milagros de la rosa',
shortName: '',
serisName: '',
description: '',
imageSrc: 'https://votvapps-ng-test.tvaas.com/RTEFacade/images/12055411.jpg',
prName: 'APT',
startDate: 1641769200000,//(timestamp)
endDate: 1641776400000,//(timestamp)
startDateAdjusted: 1641769200000,// default equal to startDate (timestamp), adjusted to fix start of day (00:00:00)
endDateAdjusted: 1641776400000,// default equal to endDate (timestamp), adjusted to fix end of day (23:59:59)
referenceProgramId: '2466657917202201091800120',
flags: 0,
responseElementType: 'Program',
price: 1,
genres: [],
prLevel: 0,
}
const programList = [
{
channelExternalId:'France24Fr2',
programs: [program,...]
},
{
channelExternalId:'ArteLoop',
programs: [program,...]
},
{
channelExternalId:'Arte',
programs: [program,...]
},
....
];
Config Defaults
You can specify config defaults that will be applied to every request.
Available tokens and their meanings are as follows:
| Prop types | meaning | examples of output |
|:---------------------------|:--------------------------------------------------|:----------------------------------------------------------|
| catchupEnabled | config catchup | catchupEnabled={false} |
| npvrEnabled | config npvr | npvrEnabled={false} |
| favouriteEnabled | config favourite | favouriteEnabled={false} |
| numberOfFutureDays | the future number is display | 1 |
| numberOfPastDays | number of day too was display | 1 |
| currentDate | current date value is selected | 'Date Fri Jan 21 2022 10:30:00 GMT+0700 (Indochina Time)' |
| todayText | text showing current date | 'Today' |
| noProgramInfoText | display text no program info | 'No Program Info' |
| loadingText | text showing loading | 'Loading' |
| programDetail | object program detail is currently selected | {} |
| isLoadingShown | status loading | false |
| isLoadingMore | status loading more | false |
| channelList | channel list data | [] |
| programList | program list data | [] |
| tvGuideContainer | Take the element tv guide to check the height | |
| onDateChanged | callback to change date | (f) => f |
| onTimeChanged | callback to change time | (f) => f |
| onProgramSelected | callback to select program item | (f) => f |
| onChannelClicked | callback on click channel | (f) => f |
| onFavoriteClicked | callback on favorite | (f) => f |
| tvGuideWrapperStyles | put css to change the style of tv guide wrapper | { marginTop: 364, background: '#000' } |
| datePickerStyles | put css to change the style of date | { fontSize: 16 } |
| timeLineCellStyles | put css to change the style of time | { fontSize: 24, color: '#fff' } |
| channelStyles | put css to change the style of channel | { number: { color: '#03143f' } } |
| programStyles | put css to change the style of program item | {current: { fontSize: 24 }, future: { color: '#fff' }} |
| programHoverBorderStyles | put css to change the style when hover at program | { '--hover-border': '1px solid #EE3325' } |
| timeBarStyles | put css to change the style of time bar | timeBar: { background: '#EE3325' } |
| loadingIcon | allow to change the loading icon | '' |
| nextIcon | allow to change the next icon | '' |
| prevIcon | allow to change the prev icon | '' |
| calendarIcon | allow to change the calendar icon | '' |
| purchaseIcon | allow to change the purchase icon | '' |
| recordIcon | allow to change the record icon | '' |
| recordedIcon | allow to change the recorded icon | '' |
| favoriteActiveIcon | allow to change the favorite active icon | '' |
| favoriteIcon | allow to change the favorite icon | '' |
| catchupIcon | allow to change the catchup icon | '' |
| scheduledRecordIcon | allow to change the scheduled record icon | '' |
| placeHolderChannelIcon | allow to change the place holder channel icon | '' |
Support library function
You can call functions from the library to get the required data.
Available tokens and their meanings are as follows:
| Function Suport | meaning | examples of output | |:---------------------------|:-----------------------------------------------------------------------|:----------------------------------------------------------| | TvGuide | component TvGuide | | | getDate | function get date to handle date change when next or prev or calendar | getDate(date) | | getTime | function get time to handle time change when next or prev | getDate(time) | | formatCurrentDate | format the current time with component | formatCurrentDate(currentDate) | | bitToBitCompare | function compare flag | bitToBitCompare(flas, 16) | | getImage | function get image from api | attachments.map((img) => getImage(img)) | | Channel | channel model | new Channel(); | | ProgramItem | program item model | new ProgramItem(); | | ProgramLine | program model | new ProgramLine(); |
Import To Your Web:
Import TV Guide Web component with default properties below:
- [x] All properties are the same TV Guide above
import React from 'react';
import { throttle } from 'lodash';
import { isMobile, isTablet } from 'react-device-detect';
import { TvGuide, getDate, getTime, formatCurrentDate, bitToBitCompare, getImage, Channel, ProgramItem, ProgramLine, FilterComponent } from './tvapps-epg-web/src';
import ICON_ARROW_LEFT from './images/arrow-left.png';
import ICON_ARROW_RIGHT from './images/arrow-right.png';
import ICON_CALENDAR from './images/calendar-icon.png';
import ICON_RECORD from './images/icon-record.png';
import ICON_CATCHUP from './images/icon-catchup.png';
import ICON_FAVORITE_ACTIVE from './images/favorite-active.png';
import ICON_FAVORITE from './images/favorite.png';
import ICON_PURCHASE from './images/purchase-icon.png';
import ICON_RECORDED from './images/icon-recorded.png';
import ICON_SCHEDULED_RECORD from './images/icon-scheduled-record.png';
import ICON_LOADING from './images/loading.svg';
import ICON_START_OVER from './images/icon-startOver.png';
import ICON_CATCHUP_IN_PROGRAM from './images/icon-catchup-in-program.png';
import PLACE_HOLDER_CHANNEL from './images/placeHolder/channel.png';
const axios = require('axios');
export class WebEpgDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
currentDate: new Date(), // Current date (today)
programDetail: {}, // obj program detail
isLoadingShown: true, // status loading when load first page TvGuide
isLoadingMore: false, // status loading more when scroll to the bottom
channelList: [], // array channel list
programList: [], // array program list
numberChannel: [], // number channel or number line show at the epg
allChannelList: [], // all channel
categories: [
'ALL',
'Favourite',
'Nacionales',
'Novelas',
'Estilo de Vida',
'Noticias e Internacionales ',
'Series y Peliculas',
'Infantiles',
'Culturales y Educativos',
'Deportes',
'Musica y Realities',
'Religiosos',
'Regionales Peru',
'Adultos',
], // categories in the filter
selectedChannelCategoryText: 'All', // selected channel category text
width: window.innerWidth, // width screen
};
this.currentProgram = true; // flag check set current program the first
}
componentDidMount() {
const { currentDate } = this.state;
this.setState({ currentDate: formatCurrentDate(currentDate) }); // format current date when load the first and only
window.scrollTo(0, 0); // set scroll to top when load page
window.addEventListener('scroll', this.handleScroll); // create event scroll and function handleScroll
window.addEventListener('resize', this.updateDimensions); // reate event scroll and function updateDimensions
}
UNSAFE_componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll); // remove event scroll and function handleScroll
window.removeEventListener('resize', this.updateDimensions); // remove event scroll and function updateDimensions
}
updateDimensions = () => {
this.setState({ width: window.innerWidth });
};
handleScroll = () => { // function handle scroll when scroll to the bottom
if (window.innerHeight + window.pageYOffset >= document.body.offsetHeight - window.innerHeight / 2) { / check height screen and scroll
this.loadMoreData(); // call load more data
}
};
loadMoreData = throttle(() => { // function load more date
const { numberChannel, currentDate, allChannelList, isLoadingMore } = this.state;
let curentList = [...numberChannel];
if (!isLoadingMore && curentList && curentList.length && allChannelList && allChannelList.length) {
if (curentList.length < allChannelList.length) {
let nextList =
allChannelList && allChannelList.length && allChannelList.slice(curentList.length, curentList.length + 10);
this.getData(currentDate, nextList, true);
this.setState((prevState) => {
return {
...prevState,
numberChannel: [...prevState.numberChannel, ...nextList],
};
});
}
}
}, 500);
getData = (currentDate, channelList, isLoadMore = false) => { // get data and set state loading more
this.setState({ isLoadingMore: isLoadMore });
this.getPrograms(currentDate, channelList, isLoadMore);
};
getChannel = (cb) => { // get channel list
const urlApi = 'https://urlApi'; // url api
const urlGetChannelList = `${urlApi}`;
const params = {
...
};
const optionChannel = {
url: urlGetChannelList,
method: 'GET',
params,
withCredentials: true,
};
axios(optionChannel)
.then((channels) => {
if (
channels.status == 200 &&
channels &&
channels.data &&
channels.data.response &&
channels.data.response.length &&
channels.data.response
) {
const channelList = [];
channels.data.response.map((item) => {
const channelModel = new Channel();
const images = [];
const attachments = item.images ? item.images : item.attachments ? item.attachments : [];
images.push(...attachments.map((img) => getImage(img))); // map and create imageSrc
if (images && images.length) {
const findImage = images.find((item) => item.name == 'HorizontalImage');
if (findImage) {
item.imageSrc = `${rightv}${findImage.url}`;
}
}
item.isNpvrActivated = bitToBitCompare(item.flags, 16); // check show icon npvr
item.isCatchupActivated = bitToBitCompare(item.flags, 128); // check show icon catchup
item.isFavouriteActivated = false; // check show icon favorite
item.isPurchaseActivated = true; // check show icon purchase
channelModel.unSerialize(item); // channel model
channelList.push(channelModel);
});
cb(channelList); // callback channel list
}
})
.catch((error) => {
console.log('clientEvents err', error);
cb([]); // callback []
});
};
getPrograms = (currentDate, channelList, isLoadMore) => { // get programList
const self = this;
const urlApi = 'https://urlApi'; // url api
const startTime = new Date(currentDate);
startTime.setHours(0, 0, 0, 0);
const endTime = new Date(currentDate);
endTime.setDate(endTime.getDate() + 1);
endTime.setHours(1, 0, 0, 0);
if (channelList && channelList.length) {
const promises = channelList.map((item) => {
return new Promise((resolve) => {
const urlGetProgramLists = `${urlApi}/GetProgramLists`;
const params = {
channel_external_ids: item.externalChannelId,
start_date: startTime.getTime(),
end_date: endTime.getTime(),
language_code: 'en',
gaps: true,
client: 'json',
};
const optionProgram = {
url: urlGetProgramLists,
method: 'GET',
params,
withCredentials: true,
};
axios(optionProgram)
.then((programs) => {
if (
programs.status == 200 &&
programs &&
programs.data &&
programs.data.response &&
programs.data.response.length &&
programs.data.response[0]
) {
resolve(
programs &&
programs.data &&
programs.data.response &&
programs.data.response.length &&
programs.data.response[0]
);
}
})
.catch((error) => {
console.log('123123 program err', error);
});
});
});
Promise.all(promises).then((results) => {
const programList = [];
const currentProgram = [];
results.map((program) => {
const programs = [];
if (program && program.programs && program.programs.length) {
program.programs.map((tvProgram) => {
const images = [];
const attachments = tvProgram.images
? tvProgram.images
: tvProgram.attachments
? tvProgram.attachments
: [];
images.push(...attachments.map((img) => getImage(img)));
if (images && images.length) {
const findImage = images.find((item) => item.name == 'HorizontalImage');
if (findImage) {
tvProgram.imageSrc = `${urlApi}${findImage.url}`;
}
}
const programItemModel = new ProgramItem();
programItemModel.unSerialize(tvProgram);
programs.push(programItemModel);
if (this.currentProgram) {
const current = new Date().getTime();
const proStartTime = tvProgram && tvProgram.startDate ? tvProgram.startDate : null;
const prpEndTime = tvProgram && tvProgram.endDate ? tvProgram.endDate : null;
if (proStartTime && prpEndTime && proStartTime <= current && prpEndTime >= current) {
currentProgram.push(tvProgram);
}
}
});
}
const programModel = new ProgramLine(program.channelExternalId, programs);
programList.push({ ...programModel });
});
if (this.currentProgram && currentProgram && currentProgram.length) {
self.setState({ programDetail: currentProgram[0] });
this.currentProgram = false;
}
if (isLoadMore) {
const newProgramList = new Set([...this.state.programList, ...programList]);
const newChannelList = new Set([...this.state.channelList, ...channelList]);
const setProgramList = Array.from(newProgramList);
const setChannelList = Array.from(newChannelList);
self.setState((prevState) => {
return {
...prevState,
programList: setProgramList,
channelList: setChannelList,
isLoadingMore: false,
};
});
} else {
self.setState((prevState) => {
return {
...prevState,
isLoadingShown: false,
programList,
channelList,
isLoadingMore: false,
};
});
}
});
}
};
handleDateChanged = (value) => { // change date
// Next or Prev
this.onDateChanged(getDate(value)); // return date
};
handleTimeChanged = (value) => { // change time
// Next or Prev
getTime(value, (res) => { // return status isChangeDate or isChangeTime
if (res && res.isChangeDate) {
this.onDateChanged(res.date);
} else if (res && res.isChangeTime) {
this.onTimeChanged(res.date);
}
});
};
onDateChanged = (date) => { // update date and get data
// Update currentDat
this.setState({
currentDate: date,
});
const { numberChannel } = this.state;
this.getData(date, numberChannel);
};
onTimeChanged = (date) => { // update time
// Update currentDate
this.setState({
currentDate: date,
});
};
handleProgramSelected = (detail) => { // change detail
this.setState({ programDetail: detail });
};
getTvGuideContainer = (element) => { // check height tv guide and set number channel the first load page
if (element) {
const heightEpg = element.clientHeight - 364; // 300 height detail
const heightEpgHeader = 40 + 20; // height 40, margin 20
const heightEpgLine = 79; // height 79, margin 12
const numberLine = Math.abs(Math.ceil((heightEpg - heightEpgHeader) / heightEpgLine)); // not margin
this.getChannel((channels) => {
const channelList = channels.slice(0, numberLine);
this.setState({ numberChannel: channelList, allChannelList: channels });
const { currentDate } = this.state;
this.getData(currentDate, channelList);
});
}
};
handleChannelClicked = (data) => {
console.log('handleChannelClicked', data);
};
handleFavoriteClicked = (data) => {
console.log('handleFavoriteClicked', data);
};
handleChannelCateogryChanged = (data) => {
console.log('handleChannelCateogryChanged', data);
this.setState({ selectedChannelCategoryText: data });
};
render() {
const { currentDate, programDetail, isLoadingShown, programList, channelList, isLoadingMore, categories, selectedChannelCategoryText, width } =
this.state;
{/* format date show preview */}
let progress_bar = null;
const progStartDate = programDetail && programDetail.startDate ? programDetail.startDate : null;
const progEndDate = programDetail && programDetail.endDate ? programDetail.endDate : null;
const now = new Date().getTime();
if (progStartDate && progEndDate) {
if (progStartDate <= now && progEndDate >= now) {
const percent = Math.floor(((now - progStartDate) * 100) / (progEndDate - progStartDate));
// Progress for curent program
progress_bar = (
<div className='tv-guide-preview-progress'>
<span>{`${new Date(progStartDate).getHours()}:${('0' + new Date(progStartDate).getMinutes()).slice(
-2
)}`}</span>
<div className='tv-guide-preview-progress-bar'>
<div className='tv-guide-preview-progress-current' style={{ width: `${percent}%` }} />
</div>
<span>{`${new Date(progEndDate).getHours()}:${('0' + new Date(progEndDate).getMinutes()).slice(-2)}`}</span>
</div>
);
}
}
{ /* End format date show preview */}
return (
<>
{ /* check show preview */}
{(!isMobile && isTablet) ||
(width > 768 && (
<div id='tv-guide-preview-wrapper'>
<div id='tv-guide-preview'>
<div className='tv-guide-preview-left'>
<img src={programDetail && programDetail.imageSrc} className='tv-guide-preview-left-img' />
</div>
<div className='tv-guide-preview-right'>
<div className='tv-guide-preview-title'>{programDetail && programDetail.name}</div>
{progress_bar}
<div className='tv-guide-preview-des'>{programDetail && programDetail.description}</div>
</div>
</div>
<div className='tv-guide-filter'>
<FilterComponent
value={selectedChannelCategoryText}
items={categories}
onChannelCateogryChanged={(data) => this.handleChannelCateogryChanged(data)}
mobileFilterLabelText='Filter'
desktopFilterLabelText='Channel'
favoriteText='Favorite'
prevIcon={ICON_ARROW_LEFT}
favouriteEnabled
/>
</div>
</div>
{ /* end show preview */}
))}
<TvGuide
catchupEnabled
npvrEnabled
favouriteEnabled
startoverEnabled
numberOfFutureDays={1}
numberOfPastDays={3}
currentDate={currentDate}
numberOfTimelineCellDisplayed={5}
todayText='Today'
noProgramInfoText='No Program Info'
loadingText='Loading'
// mobileTitle='Program guide'
isLoadingShown={isLoadingShown}
isLoadingMore={isLoadingMore}
channelList={channelList}
programList={programList}
tvGuideContainer={this.getTvGuideContainer}
onDateChanged={(value) => this.handleDateChanged(value)}
onTimeChanged={(value) => this.handleTimeChanged(value)}
onProgramSelected={(detail) => this.handleProgramSelected(detail)}
onChannelClicked={(data) => this.handleChannelClicked(data)}
onFavoriteClicked={(data) => this.handleFavoriteClicked(data)}
onChannelCateogryChanged={(data) => this.handleChannelCateogryChanged(data)}
tvGuideWrapperStyles={{ marginTop: 364, background: '#000' }}
// datePickerStyles={{ fontSize: 16 }}
// timeLineCellStyles={{ fontSize: 24, color: '#fff' }}
// channelStyles={{ number: { color: '#03143f' } }}
// programStyles={{
// selected: { background: '#EE3325', color: '#fff', boxShadow: '2px 2px 26px 19px rgba(255, 255, 255, 0.5)' },
// current: { fontSize: 24 },
// pass: { color: '#fff', fontSize: 24 },
// future: { color: '#fff' },
// noProgram: { color: 'rgba(255, 255, 255, 0.3)' },
// }}
// programHoverBorderStyles={{
// '--hover-border': '1px solid #EE3325',
// }}
// timeBarStyles={{
// timeBar: { background: '#EE3325' },
// timeBarHead: { background: '#EE3325' },
// timeBarIcon: { background: '#fff' },
// }}
tvGuideHeaderStyles={{ left: 10, margin: 0, padding: 0 }}
tvGuideBodyStyles={{ width: 'calc(100% - 20px', left: 10 }}
loadingIcon={ICON_LOADING}
nextIcon={ICON_ARROW_RIGHT}
prevIcon={ICON_ARROW_LEFT}
calendarIcon={ICON_CALENDAR}
purchaseIcon={ICON_PURCHASE}
recordIcon={ICON_RECORD}
recordedIcon={ICON_RECORDED}
favoriteActiveIcon={ICON_FAVORITE_ACTIVE}
favoriteIcon={ICON_FAVORITE}
catchupIcon={ICON_CATCHUP}
catchupInProgramIcon={ICON_CATCHUP_IN_PROGRAM}
scheduledRecordIcon={ICON_SCHEDULED_RECORD}
placeHolderChannelIcon={PLACE_HOLDER_CHANNEL}
startOverIcon={ICON_START_OVER}
categories={categories}
selectedChannelCategoryText={selectedChannelCategoryText}
mobileFilterLabelText='Filter'
desktopFilterLabelText='Channel'
favoriteText='Favorite'
/>
</>
);
}
}
WebEpgDemo.propTypes = {
};
WebEpgDemo.defaultProps = {};
export default WebEpgDemo;
Custom UI Component For Web:
-- By default, if these props are not present, the website will take the default color
You can re-adjust the color through the following props:
<TvGuide
...
tvGuideWrapperStyles={{ marginTop: 364, background: '#000' }}
datePickerStyles={{ fontSize: 16 }}
timeLineCellStyles={{ fontSize: 24, color: '#fff' }}
channelStyles={{ number: { color: '#03143f' } }}
programStyles={{
selected: { background: '#EE3325', color: '#fff', boxShadow: '2px 2px 26px 19px rgba(255, 255, 255, 0.5)' },
current: { fontSize: 24 },
pass: { color: '#fff', fontSize: 24 },
future: { color: '#fff' },
noProgram: { color: 'rgba(255, 255, 255, 0.3)' },
}}
programHoverBorderStyles={{
'--hover-border': '1px solid #EE3325',
}}
timeBarStyles={{
timeBar: { background: '#EE3325' },
timeBarHead: { background: '#EE3325' },
timeBarIcon: { background: '#fff' },
}}
/>
Change Icon In Component For Web:
-- By default, if these props are not present, the website will get the default icon
You can change the default icons back through the following props:
<TvGuide
...
loadingIcon={'path/image/...'}
nextIcon={'path/image/...'}
prevIcon={'path/image/...'}
calendarIcon={'path/image/...'}
purchaseIcon={'path/image/...'}
recordIcon={'path/image/...'}
recordedIcon={'path/image/...'}
favoriteActiveIcon={'path/image/...'}
favoriteIcon={'path/image/...'}
catchupIcon={'path/image/...'}
scheduledRecordIcon={'path/image/...'}
placeHolderChannelIcon={'path/image/...'}
/>