From b1c5664b984c50f4098ee0ed544aa2ffb5516d3f Mon Sep 17 00:00:00 2001 From: optrader Date: Fri, 14 Nov 2025 16:05:09 +0900 Subject: [PATCH] =?UTF-8?q?[251114]=20fix:=20HomePanel=20DetailPanel?= =?UTF-8?q?=EC=A0=84=ED=99=98=EC=8B=9C=20=EB=B9=84=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🕐 커밋 시간: 2025. 11. 14. 16:05:09 📊 변경 통계: • 총 파일: 4개 • 추가: +78줄 • 삭제: -12줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript): 🔄 Modified: extractProductMeta() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx (javascript): 🔄 Modified: Spottable() 📄 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() --- .../ProductAllSection/ProductAllSection.jsx | 32 +++++++++++++- .../ProductVideo/ProductVideo.v3.jsx | 27 ++++++++++-- .../views/HomePanel/HomeBanner/RandomUnit.jsx | 43 +++++++++++++++++-- .../src/views/MediaPanel/MediaPanel.v3.jsx | 18 +++++--- 4 files changed, 105 insertions(+), 15 deletions(-) diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index 931b4873..8b911566 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -24,7 +24,7 @@ import TButton from '../../../components/TButton/TButton'; import useReviews from '../../../hooks/useReviews/useReviews'; import useScrollTo from '../../../hooks/useScrollTo'; import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig'; -// import { panel_names } from '../../../utils/Config'; +import { panel_names } from '../../../utils/Config'; import { andThen, curry, @@ -530,6 +530,7 @@ export default function ProductAllSection({ const descriptionRef = useRef(null); const reviewRef = useRef(null); const youMayAlsoLikelRef = useRef(null); + const prevMediaPanelModalStateRef = useRef(null); // MediaPanel의 이전 modal 상태 추적 // 동영상과 이미지를 통합한 렌더링 아이템 리스트 생성 (Indicator.jsx 로직 기반) const renderItems = useMemo(() => { @@ -843,6 +844,35 @@ export default function ProductAllSection({ // }; // }, []); + // Redux에서 panels 상태 가져오기 + const panels = useSelector((state) => state.panels.panels); + + // MediaPanel의 전체화면 복귀 감지 및 포커스 복구 + useEffect(() => { + const topPanel = panels[panels.length - 1]; + const currentModalState = topPanel?.panelInfo?.modal; + + // 전체화면(false) → 모달(true)로 복귀하는 경우만 감지 + if ( + topPanel?.name === panel_names.MEDIA_PANEL && + currentModalState === true && + prevMediaPanelModalStateRef.current === false + ) { + console.log( + '[ProductAllSection] 🔄 MediaPanel이 전체화면에서 모달로 복귀 - ProductVideo로 포커스 복구' + ); + const focusTimer = setTimeout(() => { + Spotlight.focus('product-video-player'); + }, 100); + return () => clearTimeout(focusTimer); + } + + // 현재 modal 상태 저장 + if (topPanel?.name === panel_names.MEDIA_PANEL) { + prevMediaPanelModalStateRef.current = currentModalState; + } + }, [panels]); + // 컴포넌트 unmount 시 timer cleanup useEffect(() => { return () => { diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx index bf990476..dd77a361 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx @@ -1,5 +1,6 @@ -import React, { useCallback, useMemo, useState, useEffect } from 'react'; +import React, { useCallback, useMemo, useState, useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import Spotlight from '@enact/spotlight'; import Spottable from '@enact/spotlight/Spottable'; import { startMediaPlayer, @@ -34,22 +35,42 @@ export default function ProductVideo({ const [modalState, setModalState] = useState(true); // 모달 상태 관리 추가 const [hasAutoPlayed, setHasAutoPlayed] = useState(false); // 자동 재생 완료 여부 const [isVideoPlaying, setIsVideoPlaying] = useState(false); // 비디오 재생 여부 flag + const prevModalStateRef = useRef(null); // 이전 modal 상태 추적 const topPanel = panels[panels.length - 1]; - // MediaPanel 상태 체크 로직 + 모달 상태 복원 + // MediaPanel 상태 체크 로직 + 모달 상태 복원 + 포커스 복구 useEffect(() => { if ( topPanel && topPanel.name === panel_names.MEDIA_PANEL && topPanel.panelInfo.modal === false ) { - return; // MediaPanel이 전체화면 모드일 때는 처리하지 않음 + // 전체화면 모드: 이전 상태 저장 + prevModalStateRef.current = false; + return; + } + + // MediaPanel이 modal=true로 복귀했을 때 포커스 복구 + if ( + topPanel && + topPanel.name === panel_names.MEDIA_PANEL && + topPanel.panelInfo.modal === true && + prevModalStateRef.current === false + ) { + console.log('[ProductVideo] MediaPanel returned to modal - restoring focus to ProductVideo'); + const focusTimer = setTimeout(() => { + Spotlight.focus('product-video-player'); + prevModalStateRef.current = true; + }, 50); + return () => clearTimeout(focusTimer); } // MediaPanel이 닫혔을 때 modalState를 true로 복원 if (!topPanel || topPanel.name !== panel_names.MEDIA_PANEL) { + console.log('[ProductVideo] MediaPanel closed - restoring modal state'); setModalState(true); + prevModalStateRef.current = null; } }, [topPanel]); diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx index 7f8e0829..63161c62 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx @@ -18,7 +18,12 @@ import { changeAppStatus } from '../../../actions/commonActions'; import { updateHomeInfo } from '../../../actions/homeActions'; import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions'; import { pushPanel } from '../../../actions/panelActions'; -import { finishVideoPreview, startVideoPlayer, startVideoPlayerNew, shrinkVideoTo1px } from '../../../actions/playActions'; +import { + finishVideoPreview, + startVideoPlayer, + startVideoPlayerNew, + shrinkVideoTo1px, +} from '../../../actions/playActions'; import CustomImage from '../../../components/CustomImage/CustomImage'; import usePriceInfo from '../../../hooks/usePriceInfo'; import { @@ -391,6 +396,11 @@ export default function RandomUnit({ let action = linkType === 'DSP00507' ? startVideoPlayer : pushPanel; + const isNavigatingToDetail = linkInfo.name === panel_names.DETAIL_PANEL; + if (isNavigatingToDetail && playerPanelInfo?.modal !== false) { + dispatch(finishVideoPreview()); + } + dispatch(action(linkInfo)); sendBannerLog(true); dispatch( @@ -410,11 +420,16 @@ export default function RandomUnit({ randomData?.showId, randomData?.shptmLnkTpCd, topContentsLogInfo, + playerPanelInfo?.modal, sendBannerLog, ]); // 투데이즈딜 클릭 const todayDealClick = useCallback(() => { + if (playerPanelInfo?.modal !== false) { + dispatch(finishVideoPreview()); + } + dispatch( pushPanel({ name: panel_names.DETAIL_PANEL, @@ -434,7 +449,14 @@ export default function RandomUnit({ logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); - }, [dispatch, randomData?.patnrId, randomData?.prdtId, topContentsLogInfo, sendBannerLog]); + }, [ + dispatch, + randomData?.patnrId, + randomData?.prdtId, + topContentsLogInfo, + sendBannerLog, + playerPanelInfo?.modal, + ]); // 비디오 클릭 const videoClick = useCallback(() => { @@ -456,7 +478,10 @@ export default function RandomUnit({ // ✅ modal=true → modal=false로 전환 (또는 초기 로드 시 modal=false) // playActions의 shouldSkipVideoPlayback이 modal 상태를 확인하므로 // 모드 전환은 중복 방지 로직을 스킵하여 정상 작동 - console.log('[RandomUnit] videoClick: current playerPanel modal state:', playerPanelInfo?.modal); + console.log( + '[RandomUnit] videoClick: current playerPanel modal state:', + playerPanelInfo?.modal + ); dispatch( startVideoPlayerNew({ @@ -483,7 +508,17 @@ export default function RandomUnit({ sendBannerLog(true); onBlur(); - }, [randomData, spotlightId, topContentsLogInfo, nowMenu, randomDataRef, sendBannerLog, onBlur, playerPanelInfo?.modal, dispatch]); + }, [ + randomData, + spotlightId, + topContentsLogInfo, + nowMenu, + randomDataRef, + sendBannerLog, + onBlur, + playerPanelInfo?.modal, + dispatch, + ]); // 투데이즈 딜 가격 정보 const { originalPrice, discountedPrice, discountRate, offerInfo } = diff --git a/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx b/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx index 8734455a..cd71bbfe 100644 --- a/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx +++ b/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx @@ -898,6 +898,12 @@ const MediaPanel = React.forwardRef( }) ); } + + // 모달 복귀 시 ProductVideo로 포커스 이동 (ProductVideo.v3.jsx에서 처리) + console.log( + '[MediaPanel] Back button pressed - returning to modal, focus will be handled by ProductVideo' + ); + ev?.stopPropagation(); // ev?.preventDefault(); return; @@ -2041,13 +2047,11 @@ const MediaPanel = React.forwardRef( useEffect(() => { if (panelInfoRef.current?.modal && !panelInfo.modal && isOnTop && !videoVerticalVisible) { - const focusTimer = setTimeout(() => { - Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON); - }, 0); - - return () => { - clearTimeout(focusTimer); - }; + console.log( + '[MediaPanel] Modal to fullscreen transition detected - focus will be handled by ProductVideo' + ); + // 포커스를 ProductVideo에 맡김 (ProductVideo.v3.jsx에서 처리) + // PLAYER_BACK_BUTTON으로 포커스하지 않음 } }, [panelInfo.modal, isOnTop, videoVerticalVisible]);