[251114] fix: HomePanel DetailPanel전환시 비디오 정리

🕐 커밋 시간: 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()
This commit is contained in:
2025-11-14 16:05:09 +09:00
parent c9c6fc07a9
commit b1c5664b98
4 changed files with 105 additions and 15 deletions

View File

@@ -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 () => {

View File

@@ -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]);

View File

@@ -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 } =

View File

@@ -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]);