[251010] feat: HomeBanner 비디오 상시재생

🕐 커밋 시간: 2025. 10. 10. 13:38:25

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +150줄
  • 삭제: -16줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.module.less
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/playActions.js (javascript):
     Deleted: CLEAR_PLAYER_INFO(), resumePlayerControl()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx (javascript):
     Added: createPanelInfo()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
This commit is contained in:
2025-10-10 13:38:27 +09:00
parent a00856c461
commit 10d96f4d8a
6 changed files with 434 additions and 447 deletions

View File

@@ -1,62 +1,50 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import classNames from "classnames";
import { useDispatch, useSelector } from "react-redux";
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import Spotlight from "@enact/spotlight";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import Spottable from "@enact/spotlight/Spottable";
import { getContainerId } from "@enact/spotlight/src/container";
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { getContainerId } from '@enact/spotlight/src/container';
import btnPlay from "../../../../assets/images/btn/btn-play-thumb-nor.png";
import defaultLogoImg from "../../../../assets/images/ic-tab-partners-default@3x.png";
import emptyHorImage from "../../../../assets/images/img-home-banner-empty-hor.png";
import emptyVerImage from "../../../../assets/images/img-home-banner-empty-ver.png";
import defaultImageItem from "../../../../assets/images/img-thumb-empty-product@3x.png";
import liveShow from "../../../../assets/images/tag-liveshow.png";
import { setBannerIndex, updateHomeInfo } from "../../../actions/homeActions";
import {
sendLogTopContents,
sendLogTotalRecommend,
} from "../../../actions/logActions";
import { pushPanel } from "../../../actions/panelActions";
import { startVideoPlayer } from "../../../actions/playActions";
import CustomImage from "../../../components/CustomImage/CustomImage";
import usePriceInfo from "../../../hooks/usePriceInfo";
import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png';
import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png';
import emptyHorImage from '../../../../assets/images/img-home-banner-empty-hor.png';
import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.png';
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
import liveShow from '../../../../assets/images/tag-liveshow.png';
import { setBannerIndex, updateHomeInfo } from '../../../actions/homeActions';
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
import { pushPanel } from '../../../actions/panelActions';
import { startVideoPlayer } from '../../../actions/playActions';
import CustomImage from '../../../components/CustomImage/CustomImage';
import usePriceInfo from '../../../hooks/usePriceInfo';
import {
LOG_CONTEXT_NAME,
LOG_MENU,
LOG_MESSAGE_ID,
LOG_TP_NO,
panel_names,
} from "../../../utils/Config";
import { $L, formatGMTString } from "../../../utils/helperMethods";
import { TEMPLATE_CODE_CONF } from "../HomePanel";
import css from "./RollingUnit.module.less";
} from '../../../utils/Config';
import { $L, formatGMTString } from '../../../utils/helperMethods';
import { TEMPLATE_CODE_CONF } from '../HomePanel';
import css from './RollingUnit.module.less';
const SpottableComponent = Spottable("div");
const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused", preserveId: true },
"div"
);
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused', preserveId: true }, 'div');
const LINK_TYPES = {
FEATURED_BRANDS: "DSP00501",
TRENDING_NOW: "DSP00502",
HOT_PICKS: "DSP00503",
ON_SALE: "DSP00504",
CATEGORY: "DSP00505",
PRODUCT_DETAIL: "DSP00506",
VOD: "DSP00507",
SHOW_DETAIL: "DSP00508",
THEME: "DSP00509",
FEATURED_BRANDS: 'DSP00501',
TRENDING_NOW: 'DSP00502',
HOT_PICKS: 'DSP00503',
ON_SALE: 'DSP00504',
CATEGORY: 'DSP00505',
PRODUCT_DETAIL: 'DSP00506',
VOD: 'DSP00507',
SHOW_DETAIL: 'DSP00508',
THEME: 'DSP00509',
};
const createPanelInfo = (data, categoryData = {}) => ({
@@ -78,6 +66,7 @@ export default function RollingUnit({
isHorizontal,
handleItemFocus,
handleShelfFocus,
videoPlayerable = false,
}) {
const rollingData = bannerData.bannerDetailInfos;
const rollingDataLength = bannerData.bannerDetailInfos.length;
@@ -86,23 +75,17 @@ export default function RollingUnit({
const { curationId, curationTitle } = useSelector((state) => state.home);
const curtNm = useSelector((state) => state.home?.bannerData?.curtNm);
const shptmTmplCd = useSelector(
(state) => state.home?.bannerData?.shptmTmplCd
);
const shptmTmplCd = useSelector((state) => state.home?.bannerData?.shptmTmplCd);
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
const homeCategory = useSelector(
(state) => state.home.menuData?.data?.homeCategory
);
const homeCategory = useSelector((state) => state.home.menuData?.data?.homeCategory);
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
const bannerId = `banner-${bannerData.banrLctnNo}`;
const savedIndex = useSelector((state) => state.home.bannerIndices[bannerId]);
const [startIndex, setStartIndex] = useState(
savedIndex !== undefined ? savedIndex : 0
);
const [startIndex, setStartIndex] = useState(savedIndex !== undefined ? savedIndex : 0);
const lastIndexRef = useRef(rollingDataLength - 1);
const doRollingRef = useRef(false);
const [unitHasFocus, setUnitHasFocus] = useState(false);
@@ -124,14 +107,14 @@ export default function RollingUnit({
switch (currentRollingData?.shptmBanrTpCd) {
// case: "LIVE" or "VOD"
case "DSP00301":
case "DSP00302":
case 'DSP00301':
case 'DSP00302':
contId = currentRollingData?.showId;
contNm = currentRollingData?.showNm;
break;
// case: "Image Banner"
case "DSP00303":
case 'DSP00303':
contId = currentRollingData?.shptmLnkTpCd;
contNm = currentRollingData?.shptmLnkTpNm;
break;
@@ -144,27 +127,27 @@ export default function RollingUnit({
}
if (
currentRollingData?.shptmLnkTpCd === "DSP00503" || // "Hot Picks"
currentRollingData?.shptmLnkTpCd === "DSP00509" // "Theme"
currentRollingData?.shptmLnkTpCd === 'DSP00503' || // "Hot Picks"
currentRollingData?.shptmLnkTpCd === 'DSP00509' // "Theme"
) {
contNm = contNm + " | " + currentRollingData?.lnkCurationId;
contNm = contNm + ' | ' + currentRollingData?.lnkCurationId;
}
return {
banrNo: `${currentRollingData?.banrDpOrd}`,
banrTpNm: currentRollingData?.vtctpYn
? currentRollingData.vtctpYn === "Y"
? "Vertical"
: "Horizontal"
: "",
? currentRollingData.vtctpYn === 'Y'
? 'Vertical'
: 'Horizontal'
: '',
contId,
contNm,
contTpNm: currentRollingData?.shptmBanrTpNm ?? "",
dspyTpNm: bannerDataRef.current?.shptmDspyTpNm ?? "",
expsOrd: bannerDataRef.current?.banrLctnNo ?? "",
linkTpCd: "",
patncNm: currentRollingData?.patncNm ?? "",
patnrId: currentRollingData?.patnrId ?? "",
contTpNm: currentRollingData?.shptmBanrTpNm ?? '',
dspyTpNm: bannerDataRef.current?.shptmDspyTpNm ?? '',
expsOrd: bannerDataRef.current?.banrLctnNo ?? '',
linkTpCd: '',
patncNm: currentRollingData?.patncNm ?? '',
patnrId: currentRollingData?.patnrId ?? '',
tmplCd: shptmTmplCd,
};
}
@@ -175,19 +158,17 @@ export default function RollingUnit({
(bannerClick) => {
const data = rollingDataRef.current[startIndex];
const newParams =
bannerData.banrLctnNo === "2"
bannerData.banrLctnNo === '2'
? {
bannerType: "Horizontal",
bannerType: 'Horizontal',
}
: {
bannerType: "Vertical",
bannerType: 'Vertical',
};
if (rollingDataRef.current && nowMenu === LOG_MENU.HOME_TOP) {
const logParams = {
contextName: LOG_CONTEXT_NAME.HOME,
messageId: bannerClick
? LOG_MESSAGE_ID.BANNER_CLICK
: LOG_MESSAGE_ID.BANNER,
messageId: bannerClick ? LOG_MESSAGE_ID.BANNER_CLICK : LOG_MESSAGE_ID.BANNER,
curationId: curationId,
curationTitle: curationTitle,
contentType: data.shptmBanrTpNm,
@@ -195,7 +176,7 @@ export default function RollingUnit({
contentTitle: data.showNm,
productId: data.prdtId,
productTitle: data.prdtNm,
displayType: "button",
displayType: 'button',
partner: data.patncNm,
brand: data.brndNm,
location: bannerData.banrLctnNo,
@@ -203,9 +184,9 @@ export default function RollingUnit({
};
dispatch(sendLogTotalRecommend(logParams));
if (bannerClick) {
console.log("###bannerClick", logParams);
console.log('###bannerClick', logParams);
} else {
console.log("###bannerShow", logParams);
console.log('###bannerShow', logParams);
}
}
},
@@ -223,16 +204,14 @@ export default function RollingUnit({
const deltaTime = time - previousTimeRef.current;
if (deltaTime >= 10000 && doRollingRef.current) {
setStartIndex((prevIndex) =>
prevIndex === lastIndexRef.current ? 0 : prevIndex + 1
);
setStartIndex((prevIndex) => (prevIndex === lastIndexRef.current ? 0 : prevIndex + 1));
previousTimeRef.current = time;
}
} else {
previousTimeRef.current = time;
}
if (typeof window === "object") {
if (typeof window === 'object') {
requestRef.current = window.requestAnimationFrame(animate);
}
}, []);
@@ -270,7 +249,7 @@ export default function RollingUnit({
// 인디케이터 아래키 누를시 [<]
const prevKeyDown = (event) => {
if (event.key === "ArrowDown") {
if (event.key === 'ArrowDown') {
setNextFocus(true);
setContentsFocus(true);
}
@@ -278,7 +257,7 @@ export default function RollingUnit({
// 인디케이터 아래키 누를시 [>]
const nextKeyDown = (event) => {
if (event.key === "ArrowDown") {
if (event.key === 'ArrowDown') {
setPrevFocus(true);
setContentsFocus(true);
}
@@ -362,7 +341,7 @@ export default function RollingUnit({
dispatch(
sendLogTopContents({
...topContentsLogInfo,
inDt: formatGMTString(new Date()) ?? "",
inDt: formatGMTString(new Date()) ?? '',
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
})
);
@@ -372,23 +351,17 @@ export default function RollingUnit({
switch (linkType) {
case LINK_TYPES.FEATURED_BRANDS:
handlePushPanel(panel_names.FEATURED_BRANDS_PANEL, {
from: "gnb",
from: 'gnb',
patnrId: currentData.patnrId,
});
break;
case LINK_TYPES.TRENDING_NOW:
handlePushPanel(
panel_names.TRENDING_NOW_PANEL,
createPanelInfo(currentData)
);
handlePushPanel(panel_names.TRENDING_NOW_PANEL, createPanelInfo(currentData));
break;
case LINK_TYPES.HOT_PICKS:
handlePushPanel(
panel_names.HOT_PICKS_PANEL,
createPanelInfo(currentData)
);
handlePushPanel(panel_names.HOT_PICKS_PANEL, createPanelInfo(currentData));
break;
case LINK_TYPES.ON_SALE:
@@ -399,10 +372,7 @@ export default function RollingUnit({
case LINK_TYPES.CATEGORY:
if (Object.keys(categoryData).length > 0) {
handlePushPanel(
panel_names.CATEGORY_PANEL,
createPanelInfo(currentData, categoryData)
);
handlePushPanel(panel_names.CATEGORY_PANEL, createPanelInfo(currentData, categoryData));
}
break;
@@ -430,7 +400,7 @@ export default function RollingUnit({
dispatch(
sendLogTopContents({
...topContentsLogInfo,
inDt: formatGMTString(new Date()) ?? "",
inDt: formatGMTString(new Date()) ?? '',
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
})
);
@@ -456,7 +426,7 @@ export default function RollingUnit({
panelInfo: {
lastFocusedTargetId,
focusedContainerId: TEMPLATE_CODE_CONF.TOP,
currentSpot: currentSpot?.getAttribute("data-spotlight-id"),
currentSpot: currentSpot?.getAttribute('data-spotlight-id'),
},
})
);
@@ -472,7 +442,7 @@ export default function RollingUnit({
showUrl: currentData.showUrl,
patnrId: currentData.patnrId,
showId: currentData.showId,
shptmBanrTpNm: currentData.showId ? currentData.shptmBanrTpNm : "MEDIA",
shptmBanrTpNm: currentData.showId ? currentData.shptmBanrTpNm : 'MEDIA',
lgCatCd: currentData.lgCatCd,
chanId: currentData.brdcChnlId,
modal: false,
@@ -484,18 +454,11 @@ export default function RollingUnit({
dispatch(
sendLogTopContents({
...topContentsLogInfo,
inDt: formatGMTString(new Date()) ?? "",
inDt: formatGMTString(new Date()) ?? '',
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
})
);
}, [
rollingData,
startIndex,
bannerId,
dispatch,
handleStartVideoPlayer,
topContentsLogInfo,
]);
}, [rollingData, startIndex, bannerId, dispatch, handleStartVideoPlayer, topContentsLogInfo]);
// 10초 롤링
useEffect(() => {
@@ -504,20 +467,20 @@ export default function RollingUnit({
previousTimeRef.current = undefined;
if (rollingDataLength <= 1 || unitHasFocus) {
doRollingRef.current = false;
if (typeof window === "object") {
if (typeof window === 'object') {
window.cancelAnimationFrame(requestRef.current);
}
return;
}
doRollingRef.current = true;
if (typeof window === "object") {
if (typeof window === 'object') {
requestRef.current = window.requestAnimationFrame(animate);
}
return () => {
doRollingRef.current = false;
if (typeof window === "object") {
if (typeof window === 'object') {
window.cancelAnimationFrame(requestRef.current);
}
};
@@ -531,7 +494,7 @@ export default function RollingUnit({
const params = {
...topContentsLogInfo,
entryMenu: _entryMenu,
inDt: formatGMTString(new Date()) ?? "",
inDt: formatGMTString(new Date()) ?? '',
logTpNo: LOG_TP_NO.TOP_CONTENTS.VIEW,
nowMenu: _nowMenu,
};
@@ -557,10 +520,7 @@ export default function RollingUnit({
return (
<Container
className={classNames(
css.rollingWrap,
isHorizontal && css.isHorizontalWrap
)}
className={classNames(css.rollingWrap, isHorizontal && css.isHorizontalWrap)}
spotlightId={`container-${spotlightId}`}
onFocus={shelfFocus}
>
@@ -570,15 +530,14 @@ export default function RollingUnit({
onClick={handlePrev}
onFocus={indicatorFocus}
onBlur={indicatorBlur}
spotlightId={spotlightId + "Prev"}
spotlightId={spotlightId + 'Prev'}
spotlightDisabled={prevFocus}
onKeyDown={prevKeyDown}
aria-label="Move to left Button"
/>
) : null}
{rollingData &&
rollingData[startIndex].shptmBanrTpNm === "Image Banner" ? (
{rollingData && rollingData[startIndex].shptmBanrTpNm === 'Image Banner' ? (
<SpottableComponent
className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}
onClick={imageBannerClick}
@@ -596,7 +555,7 @@ export default function RollingUnit({
<img src={rollingData[startIndex].tmnlImgPath} />
</div>
</SpottableComponent>
) : rollingData[startIndex].shptmBanrTpNm === "LIVE" ? (
) : rollingData[startIndex].shptmBanrTpNm === 'LIVE' ? (
<SpottableComponent
className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}
onClick={videoClick}
@@ -604,31 +563,17 @@ export default function RollingUnit({
onBlur={onBlur}
spotlightId={spotlightId}
spotlightDisabled={contentsFocus}
aria-label={"LIVE " + rollingData[startIndex].showNm}
aria-label={'LIVE ' + rollingData[startIndex].showNm}
>
<p className={css.liveIcon}>
<CustomImage
delay={0}
src={liveShow}
animationSpeed="fast"
ariaLabel="LIVE icon"
/>
<CustomImage delay={0} src={liveShow} animationSpeed="fast" ariaLabel="LIVE icon" />
</p>
<div
className={classNames(
css.itemBox,
isHorizontal && css.isHorizontal
)}
>
<div className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}>
{rollingData[startIndex].tmnlImgPath == null ? (
<CustomImage
delay={0}
src={
rollingData[startIndex].vtctpYn === "Y"
? emptyVerImage
: emptyHorImage
}
src={rollingData[startIndex].vtctpYn === 'Y' ? emptyVerImage : emptyHorImage}
ariaLabel={rollingData[startIndex].tmnlImgNm}
fallbackSrc={isHorizontal ? emptyHorImage : emptyVerImage}
/>
@@ -643,13 +588,9 @@ export default function RollingUnit({
<div className={css.btnPlay}>
{rollingData[startIndex].tmnlImgPath == null ? (
""
''
) : (
<CustomImage
delay={0}
src={btnPlay}
ariaLabel="Play video Button"
/>
<CustomImage delay={0} src={btnPlay} ariaLabel="Play video Button" />
)}
</div>
</div>
@@ -664,7 +605,7 @@ export default function RollingUnit({
/>
</p>
</SpottableComponent>
) : rollingData[startIndex].shptmBanrTpNm === "VOD" ? (
) : rollingData[startIndex].shptmBanrTpNm === 'VOD' ? (
<SpottableComponent
className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}
onClick={videoClick}
@@ -674,20 +615,11 @@ export default function RollingUnit({
spotlightDisabled={contentsFocus}
aria-label={rollingData[startIndex].showNm}
>
<div
className={classNames(
css.itemBox,
isHorizontal && css.isHorizontal
)}
>
<div className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}>
{rollingData[startIndex].tmnlImgPath == null ? (
<CustomImage
delay={0}
src={
rollingData[startIndex].vtctpYn === "Y"
? emptyVerImage
: emptyHorImage
}
src={rollingData[startIndex].vtctpYn === 'Y' ? emptyVerImage : emptyHorImage}
ariaLabel={rollingData[startIndex].tmnlImgNm}
/>
) : (
@@ -700,13 +632,9 @@ export default function RollingUnit({
<div className={css.btnPlay}>
{rollingData[startIndex].tmnlImgPath == null ? (
""
''
) : (
<CustomImage
delay={0}
src={btnPlay}
ariaLabel="Play video Button"
/>
<CustomImage delay={0} src={btnPlay} ariaLabel="Play video Button" />
)}
</div>
@@ -728,8 +656,8 @@ export default function RollingUnit({
className={classNames(
css.itemBox,
css.todaysDeals,
countryCode === "RU" ? css.ru : "",
countryCode === "DE" ? css.de : "",
countryCode === 'RU' ? css.ru : '',
countryCode === 'DE' ? css.de : '',
isHorizontal && css.isHorizontal
)}
onClick={imageBannerClick}
@@ -752,7 +680,7 @@ export default function RollingUnit({
}}
/>
<div className={css.accBox}>
{parseFloat(originalPrice?.replace("$", "")) === 0
{parseFloat(originalPrice?.replace('$', '')) === 0
? rollingData[startIndex].offerInfo
: discountRate
? discountedPrice
@@ -761,10 +689,9 @@ export default function RollingUnit({
<span className={css.saleAccBox}>{originalPrice}</span>
)}
</div>
{isHorizontal &&
parseFloat(originalPrice?.replace("$", "")) !== 0 && (
<span className={css.saleAccBox}>{originalPrice}</span>
)}
{isHorizontal && parseFloat(originalPrice?.replace('$', '')) !== 0 && (
<span className={css.saleAccBox}>{originalPrice}</span>
)}
</div>
<div className={css.itemImgBox}>
@@ -786,7 +713,7 @@ export default function RollingUnit({
onClick={handleNext}
onFocus={indicatorFocus}
onBlur={indicatorBlur}
spotlightId={spotlightId + "Next"}
spotlightId={spotlightId + 'Next'}
spotlightDisabled={nextFocus}
onKeyDown={nextKeyDown}
aria-label="Move to right Button"