UNPKG

@livetv-app/tvguide

Version:

An Android TV Live Channels-like Electronic Programme Guide for React DOM and React Native applications.

446 lines (445 loc) 24.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.styles = void 0; const React = require("react"); const react_native_1 = require("react-native"); const util_1 = require("../util"); const constants_1 = require("../constants"); const context_1 = require("./context"); const types_1 = require("./types"); const channel_row_native_1 = require("./channel-row.native"); const header_native_1 = require("./header.native"); const programme_row_native_1 = require("./programme-row.native"); const debug_native_1 = require("./debug.native"); const channel_row_native_2 = require("./channel-row.native"); // @ts-ignore const DEBUG_SHOW = 'isTesting' in react_native_1.Platform && react_native_1.Platform.isTesting ? true : typeof process === 'object' && process.env.NODE_ENV === 'development' ? true : false; const DEBUG_DEFAULT_TIME = null; //new Date(2020, 11, 29, 11, 0); const DEBUG_DEFAULT_POSITION = null; class ProgrammeGuide extends React.Component { constructor(props) { var _a, _b, _c, _d; super(props); this.state = { channel: null, programme: null, time: DEBUG_DEFAULT_TIME !== null && DEBUG_DEFAULT_TIME !== void 0 ? DEBUG_DEFAULT_TIME : new Date(), leftPosition: DEBUG_DEFAULT_POSITION !== null && DEBUG_DEFAULT_POSITION !== void 0 ? DEBUG_DEFAULT_POSITION : new Date(Math.floor((Date.now() - constants_1.MIN_DEFAULT_POSITION) / constants_1.CELL_LENGTH) * constants_1.CELL_LENGTH), width: react_native_1.Dimensions.get('window').width, heightDebug: 0, heightHeader: 0, scrollTop: null, scrollBottom: null, internalScrollPosition: null, }; this.programmes_by_channel = {}; this.setSelectedChannel = (channel) => this.setState({ channel }); this.setSelectedProgramme = (programme) => this.setState({ programme }); this.setSelectedTime = (time) => this.setState({ time }); this.setLeftPosition = (leftPosition) => this.setState({ leftPosition }); this.setWidth = (width) => this.setState({ width }); this.selection = { c: this, get channel() { return this.c.state.channel; }, get programme() { return this.c.state.programme; }, get time() { return this.c.state.time; }, setSelectedChannel: channel => this.setState({ channel }), setSelectedProgramme: programme => this.setState({ programme }), setSelectedTime: time => this.setState({ time }), }; this._handleKeypressFromDebug = (key) => { switch (key) { case 'select': this.sendSelectKeypress(); break; case 'up': this.sendUpKeypress(); break; case 'down': this.sendDownKeypress(); break; case 'left': this.sendLeftKeypress(); break; case 'right': this.sendRightKeypress(); break; } }; const programmes = this.programmes_by_channel; for (const programme of this.props.programmes) { const p = (_a = programmes[programme.channel]) !== null && _a !== void 0 ? _a : (programmes[programme.channel] = []); p.push(programme); } this.state.channel = (_c = (_b = props.channel) !== null && _b !== void 0 ? _b : props.channels[0]) !== null && _c !== void 0 ? _c : null; if (this.state.channel) { const programmes = (_d = this.programmes_by_channel[this.state.channel.id]) !== null && _d !== void 0 ? _d : []; this.state.programme = util_1.getProgrammeAtTime(this.state.time, programmes); } const scrollPosition = this.calculateScrollPosition(); this.state.scrollTop = scrollPosition.scrollTop; this.state.scrollBottom = scrollPosition.scrollBottom; } get leftPosition() { return this.state.leftPosition; } componentDidUpdate(prevProps, prevState) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; if (this.props.programmes !== prevProps.programmes) { const programmes = {}; for (const programme of this.props.programmes) { const p = (_a = programmes[programme.channel]) !== null && _a !== void 0 ? _a : (programmes[programme.channel] = []); p.push(programme); } this.programmes_by_channel = programmes; } if (this.props.channel && this.props.channel !== prevProps.channel) { const time = DEBUG_DEFAULT_TIME !== null && DEBUG_DEFAULT_TIME !== void 0 ? DEBUG_DEFAULT_TIME : new Date(); const programmes = (_b = this.programmes_by_channel[this.props.channel.id]) !== null && _b !== void 0 ? _b : []; this.setState({ channel: this.props.channel, programme: util_1.getProgrammeAtTime(time, programmes), time, }); } else if (this.state.channel && !this.props.channels.includes(this.state.channel)) { const channel = (_c = this.props.channels[0]) !== null && _c !== void 0 ? _c : null; this.setState({ channel }); if (channel) { const programmes = (_d = this.programmes_by_channel[channel.id]) !== null && _d !== void 0 ? _d : []; this.setState({ programme: util_1.getProgrammeAtTime(this.state.time, programmes), }); } } if (this.props.channels !== prevProps.channels || this.props.scrollPosition !== prevProps.scrollPosition || ((_e = this.props.scrollPosition) === null || _e === void 0 ? void 0 : _e.contentOffset.y) !== ((_f = prevProps.scrollPosition) === null || _f === void 0 ? void 0 : _f.contentOffset.y) || ((_g = this.props.scrollPosition) === null || _g === void 0 ? void 0 : _g.layoutMeasurement.height) !== ((_h = prevProps.scrollPosition) === null || _h === void 0 ? void 0 : _h.layoutMeasurement.height) || this.props.scrollOffsetTop !== prevProps.scrollOffsetTop || ((_j = this.state.channel) === null || _j === void 0 ? void 0 : _j.id) !== ((_k = prevState.channel) === null || _k === void 0 ? void 0 : _k.id) || this.state.heightDebug !== prevState.heightDebug || this.state.heightHeader !== prevState.heightHeader) { this.setState(this.calculateScrollPosition()); } if (this.props.onScroll && (this.state.scrollTop !== prevState.scrollTop || this.state.scrollBottom !== prevState.scrollBottom || this.state.leftPosition !== prevState.leftPosition || this.state.width !== prevState.width)) { this.props.onScroll.call(null, { top: this.state.scrollTop, bottom: this.state.scrollBottom, left: this.state.leftPosition, right: new Date(this.state.leftPosition.getTime() + ((this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH)), }); } } setChannel(channel) { var _a; const programmes = (_a = this.programmes_by_channel[channel.id]) !== null && _a !== void 0 ? _a : []; this.setSelectedChannel(channel); this.setSelectedProgramme(util_1.getProgrammeAtTime(this.selection.time, programmes)); } setChannelUp() { if (!this.props.channels.length) return; this.setChannel(util_1.getPreviousChannel(this.selection.channel, this.props.channels)); } setChannelDown() { if (!this.props.channels.length) return; this.setChannel(util_1.getNextChannel(this.selection.channel, this.props.channels)); } setPreviousProgramme() { var _a; if (!this.selection.channel) return; const programmes = (_a = this.programmes_by_channel[this.selection.channel.id]) !== null && _a !== void 0 ? _a : []; const time = util_1.getPreviousProgrammeTime(this.selection.time, programmes); if (!time) return; this.setSelectedTime(time); const programme = util_1.getProgrammeAtTime(time, programmes); this.setSelectedProgramme(programme); } setNextProgramme() { var _a; if (!this.selection.channel) return; const programmes = (_a = this.programmes_by_channel[this.selection.channel.id]) !== null && _a !== void 0 ? _a : []; const time = util_1.getNextProgrammeTime(this.selection.time, programmes); if (!time) return; this.setSelectedTime(time); const programme = util_1.getProgrammeAtTime(time, programmes); this.setSelectedProgramme(programme); } sendSelectKeypress() { var _a, _b, _c; if (((_a = this.props.channel) === null || _a === void 0 ? void 0 : _a.id) !== ((_b = this.state.channel) === null || _b === void 0 ? void 0 : _b.id)) { if (this.state.channel) { (_c = this.props.onSelectChannel) === null || _c === void 0 ? void 0 : _c.call(null, this.state.channel); } return types_1.KeypressResult.OK; } else { return types_1.KeypressResult.IGNORED_AS_CHANNEL_ALREADY_SELECTED; } } sendLongSelectKeypress() { var _a, _b; if (!this.state.programme) return types_1.KeypressResult.IGNORED_AS_NO_PROGRAMME_SELECTED; if (!((_a = this.state.programme.actions) === null || _a === void 0 ? void 0 : _a.length)) return types_1.KeypressResult.IGNORED_AS_PROGRAMME_HAS_NO_ACTIONS; channel_row_native_1.showProgrammeActions(this.state.programme, (_b = this.props.language) !== null && _b !== void 0 ? _b : util_1.DEFAULT_LANGUAGE); return types_1.KeypressResult.OK; } sendUpKeypress(wrap = true) { const channel = util_1.getPreviousChannel(this.state.channel, this.props.channels, wrap); if (channel === null) { return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } this.setChannel(channel); // TODO: ensure the channel is visible return types_1.KeypressResult.OK; } sendDownKeypress(wrap = true) { const channel = util_1.getNextChannel(this.state.channel, this.props.channels, wrap); if (channel === null) { return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } this.setChannel(channel); // TODO: ensure the channel is visible return types_1.KeypressResult.OK; } setSelectedChannelAndProgramme(channel) { var _a; const programmes = (_a = this.programmes_by_channel[channel.id]) !== null && _a !== void 0 ? _a : []; const programme = util_1.getProgrammeAtTime(this.state.time, programmes); this.setState({ channel, programme }); } setSelectedTimeAndProgramme(time, scroll = true) { var _a; const length = (this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH; const rightPosition = new Date(this.state.leftPosition.getTime() + length); const leftPosition = !scroll ? this.state.leftPosition : this.state.leftPosition.getTime() >= (time.getTime() - constants_1.MIN_DEFAULT_POSITION) ? new Date(Math.floor(time.getTime() / constants_1.CELL_LENGTH) * constants_1.CELL_LENGTH) : rightPosition.getTime() <= (time.getTime() + constants_1.MIN_DEFAULT_POSITION) ? // new Date(((Math.floor(time.getTime() / CELL_LENGTH) * CELL_LENGTH) - length) + CELL_LENGTH) : new Date(Math.floor(((time.getTime() - length) + constants_1.CELL_LENGTH) / constants_1.CELL_LENGTH) * constants_1.CELL_LENGTH) : this.state.leftPosition; if (this.state.channel) { const programmes = (_a = this.programmes_by_channel[this.state.channel.id]) !== null && _a !== void 0 ? _a : []; const programme = util_1.getProgrammeAtTime(time, programmes); this.setState({ time, programme, leftPosition }); } else { this.setState({ time, programme: null, leftPosition }); } } scrollLeftRespectingBoundary(length) { const time = new Date(this.props.leftBoundary ? Math.max(this.props.leftBoundary.getTime(), this.state.time.getTime() - length) : this.state.time.getTime() - length); if (this.props.leftBoundary && this.props.leftBoundary.getTime() > time.getTime()) return false; this.setSelectedTimeAndProgramme(time); return true; } scrollRightRespectingBoundary(length) { const time = new Date(this.props.rightBoundary ? Math.min(this.props.rightBoundary.getTime(), this.state.time.getTime() + length) : this.state.time.getTime() + length); if (this.props.rightBoundary && this.props.rightBoundary.getTime() < time.getTime()) return false; this.setSelectedTimeAndProgramme(time); return true; } sendLeftKeypress() { var _a; const length = (this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH; const scroll_max = length * 0.8; if (!this.state.channel) { return this.scrollLeftRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK : types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } const time = util_1.getPreviousProgrammeTime(this.state.time, (_a = this.programmes_by_channel[this.state.channel.id]) !== null && _a !== void 0 ? _a : []); if (!time) { return this.scrollLeftRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK : types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } if (this.state.time.getTime() - time.getTime() > scroll_max) { return this.scrollLeftRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK : types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } if (this.props.leftBoundary && this.props.leftBoundary.getTime() > time.getTime()) { return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } this.setSelectedTimeAndProgramme(time); return types_1.KeypressResult.OK; } sendRightKeypress() { var _a; const length = (this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH; const scroll_max = length * 0.8; if (!this.state.channel) { return this.scrollRightRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK : types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } const time = util_1.getNextProgrammeTime(this.state.time, (_a = this.programmes_by_channel[this.state.channel.id]) !== null && _a !== void 0 ? _a : []); if (!time) { return this.scrollRightRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK : types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } if (time.getTime() - this.state.time.getTime() > scroll_max) { return this.scrollRightRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK : types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } if (this.props.rightBoundary && this.props.rightBoundary.getTime() < time.getTime()) { return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY; } this.setSelectedTimeAndProgramme(time); return types_1.KeypressResult.OK; } shouldRenderChannelRow(total, index, channel) { var _a, _b, _c; const scrollPosition = this.props.internalScrolling ? (_a = this.state.internalScrollPosition) !== null && _a !== void 0 ? _a : null : this.props.scrollPosition; if (((_b = this.state.channel) === null || _b === void 0 ? void 0 : _b.id) === channel.id) return true; if (scrollPosition === undefined) return true; if (scrollPosition === null) return false; const offset_top = this.props.internalScrolling ? 0 : ((_c = this.props.scrollOffsetTop) !== null && _c !== void 0 ? _c : 0) + (DEBUG_SHOW ? this.state.heightDebug : 0) + this.state.heightHeader; const position_top = offset_top + ((index * (constants_1.SCALE * 55)) - 1); const position_bottom = position_top + scrollPosition.layoutMeasurement.height; if (scrollPosition.contentOffset.y > position_bottom + (constants_1.SCALE * 50)) return false; if (scrollPosition.contentOffset.y + scrollPosition.layoutMeasurement.height < position_top - (constants_1.SCALE * 50)) return false; return true; } calculateRenderedRows() { var _a, _b, _c; const grouped = []; const scrollPosition = (_a = (this.props.internalScrolling ? this.state.internalScrollPosition : this.props.scrollPosition)) !== null && _a !== void 0 ? _a : { layoutMeasurement: { height: react_native_1.Dimensions.get('window').height } }; const activeindex = (_b = (this.props.channel && this.props.channels.findIndex(c => { var _a; return c.id === ((_a = this.props.channel) === null || _a === void 0 ? void 0 : _a.id); }))) !== null && _b !== void 0 ? _b : null; const selectedindex = (_c = this.props.channels.indexOf(this.state.channel)) !== null && _c !== void 0 ? _c : null; const maxvisiblerows = Math.ceil(scrollPosition.layoutMeasurement.height / (constants_1.SCALE * 55)); const firstrow = selectedindex - maxvisiblerows; const lastrow = selectedindex + maxvisiblerows; for (const i in this.props.channels) { const index = parseInt(i); const channel = this.props.channels[i]; if (index === activeindex || index === selectedindex || (firstrow < index && lastrow > index)) { grouped.push({ index, channel }); } else { const CHANNEL_ROW_HEIGHT = (grouped.length ? 1 : 0) + 54; // scaled in render const end = grouped[grouped.length - 1]; if (end && 'height' in end) { end.end = index; end.channels.push(channel); end.height = end.height + CHANNEL_ROW_HEIGHT; } else grouped.push({ start: index, end: index, channels: [channel], height: CHANNEL_ROW_HEIGHT, }); } } return grouped; } calculateScrollPosition() { var _a, _b; const groups = this.calculateRenderedRows(); const findFirstChannel = (c, i, arr) => 'channel' in c && (arr.length === i + 1 || (arr[i + 1] && 'height' in arr[i + 1])); const scrollTop = (_a = groups.find(findFirstChannel)) !== null && _a !== void 0 ? _a : null; const reversed = groups.reverse(); const scrollBottom = (_b = reversed.find(findFirstChannel)) !== null && _b !== void 0 ? _b : null; return { scrollTop, scrollBottom }; } render() { const { props, selection, setWidth } = this; const { channel, programme, leftPosition } = this.state; const grouped = this.calculateRenderedRows(); const channels = grouped.map((r, index) => { var _a, _b, _c; return 'height' in r ? React.createElement(react_native_1.View, { key: index, style: [ exports.styles.channelRowPlaceholder, index !== 0 ? exports.styles.channelRowPlaceholderNotFirstRow : null, index === grouped.length - 1 ? exports.styles.channelRowPlaceholderLastRow : null, { height: constants_1.SCALE * r.height }, ] }) : React.createElement(react_native_1.View, { key: r.channel.id, style: [ exports.styles.channelRowContainer, index === grouped.length - 1 ? exports.styles.channelRowContainerLastRow : null, ] }, React.createElement(channel_row_native_1.default, { channel: r.channel, programmes: (_a = this.programmes_by_channel[r.channel.id]) !== null && _a !== void 0 ? _a : [], index: r.index, isFirstRow: r.index === 0, isLastRow: r.index === props.channels.length - 1, active: r.channel.id === ((_b = this.props.channel) === null || _b === void 0 ? void 0 : _b.id), selected: r.channel.id === (channel === null || channel === void 0 ? void 0 : channel.id), isPreviousRowSelected: ((_c = props.channels[r.index - 1]) === null || _c === void 0 ? void 0 : _c.id) === (channel === null || channel === void 0 ? void 0 : channel.id), selectedProgramme: r.channel.id === (channel === null || channel === void 0 ? void 0 : channel.id) ? programme : null, programmesRowWidth: this.state.width, showChannelLogos: this.props.showChannelLogos }), r.channel.id === (channel === null || channel === void 0 ? void 0 : channel.id) ? programme ? React.createElement(programme_row_native_1.default, { channel: channel, programme: programme }) : React.createElement(programme_row_native_1.EmptyProgrammeRow, null) : null); }); return React.createElement(react_native_1.View, { style: [exports.styles.container, this.props.style] }, DEBUG_SHOW ? this.renderDebugView() : null, React.createElement(context_1.SelectionContext.Provider, { value: selection }, React.createElement(context_1.PositionContext.Provider, { value: leftPosition }, React.createElement(react_native_1.View, { style: [channel_row_native_2.styles.row, exports.styles.header], onLayout: e => this.setState({ heightHeader: e.nativeEvent.layout.height }) }, React.createElement(header_native_1.TimeCell, { start: leftPosition }), React.createElement(header_native_1.TimeList, { start: leftPosition, onResize: width => setWidth(width) })), this.props.internalScrolling ? React.createElement(react_native_1.ScrollView, { onScroll: e => this.setState({ internalScrollPosition: e.nativeEvent }), scrollEventThrottle: 20 }, channels, React.createElement(react_native_1.View, { style: exports.styles.expandScroller })) : channels))); } renderDebugView() { return React.createElement(debug_native_1.default, { channels: this.props.channels, programmes: this.props.programmes, selection: this.selection, setSelectedChannel: this.setSelectedChannel, setSelectedProgramme: this.setSelectedProgramme, setSelectedTime: this.setSelectedTime, leftPosition: this.state.leftPosition, setLeftPosition: this.setLeftPosition, width: this.state.width, scrollTop: this.state.scrollTop, scrollBottom: this.state.scrollBottom, onLayout: e => this.setState({ heightDebug: e.height }), onKeypress: this._handleKeypressFromDebug }); } } exports.default = ProgrammeGuide; exports.styles = react_native_1.StyleSheet.create({ container: { flex: 0, maxHeight: '100%', padding: constants_1.SCALE * 30, paddingRight: 0, paddingBottom: 0, borderRadius: constants_1.SCALE * 2, shadowColor: '#263238', shadowOpacity: 0.8, shadowRadius: 10, }, header: { backgroundColor: '#607d8bee', borderTopLeftRadius: constants_1.SCALE * 2, overflow: 'visible', }, channelRowContainer: { overflow: 'hidden', }, channelRowContainerLastRow: { marginBottom: 30, }, channelRowPlaceholder: { backgroundColor: '#37474fdd', }, channelRowPlaceholderNotFirstRow: { borderTopColor: '#263238dd', borderTopWidth: 1, }, channelRowPlaceholderLastRow: { borderBottomLeftRadius: constants_1.SCALE * 2, }, expandScroller: { position: 'absolute', height: '100%', top: 1, }, });