principles-ui-components
Version:
Supporting UI controller for Tizen TV web application, which developed base on React Framework.
376 lines (341 loc) • 13.1 kB
JavaScript
/**
* @author Haipeng Zhang(hp.zhang@samsung.com)
* @fileoverview This module manages text item.
* @date 2017/06/09 (last modified date)
*
* Copyright 2017 by Samsung Electronics, Inc.,
*
* This software is the confidential and proprietary information
* of Samsung Electronics, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Samsung.
*/
import React, { Component, PropTypes } from 'react';
import Immutable from 'immutable';
import './css/TextItem.css';
import CommonAPI from './common/CommonAPI';
import ScrollText from './common/ScrollText';
import { MAIN_TEXT_FONT, SUB_TEXT_FONT, TextItemFamily, AnimationEffect } from './common/CommonDefine';
export default class TextItem extends Component {
constructor(props) {
super(props);
// title & body & caption
this.mainTextFont = MAIN_TEXT_FONT;
this.subTextFont = SUB_TEXT_FONT;
this.titleGap = TextItemFamily.TITLE_TEXT_GAP;
this.bodyGap = TextItemFamily.BODY_TEXT_GAP;
this.captionGap = TextItemFamily.CAPTION_TEXT_GAP;
this.titleLength = 0;
this.bodyLength = 0;
this.captionLength = 0;
// bind function
this.setTextGapProps = this.setTextGapProps.bind(this);
this.getShadowComponent = this.getShadowComponent.bind(this);
this.getContentLabelComponent = this.getContentLabelComponent.bind(this);
this.setTextGapProps(props);
}
componentDidMount() {
console.log('TextItem componentDidMount');
}
componentWillReceiveProps(nextProps) {
if (this.props.enlarge !== nextProps.enlarge ||
this.props.data.title !== nextProps.data.title ||
this.props.data.body !== nextProps.data.body ||
this.props.data.caption !== nextProps.data.caption) {
this.setTextGapProps(nextProps);
}
}
shouldComponentUpdate(nextProps, nextState) {
const needUpdateProps = !Immutable.is(nextProps, this.props);
const needUpdateState = !Immutable.is(nextState, this.state);
const needUpdate = needUpdateProps || needUpdateState;
console.log(`TextItem shouldComponentUpdate ${needUpdate}`);
return needUpdate;
}
componentDidUpdate(prevProps, prevState) {
}
componentWillUnmount() {
}
/**
* set text length and gap of item
* @name setTextGapProps
* @method
* @memberof item
* @param {props} react props
*/
setTextGapProps(props) {
// Set Size
this.titleTextSize = TextItemFamily.TITLE2_TEXT_SIZE;
this.bodyTextSize = TextItemFamily.BODY2_TEXT_SIZE;
this.captionTextSize = TextItemFamily.CAPTION_TEXT_SIZE;
if (props.enlarge) {
this.titleTextSize = Math.ceil(this.titleTextSize * 1.2);
this.bodyTextSize = Math.ceil(this.bodyTextSize * 1.2);
this.captionTextSize = Math.ceil(this.captionTextSize * 1.2);
}
// Set Length
this.titleLength = CommonAPI.getTextWidth(props.data.title, this.titleTextSize, this.mainTextFont);
let maxLength = this.titleLength;
if (props.data.body) {
if (props.data.caption) {
this.bodyLength = CommonAPI.getTextWidth(props.data.body, this.bodyTextSize, this.mainTextFont);
if (maxLength < this.bodyLength) {
maxLength = this.bodyLength;
}
this.captionLength = CommonAPI.getTextWidth(props.data.caption, this.captionTextSize, this.subTextFont);
if (maxLength < this.captionLength) {
maxLength = this.captionLength;
}
} else {
this.bodyLength = CommonAPI.getTextWidth(props.data.body, this.bodyTextSize, this.subTextFont);
if (maxLength < this.bodyLength) {
maxLength = this.bodyLength;
}
}
}
// Set Gap
this.titleGap = (maxLength + this.titleGap) - this.titleLength;
if (props.data.body) {
if (props.data.caption) {
this.bodyGap = (maxLength + this.bodyGap) - this.bodyLength;
this.captionGap = (maxLength + this.captionGap) - this.captionLength;
} else {
this.bodyGap = (maxLength + this.bodyGap) - this.bodyLength;
}
}
}
/**
* get Shadow Component
* @name getShadowComponent
* @method
* @memberof
*/
getShadowComponent() {
const shadowClass = this.props.highContrast ? 'focus-shadow-hc' : 'focus-shadow';
return (<div className={shadowClass} />);
}
/**
* get Content Label Component
* @name getContentLabelComponent
* @method
* @memberof
*/
getContentLabelComponent() {
const { data, OSD, focus, highContrast, enlarge } = this.props;
// 1. contentLabelStyle
const contentLabelStyle = {
position: 'absolute',
top: 0,
left: 0,
height: this.props.OSD.textSize.h,
width: this.props.OSD.textSize.w,
backgroundColor: focus ? 'hsla(0,0%,100%,1)' : 'hsla(0,0%,100%,0.5)',
};
// 2. icon
let iconComponent = null;
const iconBGStyle = {
marginTop: (OSD.textSize.h - OSD.iconSize.h - 2) / 2, // BG image pos +2
width: (OSD.iconSize.w + 2),
height: (OSD.iconSize.h + 2),
};
const iconStyle = {
width: OSD.iconSize.w,
height: OSD.iconSize.h,
};
if (data.iconUrl) {
iconComponent = (<div className='icon-bg' style={iconBGStyle}>
<img className='icon-image' style={iconStyle} src={data.iconUrl} />
</div>);
}
// 3. title & sub title
// In a High Contrast || enlarge mode, do not support sliding text feature. Even if the item is focused, keep the ellipsis() mark continuously.
const marqueeFlag = focus && !highContrast && !enlarge;
let left = 20 + iconBGStyle.width + 30;
let titleStyle = {};
let marqueeW = OSD.textSize.w - left - 20;
let top = (OSD.textSize.h - 48 - 1 - 40) / 2;
if (!iconComponent) {
left = 20;
marqueeW = OSD.textSize.w - 40;
}
if (!data.body) {
top = (OSD.textSize.h - 48) / 2;
}
if (data.caption) {
top = (OSD.textSize.h - 48 - 1 - 34 - 28) / 2;
}
titleStyle = {
left,
width: marqueeW,
top,
};
// 3.1 title
let titleComponent = null;
if (data.title) {
let titleClassName = 'title2';
if (focus) {
titleClassName = 'title2 focus-title2';
if (enlarge) {
titleClassName = 'title2 focus-title2-enlarge';
}
}
titleComponent = (<div className={titleClassName} style={titleStyle}>
<ScrollText
scroll={marqueeFlag}
fontSize={this.titleTextSize}
fontFamily={this.mainTextFont}
textGap={this.titleGap}
width={marqueeW}
height={TextItemFamily.TITLE_TEXT_HEIGHT}
>
{data.title}
</ScrollText>
</div>);
}
// 3.2 body
let bodyComponent = null;
if (data.body) {
let bodyStyle = {};
top = top + 48 + 1;
if (data.caption) {
top -= 1;
}
bodyStyle = {
left,
top,
width: marqueeW,
};
let bodyClassName = data.caption ? 'body3' : 'body2';
if (focus) {
bodyClassName = data.caption ? 'body3 focus-body3' : 'body2 focus-body2';
if (enlarge) {
bodyClassName = data.caption ? 'body3 focus-body3-enlarge' : 'body2 focus-body2-enlarge';
}
}
const textFont = data.caption ? this.mainTextFont : this.subTextFont;
const textHeight = data.caption ? TextItemFamily.BODY3_TEXT_HEIGHT : TextItemFamily.BODY2_TEXT_HEIGHT;
const textLineHeight = data.caption ? TextItemFamily.BODY3_TEXT_LINEHEIGHT : TextItemFamily.BODY2_TEXT_LINEHEIGHT;
bodyComponent = (<div className={bodyClassName} style={bodyStyle}>
<ScrollText
scroll={marqueeFlag}
fontSize={this.bodyTextSize}
fontFamily={textFont}
textGap={this.bodyGap}
width={marqueeW}
height={textHeight}
lineHeight={textLineHeight}
>
{data.body}
</ScrollText>
</div>);
}
// 3.3 caption
let captionComponent = null;
if (data.caption) {
let captionStyle = {};
captionStyle = {
left,
width: marqueeW,
top: top + 34 + 1,
};
let captionClassName = 'caption';
if (focus) {
captionClassName = 'caption focus-caption';
if (enlarge) {
captionClassName = 'caption focus-caption-enlarge';
}
}
captionComponent = (<div className={captionClassName} style={captionStyle}>
<ScrollText
scroll={marqueeFlag}
fontSize={this.captionTextSize}
fontFamily={this.subTextFont}
textGap={this.captionGap}
width={marqueeW}
height={TextItemFamily.CAPTION_TEXT_HEIGHT}
lineHeight={TextItemFamily.CAPTION_TEXT_LINEHEIGHT}
>
{data.caption}
</ScrollText>
</div>);
}
return (
<div style={contentLabelStyle}>
{iconComponent}
{titleComponent}
{bodyComponent}
{captionComponent}
</div>
);
}
render() {
const { enter, focus, OSD } = this.props;
let shadowComponent = null;
if (focus) {
shadowComponent = this.getShadowComponent();
}
const contentLabelComponent = this.getContentLabelComponent();
const tileContainerStyle = {
zIndex: focus ? 5 : 1,
transform: `scale(${focus ? (1.08) : 1.0}) translateZ(10px)`,
transition: focus ? `all 0.6s ${AnimationEffect.Elastic}` : `all 0.85s ${AnimationEffect.Out}`,
width: OSD.textSize.w,
height: OSD.textSize.h,
};
const borderStyle = {
width: OSD.textSize.w,
height: OSD.textSize.h,
borderRadius: '3px',
overflow: 'hidden',
};
const domRef = (node) => {
this.node = node;
};
return (
<div ref={domRef} className='tileContainer' style={tileContainerStyle} tabIndex='-1' >
<div style={borderStyle}>
{shadowComponent}
{contentLabelComponent}
</div>
</div>
);
}
}
TextItem.defaultProps = {
focus: false,
enlarge: false,
highContrast: false,
data: null,
OSD: {
textSize: {
w: 285,
h: 126,
},
iconSize: {
w: 66,
h: 66,
},
},
};
TextItem.propTypes = {
focus: PropTypes.bool.isRequired, // The item is focus or not
enlarge: PropTypes.bool, // enlarge title and SubTitle font size flag
highContrast: PropTypes.bool, // Background image, false use r_highlight_bg_focus_9patch.png ,true use c_imageitem_highlight_bg_focus_9patch.png
data: PropTypes.shape({ // UI content
iconUrl: PropTypes.string, // Text icon
title: PropTypes.string, // Text title
body: PropTypes.string, // Text body
caption: PropTypes.string, // Text caption
}),
OSD: PropTypes.shape({ // UI content
textSize: PropTypes.shape({
w: PropTypes.number.isRequired,
h: PropTypes.number.isRequired,
}),
iconSize: PropTypes.shape({
w: PropTypes.number.isRequired,
h: PropTypes.number.isRequired,
}),
}),
};