fix: DetailPanel above PlayerPanel , FeaturedBrandsPanel-2

This commit is contained in:
2025-12-15 13:10:55 +09:00
parent ac5414a5fe
commit ab2dd7385b
5 changed files with 121 additions and 7 deletions

View File

@@ -299,19 +299,28 @@ export const navigateToDetail = ({
// PlayerPanel에서 온 경우 // PlayerPanel에서 온 경우
const { hidePlayerOverlays } = require('./videoPlayActions'); const { hidePlayerOverlays } = require('./videoPlayActions');
const statePanels = panels || getState().panels.panels; 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 오버레이 숨김 // DetailPanel push 전에 VideoPlayer 오버레이 숨김
dispatch(hidePlayerOverlays()); dispatch(hidePlayerOverlays());
// PlayerPanel이 modal=true라면 풀스크린 백그라운드로 전환 // PlayerPanel이 modal=true라면 풀스크린 백그라운드로 전환 + lockModalFalse 설정 (Detail 동안 modal 복귀 방지)
if (playerPanelEntry?.panelInfo?.modal) { if (playerPanelEntry) {
dispatch( dispatch(
updatePanel({ updatePanel({
name: panel_names.PLAYER_PANEL, name: playerPanelEntry.name,
panelInfo: { panelInfo: {
...playerPanelEntry.panelInfo, ...playerPanelEntry.panelInfo,
modal: false, modal: false,
modalContainerId: undefined,
modalStyle: undefined,
modalScale: undefined,
shouldShrinkTo1px: false,
isHidden: false,
lockModalFalse: true,
}, },
}) })
); );

View File

@@ -73,6 +73,16 @@ export const startVideoPlayer =
...rest ...rest
}) => }) =>
(dispatch, getState) => { (dispatch, getState) => {
const caller = new Error().stack?.split('\n')[2]?.trim();
console.log('[PTRACE-SP] startVideoPlayer call', {
modal,
modalContainerId,
modalClassName,
videoId,
showUrl,
caller,
});
dlog( dlog(
'[startVideoPlayer] ✅ START - videoId:', '[startVideoPlayer] ✅ START - videoId:',
videoId, videoId,
@@ -105,6 +115,9 @@ export const startVideoPlayer =
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push // 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
if (existingPlayerPanel) { 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'); dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
clearAllVideoTimers(); clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL)); dispatch(popPanel(panel_names.PLAYER_PANEL));
@@ -182,6 +195,17 @@ export const startVideoPlayerNew =
...rest ...rest
}) => }) =>
(dispatch, getState) => { (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( dlog(
'[startVideoPlayerNew] *** ✅ START - bannerId:', '[startVideoPlayerNew] *** ✅ START - bannerId:',
bannerId, bannerId,
@@ -215,6 +239,9 @@ export const startVideoPlayerNew =
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push // 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
if (existingPlayerPanel) { 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'); dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
clearAllVideoTimers(); clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL)); dispatch(popPanel(panel_names.PLAYER_PANEL));

View File

@@ -132,6 +132,60 @@ export const panelsReducer = (state = initialState, action) => {
case types.UPDATE_PANEL: { case types.UPDATE_PANEL: {
let lastIndex = -1; let lastIndex = -1;
let lastAction = 'update'; 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--) { for (let i = state.panels.length - 1; i >= 0; i--) {
if (state.panels[i].name === action.payload.name) { if (state.panels[i].name === action.payload.name) {
@@ -143,7 +197,7 @@ export const panelsReducer = (state = initialState, action) => {
index === lastIndex index === lastIndex
? { ? {
...panel, ...panel,
panelInfo: { ...panel.panelInfo, ...action.payload.panelInfo }, panelInfo: { ...panel.panelInfo, ...nextPanelInfo },
} }
: panel : panel
); );

View File

@@ -331,6 +331,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
detailPanelClosedAt: Date.now(), // ✅ 시점 기록 detailPanelClosedAt: Date.now(), // ✅ 시점 기록
detailPanelClosedFromSource: sourceMenu, // ✅ 출처 detailPanelClosedFromSource: sourceMenu, // ✅ 출처
lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달 lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달
lockModalFalse: false, // Detail 종료 시 lock 해제
}, },
}) })
); );
@@ -1117,6 +1118,24 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
dispatch(finishVideoPreview()); dispatch(finishVideoPreview());
} else if (hasPlayerPanel && launchedFromPlayer) { } else if (hasPlayerPanel && launchedFromPlayer) {
console.log('[DetailPanel] PlayerPanel modal=true detected - launched from Player, skip finishVideoPreview'); 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]); }, [panels, dispatch, panelInfo?.fromPlayer, panelInfo?.sourcePanel]);

View File

@@ -53,6 +53,7 @@ export default function HomeBanner({
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos); const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
const popupVisible = useSelector((state) => state.common.popup.popupVisible); const popupVisible = useSelector((state) => state.common.popup.popupVisible);
const panels = useSelector((state) => state.panels.panels);
// 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리 // 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리
const focusHistory = useFocusHistory({ const focusHistory = useFocusHistory({
enableLogging: true, enableLogging: true,
@@ -163,7 +164,10 @@ export default function HomeBanner({
videoData = targetBannerData.bannerDetailInfos?.[0]; 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); console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus);
dispatch( dispatch(
@@ -175,12 +179,13 @@ export default function HomeBanner({
shptmBanrTpNm: videoData.shptmBanrTpNm, shptmBanrTpNm: videoData.shptmBanrTpNm,
lgCatCd: videoData.lgCatCd, lgCatCd: videoData.lgCatCd,
chanId: videoData.brdcChnlId, chanId: videoData.brdcChnlId,
// 기본: 배너는 modal=true로 재생
modal: true, modal: true,
modalContainerId: defaultFocus, modalContainerId: defaultFocus,
}) })
); );
} }
}, [bannerDataList, defaultFocus, dispatch]); }, [bannerDataList, defaultFocus, dispatch, panels]);
const renderItem = useCallback( const renderItem = useCallback(
(index, isHorizontal) => { (index, isHorizontal) => {