[251115] fix: DetailPanel FullScree issue

🕐 커밋 시간: 2025. 11. 15. 20:10:38

📊 변경 통계:
  • 총 파일: 7개
  • 추가: +134줄
  • 삭제: -63줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/homeActions.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx
  ~ com.twin.app.shoptime/src/reducers/homeReducer.js
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/homeActions.js (javascript):
     Added: setDefaultFocus()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript):
    🔄 Modified: normalizeModalStyle()

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
This commit is contained in:
2025-11-15 20:10:39 +09:00
parent 2ac217fb10
commit 70381438ac
7 changed files with 345 additions and 286 deletions

View File

@@ -112,6 +112,7 @@ export const types = {
CHECK_ENTER_THROUGH_GNB: 'CHECK_ENTER_THROUGH_GNB',
SET_DEFAULT_FOCUS: 'SET_DEFAULT_FOCUS',
SET_BANNER_INDEX: 'SET_BANNER_INDEX',
SET_VIDEO_TRANSITION_LOCK: 'SET_VIDEO_TRANSITION_LOCK',
RESET_HOME_INFO: 'RESET_HOME_INFO',
UPDATE_HOME_INFO: 'UPDATE_HOME_INFO',

View File

@@ -483,6 +483,11 @@ export const setDefaultFocus = (focus) => ({
payload: focus,
});
export const setVideoTransitionLock = (isLocked) => ({
type: types.SET_VIDEO_TRANSITION_LOCK,
payload: Boolean(isLocked),
});
export const checkEnterThroughGNB = (boolean) => ({
type: types.CHECK_ENTER_THROUGH_GNB,
payload: boolean,

View File

@@ -1,21 +1,21 @@
import React, { useCallback, useMemo, useRef, useEffect } from "react";
import React, { useCallback, useMemo, useRef, useEffect } from 'react';
import ReactPlayer from "react-player";
import ReactPlayer from 'react-player';
import handle from "@enact/core/handle";
import handle from '@enact/core/handle';
var handledMediaEventsMap = [
"onReady",
"onStart",
"onPlay",
"onProgress",
"onDuration",
"onPause",
"onBuffer",
"onBufferEnd",
"onSeek",
"onEnded",
"onError",
'onReady',
'onStart',
'onPlay',
'onProgress',
'onDuration',
'onPause',
'onBuffer',
'onBufferEnd',
'onSeek',
'onEnded',
'onError',
];
export default function TReactPlayer({
@@ -29,21 +29,21 @@ export default function TReactPlayer({
const handleEvent = useCallback(
(type) => (ev) => {
if (type === "onReady") {
if (type === 'onReady') {
if (videoRef) {
const videoNode = playerRef.current.getInternalPlayer();
videoRef(videoNode);
const iframeEl =
typeof playerRef.current?.getInternalPlayer === "function"
? playerRef.current.getInternalPlayer("iframe")
typeof playerRef.current?.getInternalPlayer === 'function'
? playerRef.current.getInternalPlayer('iframe')
: null;
if (iframeEl) {
iframeEl.setAttribute("tabIndex", "-1");
iframeEl.setAttribute("aria-hidden", "true");
iframeEl.setAttribute('tabIndex', '-1');
iframeEl.setAttribute('aria-hidden', 'true');
}
if (
videoNode.tagName &&
!Object.prototype.hasOwnProperty.call(videoNode, "proportionPlayed")
!Object.prototype.hasOwnProperty.call(videoNode, 'proportionPlayed')
) {
Object.defineProperties(videoNode, {
error: {
@@ -60,8 +60,7 @@ export default function TReactPlayer({
get: function () {
return (
videoNode.buffered.length &&
videoNode.buffered.end(videoNode.buffered.length - 1) /
videoNode.duration
videoNode.buffered.end(videoNode.buffered.length - 1) / videoNode.duration
);
},
},
@@ -71,9 +70,7 @@ export default function TReactPlayer({
},
},
});
} else if (
!Object.prototype.hasOwnProperty.call(videoNode, "proportionPlayed")
) {
} else if (!Object.prototype.hasOwnProperty.call(videoNode, 'proportionPlayed')) {
videoNode.play = videoNode.playVideo;
videoNode.pause = videoNode.pauseVideo;
videoNode.seek = videoNode.seekTo;
@@ -130,9 +127,9 @@ export default function TReactPlayer({
});
}
}
handle.forward("onLoadStart", { type, ev }, rest);
handle.forward('onLoadStart', { type, ev }, rest);
}
handle.forward("onUpdate", { type, ev }, rest);
handle.forward('onUpdate', { type, ev }, rest);
},
[videoRef]
);
@@ -151,7 +148,7 @@ export default function TReactPlayer({
const videoNode = playerRef.current?.getInternalPlayer();
if (videoNode && videoNode.pause) {
videoNode.pause();
videoNode.src = "";
videoNode.src = '';
videoNode.srcObject = null;
}
};
@@ -162,6 +159,7 @@ export default function TReactPlayer({
ref={playerRef}
url={url}
progressInterval={1000}
config={rest.config}
{...handledMediaEvents}
{...rest}
playsinline // Add playsinline attribute here

View File

@@ -28,6 +28,7 @@ const initialState = {
termsIdMap: {}, // added new property to initialState
optionalTermsAvailable: false, // 선택약관 존재 여부
persistentVideoInfo: null, // 영구재생 비디오 정보
videoTransitionLocked: false,
};
export const homeReducer = (state = initialState, action) => {
@@ -192,6 +193,12 @@ export const homeReducer = (state = initialState, action) => {
};
}
case types.SET_VIDEO_TRANSITION_LOCK:
return {
...state,
videoTransitionLocked: action.payload,
};
case types.CHECK_ENTER_THROUGH_GNB: {
return {
...state,

View File

@@ -15,7 +15,7 @@ import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.p
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
import liveShow from '../../../../assets/images/tag-liveshow.png';
import { changeAppStatus } from '../../../actions/commonActions';
import { updateHomeInfo } from '../../../actions/homeActions';
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
import { pushPanel } from '../../../actions/panelActions';
import {
@@ -83,8 +83,13 @@ export default function RandomUnit({
const [isFocused, setIsFocused] = useState(false);
const [videoError, setVideoError] = useState(false);
const [liveIndicies, setLiveIndicies] = useState([]);
const defaultFocus = useSelector((state) => state.home.defaultFocus);
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
const timerRef = useRef();
const keepTimerOnBlurRef = useRef(false);
const hasAutoPlayStartedRef = useRef(false);
const isDefaultAutoPlayTarget = defaultFocus === spotlightId;
const bannerDataRef = useRef(bannerData);
const randomDataRef = useRef(
bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0
@@ -92,12 +97,36 @@ export default function RandomUnit({
: null
);
// ✅ 현재 PlayerPanel의 modal 상태 추적
const playerPanelInfo = useSelector((state) => {
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
return playerPanel?.panelInfo;
});
const handleStartVideo = useCallback(
(videoProps) => {
dispatch(setVideoTransitionLock(true));
dispatch(startVideoPlayerNew(videoProps));
},
[dispatch]
);
const finishAndUnlock = useCallback(() => {
dispatch(finishVideoPreview());
dispatch(setVideoTransitionLock(false));
}, [dispatch]);
useEffect(() => {
if (isVideoTransitionLocked && isCurrentBannerVideoPlaying) {
dispatch(setVideoTransitionLock(false));
}
}, [dispatch, isCurrentBannerVideoPlaying, isVideoTransitionLocked]);
useEffect(() => {
if (isVideoTransitionLocked && videoError) {
dispatch(setVideoTransitionLock(false));
}
}, [dispatch, isVideoTransitionLocked, videoError]);
const topContentsLogInfo = useMemo(() => {
if (randomDataRef.current) {
const currentRandomData = randomDataRef.current;
@@ -403,8 +432,8 @@ export default function RandomUnit({
let action = linkType === 'DSP00507' ? startVideoPlayer : pushPanel;
const isNavigatingToDetail = linkInfo.name === panel_names.DETAIL_PANEL;
if (isNavigatingToDetail && playerPanelInfo?.modal !== false && isCurrentBannerVideoPlaying) {
dispatch(finishVideoPreview());
if (isNavigatingToDetail && playerPanelInfo?.modal !== false) {
finishAndUnlock();
}
dispatch(action(linkInfo));
@@ -428,13 +457,13 @@ export default function RandomUnit({
topContentsLogInfo,
playerPanelInfo?.modal,
sendBannerLog,
isCurrentBannerVideoPlaying,
finishAndUnlock,
]);
// 투데이즈딜 클릭
const todayDealClick = useCallback(() => {
if (playerPanelInfo?.modal !== false && isCurrentBannerVideoPlaying) {
dispatch(finishVideoPreview());
if (playerPanelInfo?.modal !== false) {
finishAndUnlock();
}
dispatch(
@@ -463,7 +492,7 @@ export default function RandomUnit({
topContentsLogInfo,
sendBannerLog,
playerPanelInfo?.modal,
isCurrentBannerVideoPlaying,
finishAndUnlock,
]);
// 비디오 클릭
@@ -491,8 +520,7 @@ export default function RandomUnit({
playerPanelInfo?.modal
);
dispatch(
startVideoPlayerNew({
handleStartVideo({
bannerId: spotlightId,
showUrl: randomData.showUrl,
patnrId: randomData.patnrId,
@@ -503,8 +531,7 @@ export default function RandomUnit({
modal: false,
modalContainerId: spotlightId,
modalClassName: css.videoModal,
})
);
});
dispatch(
sendLogTopContents({
@@ -526,6 +553,7 @@ export default function RandomUnit({
onBlur,
playerPanelInfo?.modal,
dispatch,
handleStartVideo,
]);
// 투데이즈 딜 가격 정보
@@ -574,10 +602,12 @@ export default function RandomUnit({
useEffect(() => {
if (isFocused && !videoError) {
timerRef.current = setTimeout(
() =>
dispatch(
startVideoPlayerNew({
if (timerRef.current) {
clearTimeout(timerRef.current);
}
keepTimerOnBlurRef.current = isDefaultAutoPlayTarget && !hasAutoPlayStartedRef.current;
timerRef.current = setTimeout(() => {
handleStartVideo({
bannerId: spotlightId,
showUrl: randomData.showUrl,
patnrId: randomData.patnrId,
@@ -589,19 +619,38 @@ export default function RandomUnit({
modalContainerId: spotlightId,
modalClassName: css.videoModal,
isVerticalModal: !isHorizontal,
})
),
1000
);
});
if (isDefaultAutoPlayTarget) {
hasAutoPlayStartedRef.current = true;
}
keepTimerOnBlurRef.current = false;
timerRef.current = null;
}, 1000);
}
if (!isFocused) {
setVideoError(false);
// dispatch(finishVideoPreview());
if (timerRef.current && !keepTimerOnBlurRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
keepTimerOnBlurRef.current = false;
};
}, [isFocused]);
}, [
isFocused,
videoError,
isHorizontal,
randomData,
dispatch,
isDefaultAutoPlayTarget,
spotlightId,
handleStartVideo,
]);
useEffect(() => {
if (isFocused && broadcast?.type === 'videoError') {

View File

@@ -1,16 +1,7 @@
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 { useDispatch, useSelector } from 'react-redux';
import { applyMiddleware } from 'redux';
import Spotlight from '@enact/spotlight';
@@ -33,14 +24,8 @@ import {
getHomeMainContents,
updateHomeInfo,
} from '../../actions/homeActions';
import {
sendLogGNB,
sendLogTotalRecommend,
} from '../../actions/logActions';
import {
getSubCategory,
getTop20Show,
} from '../../actions/mainActions';
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
import { getSubCategory, getTop20Show } from '../../actions/mainActions';
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
import { updatePanel } from '../../actions/panelActions';
import {
@@ -53,8 +38,7 @@ import TBody from '../../components/TBody/TBody';
import TButton, { TYPES } from '../../components/TButton/TButton';
import TPanel from '../../components/TPanel/TPanel';
import TPopUp from '../../components/TPopUp/TPopUp';
import TVerticalPagenator
from '../../components/TVerticalPagenator/TVerticalPagenator';
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
import useDebugKey from '../../hooks/useDebugKey';
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
import usePrevious from '../../hooks/usePrevious';
@@ -193,6 +177,7 @@ const HomePanel = ({ isOnTop }) => {
const focusedContainerIdRef = usePrevious(focusedContainerId);
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
const onCancel = useCallback(() => {
const currentSpot = Spotlight.getCurrent();
@@ -225,7 +210,7 @@ const HomePanel = ({ isOnTop }) => {
visible: false,
})
);
}, [dispatch]);
}, [dispatch, isVideoTransitionLocked]);
const onClose = useCallback(() => {
dispatch(setHidePopup());
@@ -482,7 +467,8 @@ const HomePanel = ({ isOnTop }) => {
}, []);
// ✅ useCallback: 의존성은 dispatch만
const _onScroll = useCallback((e) => {
const _onScroll = useCallback(
(e) => {
const currentScrollTop = e.scrollTop;
const prevScrollTop = prevScrollTopRef.current;
@@ -519,7 +505,9 @@ const HomePanel = ({ isOnTop }) => {
if (currentScrollTop > prevScrollTop) {
// 아래로 스크롤: 비디오를 1px로 축소
// console.log('[HomePanel] Scrolling down - shrinking video');
if (!isVideoTransitionLocked) {
dispatch(shrinkVideoTo1px());
}
// 기존 타이머 취소
if (scrollExpandTimerRef.current) {
clearTimeout(scrollExpandTimerRef.current);
@@ -544,7 +532,9 @@ const HomePanel = ({ isOnTop }) => {
// 이전 scrollTop 업데이트
prevScrollTopRef.current = currentScrollTop;
}, [dispatch]);
},
[dispatch]
);
const _onFocusedContainerId = useCallback(
(containerId) => {
@@ -584,7 +574,16 @@ const HomePanel = ({ isOnTop }) => {
}, 0);
}
},
[panelInfo, firstSpot, enterThroughGNB, defaultFocus, cbChangePageRef, dispatch, isOnTop, panels]
[
panelInfo,
firstSpot,
enterThroughGNB,
defaultFocus,
cbChangePageRef,
dispatch,
isOnTop,
panels,
]
);
const bestSellerLoaded = useCallback(() => {

View File

@@ -941,7 +941,7 @@ const MediaPanel = React.forwardRef(
// fullscreen 전환 완료 후 플래그 초기화
isTransitioningToFullscreen.current = false;
};
}, [panelInfo?.modal, isOnTop]);
}, [isOnTop]);
useEffect(() => {
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {