UNPKG

principles-ui-components

Version:

Supporting UI controller for Tizen TV web application, which developed base on React Framework.

462 lines (394 loc) 16.9 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { is, Map, fromJS } from 'immutable'; import { KEY, VOICE_GUIDE_DELAY, AnimationEffect, TTSTYPE } from './common/CommonDefine'; import ScrollBar from './ScrollBar'; import ImageItem from './ImageItem'; import ScrollText from './common/ScrollText'; import TTS from './common/TTS'; const SCROLL_BAR_WIDTH = 3; const SCROLL_BAR_TYPE = 'v'; const Offset = { NONE: 0, DOWN: 1, UP: 2, }; export default class VMultiLineList extends Component { constructor(props) { super(props); this.state = { focusedIndex: 0, // index of focused item, begin with 0 scrollTranslate: 0, // translate Y for the grid scrollPercent: 0, // the scroll percent for scroll bar enter: false, // do enter animation or not hasFocus: false, }; this.enterCB = this.enterCB.bind(this); this.focusedIdx = { index: 0, row: 0, column: 0 }; // the focused item's index and coordinates(i, j) this.scrollTop = 0; // scrollTop for translate Y this.focusChangeObject = { voiceGuideText: 'focus changed' }; this.offsetAction = Offset.NONE; this.dataOffset = 0; } componentWillMount() { // when this component getting new props. Even though you call here, setState, Render is not triggered !! this.setInfo(this.props); } componentDidMount() { } componentWillReceiveProps(nextProps) { this.setInfo(nextProps); } shouldComponentUpdate(nextProps, nextState) { return (JSON.stringify(nextProps) !== JSON.stringify(this.props)) || (JSON.stringify(nextState) !== JSON.stringify(this.state)); } componentDidUpdate(prevProps, prevState) { // console.error(`componentDidUpdate ${this.listnode.offsetHeight}`); } componentWillUnmount() { } setInfo(props) { this.itemW = props.OSD[0].layout.w; // list item width this.itemH = props.OSD[0].layout.h; // list item height this.displayRow = Math.ceil( (props.layout.h - props.list.t) / (this.itemH + props.list.vSpacing)); this.listLength = props.OSD.length; // list data length this.listRow = Math.ceil(this.listLength / props.countPerRow); // list row number this.listH = (props.list.t * 2) + (this.itemH * this.listRow) + ((this.listRow - 1) * props.list.vSpacing); // list area height with top and bottom gap this.listW = (props.list.l * 2) + (this.itemW * props.countPerRow) + ((props.countPerRow - 1) * props.list.hSpacing); // list area width with left and right gap this.sequence = []; const dataBuffer = (props.buffer * 2) + this.displayRow; for (let idx = 0; idx < dataBuffer; idx++) { this.sequence.push(idx); } } /* * function: getDyadicArray * array: [A,B,..,C,D,..] * part: count per row * return: [A,B,..][C,D,..] */ getDyadicArray(array, part, offset, buffer) { const dyadicArray = []; for (let i = offset * part; i < array.length && i < (offset + buffer) * part; i += part) { dyadicArray.push(array.slice(i, i + part)); } // console.log(`[GridList.js::getDyadicArray()]array.length:${array.length}, dyadicArray:${dyadicArray.length}`); return dyadicArray; } getElementPos(elem) { let x = 0; let y = 0; let mElem = elem; while (mElem != null) { x += mElem.offsetLeft; y += mElem.offsetTop; mElem = mElem.offsetParent; } return { x, y }; } /* *function: setScrollTop *description: calculate scrolltop value */ setScrollTop() { const { layout, list } = this.props; const focusItemY = list.t + (this.focusedIdx.row * (list.vSpacing + this.itemH)); if (focusItemY + this.itemH + this.scrollTop > layout.h - list.t) { this.scrollTop = (-focusItemY) + (layout.h - this.itemH - list.t); } else if (focusItemY + this.scrollTop < list.t) { this.scrollTop = (-focusItemY) + list.t; } } setDataOffset() { const { layout, list, buffer } = this.props; if (this.listRow - ((buffer * 2) + this.displayRow) <= 0) { this.dataOffset = 0; this.offsetAction = Offset.NONE; console.log(`data length [${this.listRow}] short. buffer[${buffer}],displayRow[${this.displayRow}]`); return; } const focusItemY = list.t + (this.focusedIdx.row * (list.vSpacing + this.itemH)); if (focusItemY + this.scrollTop > layout.h) { this.offsetAction = Offset.DOWN; this.dataOffset = (this.focusedIdx.row + 1) - (buffer + this.displayRow); // console.error(`offsetAction ${this.offsetAction}, dataOffset ${this.dataOffset}`); if (this.dataOffset === 0) { // start, '==', bodrder, reset offsetaction this.offsetAction = Offset.NONE; } } else if (focusItemY + this.itemH + this.scrollTop < 0) { this.offsetAction = Offset.UP; this.dataOffset = this.focusedIdx.row - buffer; // console.error(`offsetAction ${this.offsetAction}, dataOffset ${this.dataOffset}`); if (this.dataOffset === this.listRow - ((buffer * 2) + this.displayRow)) { this.offsetAction = Offset.NONE; } } else { this.offsetAction = Offset.NONE; return; } if (this.dataOffset < 0) { // start this.dataOffset = 0; this.offsetAction = Offset.NONE; } else if (this.dataOffset > this.listRow - ((buffer * 2) + this.displayRow)) { // end, '==', bodrder, reset offsetaction this.dataOffset = this.listRow - ((buffer * 2) + this.displayRow); this.offsetAction = Offset.NONE; } if (this.offsetAction === Offset.DOWN) { this.sequence = [this.sequence[this.sequence.length - 1], ...this.sequence.slice(0, this.sequence.length - 1)]; } else if (this.offsetAction === Offset.UP) { this.sequence = [...this.sequence.slice(1, this.sequence.length), this.sequence[0]]; } // console.log(`offsetAction ${this.offsetAction}, dataOffset ${this.dataOffset}`); } playTTS(type) { if (this.props.ttsEnable) { console.log(`VMultiline list play TTS, type[${type}], index[${this.focusedIdx.index}]`); if (type === TTSTYPE.LIST) { this.TTSnode.playTTS(); } else if (type === TTSTYPE.ITEM) { if (this[`item${this.focusedIdx.index}`]) { this[`item${this.focusedIdx.index}`].playTTS(); } } } } keyFocusIn() { if (this.state.hasFocus === true) { console.log('list had focus, do nothing!'); return; } this.playTTS(TTSTYPE.LIST); if (this.props.onGridFocus && typeof this.props.onGridFocus === 'function') { this.props.onGridFocus(); } this.setState({ hasFocus: true, enter: false }); } keyFocusOut() { if (this.state.hasFocus === false) { console.log('list had lose focus, do nothing!'); return; } this.setState({ hasFocus: false, enter: false }); } handleKey(event) { if (this.state.hasFocus === false) { console.log('list have no focus, do nothing!'); return false; } const preFocus = this.focusedIdx.index; const preRow = this.focusedIdx.row; const { countPerRow } = this.props; let isEnter = false; switch (event.keyCode) { case KEY.LEFT: if (this.focusedIdx.column === 0) { // block } else { this.focusedIdx.index -= 1; this.focusedIdx.column -= 1; } break; case KEY.RIGHT: if (this.focusedIdx.column === countPerRow - 1) { // block } else if (this.focusedIdx.index === this.listLength - 1) { if (this.focusedIdx.row === 0) { break; } this.focusedIdx.row -= 1; this.focusedIdx.column = countPerRow - 1; this.focusedIdx.index = (this.focusedIdx.row * countPerRow) + this.focusedIdx.column; } else { this.focusedIdx.index += 1; this.focusedIdx.column += 1; } break; case KEY.UP: if (this.focusedIdx.row === 0) { // block } else { this.focusedIdx.row -= 1; this.focusedIdx.index -= countPerRow; } break; case KEY.DOWN: if (this.focusedIdx.row === (this.listRow - 1)) { // block } else { this.focusedIdx.row += 1; if ((this.focusedIdx.index + countPerRow) > (this.listLength - 1)) { this.focusedIdx.index = this.listLength - 1; this.focusedIdx.column = (this.listLength % countPerRow) - 1; } else { this.focusedIdx.index += countPerRow; } } break; case KEY.ENTER: isEnter = true; this.setState({ enter: isEnter }); break; default: return false; // false: pass event to base component, true: not pass // break; } // console.log(`[GridList.js::handleKey()] ${event.keyCode} ${JSON.stringify(this.focusedIdx)}`); if (this.focusedIdx.index !== preFocus) { this.playTTS(TTSTYPE.ITEM); if (this.props.onFocusChanged && typeof this.props.onFocusChanged === 'function') { this.focusChangeObject = this.props.onFocusChanged(preFocus, this.focusedIdx.index); } if (this.focusedIdx.row !== preRow) { this.setDataOffset(); this.setScrollTop(); } this.setState({ focusedIndex: this.focusedIdx.index, scrollTranslate: this.scrollTop, scrollPercent: (-this.scrollTop) / (this.listH - this.props.layout.h), }); } return true; } enterCB() { if (this.props.onItemPress && typeof this.props.onItemPress === 'function') { this.props.onItemPress(this.focusedIdx.index); } this.setState({ enter: false }); } render() { const { OSD, layout, list, scrollbar, scrollBarEnable, countPerRow, enlarge, highContrast, ttsEnable, ttsText, buffer } = this.props; const layoutStyle = { position: 'absolute', left: layout.l, top: layout.t, width: layout.w, height: layout.h, overflow: 'hidden', }; const listStyle = { position: 'absolute', left: list.l - (list.hSpacing / 2), top: list.t - (list.vSpacing / 2), width: this.listW, height: this.listH, transform: `translateY(${this.state.scrollTranslate}px) translateZ(100px)`, transition: 'all 0.3s', }; let scrollbarComponent = null; if (scrollBarEnable && (this.listH > layout.h)) { scrollbarComponent = (<ScrollBar left={scrollbar.l} top={scrollbar.t} barType={SCROLL_BAR_TYPE} bgWidth={SCROLL_BAR_WIDTH} bgHeight={layout.h - (scrollbar.t * 2)} barPercent={layout.h / this.listH} scrollPercent={this.state.scrollPercent} highContarstValue={highContrast} />); } let TTSComponent = null; if (ttsEnable) { TTSComponent = <TTS ref={(TTSnode) => { this.TTSnode = TTSnode; }} ttsEnable={ttsEnable} ttsText={ttsText} />; } const dyadicArray = this.getDyadicArray(OSD, countPerRow, this.dataOffset, (buffer * 2) + this.displayRow); const rowLength = dyadicArray.length; const listComponent = []; for (let idxR = 0; idxR < rowLength; idxR++) { const rowComponent = []; const columnLength = dyadicArray[this.sequence[idxR]].length; for (let idxC = 0; idxC < columnLength; idxC++) { const itemStyle = { position: 'absolute', padding: `${list.vSpacing / 2}px ${list.hSpacing / 2}px`, width: this.itemW + list.hSpacing, height: this.itemH + list.vSpacing, left: idxC * (this.itemW + list.hSpacing), top: (this.sequence[idxR] + this.dataOffset) * (this.itemH + list.vSpacing), }; // console.error(`${this.sequence[idxR] + this.dataOffset}`); const itemIdx = ((this.sequence[idxR] + this.dataOffset) * countPerRow) + idxC; const focus = (this.state.hasFocus && itemIdx === this.state.focusedIndex); rowComponent.push(<div key={idxC} style={itemStyle}> <ImageItem OSD={dyadicArray[this.sequence[idxR]][idxC]} ref={(node) => { this[`item${itemIdx}`] = node; }} focus={focus} enterCB={this.enterCB} enter={this.state.enter && focus} enlarge={enlarge} highContrast={highContrast} ttsEnable={ttsEnable} /> </div>); } listComponent.push(<div key={idxR} style={{}}>{rowComponent}</div>); } return (<div style={layoutStyle}> {scrollbarComponent} <div style={listStyle} ref={(listnode) => { this.listnode = listnode; }}> {listComponent} </div> {TTSComponent} </div>); } } VMultiLineList.defaultProps = { scrollBarEnable: true, enlarge: false, highContrast: false, ttsEnable: false, ttsText: '', buffer: 1, list: { l: 80, t: 100, hSpacing: 80, // left and right side vSpacing: 145, // spacing between rows }, title: undefined, scrollbar: { l: 30, t: 1, }, onItemPress: undefined, // function (wz, itemData, data) // onEnterKeyLongPressed:undefined // function (wzFocused, itemData, type) onGridFocus: undefined, // function (wzFocused,itemIndex,groupIndex) // onGridBlur:undefined // function (wzFocused) onFocusChanged: undefined, // function (fromItemIndex, toItemIndex) }; VMultiLineList.propTypes = { OSD: PropTypes.array.isRequired, // array for imageitem osd,, element type checkeing at image item component countPerRow: PropTypes.number.isRequired, buffer: PropTypes.number, scrollBarEnable: PropTypes.bool, enlarge: PropTypes.bool, highContrast: PropTypes.bool, ttsEnable: PropTypes.bool, ttsText: PropTypes.string, layout: PropTypes.shape({ l: PropTypes.number.isRequired, t: PropTypes.number.isRequired, w: PropTypes.number.isRequired, h: PropTypes.number.isRequired, }).isRequired, list: PropTypes.shape({ l: PropTypes.number, t: PropTypes.number, hSpacing: PropTypes.number, vSpacing: PropTypes.number, }), scrollbar: PropTypes.shape({ l: PropTypes.number, t: PropTypes.number, }), onItemPress: PropTypes.func, // onEnterKeyLongPressed: PropTypes.func, onGridFocus: PropTypes.func, // onGridBlur: PropTypes.func, onFocusChanged: PropTypes.func, };