diff --git a/com.twin.app.shoptime/src/actions/panelActions.js b/com.twin.app.shoptime/src/actions/panelActions.js index 6706a6f8..8e5fcddf 100644 --- a/com.twin.app.shoptime/src/actions/panelActions.js +++ b/com.twin.app.shoptime/src/actions/panelActions.js @@ -299,19 +299,28 @@ export const navigateToDetail = ({ // PlayerPanel에서 온 경우 const { hidePlayerOverlays } = require('./videoPlayActions'); const statePanels = panels || getState().panels.panels; - const playerPanelEntry = statePanels.find((p) => p.name === panel_names.PLAYER_PANEL); + const playerPanelEntry = + [...statePanels].reverse().find( + (p) => p.name === panel_names.PLAYER_PANEL || p.name === panel_names.PLAYER_PANEL_NEW + ) || null; // DetailPanel push 전에 VideoPlayer 오버레이 숨김 dispatch(hidePlayerOverlays()); - // PlayerPanel이 modal=true라면 풀스크린 백그라운드로 전환 - if (playerPanelEntry?.panelInfo?.modal) { + // PlayerPanel이 modal=true라면 풀스크린 백그라운드로 전환 + lockModalFalse 설정 (Detail 동안 modal 복귀 방지) + if (playerPanelEntry) { dispatch( updatePanel({ - name: panel_names.PLAYER_PANEL, + name: playerPanelEntry.name, panelInfo: { ...playerPanelEntry.panelInfo, modal: false, + modalContainerId: undefined, + modalStyle: undefined, + modalScale: undefined, + shouldShrinkTo1px: false, + isHidden: false, + lockModalFalse: true, }, }) ); diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 5a377615..23f2b176 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -73,6 +73,16 @@ export const startVideoPlayer = ...rest }) => (dispatch, getState) => { + const caller = new Error().stack?.split('\n')[2]?.trim(); + console.log('[PTRACE-SP] startVideoPlayer call', { + modal, + modalContainerId, + modalClassName, + videoId, + showUrl, + caller, + }); + dlog( '[startVideoPlayer] ✅ START - videoId:', videoId, @@ -105,6 +115,9 @@ export const startVideoPlayer = // 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push if (existingPlayerPanel) { + console.log('[PTRACE-SP] startVideoPlayer: popping existing player before push', { + stack: panels.map((p) => p.name), + }); dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start'); clearAllVideoTimers(); dispatch(popPanel(panel_names.PLAYER_PANEL)); @@ -182,6 +195,17 @@ export const startVideoPlayerNew = ...rest }) => (dispatch, getState) => { + const caller = new Error().stack?.split('\n')[2]?.trim(); + console.log('[PTRACE-SPN] startVideoPlayerNew call', { + bannerId, + modal, + modalContainerId, + modalClassName, + videoId, + showUrl, + caller, + }); + dlog( '[startVideoPlayerNew] *** ✅ START - bannerId:', bannerId, @@ -215,6 +239,9 @@ export const startVideoPlayerNew = // 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push if (existingPlayerPanel) { + console.log('[PTRACE-SPN] popping existing player before push', { + stack: panels.map((p) => p.name), + }); dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start'); clearAllVideoTimers(); dispatch(popPanel(panel_names.PLAYER_PANEL)); diff --git a/com.twin.app.shoptime/src/reducers/panelReducer.js b/com.twin.app.shoptime/src/reducers/panelReducer.js index 953409f3..a1518e47 100644 --- a/com.twin.app.shoptime/src/reducers/panelReducer.js +++ b/com.twin.app.shoptime/src/reducers/panelReducer.js @@ -132,6 +132,60 @@ export const panelsReducer = (state = initialState, action) => { case types.UPDATE_PANEL: { let lastIndex = -1; let lastAction = 'update'; + const hasDetailPanel = state.panels.some((p) => p.name === panel_names.DETAIL_PANEL); + const isPlayerPanel = + action.payload.name === panel_names.PLAYER_PANEL || + action.payload.name === panel_names.PLAYER_PANEL_NEW; + const existingPanel = state.panels.find((p) => p.name === action.payload.name); + let nextPanelInfo = action.payload.panelInfo || {}; + + // lockModalFalse 플래그 처리: DetailPanel이 스택에 있거나 lock이 이미 true면 modal=true 업데이트를 차단 + if (isPlayerPanel && existingPanel) { + const lockFlag = + existingPanel.panelInfo?.lockModalFalse === true || nextPanelInfo.lockModalFalse === true; + + // unlock 명시 시 그대로 진행 + if (nextPanelInfo.lockModalFalse === false) { + // do nothing + } else if (lockFlag && nextPanelInfo.modal === true) { + nextPanelInfo = { + ...nextPanelInfo, + modal: false, + modalContainerId: undefined, + lockModalFalse: true, + modalStyle: undefined, + modalScale: undefined, + shouldShrinkTo1px: false, + isHidden: false, + }; + } else if (lockFlag && nextPanelInfo.modal === undefined && hasDetailPanel) { + nextPanelInfo = { + ...nextPanelInfo, + modal: + existingPanel.panelInfo?.modal === true ? false : existingPanel.panelInfo?.modal, + modalContainerId: + existingPanel.panelInfo?.modal === true + ? undefined + : existingPanel.panelInfo?.modalContainerId, + lockModalFalse: true, + modalStyle: existingPanel.panelInfo?.modal === true ? undefined : nextPanelInfo.modalStyle, + modalScale: existingPanel.panelInfo?.modal === true ? undefined : nextPanelInfo.modalScale, + shouldShrinkTo1px: false, + isHidden: false, + }; + } else if (hasDetailPanel && nextPanelInfo.modal === true) { + // DetailPanel 존재 시 modal=true 업데이트 차단 + nextPanelInfo = { + ...nextPanelInfo, + modal: false, + modalContainerId: undefined, + modalStyle: undefined, + modalScale: undefined, + shouldShrinkTo1px: false, + isHidden: false, + }; + } + } // 배열의 끝에서부터 시작하여 조건에 맞는 마지막 인덱스 찾기 for (let i = state.panels.length - 1; i >= 0; i--) { if (state.panels[i].name === action.payload.name) { @@ -143,7 +197,7 @@ export const panelsReducer = (state = initialState, action) => { index === lastIndex ? { ...panel, - panelInfo: { ...panel.panelInfo, ...action.payload.panelInfo }, + panelInfo: { ...panel.panelInfo, ...nextPanelInfo }, } : panel ); diff --git a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx index 78034238..630f29dd 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx @@ -331,6 +331,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedFromSource: sourceMenu, // ✅ 출처 lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달 + lockModalFalse: false, // Detail 종료 시 lock 해제 }, }) ); @@ -1117,6 +1118,24 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { dispatch(finishVideoPreview()); } else if (hasPlayerPanel && launchedFromPlayer) { console.log('[DetailPanel] PlayerPanel modal=true detected - launched from Player, skip finishVideoPreview'); + + // Detail 동안 modal=true로 바뀌지 않도록 lockModalFalse 설정 + const playerPanelEntry = panels.find( + (p) => p.name === panel_names.PLAYER_PANEL || p.name === panel_names.PLAYER_PANEL_NEW + ); + if (playerPanelEntry?.panelInfo?.modal === true) { + dispatch( + updatePanel({ + name: playerPanelEntry.name, + panelInfo: { + ...playerPanelEntry.panelInfo, + modal: false, + modalContainerId: undefined, + lockModalFalse: true, + }, + }) + ); + } } }, [panels, dispatch, panelInfo?.fromPlayer, panelInfo?.sourcePanel]); diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx index dc3723a9..40141919 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx @@ -53,6 +53,7 @@ export default function HomeBanner({ const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos); const popupVisible = useSelector((state) => state.common.popup.popupVisible); + const panels = useSelector((state) => state.panels.panels); // 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리 const focusHistory = useFocusHistory({ enableLogging: true, @@ -163,7 +164,10 @@ export default function HomeBanner({ videoData = targetBannerData.bannerDetailInfos?.[0]; } - if (videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) { + // DetailPanel이 떠 있는 동안에는 배너 자동 재생을 스킵 (PlayerPanel 모달 재설정 방지) + const hasDetailPanel = panels.some((p) => p.name === panel_names.DETAIL_PANEL); + + if (!hasDetailPanel && videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) { console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus); dispatch( @@ -175,12 +179,13 @@ export default function HomeBanner({ shptmBanrTpNm: videoData.shptmBanrTpNm, lgCatCd: videoData.lgCatCd, chanId: videoData.brdcChnlId, + // 기본: 배너는 modal=true로 재생 modal: true, modalContainerId: defaultFocus, }) ); } - }, [bannerDataList, defaultFocus, dispatch]); + }, [bannerDataList, defaultFocus, dispatch, panels]); const renderItem = useCallback( (index, isHorizontal) => {