[251012] fix: PlayerPanel,TabContainerV2 focus - 1
🕐 커밋 시간: 2025. 10. 12. 18:13:40 📊 변경 통계: • 총 파일: 7개 • 추가: +169줄 • 삭제: -25줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx (javascript): ✅ Added: onSpotlightMoveBelowTab() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -710,6 +710,10 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
reactPlayerConfig: PropTypes.any, //for ReactPlayer
|
reactPlayerConfig: PropTypes.any, //for ReactPlayer
|
||||||
qrCurrentItem: PropTypes.any,
|
qrCurrentItem: PropTypes.any,
|
||||||
modalScale: PropTypes.number,
|
modalScale: PropTypes.number,
|
||||||
|
setBelowContentsVisible: PropTypes.func,
|
||||||
|
belowContentsVisible: PropTypes.bool,
|
||||||
|
tabContainerVersion: PropTypes.number,
|
||||||
|
tabIndexV2: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextType = FloatingLayerContext;
|
static contextType = FloatingLayerContext;
|
||||||
@@ -854,6 +858,29 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.showControls();
|
this.showControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TabContainerV2와 mediaControls 동기화
|
||||||
|
if (
|
||||||
|
this.props.tabContainerVersion === 2 &&
|
||||||
|
this.props.belowContentsVisible !== undefined &&
|
||||||
|
prevProps.belowContentsVisible !== this.props.belowContentsVisible
|
||||||
|
) {
|
||||||
|
console.log('[VideoPlayer] belowContentsVisible 변경 감지:', {
|
||||||
|
prev: prevProps.belowContentsVisible,
|
||||||
|
current: this.props.belowContentsVisible,
|
||||||
|
mediaControlsVisible: this.state.mediaControlsVisible,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.props.belowContentsVisible && !this.state.mediaControlsVisible) {
|
||||||
|
// TabContainerV2가 표시될 때 controls도 표시
|
||||||
|
console.log('[VideoPlayer] TabContainerV2 표시 - controls 강제 표시');
|
||||||
|
this.showControls();
|
||||||
|
} else if (!this.props.belowContentsVisible && this.state.mediaControlsVisible) {
|
||||||
|
// TabContainerV2가 숨겨질 때 controls도 숨김
|
||||||
|
console.log('[VideoPlayer] TabContainerV2 숨김 - controls 숨김');
|
||||||
|
this.hideControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
(!this.state.mediaControlsVisible &&
|
(!this.state.mediaControlsVisible &&
|
||||||
prevState.mediaControlsVisible !== this.state.mediaControlsVisible) ||
|
prevState.mediaControlsVisible !== this.state.mediaControlsVisible) ||
|
||||||
@@ -1016,6 +1043,13 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
// If this.state.more is used as a reference for when this function should fire, timing for
|
// If this.state.more is used as a reference for when this function should fire, timing for
|
||||||
// detection of when "more" is pressed vs when the state is updated is mismatched. Using an
|
// detection of when "more" is pressed vs when the state is updated is mismatched. Using an
|
||||||
// instance variable that's only set and used for this express purpose seems cleanest.
|
// instance variable that's only set and used for this express purpose seems cleanest.
|
||||||
|
|
||||||
|
// TabContainerV2가 표시 중이면 자동으로 닫지 않음
|
||||||
|
if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) {
|
||||||
|
console.log('[VideoPlayer] TabContainerV2 표시 중 - autoClose 비활성화');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.autoCloseTimeout && !this.props.sideContentsVisible) {
|
if (this.props.autoCloseTimeout && !this.props.sideContentsVisible) {
|
||||||
this.autoCloseJob.startAfter(this.props.autoCloseTimeout);
|
this.autoCloseJob.startAfter(this.props.autoCloseTimeout);
|
||||||
}
|
}
|
||||||
@@ -1947,10 +1981,13 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabContainerV2 토글
|
// tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글
|
||||||
if (this.props.setBelowContentsVisible && this.props.belowContentsVisible !== undefined) {
|
if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) {
|
||||||
console.log('[VideoPlayer] belowContentsVisible 토글:', !this.props.belowContentsVisible);
|
const willShowControls = !this.state.mediaControlsVisible;
|
||||||
this.props.setBelowContentsVisible(!this.props.belowContentsVisible);
|
console.log('[VideoPlayer] 클릭 - 상태 동기화 토글:', willShowControls);
|
||||||
|
|
||||||
|
// belowContentsVisible을 먼저 변경 (componentDidUpdate에서 mediaControls 동기화됨)
|
||||||
|
this.props.setBelowContentsVisible(willShowControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toggleControls();
|
this.toggleControls();
|
||||||
@@ -2152,6 +2189,8 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
isYoutube,
|
isYoutube,
|
||||||
sideContentsVisible,
|
sideContentsVisible,
|
||||||
setSideContentsVisible,
|
setSideContentsVisible,
|
||||||
|
belowContentsVisible,
|
||||||
|
tabContainerVersion,
|
||||||
disclaimer,
|
disclaimer,
|
||||||
liveTotalTime,
|
liveTotalTime,
|
||||||
currentLiveTimeSeconds,
|
currentLiveTimeSeconds,
|
||||||
@@ -2196,6 +2235,10 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
delete mediaProps.thumbnailUnavailable;
|
delete mediaProps.thumbnailUnavailable;
|
||||||
delete mediaProps.titleHideDelay;
|
delete mediaProps.titleHideDelay;
|
||||||
delete mediaProps.videoPath;
|
delete mediaProps.videoPath;
|
||||||
|
delete mediaProps.setBelowContentsVisible;
|
||||||
|
delete mediaProps.belowContentsVisible;
|
||||||
|
delete mediaProps.tabContainerVersion;
|
||||||
|
delete mediaProps.tabIndexV2;
|
||||||
|
|
||||||
mediaProps.autoPlay = !noAutoPlay;
|
mediaProps.autoPlay = !noAutoPlay;
|
||||||
mediaProps.className = type !== 'MEDIA' ? css.video : css.media;
|
mediaProps.className = type !== 'MEDIA' ? css.video : css.media;
|
||||||
@@ -2393,6 +2436,9 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
setIsSubtitleActive={setIsSubtitleActive}
|
setIsSubtitleActive={setIsSubtitleActive}
|
||||||
sideContentsVisible={sideContentsVisible}
|
sideContentsVisible={sideContentsVisible}
|
||||||
setSideContentsVisible={setSideContentsVisible}
|
setSideContentsVisible={setSideContentsVisible}
|
||||||
|
belowContentsVisible={belowContentsVisible}
|
||||||
|
tabContainerVersion={tabContainerVersion}
|
||||||
|
tabIndexV2={this.props.tabIndexV2}
|
||||||
videoVerticalVisible={videoVerticalVisible}
|
videoVerticalVisible={videoVerticalVisible}
|
||||||
handleIndicatorUpClick={handleIndicatorUpClick}
|
handleIndicatorUpClick={handleIndicatorUpClick}
|
||||||
handleIndicatorDownClick={handleIndicatorDownClick}
|
handleIndicatorDownClick={handleIndicatorDownClick}
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Spotlight from "@enact/spotlight";
|
import Spotlight from '@enact/spotlight';
|
||||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from "@enact/spotlight/Spottable";
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
import Marquee from "@enact/ui/Marquee";
|
import Marquee from '@enact/ui/Marquee';
|
||||||
|
|
||||||
import defaultLogoImg from "../../../../assets/images/ic-tab-partners-default@3x.png";
|
import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
||||||
import { setShowPopup } from "../../../actions/commonActions";
|
import { setShowPopup } from '../../../actions/commonActions';
|
||||||
import CustomImage from "../../../components/CustomImage/CustomImage";
|
import CustomImage from '../../../components/CustomImage/CustomImage';
|
||||||
import { ACTIVE_POPUP } from "../../../utils/Config";
|
import { ACTIVE_POPUP } from '../../../utils/Config';
|
||||||
import { SpotlightIds } from "../../../utils/SpotlightIds";
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
import PlayerTabButton from "../PlayerTabContents/TabButton/PlayerTabButton";
|
import PlayerTabButton from '../PlayerTabContents/TabButton/PlayerTabButton';
|
||||||
import css from "./PlayerOverlayContents.module.less";
|
import css from './PlayerOverlayContents.module.less';
|
||||||
|
|
||||||
const SpottableBtn = Spottable("button");
|
const SpottableBtn = Spottable('button');
|
||||||
|
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator({ enterTo: 'default-element' }, 'div');
|
||||||
{ enterTo: "default-element" },
|
|
||||||
"div"
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function PlayerOverlayContents({
|
export default function PlayerOverlayContents({
|
||||||
type,
|
type,
|
||||||
@@ -35,8 +32,11 @@ export default function PlayerOverlayContents({
|
|||||||
videoVerticalVisible,
|
videoVerticalVisible,
|
||||||
sideContentsVisible,
|
sideContentsVisible,
|
||||||
setSideContentsVisible,
|
setSideContentsVisible,
|
||||||
|
belowContentsVisible,
|
||||||
handleIndicatorUpClick,
|
handleIndicatorUpClick,
|
||||||
handleIndicatorDownClick,
|
handleIndicatorDownClick,
|
||||||
|
tabContainerVersion,
|
||||||
|
tabIndexV2,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
|
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
|
||||||
@@ -49,7 +49,7 @@ export default function PlayerOverlayContents({
|
|||||||
const backBtnRef = useRef(null);
|
const backBtnRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type === "MEDIA" && !panelInfo.modal && backBtnRef.current) {
|
if (type === 'MEDIA' && !panelInfo.modal && backBtnRef.current) {
|
||||||
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||||
}
|
}
|
||||||
}, [panelInfo?.shptmBanrTpNm, panelInfo.modal, backBtnRef.current]);
|
}, [panelInfo?.shptmBanrTpNm, panelInfo.modal, backBtnRef.current]);
|
||||||
@@ -64,7 +64,7 @@ export default function PlayerOverlayContents({
|
|||||||
|
|
||||||
const patncLogoPath = useMemo(() => {
|
const patncLogoPath = useMemo(() => {
|
||||||
let logo = playListInfo[selectedIndex]?.patncLogoPath;
|
let logo = playListInfo[selectedIndex]?.patncLogoPath;
|
||||||
if (type === "MEDIA") {
|
if (type === 'MEDIA') {
|
||||||
logo = panelInfo?.patncLogoPath;
|
logo = panelInfo?.patncLogoPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ export default function PlayerOverlayContents({
|
|||||||
|
|
||||||
const partnerName = useMemo(() => {
|
const partnerName = useMemo(() => {
|
||||||
let name = playListInfo[selectedIndex]?.patncNm;
|
let name = playListInfo[selectedIndex]?.patncNm;
|
||||||
if (type === "MEDIA") {
|
if (type === 'MEDIA') {
|
||||||
name = panelInfo?.patncNm;
|
name = panelInfo?.patncNm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,11 +82,11 @@ export default function PlayerOverlayContents({
|
|||||||
|
|
||||||
const showName = useMemo(() => {
|
const showName = useMemo(() => {
|
||||||
let name = playListInfo[selectedIndex]?.showNm;
|
let name = playListInfo[selectedIndex]?.showNm;
|
||||||
if (type === "MEDIA") {
|
if (type === 'MEDIA') {
|
||||||
name = panelInfo?.showNm;
|
name = panelInfo?.showNm;
|
||||||
}
|
}
|
||||||
|
|
||||||
return name ? name.replace(/<br\s*\/?>/gi, " ") : "";
|
return name ? name.replace(/<br\s*\/?>/gi, ' ') : '';
|
||||||
}, [playListInfo, selectedIndex, panelInfo]);
|
}, [playListInfo, selectedIndex, panelInfo]);
|
||||||
|
|
||||||
const onSpotlightMoveTabButton = (e) => {
|
const onSpotlightMoveTabButton = (e) => {
|
||||||
@@ -97,15 +97,15 @@ export default function PlayerOverlayContents({
|
|||||||
|
|
||||||
const onSpotlightMoveMediaButton = (e) => {
|
const onSpotlightMoveMediaButton = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (type === "LIVE") {
|
if (type === 'LIVE') {
|
||||||
return Spotlight.focus("videoIndicator-down-button");
|
return Spotlight.focus('videoIndicator-down-button');
|
||||||
}
|
}
|
||||||
Spotlight.focus("videoPlayer_mediaControls");
|
Spotlight.focus('videoPlayer_mediaControls');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSpotlightMoveSlider = useCallback(
|
const onSpotlightMoveSlider = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (type === "VOD") {
|
if (type === 'VOD') {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
|
Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
|
||||||
@@ -117,7 +117,24 @@ export default function PlayerOverlayContents({
|
|||||||
const onSpotlightMoveSideTab = (e) => {
|
const onSpotlightMoveSideTab = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
Spotlight.focus("tab-0");
|
Spotlight.focus('tab-0');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSpotlightMoveBelowTab = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// tabIndexV2에 따라 다른 버튼으로 포커스 이동
|
||||||
|
if (tabIndexV2 === 0) {
|
||||||
|
// ShopNow 탭: Close 버튼으로
|
||||||
|
Spotlight.focus('below-tab-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');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSpotlightMoveBackButton = () => {
|
const onSpotlightMoveBackButton = () => {
|
||||||
@@ -125,11 +142,16 @@ export default function PlayerOverlayContents({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentSideButtonStatus = useMemo(() => {
|
const currentSideButtonStatus = useMemo(() => {
|
||||||
if (type !== "MEDIA" && !panelInfo?.modal && !sideContentsVisible) {
|
if (
|
||||||
|
type !== 'MEDIA' &&
|
||||||
|
!panelInfo?.modal &&
|
||||||
|
!sideContentsVisible &&
|
||||||
|
tabContainerVersion === 1
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}, [panelInfo, sideContentsVisible]);
|
}, [panelInfo, sideContentsVisible, tabContainerVersion]);
|
||||||
|
|
||||||
const noLiveContentsVisible = useMemo(() => {
|
const noLiveContentsVisible = useMemo(() => {
|
||||||
if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
|
if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
|
||||||
@@ -147,54 +169,63 @@ export default function PlayerOverlayContents({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container className={css.overlayContainer}>
|
<Container className={css.overlayContainer}>
|
||||||
{type !== "MEDIA" &&
|
{type !== 'MEDIA' && playListInfo.length > 1 && noLiveContentsVisible && (
|
||||||
playListInfo.length > 1 &&
|
<>
|
||||||
noLiveContentsVisible && (
|
<div className={css.indicatorUpButton}>
|
||||||
<>
|
<SpottableBtn
|
||||||
<div className={css.indicatorUpButton}>
|
onClick={handleIndicatorUpClick}
|
||||||
<SpottableBtn
|
spotlightId="videoIndicator-up-button"
|
||||||
onClick={handleIndicatorUpClick}
|
onSpotlightRight={
|
||||||
spotlightId="videoIndicator-up-button"
|
videoVerticalVisible
|
||||||
onSpotlightRight={
|
? onSpotlightMoveSideTab
|
||||||
videoVerticalVisible
|
: tabContainerVersion === 1
|
||||||
? onSpotlightMoveSideTab
|
? onSpotlightMoveTabButton
|
||||||
: onSpotlightMoveTabButton
|
: undefined
|
||||||
}
|
}
|
||||||
onSpotlightDown={onSpotlightMoveSlider}
|
onSpotlightDown={
|
||||||
aria-label="Previous channel"
|
tabContainerVersion === 2 && belowContentsVisible
|
||||||
/>
|
? onSpotlightMoveBelowTab
|
||||||
</div>
|
: onSpotlightMoveSlider
|
||||||
<div className={css.indicatorDownButton}>
|
}
|
||||||
<SpottableBtn
|
aria-label="Previous channel"
|
||||||
onClick={handleIndicatorDownClick}
|
/>
|
||||||
spotlightId="videoIndicator-down-button"
|
</div>
|
||||||
onSpotlightLeft={onSpotlightMoveSlider}
|
<div className={css.indicatorDownButton}>
|
||||||
onSpotlightUp={onSpotlightMoveSlider}
|
<SpottableBtn
|
||||||
onSpotlightRight={
|
onClick={handleIndicatorDownClick}
|
||||||
videoVerticalVisible
|
spotlightId="videoIndicator-down-button"
|
||||||
? onSpotlightMoveSideTab
|
onSpotlightLeft={onSpotlightMoveSlider}
|
||||||
: onSpotlightMoveSlider
|
onSpotlightUp={onSpotlightMoveSlider}
|
||||||
}
|
onSpotlightRight={
|
||||||
aria-label="Next channel"
|
videoVerticalVisible
|
||||||
/>
|
? onSpotlightMoveSideTab
|
||||||
</div>
|
: tabContainerVersion === 1
|
||||||
</>
|
? onSpotlightMoveTabButton
|
||||||
)}
|
: undefined
|
||||||
|
}
|
||||||
|
onSpotlightDown={
|
||||||
|
tabContainerVersion === 2 && belowContentsVisible
|
||||||
|
? onSpotlightMoveBelowTab
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
aria-label="Next channel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentSideButtonStatus && !videoVerticalVisible && (
|
{currentSideButtonStatus && !videoVerticalVisible && (
|
||||||
<PlayerTabButton
|
<PlayerTabButton
|
||||||
setSideContentsVisible={setSideContentsVisible}
|
setSideContentsVisible={setSideContentsVisible}
|
||||||
sideContentsVisible={sideContentsVisible}
|
sideContentsVisible={sideContentsVisible}
|
||||||
onSpotlightLeft={
|
onSpotlightLeft={
|
||||||
type !== "MEDIA" &&
|
type !== 'MEDIA' && playListInfo.length < 2 && onSpotlightMoveBackButton
|
||||||
playListInfo.length < 2 &&
|
|
||||||
onSpotlightMoveBackButton
|
|
||||||
}
|
}
|
||||||
videoType={type}
|
videoType={type}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{cntry_cd === "US" && (
|
{cntry_cd === 'US' && (
|
||||||
<div className={css.videoButtonContainer}>
|
<div className={css.videoButtonContainer}>
|
||||||
<SpottableBtn
|
<SpottableBtn
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -214,13 +245,17 @@ export default function PlayerOverlayContents({
|
|||||||
onClick={onClickBack}
|
onClick={onClickBack}
|
||||||
className={css.backIcon}
|
className={css.backIcon}
|
||||||
spotlightId="player-back-button"
|
spotlightId="player-back-button"
|
||||||
onSpotlightDown={onSpotlightMoveMediaButton}
|
onSpotlightDown={
|
||||||
|
tabContainerVersion === 2 && belowContentsVisible
|
||||||
|
? onSpotlightMoveBelowTab
|
||||||
|
: onSpotlightMoveMediaButton
|
||||||
|
}
|
||||||
aria-label="Video Player Close"
|
aria-label="Video Player Close"
|
||||||
ref={backBtnRef}
|
ref={backBtnRef}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classNames(type === "LIVE" && css.liveIcon)}
|
className={classNames(type === 'LIVE' && css.liveIcon)}
|
||||||
aria-label={type === "LIVE" && "Live Icon"}
|
aria-label={type === 'LIVE' && 'Live Icon'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{partnerName && (
|
{partnerName && (
|
||||||
@@ -235,17 +270,14 @@ export default function PlayerOverlayContents({
|
|||||||
<h2 className={css.patnerName}>{partnerName}</h2>
|
<h2 className={css.patnerName}>{partnerName}</h2>
|
||||||
|
|
||||||
<Marquee
|
<Marquee
|
||||||
className={classNames(
|
className={classNames(css.title, videoVerticalVisible && css.videoVerticalMarquee)}
|
||||||
css.title,
|
|
||||||
videoVerticalVisible && css.videoVerticalMarquee
|
|
||||||
)}
|
|
||||||
marqueeOn="render"
|
marqueeOn="render"
|
||||||
>
|
>
|
||||||
{showName}
|
{showName}
|
||||||
</Marquee>
|
</Marquee>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
{type === "VOD" && disclaimer && (
|
{type === 'VOD' && disclaimer && (
|
||||||
<div className={css.disclaimer}>
|
<div className={css.disclaimer}>
|
||||||
<span className={css.icon} />
|
<span className={css.icon} />
|
||||||
<h3 aria-label={disclaimer}>{disclaimer}</h3>
|
<h3 aria-label={disclaimer}>{disclaimer}</h3>
|
||||||
|
|||||||
@@ -971,7 +971,11 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!panelInfo.modal && !videoVerticalVisible && !hasProperSpot) {
|
if (!panelInfo.modal && !videoVerticalVisible && !hasProperSpot) {
|
||||||
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
if (tabContainerVersion === 1) {
|
||||||
|
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
||||||
|
} else if (tabContainerVersion === 2) {
|
||||||
|
Spotlight.focus('below-tab-live-channel-button');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//비디오 진입시 포커스
|
//비디오 진입시 포커스
|
||||||
@@ -986,8 +990,8 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
panelInfo.modal,
|
panelInfo.modal,
|
||||||
panelInfo.isUpdatedByClick,
|
panelInfo.isUpdatedByClick,
|
||||||
panelInfo.isIndicatorByClick,
|
panelInfo.isIndicatorByClick,
|
||||||
|
|
||||||
panelInfo.shptmBanrTpNm,
|
panelInfo.shptmBanrTpNm,
|
||||||
|
tabContainerVersion,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 최상단 패널 정보 (여러 useMemo에서 공통으로 사용)
|
// 최상단 패널 정보 (여러 useMemo에서 공통으로 사용)
|
||||||
@@ -1943,10 +1947,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
console.log('[PlayerPanel] isOnTop true - 오버레이 표시');
|
console.log('[PlayerPanel] isOnTop true - 오버레이 표시');
|
||||||
setSideContentsVisible(true);
|
setSideContentsVisible(true);
|
||||||
setBelowContentsVisible(true);
|
setBelowContentsVisible(true);
|
||||||
|
// VideoPlayer가 belowContentsVisible prop을 감지해서 자동으로 controls 표시함
|
||||||
if (videoPlayer.current?.showControls) {
|
|
||||||
videoPlayer.current.showControls();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [isOnTop, panelInfo.modal, videoVerticalVisible]);
|
}, [isOnTop, panelInfo.modal, videoVerticalVisible]);
|
||||||
|
|
||||||
@@ -2165,12 +2166,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
noAutoPlay={cannotPlay}
|
noAutoPlay={cannotPlay}
|
||||||
autoCloseTimeout={3000}
|
autoCloseTimeout={3000}
|
||||||
onBackButton={onClickBack}
|
onBackButton={onClickBack}
|
||||||
spotlightDisabled={
|
spotlightDisabled={panelInfo.modal}
|
||||||
(!videoVerticalVisible &&
|
|
||||||
panelInfo?.shptmBanrTpNm !== 'MEDIA' &&
|
|
||||||
sideContentsVisible) ||
|
|
||||||
panelInfo.modal
|
|
||||||
}
|
|
||||||
isYoutube={isYoutube}
|
isYoutube={isYoutube}
|
||||||
src={currentPlayingUrl}
|
src={currentPlayingUrl}
|
||||||
style={panelInfo.modal ? modalStyle : {}}
|
style={panelInfo.modal ? modalStyle : {}}
|
||||||
@@ -2211,6 +2207,8 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
setCurrentTime={setCurrentTime}
|
setCurrentTime={setCurrentTime}
|
||||||
setIsVODPaused={setIsVODPaused}
|
setIsVODPaused={setIsVODPaused}
|
||||||
broadcast={broadcast}
|
broadcast={broadcast}
|
||||||
|
tabContainerVersion={tabContainerVersion}
|
||||||
|
tabIndexV2={tabIndexV2}
|
||||||
>
|
>
|
||||||
{typeof window === 'object' && window.PalmSystem && (
|
{typeof window === 'object' && window.PalmSystem && (
|
||||||
<source src={currentPlayingUrl} type={videoType} />
|
<source src={currentPlayingUrl} type={videoType} />
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ export default function LiveChannelContents({
|
|||||||
patnerName={patncNm}
|
patnerName={patncNm}
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
onFocus={handleFocus()}
|
onFocus={handleFocus()}
|
||||||
|
onSpotlightUp={
|
||||||
|
version === 2 && index === 0
|
||||||
|
? (e) => {
|
||||||
|
// v2에서 첫 번째 아이템일 때 위로 가면 LIVE CHANNEL 버튼으로
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
Spotlight.focus('below-tab-live-channel-button');
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
type={TYPES.liveHorizontal}
|
type={TYPES.liveHorizontal}
|
||||||
spotlightId={`tabChannel-video-${index}`}
|
spotlightId={`tabChannel-video-${index}`}
|
||||||
liveInfo={liveInfos[index]}
|
liveInfo={liveInfos[index]}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { Job } from '@enact/core/util';
|
import { Job } from '@enact/core/util';
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import { getContainerNode, setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
import { getContainerNode, setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
||||||
|
|
||||||
@@ -169,6 +170,17 @@ export default function ShopNowContents({
|
|||||||
productId={prdtId}
|
productId={prdtId}
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
onFocus={handleFocus()}
|
onFocus={handleFocus()}
|
||||||
|
spotlightId={`shop-now-item-${index}`}
|
||||||
|
onSpotlightUp={
|
||||||
|
version === 2 && index === 0
|
||||||
|
? (e) => {
|
||||||
|
// v2에서 첫 번째 아이템일 때 위로 가면 Close 버튼으로
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
Spotlight.focus('below-tab-close-button');
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
type={TYPES.horizontal}
|
type={TYPES.horizontal}
|
||||||
version={version}
|
version={version}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
|
import { SpotlightIds } from '../../../../utils/SpotlightIds';
|
||||||
import css from './ShopNowButton.module.less';
|
import css from './ShopNowButton.module.less';
|
||||||
|
|
||||||
const SpottableDiv = Spottable("div");
|
const SpottableDiv = Spottable('div');
|
||||||
|
|
||||||
export default function ShopNowButton({ onClick }) {
|
export default function ShopNowButton({ onClick }) {
|
||||||
return (
|
return (
|
||||||
<div className={css.container} spotlightId="shop-now-button-container">
|
<div className={css.container}>
|
||||||
<SpottableDiv className={css.shopNowButton} onClick={onClick}>
|
<SpottableDiv
|
||||||
|
className={css.shopNowButton}
|
||||||
|
onClick={onClick}
|
||||||
|
spotlightId="below-tab-shop-now-button"
|
||||||
|
onSpotlightUp={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span className={css.buttonText}>SHOP NOW</span>
|
<span className={css.buttonText}>SHOP NOW</span>
|
||||||
</SpottableDiv>
|
</SpottableDiv>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
import React, {
|
import React, { useCallback, useEffect } from 'react';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
import SpotlightContainerDecorator
|
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import icon_arrow_dwon
|
import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
|
||||||
from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
|
import icon_shop_now from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png';
|
||||||
import icon_shop_now
|
|
||||||
from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png';
|
|
||||||
import { LOG_MENU } from '../../../../utils/Config';
|
import { LOG_MENU } from '../../../../utils/Config';
|
||||||
import { $L } from '../../../../utils/helperMethods';
|
import { $L } from '../../../../utils/helperMethods';
|
||||||
|
import { SpotlightIds } from '../../../../utils/SpotlightIds';
|
||||||
import LiveChannelContents from '../TabContents/LiveChannelContents';
|
import LiveChannelContents from '../TabContents/LiveChannelContents';
|
||||||
import ShopNowContents from '../TabContents/ShopNowContents';
|
import ShopNowContents from '../TabContents/ShopNowContents';
|
||||||
import YouMayLikeContents from '../TabContents/YouMayLikeContents';
|
import YouMayLikeContents from '../TabContents/YouMayLikeContents';
|
||||||
import ShopNowButton from './ShopNowButton';
|
import ShopNowButton from './ShopNowButton';
|
||||||
import css from './TabContainer.v2.module.less';
|
import css from './TabContainer.v2.module.less';
|
||||||
|
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||||
{ enterTo: "last-focused" },
|
|
||||||
"div"
|
|
||||||
);
|
|
||||||
|
|
||||||
const SpottableDiv = Spottable("div");
|
const SpottableDiv = Spottable('div');
|
||||||
|
|
||||||
export default function TabContainerV2({
|
export default function TabContainerV2({
|
||||||
panelInfo,
|
panelInfo,
|
||||||
@@ -48,10 +40,8 @@ export default function TabContainerV2({
|
|||||||
tabVisible,
|
tabVisible,
|
||||||
}) {
|
}) {
|
||||||
const tabList = [
|
const tabList = [
|
||||||
$L("SHOP NOW"),
|
$L('SHOP NOW'),
|
||||||
panelInfo?.shptmBanrTpNm === "LIVE"
|
panelInfo?.shptmBanrTpNm === 'LIVE' ? $L('LIVE CHANNEL') : $L('FEATURED SHOWS'),
|
||||||
? $L("LIVE CHANNEL")
|
|
||||||
: $L("FEATURED SHOWS"),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -62,10 +52,8 @@ export default function TabContainerV2({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tabIndex === 1) {
|
if (tabIndex === 1) {
|
||||||
const isLive = panelInfo?.shptmBanrTpNm === "LIVE";
|
const isLive = panelInfo?.shptmBanrTpNm === 'LIVE';
|
||||||
nowMenu = isLive
|
nowMenu = isLive ? LOG_MENU.FULL_LIVE_CHANNELS : LOG_MENU.FULL_FEATURED_SHOWS;
|
||||||
? LOG_MENU.FULL_LIVE_CHANNELS
|
|
||||||
: LOG_MENU.FULL_FEATURED_SHOWS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nowMenu) {
|
if (nowMenu) {
|
||||||
@@ -87,7 +75,7 @@ export default function TabContainerV2({
|
|||||||
if (videoVerticalVisible) {
|
if (videoVerticalVisible) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
Spotlight.focus("spotlightId-video-contaienr");
|
Spotlight.focus('spotlightId-video-contaienr');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[videoVerticalVisible]
|
[videoVerticalVisible]
|
||||||
@@ -98,12 +86,22 @@ export default function TabContainerV2({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (onTabClose) {
|
if (onTabClose) {
|
||||||
onTabClose(1); // tabIndex를 -1로 설정
|
onTabClose(1); // tabIndex를 1로 설정
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onTabClose]
|
[onTabClose]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 위 방향 포커스 이동 시 백 버튼으로 이동
|
||||||
|
const handleSpotlightUpToBackButton = useCallback((e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// VideoPlayer가 belowContentsVisible prop을 감지해서 이미 controls를 표시했으므로
|
||||||
|
// 바로 포커스 이동
|
||||||
|
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// useEffect(()=>{
|
// useEffect(()=>{
|
||||||
// console.log('[tabIndex]',tabIndex)
|
// console.log('[tabIndex]',tabIndex)
|
||||||
// },[tabIndex])
|
// },[tabIndex])
|
||||||
@@ -122,17 +120,21 @@ export default function TabContainerV2({
|
|||||||
<div className={css.shopNowHeader}>
|
<div className={css.shopNowHeader}>
|
||||||
<div className={css.shopNowHeaderLeft}>
|
<div className={css.shopNowHeaderLeft}>
|
||||||
<div className={css.shopNowIconWrapper}>
|
<div className={css.shopNowIconWrapper}>
|
||||||
<img
|
<img src={icon_shop_now} alt="shop now icon" className={css.shopNowIcon} />
|
||||||
src={icon_shop_now}
|
|
||||||
alt="shop now icon"
|
|
||||||
className={css.shopNowIcon}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={css.shopNowHeaderText}>SHOP NOW</div>
|
<div className={css.shopNowHeaderText}>SHOP NOW</div>
|
||||||
</div>
|
</div>
|
||||||
<SpottableDiv
|
<SpottableDiv
|
||||||
className={css.closeButton}
|
className={css.closeButton}
|
||||||
onClick={handleCloseButtonClick}
|
onClick={handleCloseButtonClick}
|
||||||
|
spotlightId="below-tab-close-button"
|
||||||
|
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||||
|
onSpotlightDown={(e) => {
|
||||||
|
// 첫 번째 ShopNow 아이템으로 포커스 이동
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
Spotlight.focus('shop-now-item-0');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</SpottableDiv>
|
</SpottableDiv>
|
||||||
@@ -166,6 +168,11 @@ export default function TabContainerV2({
|
|||||||
className={css.liveChannelButton}
|
className={css.liveChannelButton}
|
||||||
onClick={onLiveChannelButtonClick}
|
onClick={onLiveChannelButtonClick}
|
||||||
spotlightId="below-tab-live-channel-button"
|
spotlightId="below-tab-live-channel-button"
|
||||||
|
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||||
|
onSpotlightDown={(e) => {
|
||||||
|
// 첫 번째 PlayerItem으로 포커스 이동
|
||||||
|
Spotlight.focus('tabChannel-video-0');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className={css.buttonText}>LIVE CHANNEL</span>
|
<span className={css.buttonText}>LIVE CHANNEL</span>
|
||||||
<div className={css.arrowIcon}>
|
<div className={css.arrowIcon}>
|
||||||
@@ -173,7 +180,7 @@ export default function TabContainerV2({
|
|||||||
</div>
|
</div>
|
||||||
</SpottableDiv>
|
</SpottableDiv>
|
||||||
|
|
||||||
{panelInfo?.shptmBanrTpNm === "LIVE" && playListInfo && (
|
{panelInfo?.shptmBanrTpNm === 'LIVE' && playListInfo && (
|
||||||
<LiveChannelContents
|
<LiveChannelContents
|
||||||
tabTitle={tabList}
|
tabTitle={tabList}
|
||||||
selectedIndex={selectedIndex}
|
selectedIndex={selectedIndex}
|
||||||
@@ -192,9 +199,7 @@ export default function TabContainerV2({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tabVisible && tabIndex === 2 && (
|
{tabVisible && tabIndex === 2 && <ShopNowButton onClick={onShopNowButtonClick} />}
|
||||||
<ShopNowButton onClick={onShopNowButtonClick} />
|
|
||||||
)}
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user