🕐 커밋 시간: 2025. 11. 15. 22:03:44 📊 변경 통계: • 총 파일: 17개 • 추가: +573줄 • 삭제: -87줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/MediaOverlayContents.jsx 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/panelActions.js ~ com.twin.app.shoptime/src/components/MediaPlayer/MediaControls.js ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.module.less ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.js ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less ~ com.twin.app.shoptime/src/utils/SpotlightIds.js ~ com.twin.app.shoptime/src/views/HomePanel/BestSeller/BestSeller.jsx ~ com.twin.app.shoptime/src/views/HomePanel/EventPopUpBanner/EventPopUpBanner.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx ~ com.twin.app.shoptime/src/views/HomePanel/PickedForYou/PickedForYou.jsx ~ com.twin.app.shoptime/src/views/HomePanel/SubCategory/SubCategory.jsx ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/panelActions.js (javascript): ✅ Added: updatePanel() 📄 com.twin.app.shoptime/src/components/MediaPlayer/MediaControls.js (javascript): ✅ Added: onSpotlightRight(), onSpotlightUp(), MediaControlsDecoratorHOC(), handleCancel() ❌ Deleted: onSpotlightRight(), onSpotlightUp(), MediaControlsDecoratorHOC(), handleCancel() 📄 com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less (unknown): ✅ Added: position() ❌ Deleted: position() 📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/src/views/HomePanel/SubCategory/SubCategory.jsx (javascript): 🔄 Modified: getExpsOrdByLgCatCd() 📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript): 🔄 Modified: normalizeModalStyle() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator(), PlayerOverlayContents() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/MediaOverlayContents.jsx (javascript): ✅ Added: MediaOverlayContents() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선 • 공통 유틸리티 함수 최적화
361 lines
12 KiB
JavaScript
361 lines
12 KiB
JavaScript
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
|
|
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 Marquee from '@enact/ui/Marquee';
|
|
|
|
import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
|
import { setShowPopup } from '../../../actions/commonActions';
|
|
import CustomImage from '../../../components/CustomImage/CustomImage';
|
|
import { ACTIVE_POPUP } from '../../../utils/Config';
|
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
|
import PlayerTabButton from '../PlayerTabContents/TabButton/PlayerTabButton';
|
|
import css from './PlayerOverlayContents.module.less';
|
|
|
|
const SpottableBtn = Spottable('button');
|
|
|
|
const Container = SpotlightContainerDecorator({ enterTo: 'default-element' }, 'div');
|
|
|
|
function PlayerOverlayContents({
|
|
type,
|
|
onClick,
|
|
panelInfo,
|
|
disclaimer,
|
|
playListInfo,
|
|
captionEnable,
|
|
selectedIndex,
|
|
setIsSubtitleActive,
|
|
videoVerticalVisible,
|
|
sideContentsVisible,
|
|
setSideContentsVisible,
|
|
belowContentsVisible,
|
|
handleIndicatorUpClick,
|
|
handleIndicatorDownClick,
|
|
tabContainerVersion,
|
|
tabIndexV2,
|
|
forceShowMediaOverlay = false,
|
|
}) {
|
|
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
|
|
const dispatch = useDispatch();
|
|
const onClickBack = (ev) => {
|
|
// TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지
|
|
// (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음)
|
|
if (tabContainerVersion === 2 && belowContentsVisible) {
|
|
ev.stopPropagation();
|
|
}
|
|
|
|
if (onClick) {
|
|
onClick(ev);
|
|
}
|
|
};
|
|
const backBtnRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
if (type === 'MEDIA' && !panelInfo.modal && backBtnRef.current) {
|
|
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
|
}
|
|
}, [type, panelInfo.modal, backBtnRef]);
|
|
|
|
const handleSubtitleOnClick = useCallback(() => {
|
|
if (!captionEnable) {
|
|
return dispatch(setShowPopup(ACTIVE_POPUP.alertPopup));
|
|
}
|
|
|
|
setIsSubtitleActive((prev) => !prev);
|
|
}, [dispatch, captionEnable, setIsSubtitleActive]);
|
|
|
|
const patncLogoPath = useMemo(() => {
|
|
let logo = playListInfo[selectedIndex]?.patncLogoPath;
|
|
if (type === 'MEDIA') {
|
|
logo = panelInfo?.patncLogoPath;
|
|
}
|
|
|
|
return logo;
|
|
}, [playListInfo, selectedIndex, panelInfo, type]);
|
|
|
|
const partnerName = useMemo(() => {
|
|
let name = playListInfo[selectedIndex]?.patncNm;
|
|
if (type === 'MEDIA') {
|
|
name = panelInfo?.patncNm;
|
|
}
|
|
|
|
return name;
|
|
}, [playListInfo, selectedIndex, panelInfo, type]);
|
|
|
|
const showName = useMemo(() => {
|
|
let name = playListInfo[selectedIndex]?.showNm;
|
|
if (type === 'MEDIA') {
|
|
name = panelInfo?.showNm;
|
|
}
|
|
|
|
return name ? name.replace(/<br\s*\/?>/gi, ' ') : '';
|
|
}, [playListInfo, selectedIndex, panelInfo, type]);
|
|
|
|
const onSpotlightMoveTabButton = useCallback((e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
|
}, []);
|
|
|
|
const onSpotlightMoveMediaButton = useCallback(
|
|
(e) => {
|
|
e.stopPropagation();
|
|
if (type === 'LIVE') {
|
|
return Spotlight.focus('videoIndicator-down-button');
|
|
}
|
|
return Spotlight.focus(SpotlightIds.PLAYER_PLAY_BUTTON);
|
|
},
|
|
[type]
|
|
);
|
|
|
|
const onSpotlightMoveSubtitleButton = useCallback(() => {
|
|
return Spotlight.focus('player-subtitlebutton');
|
|
}, []);
|
|
|
|
const onSpotlightMoveSlider = useCallback(
|
|
(e) => {
|
|
if (type === 'VOD') {
|
|
e.stopPropagation();
|
|
|
|
Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
|
|
}
|
|
},
|
|
[type]
|
|
);
|
|
|
|
const onSpotlightMoveSideTab = useCallback((e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
Spotlight.focus('tab-0');
|
|
}, []);
|
|
|
|
const onSpotlightMoveBelowTab = useCallback(
|
|
(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
// tabIndexV2에 따라 다른 버튼으로 포커스 이동
|
|
if (tabIndexV2 === 0) {
|
|
// ShopNow 탭: Close 버튼으로
|
|
// Spotlight.focus('below-tab-close-button');
|
|
Spotlight.focus('shownow_close_button');
|
|
} else if (tabIndexV2 === 1) {
|
|
// LIVE CHANNEL 탭: LIVE CHANNEL 버튼으로
|
|
Spotlight.focus('below-tab-live-channel-button');
|
|
} else if (tabIndexV2 === 2) {
|
|
// ShopNowButton: ShopNowButton으로
|
|
Spotlight.focus('below-tab-shop-now-button');
|
|
}
|
|
},
|
|
[tabIndexV2]
|
|
);
|
|
|
|
const onSpotlightMoveBackButton = useCallback(() => {
|
|
return Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
|
}, []);
|
|
|
|
const handleOverlayKeyDownCapture = useCallback(
|
|
(ev) => {
|
|
const currentId = Spotlight.getCurrent()?.getAttribute('data-spotlight-id');
|
|
if (ev.keyCode === 40 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
return onSpotlightMoveMediaButton(ev);
|
|
}
|
|
if (ev.keyCode === 39 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
return onSpotlightMoveSubtitleButton(ev);
|
|
}
|
|
if (ev.keyCode === 37 && currentId === SpotlightIds.PLAYER_SUBTITLE_BUTTON) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
return onSpotlightMoveMediaButton(ev);
|
|
}
|
|
if (ev.keyCode === 38 && currentId === SpotlightIds.PLAYER_PLAY_BUTTON) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
return onSpotlightMoveBackButton();
|
|
}
|
|
},
|
|
[onSpotlightMoveBackButton, onSpotlightMoveMediaButton, onSpotlightMoveSubtitleButton]
|
|
);
|
|
|
|
const shouldShowExtendedControls = useMemo(
|
|
() => forceShowMediaOverlay || type !== 'MEDIA',
|
|
[forceShowMediaOverlay, type]
|
|
);
|
|
|
|
const currentSideButtonStatus = useMemo(() => {
|
|
if (
|
|
shouldShowExtendedControls &&
|
|
!panelInfo?.modal &&
|
|
!sideContentsVisible &&
|
|
tabContainerVersion === 1
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}, [shouldShowExtendedControls, panelInfo, sideContentsVisible, tabContainerVersion]);
|
|
|
|
const noLiveContentsVisible = useMemo(() => {
|
|
if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
const noShowIdCount = playListInfo.filter((item) => !item.showId).length;
|
|
if (playListInfo.length - 1 === noShowIdCount) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}, [playListInfo]);
|
|
|
|
return (
|
|
<>
|
|
<Container className={css.overlayContainer} onKeyDownCapture={handleOverlayKeyDownCapture}>
|
|
{shouldShowExtendedControls && playListInfo.length > 1 && noLiveContentsVisible && (
|
|
<>
|
|
<div className={css.indicatorUpButton}>
|
|
<SpottableBtn
|
|
onClick={handleIndicatorUpClick}
|
|
spotlightId="videoIndicator-up-button"
|
|
onSpotlightRight={
|
|
videoVerticalVisible
|
|
? onSpotlightMoveSideTab
|
|
: tabContainerVersion === 1
|
|
? onSpotlightMoveTabButton
|
|
: undefined
|
|
}
|
|
onSpotlightDown={
|
|
tabContainerVersion === 2 && belowContentsVisible
|
|
? onSpotlightMoveBelowTab
|
|
: onSpotlightMoveSlider
|
|
}
|
|
aria-label="Previous channel"
|
|
/>
|
|
</div>
|
|
<div className={css.indicatorDownButton}>
|
|
<SpottableBtn
|
|
onClick={handleIndicatorDownClick}
|
|
spotlightId="videoIndicator-down-button"
|
|
onSpotlightLeft={onSpotlightMoveSlider}
|
|
onSpotlightUp={onSpotlightMoveSlider}
|
|
onSpotlightRight={
|
|
videoVerticalVisible
|
|
? onSpotlightMoveSideTab
|
|
: tabContainerVersion === 1
|
|
? onSpotlightMoveTabButton
|
|
: undefined
|
|
}
|
|
onSpotlightDown={
|
|
tabContainerVersion === 2 && belowContentsVisible
|
|
? onSpotlightMoveBelowTab
|
|
: undefined
|
|
}
|
|
aria-label="Next channel"
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{currentSideButtonStatus && !videoVerticalVisible && (
|
|
<PlayerTabButton
|
|
setSideContentsVisible={setSideContentsVisible}
|
|
sideContentsVisible={sideContentsVisible}
|
|
onSpotlightLeft={
|
|
shouldShowExtendedControls && playListInfo.length < 2 && onSpotlightMoveBackButton
|
|
}
|
|
videoType={type}
|
|
/>
|
|
)}
|
|
|
|
{cntry_cd === 'US' && (
|
|
<div className={css.videoButtonContainer}>
|
|
<SpottableBtn
|
|
className={classNames(
|
|
css.subtitleButton,
|
|
videoVerticalVisible && css.videoVericalSubtitleButton
|
|
)}
|
|
onClick={handleSubtitleOnClick}
|
|
spotlightId="player-subtitlebutton"
|
|
onSpotlightUp={onSpotlightMoveBackButton}
|
|
onSpotlightLeft={onSpotlightMoveBackButton}
|
|
onSpotlightRight={onSpotlightMoveMediaButton}
|
|
onSpotlightDown={onSpotlightMoveMediaButton}
|
|
aria-label="Caption"
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className={css.overlayHeader}>
|
|
<SpottableBtn
|
|
onClick={onClickBack}
|
|
className={css.backIcon}
|
|
spotlightId="player-back-button"
|
|
onSpotlightDown={
|
|
tabContainerVersion === 2 && belowContentsVisible
|
|
? onSpotlightMoveBelowTab
|
|
: onSpotlightMoveMediaButton
|
|
}
|
|
onSpotlightRight={onSpotlightMoveSubtitleButton}
|
|
onSpotlightUp={onSpotlightMoveSubtitleButton}
|
|
aria-label="Video Player Close"
|
|
ref={backBtnRef}
|
|
/>
|
|
<div
|
|
className={classNames(type === 'LIVE' && css.liveIcon)}
|
|
aria-label={type === 'LIVE' && 'Live Icon'}
|
|
/>
|
|
|
|
{partnerName && (
|
|
<CustomImage
|
|
src={patncLogoPath}
|
|
fallbackSrc={defaultLogoImg}
|
|
alt={partnerName}
|
|
aria-label={partnerName}
|
|
/>
|
|
)}
|
|
|
|
<h2 className={css.patnerName}>{partnerName}</h2>
|
|
|
|
<Marquee
|
|
className={classNames(css.title, videoVerticalVisible && css.videoVerticalMarquee)}
|
|
marqueeOn="render"
|
|
>
|
|
{showName}
|
|
</Marquee>
|
|
</div>
|
|
</Container>
|
|
{type === 'VOD' && disclaimer && (
|
|
<div className={css.disclaimer}>
|
|
<span className={css.icon} />
|
|
<h3 aria-label={disclaimer}>{disclaimer}</h3>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
const propsAreEqual = (prev, next) => {
|
|
return (
|
|
prev.type === next.type &&
|
|
prev.panelInfo?.showId === next.panelInfo?.showId &&
|
|
prev.disclaimer === next.disclaimer &&
|
|
prev.playListInfo === next.playListInfo &&
|
|
prev.captionEnable === next.captionEnable &&
|
|
prev.selectedIndex === next.selectedIndex &&
|
|
prev.videoVerticalVisible === next.videoVerticalVisible &&
|
|
prev.sideContentsVisible === next.sideContentsVisible &&
|
|
prev.belowContentsVisible === next.belowContentsVisible &&
|
|
prev.tabContainerVersion === next.tabContainerVersion &&
|
|
prev.tabIndexV2 === next.tabIndexV2 &&
|
|
prev.forceShowMediaOverlay === next.forceShowMediaOverlay
|
|
);
|
|
};
|
|
|
|
export default React.memo(PlayerOverlayContents, propsAreEqual);
|