[251006] feat: views - PlayerPanel.jsx, BelowTabContainer.jsx, BelowTa...
🕐 커밋 시간: 2025. 10. 06. 16:40:08 📊 변경 통계: • 총 파일: 6개 • 추가: +51줄 • 삭제: -109줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ChannelCard.jsx + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/LiveChannelContents.jsx + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/LiveChannelContents.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.module.less 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.jsx (javascript): ❌ Deleted: handleChannelClick() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/LiveChannelContents.module.less (unknown): ✅ Added: child() Performance: 코드 최적화로 성능 개선 기대
This commit is contained in:
@@ -2059,39 +2059,16 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
{showBelowContents && (
|
||||
<BelowTabContainer
|
||||
channelList={[
|
||||
{
|
||||
channelName: 'QVC',
|
||||
programName: 'JAI Jewelry',
|
||||
logoSrc: 'https://via.placeholder.com/40',
|
||||
thumbnailSrc: 'https://via.placeholder.com/212x119',
|
||||
progressPercent: 65,
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
{
|
||||
channelName: 'ShopLC',
|
||||
programName: 'JAI Jewelry',
|
||||
logoSrc: 'https://via.placeholder.com/40',
|
||||
thumbnailSrc: 'https://via.placeholder.com/212x119',
|
||||
progressPercent: 65,
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
{
|
||||
channelName: 'HSN',
|
||||
programName: 'JAI Jewelry',
|
||||
logoSrc: 'https://via.placeholder.com/40',
|
||||
thumbnailSrc: 'https://via.placeholder.com/212x119',
|
||||
progressPercent: 65,
|
||||
backgroundColor: '#1E77BC',
|
||||
},
|
||||
{
|
||||
channelName: 'QVC',
|
||||
programName: 'JAI Jewelry',
|
||||
logoSrc: 'https://via.placeholder.com/40',
|
||||
thumbnailSrc: 'https://via.placeholder.com/212x119',
|
||||
progressPercent: 65,
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
panelInfo={panelInfo}
|
||||
liveInfos={playListInfo}
|
||||
currentTime={currentTime}
|
||||
setSelectedIndex={setSelectedIndex}
|
||||
videoVerticalVisible={videoVerticalVisible}
|
||||
currentVideoShowId={playListInfo && playListInfo[selectedIndex]?.showId}
|
||||
handleItemFocus={handleItemFocus}
|
||||
tabTitle={[
|
||||
$L('SHOP NOW'),
|
||||
panelInfo?.shptmBanrTpNm === 'LIVE' ? $L('LIVE CHANNEL') : $L('FEATURED SHOWS'),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,65 +4,23 @@ import classNames from 'classnames';
|
||||
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||
import css from './BelowTabContainer.module.less';
|
||||
import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
|
||||
import LiveChannelContents from './LiveChannelContents';
|
||||
|
||||
const SpottableDiv = Spottable('div');
|
||||
|
||||
const ChannelCard = ({
|
||||
channelName,
|
||||
programName,
|
||||
logoSrc,
|
||||
thumbnailSrc,
|
||||
progressPercent = 65,
|
||||
backgroundColor = '#FFFFFF',
|
||||
onFocus,
|
||||
onBlur,
|
||||
onClick,
|
||||
spotlightId,
|
||||
}) => {
|
||||
return (
|
||||
<SpottableDiv
|
||||
className={css.channelCard}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onClick={onClick}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<div className={css.thumbnail}>
|
||||
<CustomImage src={thumbnailSrc} alt={channelName} />
|
||||
</div>
|
||||
<div className={css.cardContent}>
|
||||
<div className={css.channelHeader}>
|
||||
<div className={css.logoWrapper} style={{ background: backgroundColor }}>
|
||||
<CustomImage src={logoSrc} alt={`${channelName} logo`} className={css.logo} />
|
||||
</div>
|
||||
<h3 className={css.channelName}>{channelName}</h3>
|
||||
</div>
|
||||
<div className={css.programName}>{programName}</div>
|
||||
<div className={css.progressBarWrapper}>
|
||||
<div className={css.progressBar}>
|
||||
<div className={css.progressTrack} />
|
||||
<div className={css.progressFill} style={{ width: `${progressPercent}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
);
|
||||
};
|
||||
|
||||
export default function BelowTabContainer({
|
||||
channelList = [],
|
||||
onChannelClick,
|
||||
liveInfos,
|
||||
currentTime,
|
||||
setSelectedIndex,
|
||||
videoVerticalVisible,
|
||||
currentVideoShowId,
|
||||
handleItemFocus,
|
||||
tabTitle,
|
||||
panelInfo,
|
||||
onLiveChannelButtonClick,
|
||||
}) {
|
||||
const handleChannelClick = (channel, index) => () => {
|
||||
if (onChannelClick) {
|
||||
onChannelClick(channel, index);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={css.container}>
|
||||
<SpottableDiv
|
||||
@@ -76,21 +34,16 @@ export default function BelowTabContainer({
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
|
||||
<div className={css.channelList}>
|
||||
{channelList.map((channel, index) => (
|
||||
<ChannelCard
|
||||
key={`channel-${index}`}
|
||||
channelName={channel.channelName}
|
||||
programName={channel.programName}
|
||||
logoSrc={channel.logoSrc}
|
||||
thumbnailSrc={channel.thumbnailSrc}
|
||||
progressPercent={channel.progressPercent}
|
||||
backgroundColor={channel.backgroundColor}
|
||||
onClick={handleChannelClick(channel, index)}
|
||||
spotlightId={`below-tab-channel-${index}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<LiveChannelContents
|
||||
liveInfos={liveInfos}
|
||||
currentTime={currentTime}
|
||||
setSelectedIndex={setSelectedIndex}
|
||||
videoVerticalVisible={videoVerticalVisible}
|
||||
currentVideoShowId={currentVideoShowId}
|
||||
handleItemFocus={handleItemFocus}
|
||||
tabTitle={tabTitle}
|
||||
panelInfo={panelInfo}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
border-top: 1px solid rgba(234, 234, 234, 0.3);
|
||||
overflow: hidden;
|
||||
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column);
|
||||
gap: 20px;
|
||||
z-index: 5;
|
||||
|
||||
> * + * {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
@@ -24,11 +27,11 @@
|
||||
.size(@w: 100%, @h: 100%);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(199, 8, 80, 0.15) 0%,
|
||||
rgba(199, 8, 80, 0.05) 30%,
|
||||
rgba(30, 30, 30, 0.4) 0%,
|
||||
rgba(50, 50, 50, 0.25) 30%,
|
||||
transparent 50%,
|
||||
rgba(199, 8, 80, 0.05) 70%,
|
||||
rgba(199, 8, 80, 0.15) 100%
|
||||
rgba(50, 50, 50, 0.25) 70%,
|
||||
rgba(30, 30, 30, 0.4) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
@@ -82,9 +85,12 @@
|
||||
.channelList {
|
||||
width: 100%;
|
||||
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
|
||||
gap: 30px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
> * + * {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.channelCard {
|
||||
@@ -96,9 +102,12 @@
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
flex-shrink: 0;
|
||||
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
|
||||
gap: 10px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
> * + * {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
flex: 1 1 0;
|
||||
.size(@w: 212px, @h: 119px);
|
||||
@@ -120,7 +129,10 @@
|
||||
.channelHeader {
|
||||
width: 100%;
|
||||
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center);
|
||||
gap: 9px;
|
||||
|
||||
> * + * {
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.logoWrapper {
|
||||
.size(@w: 40px, @h: 40px);
|
||||
@@ -159,7 +171,10 @@
|
||||
.progressBarWrapper {
|
||||
width: 100%;
|
||||
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
|
||||
gap: 10px;
|
||||
|
||||
> * + * {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
flex: 1 1 0;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||
import css from './BelowTabContainer.module.less';
|
||||
|
||||
const SpottableDiv = Spottable('div');
|
||||
|
||||
const ChannelCard = ({
|
||||
channelName,
|
||||
programName,
|
||||
logoSrc,
|
||||
thumbnailSrc,
|
||||
progressPercent = 0,
|
||||
backgroundColor = '#FFFFFF',
|
||||
onFocus,
|
||||
onBlur,
|
||||
onClick,
|
||||
spotlightId,
|
||||
}) => {
|
||||
return (
|
||||
<SpottableDiv
|
||||
className={css.channelCard}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onClick={onClick}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<div className={css.thumbnail}>
|
||||
<CustomImage src={thumbnailSrc} alt={channelName} />
|
||||
</div>
|
||||
<div className={css.cardContent}>
|
||||
<div className={css.channelHeader}>
|
||||
<div className={css.logoWrapper} style={{ background: backgroundColor }}>
|
||||
<CustomImage src={logoSrc} alt={`${channelName} logo`} className={css.logo} />
|
||||
</div>
|
||||
<h3 className={css.channelName}>{channelName}</h3>
|
||||
</div>
|
||||
<div className={css.programName}>{programName}</div>
|
||||
<div className={css.progressBarWrapper}>
|
||||
<div className={css.progressBar}>
|
||||
<div className={css.progressTrack} />
|
||||
<div className={css.progressFill} style={{ width: `${progressPercent}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelCard;
|
||||
@@ -0,0 +1,144 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { updatePanel } from '../../../../actions/panelActions';
|
||||
import { sendLogTotalRecommend } from '../../../../actions/logActions';
|
||||
import TScroller from '../../../../components/TScroller/TScroller';
|
||||
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
|
||||
import { $L } from '../../../../utils/helperMethods';
|
||||
import { convertUtcToLocal } from '../../../../components/MediaPlayer/util';
|
||||
import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
|
||||
import ChannelCard from './ChannelCard';
|
||||
import css from './LiveChannelContents.module.less';
|
||||
|
||||
export default function LiveChannelContents({
|
||||
liveInfos,
|
||||
currentTime,
|
||||
setSelectedIndex,
|
||||
videoVerticalVisible,
|
||||
currentVideoShowId,
|
||||
handleItemFocus,
|
||||
tabTitle,
|
||||
panelInfo,
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const isClickBlocked = useRef(false);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
() => () => {
|
||||
if (handleItemFocus) {
|
||||
handleItemFocus(LOG_MENU.FULL_LIVE_CHANNELS);
|
||||
}
|
||||
},
|
||||
[handleItemFocus]
|
||||
);
|
||||
|
||||
const calculateProgress = useCallback((startDt, endDt) => {
|
||||
if (!startDt || !endDt) return 0;
|
||||
|
||||
let localStartDt = convertUtcToLocal(startDt);
|
||||
let localEndDt = convertUtcToLocal(endDt);
|
||||
let curDt = new Date();
|
||||
let localStartSec = localStartDt?.getTime() / 1000;
|
||||
let localEndSec = localEndDt?.getTime() / 1000;
|
||||
let curSec = curDt?.getTime() / 1000;
|
||||
let fullSec = localEndSec - localStartSec;
|
||||
let diff = curSec - localStartSec;
|
||||
|
||||
let percent = Math.floor((diff * 100) / fullSec);
|
||||
|
||||
if (percent > 100 || percent < 0) {
|
||||
return 0;
|
||||
}
|
||||
return percent;
|
||||
}, []);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(channel, index) => () => {
|
||||
const params = {
|
||||
tabTitle: tabTitle && tabTitle[1] ? tabTitle[1] : 'LIVE CHANNEL',
|
||||
showId: channel.showId,
|
||||
showTitle: channel.showNm,
|
||||
showType: panelInfo?.shptmBanrTpNm,
|
||||
category: channel.catNm,
|
||||
partner: channel.patncNm,
|
||||
contextName: LOG_CONTEXT_NAME.SHOW,
|
||||
messageId: LOG_MESSAGE_ID.CONTENTCLICK,
|
||||
};
|
||||
dispatch(sendLogTotalRecommend(params));
|
||||
|
||||
// 중복클릭방지
|
||||
if (isClickBlocked.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isClickBlocked.current = true;
|
||||
setTimeout(() => {
|
||||
isClickBlocked.current = false;
|
||||
}, 600);
|
||||
|
||||
if (!channel.showId) return;
|
||||
|
||||
if (currentVideoShowId && currentVideoShowId === channel.showId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedIndex(index);
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
panelInfo: {
|
||||
chanId: channel.chanId,
|
||||
patnrId: channel.patnrId,
|
||||
showId: channel.showId,
|
||||
shptmBanrTpNm: 'LIVE',
|
||||
lgCatCd: channel.catCd,
|
||||
isUpdatedByClick: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[currentVideoShowId, isClickBlocked, dispatch, tabTitle, panelInfo, setSelectedIndex]
|
||||
);
|
||||
|
||||
// 표시 조건: panelInfo.shptmBanrTpNm === "LIVE" && liveInfos 존재
|
||||
if (panelInfo?.shptmBanrTpNm !== 'LIVE' || !liveInfos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={css.container}>
|
||||
{liveInfos.length > 0 ? (
|
||||
<TScroller direction="horizontal" noScrollByWheel className={css.channelList}>
|
||||
<>
|
||||
{liveInfos.map((channel, index) => {
|
||||
const programName = !channel.showId
|
||||
? $L('No Livestream scheduled yet')
|
||||
: channel.showNm || channel.patncNm;
|
||||
|
||||
return (
|
||||
<ChannelCard
|
||||
key={channel.prdtId || `channel-${index}`}
|
||||
channelName={channel.patncNm}
|
||||
programName={programName}
|
||||
logoSrc={channel.patncLogoPath}
|
||||
thumbnailSrc={channel.thumbnailUrl || channel.dfltThumbnailImgPath}
|
||||
progressPercent={calculateProgress(channel.strtDt, channel.endDt)}
|
||||
backgroundColor="#FFFFFF"
|
||||
onClick={handleItemClick(channel, index)}
|
||||
onFocus={handleFocus()}
|
||||
spotlightId={`tabChannel-video-${index}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</TScroller>
|
||||
) : (
|
||||
<ListEmptyContents tabIndex={1} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@import "../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 155px;
|
||||
|
||||
> div:nth-child(1) {
|
||||
.size(@w: 100%, @h: 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.channelList {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
height: 155px;
|
||||
> div {
|
||||
display: flex;
|
||||
|
||||
> div {
|
||||
flex: none;
|
||||
margin-right: 30px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user