Compare commits
48 Commits
a
...
develop_si
| Author | SHA1 | Date | |
|---|---|---|---|
| daac18afa8 | |||
| adfede9b44 | |||
| 1fae88878f | |||
| 4ecb03002f | |||
| 27e1e3bb6a | |||
| b9d52d452c | |||
| fd55c04c83 | |||
| 30472bfe17 | |||
|
|
51d587a1a1 | ||
|
|
349688092c | ||
| d933ca6bb7 | |||
| e86b56e14e | |||
|
|
eee8e73b97 | ||
| ec76d2cfc9 | |||
| a7161b8a80 | |||
| 13e32298a7 | |||
| 5ef0d8afae | |||
| f6073d78c1 | |||
|
|
0223499e12 | ||
| 3fd3b66cb3 | |||
| be9b1faeec | |||
| 07a042cca6 | |||
|
|
d93960f40a | ||
| 4dfa15b4c0 | |||
| d83e9d38f0 | |||
| 8589cde061 | |||
| 92964a5063 | |||
| dba79789a8 | |||
|
|
8a882c28ca | ||
| 61f67708a9 | |||
| c9b2e5daf5 | |||
| 4f4887ebdb | |||
| 9d8cafc0a9 | |||
|
|
929a9020a1 | ||
|
|
83905a092d | ||
| e4a64644dd | |||
| 55af96bd00 | |||
|
|
8325070138 | ||
|
|
bbb9e64120 | ||
| 99ea3e6595 | |||
| 3dc4699479 | |||
|
|
8a3bcc1f9c | ||
|
|
486fb5efd5 | ||
|
|
802484debd | ||
|
|
c540378cb5 | ||
|
|
cb3a4e9bc7 | ||
|
|
3ce4398e67 | ||
|
|
78153bae0c |
@@ -85,6 +85,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
|||||||
patnrId: patnrId,
|
patnrId: patnrId,
|
||||||
chanId: chanId,
|
chanId: chanId,
|
||||||
shptmBanrTpNm: "LIVE",
|
shptmBanrTpNm: "LIVE",
|
||||||
|
modal: false, // DeepLink 진입 시 fullscreen으로 재생
|
||||||
// expsOrd: expsOrd,
|
// expsOrd: expsOrd,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -101,6 +102,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
|||||||
patnrId: patnrId,
|
patnrId: patnrId,
|
||||||
showId: showId,
|
showId: showId,
|
||||||
shptmBanrTpNm: "VOD",
|
shptmBanrTpNm: "VOD",
|
||||||
|
modal: false, // DeepLink 진입 시 fullscreen으로 재생
|
||||||
// expsOrd: expsOrd,
|
// expsOrd: expsOrd,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -274,6 +276,18 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
|||||||
const action =
|
const action =
|
||||||
panelName === panel_names.HOME_PANEL ? updateHomeInfo : pushPanel;
|
panelName === panel_names.HOME_PANEL ? updateHomeInfo : pushPanel;
|
||||||
|
|
||||||
|
// 🔽 LS(Live Show) 또는 VS(VOD Show)인 경우 DeepLink 진입 플래그 설정
|
||||||
|
if ((type === 'LS' || type === 'VS') && action === pushPanel) {
|
||||||
|
dispatch(
|
||||||
|
updateHomeInfo({
|
||||||
|
name: panel_names.HOME_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
isDeepLinkEntry: true, // DeepLink PlayerPanel 진입 플래그
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
action({
|
action({
|
||||||
name: panelName,
|
name: panelName,
|
||||||
|
|||||||
@@ -99,17 +99,25 @@ export const finishMediaPreview = () => (dispatch, getState) => {
|
|||||||
export const finishModalMediaForce = () => (dispatch, getState) => {
|
export const finishModalMediaForce = () => (dispatch, getState) => {
|
||||||
const panels = getState().panels.panels;
|
const panels = getState().panels.panels;
|
||||||
|
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] finishModalMediaForce called', {
|
||||||
|
// panelCount: panels.length,
|
||||||
|
// panelNames: panels.map((p) => p.name),
|
||||||
|
// });
|
||||||
|
|
||||||
const hasProductVideoPanel = panels.some(
|
const hasProductVideoPanel = panels.some(
|
||||||
(panel) =>
|
(panel) =>
|
||||||
panel.name === panel_names.MEDIA_PANEL &&
|
panel.name === panel_names.MEDIA_PANEL &&
|
||||||
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
|
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] hasProductVideoPanel:', hasProductVideoPanel);
|
||||||
|
|
||||||
if (hasProductVideoPanel) {
|
if (hasProductVideoPanel) {
|
||||||
if (startMediaFocusTimer) {
|
if (startMediaFocusTimer) {
|
||||||
clearTimeout(startMediaFocusTimer);
|
clearTimeout(startMediaFocusTimer);
|
||||||
startMediaFocusTimer = null;
|
startMediaFocusTimer = null;
|
||||||
}
|
}
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] Calling popPanel(panel_names.MEDIA_PANEL)');
|
||||||
dispatch(popPanel(panel_names.MEDIA_PANEL));
|
dispatch(popPanel(panel_names.MEDIA_PANEL));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { updateHomeInfo } from './homeActions';
|
|||||||
import { createDebugHelpers } from '../utils/debug';
|
import { createDebugHelpers } from '../utils/debug';
|
||||||
|
|
||||||
// 디버그 헬퍼 설정
|
// 디버그 헬퍼 설정
|
||||||
const DEBUG_MODE = true;
|
const DEBUG_MODE = false;
|
||||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||||
|
|
||||||
// 시작 메뉴 추적을 위한 상수
|
// 시작 메뉴 추적을 위한 상수
|
||||||
@@ -40,12 +40,20 @@ export const pushPanel = (panel, duplicatable = false) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const popPanel = (panelName) => {
|
export const popPanel = (panelName) => {
|
||||||
|
const stack = new Error().stack;
|
||||||
|
const stackLines = stack?.split('\n') || [];
|
||||||
|
|
||||||
|
// console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action dispatcher - REMOVING PANEL:', {
|
||||||
|
// panelName,
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// fullStack: stackLines.slice(1, 6).map((line) => line.trim()),
|
||||||
|
// });
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
console.log('[PANEL-TRACE] popPanel action creator', {
|
console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action creator stack:', {
|
||||||
panelName,
|
panelName,
|
||||||
caller: new Error().stack?.split('\n')[2]?.trim(),
|
caller: stackLines[2]?.trim(),
|
||||||
});
|
});
|
||||||
console.trace('[PANEL-TRACE] popPanel stack trace');
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: types.POP_PANEL,
|
type: types.POP_PANEL,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const startVideoPlayer =
|
|||||||
}) =>
|
}) =>
|
||||||
(dispatch, getState) => {
|
(dispatch, getState) => {
|
||||||
const caller = new Error().stack?.split('\n')[2]?.trim();
|
const caller = new Error().stack?.split('\n')[2]?.trim();
|
||||||
console.log('[PTRACE-SP] startVideoPlayer call', {
|
dlog('[PTRACE-SP] startVideoPlayer call', {
|
||||||
modal,
|
modal,
|
||||||
modalContainerId,
|
modalContainerId,
|
||||||
modalClassName,
|
modalClassName,
|
||||||
@@ -115,7 +115,7 @@ export const startVideoPlayer =
|
|||||||
|
|
||||||
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
||||||
if (existingPlayerPanel) {
|
if (existingPlayerPanel) {
|
||||||
console.log('[PTRACE-SP] startVideoPlayer: popping existing player before push', {
|
dlog('[PTRACE-SP] startVideoPlayer: popping existing player before push', {
|
||||||
stack: panels.map((p) => p.name),
|
stack: panels.map((p) => p.name),
|
||||||
});
|
});
|
||||||
dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
|
dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
|
||||||
@@ -148,14 +148,14 @@ export const startVideoPlayer =
|
|||||||
|
|
||||||
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
||||||
// if (modal && modalContainerId && !spotlightDisable) {
|
// if (modal && modalContainerId && !spotlightDisable) {
|
||||||
// console.log('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
// dlog('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||||
// Spotlight.setPointerMode(false);
|
// Spotlight.setPointerMode(false);
|
||||||
// startVideoFocusTimer = setTimeout(() => {
|
// startVideoFocusTimer = setTimeout(() => {
|
||||||
// console.log('[startVideoPlayer] 🔍 Spotlight.focus called');
|
// dlog('[startVideoPlayer] 🔍 Spotlight.focus called');
|
||||||
// Spotlight.focus(modalContainerId);
|
// Spotlight.focus(modalContainerId);
|
||||||
// }, 0);
|
// }, 0);
|
||||||
// } else {
|
// } else {
|
||||||
// console.log('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
// dlog('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
dlog('[startVideoPlayer] ✅ END');
|
dlog('[startVideoPlayer] ✅ END');
|
||||||
@@ -196,7 +196,7 @@ export const startVideoPlayerNew =
|
|||||||
}) =>
|
}) =>
|
||||||
(dispatch, getState) => {
|
(dispatch, getState) => {
|
||||||
const caller = new Error().stack?.split('\n')[2]?.trim();
|
const caller = new Error().stack?.split('\n')[2]?.trim();
|
||||||
console.log('[PTRACE-SPN] startVideoPlayerNew call', {
|
dlog('[PTRACE-SPN] startVideoPlayerNew call', {
|
||||||
bannerId,
|
bannerId,
|
||||||
modal,
|
modal,
|
||||||
modalContainerId,
|
modalContainerId,
|
||||||
@@ -239,7 +239,7 @@ export const startVideoPlayerNew =
|
|||||||
|
|
||||||
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
||||||
if (existingPlayerPanel) {
|
if (existingPlayerPanel) {
|
||||||
console.log('[PTRACE-SPN] popping existing player before push', {
|
dlog('[PTRACE-SPN] popping existing player before push', {
|
||||||
stack: panels.map((p) => p.name),
|
stack: panels.map((p) => p.name),
|
||||||
});
|
});
|
||||||
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
|
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
|
||||||
@@ -332,14 +332,14 @@ export const startVideoPlayerNew =
|
|||||||
|
|
||||||
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
||||||
// if (modal && modalContainerId && !spotlightDisable) {
|
// if (modal && modalContainerId && !spotlightDisable) {
|
||||||
// console.log('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
// dlog('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||||
// Spotlight.setPointerMode(false);
|
// Spotlight.setPointerMode(false);
|
||||||
// startVideoFocusTimer = setTimeout(() => {
|
// startVideoFocusTimer = setTimeout(() => {
|
||||||
// console.log('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
|
// dlog('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
|
||||||
// Spotlight.focus(modalContainerId);
|
// Spotlight.focus(modalContainerId);
|
||||||
// }, 0);
|
// }, 0);
|
||||||
// } else {
|
// } else {
|
||||||
// console.log('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
// dlog('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
dlog('[startVideoPlayerNew] *** ✅ END');
|
dlog('[startVideoPlayerNew] *** ✅ END');
|
||||||
@@ -352,7 +352,7 @@ export const finishVideoPreview = () => (dispatch, getState) => {
|
|||||||
const panels = getState().panels.panels;
|
const panels = getState().panels.panels;
|
||||||
const topPanel = panels[panels.length - 1];
|
const topPanel = panels[panels.length - 1];
|
||||||
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
|
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
|
||||||
console.log('[PANEL-TRACE] finishVideoPreview: popping modal player', {
|
dlog('[PANEL-TRACE] finishVideoPreview: popping modal player', {
|
||||||
topPanelName: topPanel.name,
|
topPanelName: topPanel.name,
|
||||||
modal: topPanel.panelInfo.modal,
|
modal: topPanel.panelInfo.modal,
|
||||||
stack: panels.map((p) => p.name),
|
stack: panels.map((p) => p.name),
|
||||||
@@ -417,7 +417,7 @@ export const pauseModalVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ⏸️ pauseModalVideo - Pausing modal video', {
|
dlog('[Detail-BG] ⏸️ pauseModalVideo - Pausing modal video', {
|
||||||
found: !!modalPlayerPanel,
|
found: !!modalPlayerPanel,
|
||||||
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
||||||
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||||
@@ -438,11 +438,11 @@ export const pauseModalVideo = () => (dispatch, getState) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ✅ pauseModalVideo - Modal video paused successfully', {
|
dlog('[Detail-BG] ✅ pauseModalVideo - Modal video paused successfully', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('[Detail-BG] ⚠️ pauseModalVideo - No modal PlayerPanel found', {
|
dlog('[Detail-BG] ⚠️ pauseModalVideo - No modal PlayerPanel found', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -457,7 +457,7 @@ export const resumeModalVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ▶️ resumeModalVideo - Resuming modal video', {
|
dlog('[Detail-BG] ▶️ resumeModalVideo - Resuming modal video', {
|
||||||
found: !!modalPlayerPanel,
|
found: !!modalPlayerPanel,
|
||||||
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
||||||
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||||
@@ -478,11 +478,11 @@ export const resumeModalVideo = () => (dispatch, getState) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ✅ resumeModalVideo - Modal video resumed successfully', {
|
dlog('[Detail-BG] ✅ resumeModalVideo - Modal video resumed successfully', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('[Detail-BG] ⚠️ resumeModalVideo - Modal video not paused or panel not found', {
|
dlog('[Detail-BG] ⚠️ resumeModalVideo - Modal video not paused or panel not found', {
|
||||||
found: !!modalPlayerPanel,
|
found: !!modalPlayerPanel,
|
||||||
isPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
isPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -499,7 +499,7 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
|
dlog('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
|
||||||
found: !!fullscreenPlayerPanel,
|
found: !!fullscreenPlayerPanel,
|
||||||
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
||||||
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||||
@@ -517,11 +517,11 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ✅ pauseFullscreenVideo - Fullscreen video paused successfully', {
|
dlog('[Detail-BG] ✅ pauseFullscreenVideo - Fullscreen video paused successfully', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('[Detail-BG] ⚠️ pauseFullscreenVideo - No fullscreen PlayerPanel found', {
|
dlog('[Detail-BG] ⚠️ pauseFullscreenVideo - No fullscreen PlayerPanel found', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -536,7 +536,7 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
|
dlog('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
|
||||||
found: !!fullscreenPlayerPanel,
|
found: !!fullscreenPlayerPanel,
|
||||||
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
||||||
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||||
@@ -554,11 +554,11 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ✅ resumeFullscreenVideo - Fullscreen video resumed successfully', {
|
dlog('[Detail-BG] ✅ resumeFullscreenVideo - Fullscreen video resumed successfully', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('[Detail-BG] ⚠️ resumeFullscreenVideo - Fullscreen video not paused or panel not found', {
|
dlog('[Detail-BG] ⚠️ resumeFullscreenVideo - Fullscreen video not paused or panel not found', {
|
||||||
found: !!fullscreenPlayerPanel,
|
found: !!fullscreenPlayerPanel,
|
||||||
isPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
isPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -601,7 +601,7 @@ export const hideModalVideo = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log('[HomePanel] hideModalVideo: saving shrinkInfo', {
|
// dlog('[HomePanel] hideModalVideo: saving shrinkInfo', {
|
||||||
// shrinkInfo: updatedPlayerState.shrinkInfo,
|
// shrinkInfo: updatedPlayerState.shrinkInfo,
|
||||||
// modalStyle: panelInfo.modalStyle,
|
// modalStyle: panelInfo.modalStyle,
|
||||||
// });
|
// });
|
||||||
@@ -1038,7 +1038,7 @@ export const resumePlayerControl = (ownerId) => (dispatch, getState) => {
|
|||||||
* 이 액션은 어떤 배너에서든 클릭 시 호출됩니다.
|
* 이 액션은 어떤 배너에서든 클릭 시 호출됩니다.
|
||||||
*/
|
*/
|
||||||
export const goToFullScreen = () => (dispatch, getState) => {
|
export const goToFullScreen = () => (dispatch, getState) => {
|
||||||
console.log('[Detail-BG] 🎬 goToFullScreen - Setting PlayerPanel to fullscreen mode', {
|
dlog('[Detail-BG] 🎬 goToFullScreen - Setting PlayerPanel to fullscreen mode', {
|
||||||
targetModal: false,
|
targetModal: false,
|
||||||
action: 'updatePanel',
|
action: 'updatePanel',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -1055,7 +1055,7 @@ export const goToFullScreen = () => (dispatch, getState) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ✅ goToFullScreen - PlayerPanel modal set to false (fullscreen)', {
|
dlog('[Detail-BG] ✅ goToFullScreen - PlayerPanel modal set to false (fullscreen)', {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1268,7 +1268,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
...rest
|
...rest
|
||||||
} = videoInfo;
|
} = videoInfo;
|
||||||
|
|
||||||
console.log('[Detail-BG] 🎥 startBannerVideo - Starting banner video', {
|
dlog('[Detail-BG] 🎥 startBannerVideo - Starting banner video', {
|
||||||
modalStatus: modal,
|
modalStatus: modal,
|
||||||
bannerId,
|
bannerId,
|
||||||
displayMode: modal ? 'VISIBLE (modal=true)' : 'FULLSCREEN (modal=false)',
|
displayMode: modal ? 'VISIBLE (modal=true)' : 'FULLSCREEN (modal=false)',
|
||||||
@@ -1295,7 +1295,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
// 기존 PlayerPanel이 있으면 초기화
|
// 기존 PlayerPanel이 있으면 초기화
|
||||||
if (existingPlayerPanel) {
|
if (existingPlayerPanel) {
|
||||||
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
|
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
|
||||||
console.log('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
|
dlog('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
|
||||||
existingModalStatus: existingPlayerPanel.panelInfo?.modal,
|
existingModalStatus: existingPlayerPanel.panelInfo?.modal,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
@@ -1304,7 +1304,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 새로운 PlayerPanel push
|
// 새로운 PlayerPanel push
|
||||||
console.log('[Detail-BG] ➕ startBannerVideo - Pushing new PlayerPanel with modal status', {
|
dlog('[Detail-BG] ➕ startBannerVideo - Pushing new PlayerPanel with modal status', {
|
||||||
modal,
|
modal,
|
||||||
modalContainerId,
|
modalContainerId,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -1331,7 +1331,7 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[Detail-BG] ✅ startBannerVideo - PlayerPanel pushed with modal=' + modal, {
|
dlog('[Detail-BG] ✅ startBannerVideo - PlayerPanel pushed with modal=' + modal, {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
padding: @slider-padding-v 0;
|
padding: @slider-padding-v 0;
|
||||||
height: @sand-mediaplayer-slider-height;
|
height: @sand-mediaplayer-slider-height;
|
||||||
right: 154px;
|
right: 154px;
|
||||||
width: 1466px;
|
width: 1558px;
|
||||||
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
|
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
font-family: @baseFont;
|
font-family: @baseFont;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
right: 90px;
|
right: 20px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
letter-spacing: -1px;
|
letter-spacing: -1px;
|
||||||
.separator {
|
.separator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 105px;
|
right: 95px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
}
|
}
|
||||||
.currentTime {
|
.currentTime {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 130px;
|
right: 120px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
}
|
}
|
||||||
.totalTime {
|
.totalTime {
|
||||||
|
|||||||
@@ -483,7 +483,7 @@
|
|||||||
.default-style();
|
.default-style();
|
||||||
|
|
||||||
.scrollInfo {
|
.scrollInfo {
|
||||||
width: 900px;
|
width: 850px;
|
||||||
background-color: @BG_COLOR_01;
|
background-color: @BG_COLOR_01;
|
||||||
color: @COLOR_GRAY03;
|
color: @COLOR_GRAY03;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -840,9 +840,11 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.state.mediaSliderVisible === nextState.mediaSliderVisible &&
|
this.state.mediaSliderVisible === nextState.mediaSliderVisible &&
|
||||||
this.state.loading === nextState.loading &&
|
this.state.loading === nextState.loading &&
|
||||||
this.props.loading === nextProps.loading &&
|
this.props.loading === nextProps.loading &&
|
||||||
(this.state.currentTime !== nextState.currentTime ||
|
this.state.currentTime === nextState.currentTime &&
|
||||||
this.state.proportionPlayed !== nextState.proportionPlayed ||
|
this.state.proportionPlayed === nextState.proportionPlayed &&
|
||||||
this.state.sliderTooltipTime !== nextState.sliderTooltipTime)
|
this.state.sliderTooltipTime === nextState.sliderTooltipTime &&
|
||||||
|
this.state.mediaControlsVisible === nextState.mediaControlsVisible &&
|
||||||
|
this.state.bottomControlsRendered === nextState.bottomControlsRendered
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1279,15 +1281,12 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
sourceUnavailable: true,
|
sourceUnavailable: true,
|
||||||
proportionPlayed: 0,
|
proportionPlayed: 0,
|
||||||
proportionLoaded: 0,
|
proportionLoaded: 0,
|
||||||
|
bottomControlsRendered: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.props.noAutoShowMediaControls) {
|
if (!this.props.noAutoShowMediaControls) {
|
||||||
if (!this.state.bottomControlsRendered) {
|
|
||||||
this.renderBottomControl.idle();
|
|
||||||
} else {
|
|
||||||
this.showControls();
|
this.showControls();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePlay = this.handle(forwardPlay, this.shouldShowMiniFeedback, () => this.play());
|
handlePlay = this.handle(forwardPlay, this.shouldShowMiniFeedback, () => this.play());
|
||||||
|
|||||||
@@ -779,6 +779,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.times {
|
.times {
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
|||||||
{/* Overlay */}
|
{/* Overlay */}
|
||||||
<Overlay bottomControlsVisible={controlsVisible} onClick={handleVideoClick}>
|
<Overlay bottomControlsVisible={controlsVisible} onClick={handleVideoClick}>
|
||||||
{/* Loading + Thumbnail */}
|
{/* Loading + Thumbnail */}
|
||||||
{loading && thumbnailUrl && (
|
{/* {loading && thumbnailUrl && (
|
||||||
<>
|
<>
|
||||||
<p className={classNames(css.thumbnail, isModal && css.smallThumbnail)}>
|
<p className={classNames(css.thumbnail, isModal && css.smallThumbnail)}>
|
||||||
<img src={thumbnailUrl} alt="" />
|
<img src={thumbnailUrl} alt="" />
|
||||||
@@ -648,7 +648,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{/* Controls with MediaSlider */}
|
{/* Controls with MediaSlider */}
|
||||||
{controlsVisible && !isModal && (
|
{controlsVisible && !isModal && (
|
||||||
|
|||||||
@@ -855,9 +855,11 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.state.mediaSliderVisible === nextState.mediaSliderVisible &&
|
this.state.mediaSliderVisible === nextState.mediaSliderVisible &&
|
||||||
this.state.loading === nextState.loading &&
|
this.state.loading === nextState.loading &&
|
||||||
this.props.loading === nextProps.loading &&
|
this.props.loading === nextProps.loading &&
|
||||||
(this.state.currentTime !== nextState.currentTime ||
|
this.state.currentTime === nextState.currentTime &&
|
||||||
this.state.proportionPlayed !== nextState.proportionPlayed ||
|
this.state.proportionPlayed === nextState.proportionPlayed &&
|
||||||
this.state.sliderTooltipTime !== nextState.sliderTooltipTime)
|
this.state.sliderTooltipTime === nextState.sliderTooltipTime &&
|
||||||
|
this.state.mediaControlsVisible === nextState.mediaControlsVisible &&
|
||||||
|
this.state.bottomControlsRendered === nextState.bottomControlsRendered
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1423,15 +1425,12 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
sourceUnavailable: true,
|
sourceUnavailable: true,
|
||||||
proportionPlayed: 0,
|
proportionPlayed: 0,
|
||||||
proportionLoaded: 0,
|
proportionLoaded: 0,
|
||||||
|
bottomControlsRendered: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.props.noAutoShowMediaControls) {
|
if (!this.props.noAutoShowMediaControls) {
|
||||||
if (!this.state.bottomControlsRendered) {
|
|
||||||
this.renderBottomControl.idle();
|
|
||||||
} else {
|
|
||||||
this.showControls();
|
this.showControls();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePlay = this.handle(forwardPlay, this.shouldShowMiniFeedback, () => this.play());
|
handlePlay = this.handle(forwardPlay, this.shouldShowMiniFeedback, () => this.play());
|
||||||
@@ -1636,103 +1635,6 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
updatedState.thumbnailUrl = null;
|
updatedState.thumbnailUrl = null;
|
||||||
}
|
}
|
||||||
this.setState(updatedState);
|
this.setState(updatedState);
|
||||||
|
|
||||||
// Redux에 비디오 재생 상태 업데이트 (기존 로직 유지)
|
|
||||||
if (this.props.dispatch) {
|
|
||||||
// 🔥 onProgress 이벤트는 Redux 업데이트하지 않음 (빈번한 이벤트)
|
|
||||||
const shouldUpdateRedux = !['onProgress'].includes(ev.type);
|
|
||||||
|
|
||||||
if (shouldUpdateRedux) {
|
|
||||||
const updateState = {
|
|
||||||
isPlaying: !updatedState.paused,
|
|
||||||
isPaused: updatedState.paused,
|
|
||||||
currentTime: updatedState.currentTime,
|
|
||||||
duration: updatedState.duration,
|
|
||||||
playbackRate: updatedState.playbackRate,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 가장 중요한 이벤트만 로그
|
|
||||||
const shouldLogEvent = ['play', 'pause', 'ended'].includes(ev.type);
|
|
||||||
if (shouldLogEvent) {
|
|
||||||
dlog('🔄 [PlayerPanel][VideoPlayer] Event-driven Redux update', {
|
|
||||||
eventType: ev.type,
|
|
||||||
videoState: updatedState,
|
|
||||||
updateState,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔍 Redux dispatch 확인
|
|
||||||
dlog('📤 [PlayerPanel][VideoPlayer] Dispatching Redux update', {
|
|
||||||
eventType: ev.type,
|
|
||||||
updateState,
|
|
||||||
hasDispatch: !!this.props.dispatch,
|
|
||||||
propsVideoPlayState: this.props.videoPlayState,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.dispatch(updateVideoPlayState(updateState));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
derror('❌ [PlayerPanel][VideoPlayer] No dispatch prop available', {
|
|
||||||
props: Object.keys(this.props),
|
|
||||||
hasDispatch: !!this.props.dispatch,
|
|
||||||
hasVideoPlayState: !!this.props.videoPlayState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔹 [강화] 내부 상태와 Redux 상태 동기화
|
|
||||||
// Redux 상태를 우선적으로 사용하여 내부 상태 일관성 확보
|
|
||||||
if (this.props.videoPlayState && typeof this.props.videoPlayState === 'object') {
|
|
||||||
// Redux 상태 디버깅 (최소한의 중요 이벤트만)
|
|
||||||
if (ev.type === 'play' || ev.type === 'pause') {
|
|
||||||
dlog('🔍 [PlayerPanel][VideoPlayer] Redux state debug', {
|
|
||||||
videoPlayState: this.props.videoPlayState,
|
|
||||||
isPaused: this.props.videoPlayState?.isPaused,
|
|
||||||
isPlaying: this.props.videoPlayState?.isPlaying,
|
|
||||||
currentTime: this.props.videoPlayState?.currentTime,
|
|
||||||
eventType: ev.type,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const { currentTime, paused, playbackRate } = this.props.videoPlayState;
|
|
||||||
|
|
||||||
// Redux 상태와 현재 내부 상태가 크게 다를 경우 내부 상태 업데이트
|
|
||||||
const timeDiff = Math.abs(currentTime - this.state.currentTime);
|
|
||||||
const shouldUpdateTime = timeDiff > 0.5; // 0.5초 이상 차이 시 업데이트
|
|
||||||
|
|
||||||
// 빈번한 이벤트는 로그에서 제외
|
|
||||||
const isFrequentEvent = [
|
|
||||||
'onProgress',
|
|
||||||
'onBuffer',
|
|
||||||
'onBufferEnd',
|
|
||||||
'onReady',
|
|
||||||
'onDuration',
|
|
||||||
'onStart',
|
|
||||||
].includes(ev.type);
|
|
||||||
const hasSignificantChange =
|
|
||||||
shouldUpdateTime || (paused !== this.state.paused && !isFrequentEvent);
|
|
||||||
|
|
||||||
// 중요한 상태 변화가 있고 빈번한 이벤트가 아닐 때만 로그
|
|
||||||
if (hasSignificantChange && !isFrequentEvent) {
|
|
||||||
dlog('🔄 [PlayerPanel][VideoPlayer] Syncing internal state with Redux', {
|
|
||||||
timeDiff,
|
|
||||||
shouldUpdateTime,
|
|
||||||
pausedDiff: paused !== this.state.paused,
|
|
||||||
reduxPaused: paused,
|
|
||||||
internalPaused: this.state.paused,
|
|
||||||
eventType: ev.type,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasSignificantChange) {
|
|
||||||
this.setState({
|
|
||||||
currentTime: shouldUpdateTime ? currentTime : this.state.currentTime,
|
|
||||||
paused: paused !== undefined ? paused : this.state.paused,
|
|
||||||
playbackRate: playbackRate !== undefined ? playbackRate : this.state.playbackRate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderBottomControl = new Job(() => {
|
renderBottomControl = new Job(() => {
|
||||||
@@ -1744,7 +1646,6 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* Returns an object with the current state of the media including `currentTime`, `duration`,
|
* Returns an object with the current state of the media including `currentTime`, `duration`,
|
||||||
* `paused`, `playbackRate`, `proportionLoaded`, and `proportionPlayed`.
|
* `paused`, `playbackRate`, `proportionLoaded`, and `proportionPlayed`.
|
||||||
* Redux 상태와 내부 상태를 우선적으로 사용하여 일관성 보장
|
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
* @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype
|
* @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype
|
||||||
@@ -1752,19 +1653,13 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
getMediaState = () => {
|
getMediaState = () => {
|
||||||
// Redux 상태를 우선적으로 사용하여 일관성 보장
|
|
||||||
// Redux 상태가 없으면 내부 상태 사용 (fallback)
|
|
||||||
const reduxState = this.props.videoPlayState;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentTime: reduxState?.currentTime ?? this.state.currentTime,
|
currentTime: this.state.currentTime,
|
||||||
duration: reduxState?.duration ?? this.state.duration,
|
duration: this.state.duration,
|
||||||
paused: reduxState?.isPaused ?? this.state.paused,
|
paused: this.state.paused,
|
||||||
playbackRate: reduxState?.playbackRate ?? this.video?.playbackRate ?? this.state.playbackRate,
|
playbackRate: this.video?.playbackRate,
|
||||||
proportionLoaded: this.state.proportionLoaded,
|
proportionLoaded: this.state.proportionLoaded,
|
||||||
proportionPlayed: this.state.proportionPlayed,
|
proportionPlayed: this.state.proportionPlayed,
|
||||||
// Redux 상태 정보도 포함
|
|
||||||
isPlaying: reduxState?.isPlaying ?? !this.state.paused,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1793,16 +1688,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
play = () => {
|
play = () => {
|
||||||
dlog('🟢 [PlayerPanel][VideoPlayer] play() called', {
|
|
||||||
currentTime: this.state.currentTime,
|
|
||||||
duration: this.state.duration,
|
|
||||||
paused: this.state.paused,
|
|
||||||
sourceUnavailable: this.state.sourceUnavailable,
|
|
||||||
prevCommand: this.prevCommand,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.state.sourceUnavailable) {
|
if (this.state.sourceUnavailable) {
|
||||||
dwarn('⚠️ [PlayerPanel][VideoPlayer] play() aborted - source unavailable');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1814,19 +1700,6 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.send('play');
|
this.send('play');
|
||||||
this.announce($L('Play'));
|
this.announce($L('Play'));
|
||||||
this.startDelayedMiniFeedbackHide(5000);
|
this.startDelayedMiniFeedbackHide(5000);
|
||||||
|
|
||||||
// Redux 상태 업데이트 - 재생 상태로 변경
|
|
||||||
if (this.props.dispatch) {
|
|
||||||
this.props.dispatch(
|
|
||||||
updateVideoPlayState({
|
|
||||||
isPlaying: true,
|
|
||||||
isPaused: false,
|
|
||||||
currentTime: this.state.currentTime,
|
|
||||||
duration: this.state.duration,
|
|
||||||
playbackRate: 1,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1837,16 +1710,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
pause = () => {
|
pause = () => {
|
||||||
dlog('🔴 [VideoPlayer] pause() called', {
|
|
||||||
currentTime: this.state.currentTime,
|
|
||||||
duration: this.state.duration,
|
|
||||||
paused: this.state.paused,
|
|
||||||
sourceUnavailable: this.state.sourceUnavailable,
|
|
||||||
prevCommand: this.prevCommand,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.state.sourceUnavailable) {
|
if (this.state.sourceUnavailable) {
|
||||||
dwarn('⚠️ [VideoPlayer] pause() aborted - source unavailable');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1858,22 +1722,6 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.send('pause');
|
this.send('pause');
|
||||||
this.announce($L('Pause'));
|
this.announce($L('Pause'));
|
||||||
this.stopDelayedMiniFeedbackHide();
|
this.stopDelayedMiniFeedbackHide();
|
||||||
|
|
||||||
// Redux 상태 업데이트 - 일시정지 상태로 변경
|
|
||||||
if (this.props.dispatch) {
|
|
||||||
const pauseState = {
|
|
||||||
isPlaying: false,
|
|
||||||
isPaused: true,
|
|
||||||
currentTime: this.state.currentTime,
|
|
||||||
duration: this.state.duration,
|
|
||||||
playbackRate: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
dlog('📤 [VideoPlayer] Dispatching pause state', pauseState);
|
|
||||||
this.props.dispatch(updateVideoPlayState(pauseState));
|
|
||||||
} else {
|
|
||||||
dwarn('⚠️ [VideoPlayer] No dispatch prop available - Redux state not updated');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1885,15 +1733,6 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
seek = (timeIndex) => {
|
seek = (timeIndex) => {
|
||||||
dlog('⏩ [VideoPlayer] seek() called', {
|
|
||||||
timeIndex,
|
|
||||||
currentTime: this.state.currentTime,
|
|
||||||
duration: this.state.duration,
|
|
||||||
videoDuration: this.video?.duration,
|
|
||||||
seekDisabled: this.props.seekDisabled,
|
|
||||||
sourceUnavailable: this.state.sourceUnavailable,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.video) {
|
if (this.video) {
|
||||||
if (
|
if (
|
||||||
!this.props.seekDisabled &&
|
!this.props.seekDisabled &&
|
||||||
@@ -1904,34 +1743,9 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
const actualSeekTime =
|
const actualSeekTime =
|
||||||
timeIndex >= this.video.duration ? this.video.duration - 1 : timeIndex;
|
timeIndex >= this.video.duration ? this.video.duration - 1 : timeIndex;
|
||||||
this.video.currentTime = actualSeekTime;
|
this.video.currentTime = actualSeekTime;
|
||||||
|
|
||||||
dlog('⏩ [VideoPlayer] Video seek completed', {
|
|
||||||
requestedTime: timeIndex,
|
|
||||||
actualTime: actualSeekTime,
|
|
||||||
videoDuration: this.video.duration,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Redux 상태 업데이트 - 시간 이동 상태 반영
|
|
||||||
if (this.props.dispatch) {
|
|
||||||
const seekState = {
|
|
||||||
isPlaying: !this.state.paused,
|
|
||||||
isPaused: this.state.paused,
|
|
||||||
currentTime: actualSeekTime,
|
|
||||||
duration: this.state.duration,
|
|
||||||
playbackRate: this.state.playbackRate,
|
|
||||||
};
|
|
||||||
|
|
||||||
dlog('📤 [VideoPlayer] Dispatching seek state', seekState);
|
|
||||||
this.props.dispatch(updateVideoPlayState(seekState));
|
|
||||||
} else {
|
} else {
|
||||||
dwarn('⚠️ [VideoPlayer] No dispatch prop available - Redux state not updated');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
derror('❌ [VideoPlayer] seek failed - disabled or source unavailable');
|
|
||||||
forward('onSeekFailed', {}, this.props);
|
forward('onSeekFailed', {}, this.props);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
derror('❌ [VideoPlayer] seek failed - no video element');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -695,6 +695,7 @@
|
|||||||
height: 70px;
|
height: 70px;
|
||||||
width:1800px;
|
width:1800px;
|
||||||
margin-left:60px;
|
margin-left:60px;
|
||||||
|
margin-right: 59px;
|
||||||
bottom:92px;
|
bottom:92px;
|
||||||
> *:first-child {
|
> *:first-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|||||||
@@ -2467,9 +2467,9 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className={css.loaderWrap}>
|
{/* <div className={css.loaderWrap}>
|
||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div> */}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{this.state.mediaControlsVisible && (
|
{this.state.mediaControlsVisible && (
|
||||||
@@ -2534,7 +2534,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
videoVerticalVisible && css.videoVertical
|
videoVerticalVisible && css.videoVertical
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{this.state.mediaSliderVisible && type ? (
|
{this.state.mediaSliderVisible && type && !panelInfo.modal ? (
|
||||||
<Times
|
<Times
|
||||||
noCurrentTime
|
noCurrentTime
|
||||||
total={type === 'LIVE' ? liveTotalTime : this.state.duration}
|
total={type === 'LIVE' ? liveTotalTime : this.state.duration}
|
||||||
@@ -2542,7 +2542,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
type={type}
|
type={type}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{this.state.mediaSliderVisible && type ? (
|
{this.state.mediaSliderVisible && type && !panelInfo.modal ? (
|
||||||
<Times
|
<Times
|
||||||
noTotalTime
|
noTotalTime
|
||||||
current={type === 'LIVE' ? currentLiveTimeSeconds : this.state.currentTime}
|
current={type === 'LIVE' ? currentLiveTimeSeconds : this.state.currentTime}
|
||||||
|
|||||||
@@ -718,7 +718,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sliderContainer {
|
.sliderContainer {
|
||||||
// display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: 60px;
|
margin-left: 60px;
|
||||||
@@ -727,6 +727,10 @@
|
|||||||
bottom: -20px;
|
bottom: -20px;
|
||||||
> *:first-child {
|
> *:first-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
> *:nth-child(2) {
|
||||||
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enact-locale-rtl({
|
.enact-locale-rtl({
|
||||||
@@ -787,79 +791,4 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== MediaPlayer.v2 Controls ==========
|
|
||||||
.controlsContainer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 20px 40px 30px;
|
|
||||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.7) 60%, transparent 100%);
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sliderContainer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.times {
|
|
||||||
min-width: 80px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controlsButtons {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playPauseBtn {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
font-size: 24px;
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
|
||||||
border-radius: 50%;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.backBtn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 18px;
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
|
||||||
border-radius: 8px;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { calculateIsPanelOnTop } from '../utils/panelUtils'; // 🎯 isOnTop 유
|
|||||||
|
|
||||||
// DEBUG_MODE - true인 경우에만 로그 출력
|
// DEBUG_MODE - true인 경우에만 로그 출력
|
||||||
// ⚠️ [251122] panelHistory 로그 비활성화 - 로그 생성 차단
|
// ⚠️ [251122] panelHistory 로그 비활성화 - 로그 생성 차단
|
||||||
const DEBUG_MODE = true;
|
const DEBUG_MODE = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel history middleware
|
* Panel history middleware
|
||||||
@@ -33,8 +33,8 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
(action.type.includes('PANEL') || action.type === 'CLEAR_PANEL_HISTORY')
|
(action.type.includes('PANEL') || action.type === 'CLEAR_PANEL_HISTORY')
|
||||||
) {
|
) {
|
||||||
const caller = new Error().stack.split('\n')[1]?.trim();
|
const caller = new Error().stack.split('\n')[1]?.trim();
|
||||||
console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
|
// console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
|
||||||
console.log(' Payload:', action.payload);
|
// console.log(' Payload:', action.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GNB 호출 식별을 위한 helper 함수
|
// GNB 호출 식별을 위한 helper 함수
|
||||||
@@ -81,7 +81,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const isGNB = isGNBCall();
|
const isGNB = isGNBCall();
|
||||||
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL] PUSH_PANEL:', {
|
console.log(`[PANEL] PUSH_PANEL: ${panelName}`, {
|
||||||
panelName,
|
panelName,
|
||||||
panelInfo,
|
panelInfo,
|
||||||
isGNB,
|
isGNB,
|
||||||
@@ -106,7 +106,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After PUSH_PANEL:', {
|
console.log(`[PANEL_HISTORY] PUSH_PANEL: ${panelName}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
@@ -126,7 +126,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
if (panels.length > 0) {
|
if (panels.length > 0) {
|
||||||
const topPanel = panels[panels.length - 1];
|
const topPanel = panels[panels.length - 1];
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
console.log('[PANEL-TRACE] POP_PANEL middleware stack', {
|
console.log(`[PANEL-TRACE] POP_PANEL middleware stack: ${topPanel?.name}`, {
|
||||||
stack: panels.map((p) => p.name),
|
stack: panels.map((p) => p.name),
|
||||||
topPanel: topPanel?.name,
|
topPanel: topPanel?.name,
|
||||||
payload: action.payload,
|
payload: action.payload,
|
||||||
@@ -137,7 +137,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const isGNB = isGNBCall();
|
const isGNB = isGNBCall();
|
||||||
const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산
|
const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL] POP_PANEL:', {
|
console.log(`[PANEL] POP_PANEL: ${topPanel.name}`, {
|
||||||
panelName: topPanel.name,
|
panelName: topPanel.name,
|
||||||
panelInfo: topPanel.panelInfo || {},
|
panelInfo: topPanel.panelInfo || {},
|
||||||
isGNB,
|
isGNB,
|
||||||
@@ -162,7 +162,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After POP_PANEL:', {
|
console.log(`[PANEL_HISTORY] POP_PANEL: ${topPanel.name}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
@@ -185,7 +185,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const isGNB = isGNBCall();
|
const isGNB = isGNBCall();
|
||||||
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL] UPDATE_PANEL:', {
|
console.log(`[PANEL] UPDATE_PANEL: ${panelName}`, {
|
||||||
panelName,
|
panelName,
|
||||||
panelInfo,
|
panelInfo,
|
||||||
isGNB,
|
isGNB,
|
||||||
@@ -210,7 +210,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After UPDATE_PANEL:', {
|
console.log(`[PANEL_HISTORY] UPDATE_PANEL: ${panelName}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
@@ -226,11 +226,15 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
|
|
||||||
// RESET_PANELS: GNB 네비게이션 또는 완전 초기화
|
// RESET_PANELS: GNB 네비게이션 또는 완전 초기화
|
||||||
case types.RESET_PANELS: {
|
case types.RESET_PANELS: {
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE) {
|
||||||
console.log('[PANEL] RESET_PANELS:', {
|
const resetPanelNameForLog = (action.payload && action.payload.length > 0)
|
||||||
|
? action.payload[0].name
|
||||||
|
: 'homepanel';
|
||||||
|
console.log(`[PANEL] RESET_PANELS: ${resetPanelNameForLog}`, {
|
||||||
payload: action.payload,
|
payload: action.payload,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL_HISTORY] Before RESET_PANELS:', store.getState().panelHistory);
|
console.log('[PANEL_HISTORY] Before RESET_PANELS:', store.getState().panelHistory);
|
||||||
|
|
||||||
@@ -292,7 +296,10 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After RESET_PANELS:', {
|
const resetPanelName = (action.payload && action.payload.length > 0)
|
||||||
|
? action.payload[0].name
|
||||||
|
: 'homepanel';
|
||||||
|
console.log(`[PANEL_HISTORY] RESET_PANELS: ${resetPanelName}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ export const panelsReducer = (state = initialState, action) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case types.POP_PANEL: {
|
case types.POP_PANEL: {
|
||||||
|
// console.log('[💜UNIQUE_PANEL_STACK💜] POP_PANEL reducer START', {
|
||||||
|
// targetPanel: action.payload || 'last_panel',
|
||||||
|
// currentPanels: state.panels.map((p) => p.name),
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
dlog('[panelReducer] 🔴 POP_PANEL START', {
|
dlog('[panelReducer] 🔴 POP_PANEL START', {
|
||||||
targetPanel: action.payload || 'last_panel',
|
targetPanel: action.payload || 'last_panel',
|
||||||
currentPanels: state.panels.map((p) => p.name),
|
currentPanels: state.panels.map((p) => p.name),
|
||||||
@@ -118,6 +124,13 @@ export const panelsReducer = (state = initialState, action) => {
|
|||||||
resultPanels = state.panels.slice(0, state.panels.length - 1);
|
resultPanels = state.panels.slice(0, state.panels.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[💜UNIQUE_PANEL_STACK💜] POP_PANEL reducer END', {
|
||||||
|
// resultPanels: resultPanels.map((p) => p.name),
|
||||||
|
// panelCount: resultPanels.length,
|
||||||
|
// lastAction,
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
dlog('[panelReducer] 🔴 POP_PANEL END', {
|
dlog('[panelReducer] 🔴 POP_PANEL END', {
|
||||||
resultPanels: resultPanels.map((p) => p.name),
|
resultPanels: resultPanels.map((p) => p.name),
|
||||||
lastAction,
|
lastAction,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
.position(@position: absolute, @top: 0, @left: 0, @right: 0, @bottom: 0);
|
.position(@position: absolute, @top: 0, @left: 0, @right: 0, @bottom: 0);
|
||||||
z-index: 19;
|
z-index: 19;
|
||||||
border: 4px solid @PRIMARY_COLOR_RED;
|
border: 4px solid @PRIMARY_COLOR_RED;
|
||||||
box-shadow: 0 0 @boxShadow 0 rgba(0, 0, 0, 0.5);
|
|
||||||
border-radius: @borderRadius;
|
border-radius: @borderRadius;
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ export const ACTIVE_POPUP = {
|
|||||||
optionalConfirm: 'optionalConfirm',
|
optionalConfirm: 'optionalConfirm',
|
||||||
energyPopup: 'energyPopup',
|
energyPopup: 'energyPopup',
|
||||||
addCartPopup: 'addCartPopup',
|
addCartPopup: 'addCartPopup',
|
||||||
|
scrollPopup: 'scrollPopup',
|
||||||
};
|
};
|
||||||
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
||||||
export const AUTO_SCROLL_DELAY = 600;
|
export const AUTO_SCROLL_DELAY = 600;
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ const tap = fp.curry((fn, value) => {
|
|||||||
* @param {*} value 대상 값
|
* @param {*} value 대상 값
|
||||||
*/
|
*/
|
||||||
const trace = fp.curry((label, value) => {
|
const trace = fp.curry((label, value) => {
|
||||||
console.log(label, value);
|
// console.log(label, value);
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] DetailPanel cleanup - calling finishModalMediaForce');
|
||||||
dispatch(finishModalMediaForce());
|
dispatch(finishModalMediaForce());
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@@ -303,26 +304,25 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
const sourcePanel = panelInfo?.sourcePanel;
|
const sourcePanel = panelInfo?.sourcePanel;
|
||||||
const sourceMenu = panelInfo?.sourceMenu;
|
const sourceMenu = panelInfo?.sourceMenu;
|
||||||
|
|
||||||
console.log('[DP-TRACE] Detail unmount start', {
|
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel cleanup/unmount triggered', {
|
||||||
sourcePanel,
|
// sourcePanel,
|
||||||
sourceMenu,
|
// sourceMenu,
|
||||||
panelsSnapshot: panels.map((p) => p.name),
|
// panelsSnapshot: panels.map((p) => p.name),
|
||||||
});
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
console.log('[Detail-BG] 306-line sourcePanel:', sourcePanel, 'sourceMenu:', sourceMenu);
|
|
||||||
|
|
||||||
// DetailPanel이 unmount되는 시점
|
// DetailPanel이 unmount되는 시점
|
||||||
console.log('[DetailPanel] unmount:', {
|
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel unmount details:', {
|
||||||
sourcePanel,
|
// sourcePanel,
|
||||||
sourceMenu,
|
// sourceMenu,
|
||||||
timestamp: Date.now(),
|
// timestamp: Date.now(),
|
||||||
});
|
// });
|
||||||
|
|
||||||
// sourcePanel에 따른 상태 업데이트
|
// sourcePanel에 따른 상태 업데이트
|
||||||
switch (sourcePanel) {
|
switch (sourcePanel) {
|
||||||
case panel_names.PLAYER_PANEL: {
|
case panel_names.PLAYER_PANEL: {
|
||||||
// PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달
|
// PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달
|
||||||
console.log('[DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
|
console.log('[PANEL][DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -385,6 +385,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
const sourcePanel = panelInfo?.sourcePanel;
|
const sourcePanel = panelInfo?.sourcePanel;
|
||||||
const sourceMenu = panelInfo?.sourceMenu;
|
const sourceMenu = panelInfo?.sourceMenu;
|
||||||
|
|
||||||
|
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] onBackClick triggered', {
|
||||||
|
// sourcePanel,
|
||||||
|
// sourceMenu,
|
||||||
|
// isCancelClick,
|
||||||
|
// currentPanels: panels.map((p) => p.name),
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
fp.pipe(
|
fp.pipe(
|
||||||
() => {
|
() => {
|
||||||
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
|
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
|
||||||
@@ -393,7 +401,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
switch (sourcePanel) {
|
switch (sourcePanel) {
|
||||||
case panel_names.PLAYER_PANEL:
|
case panel_names.PLAYER_PANEL:
|
||||||
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
|
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
|
||||||
console.log('[DetailPanel] onBackClick - PlayerPanel 출신: 모달 정리만 수행');
|
console.log('[🟠UNIQUE_DETAIL_BACK🟠] PlayerPanel 출신: 모달 정리만 수행');
|
||||||
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
break;
|
break;
|
||||||
@@ -402,17 +410,18 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
case panel_names.SEARCH_PANEL:
|
case panel_names.SEARCH_PANEL:
|
||||||
default:
|
default:
|
||||||
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
|
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
|
||||||
console.log(
|
// console.log(
|
||||||
'[DetailPanel] onBackClick - source panel:',
|
// '[🟠UNIQUE_DETAIL_BACK🟠] source panel:',
|
||||||
sourcePanel,
|
// sourcePanel,
|
||||||
'백그라운드 비디오 일시 중지'
|
// '백그라운드 비디오 일시 중지'
|
||||||
);
|
// );
|
||||||
dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지
|
dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지
|
||||||
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] Calling popPanel(DETAIL_PANEL)');
|
||||||
dispatch(popPanel(panel_names.DETAIL_PANEL));
|
dispatch(popPanel(panel_names.DETAIL_PANEL));
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -434,7 +443,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
if (shouldUpdatePanel) {
|
if (shouldUpdatePanel) {
|
||||||
console.log(
|
console.log(
|
||||||
'[DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
|
'[PANEL][DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,32 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, {
|
||||||
import css from "./ProductDescription.module.less";
|
useCallback,
|
||||||
import { $L, removeSpecificTags } from "../../../../utils/helperMethods";
|
useMemo,
|
||||||
import Spottable from "@enact/spotlight/Spottable";
|
} from 'react';
|
||||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
|
||||||
import Spotlight from "@enact/spotlight";
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
setHidePopup,
|
||||||
|
setShowPopup,
|
||||||
|
} from '../../../../actions/commonActions';
|
||||||
|
import TButtonScroller
|
||||||
|
from '../../../../components/TButtonScroller/TButtonScroller';
|
||||||
|
import TNewPopUp from '../../../../components/TPopUp/TNewPopUp';
|
||||||
|
import * as Config from '../../../../utils/Config';
|
||||||
|
import {
|
||||||
|
$L,
|
||||||
|
removeSpecificTags,
|
||||||
|
} from '../../../../utils/helperMethods';
|
||||||
|
import css from './ProductDescription.module.less';
|
||||||
|
|
||||||
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
||||||
|
|
||||||
const SpottableComponent = Spottable("div");
|
const SpottableComponent = Spottable("div");
|
||||||
@@ -19,11 +42,22 @@ const Container = SpotlightContainerDecorator(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default function ProductDescription({ productInfo }) {
|
export default function ProductDescription({ productInfo }) {
|
||||||
|
const { popupVisible, activePopup } = useSelector(
|
||||||
|
(state) => state.common.popup
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
|
||||||
const productDescription = useCallback(() => {
|
const productDescription = useCallback(() => {
|
||||||
const sanitizedString = removeSpecificTags(productInfo?.prdtDesc);
|
const sanitizedString = removeSpecificTags(productInfo?.prdtDesc);
|
||||||
return { __html: sanitizedString };
|
return { __html: sanitizedString };
|
||||||
}, [productInfo?.prdtDesc]);
|
}, [productInfo?.prdtDesc]);
|
||||||
|
|
||||||
|
const productDescriptionText = useMemo(() => {
|
||||||
|
return removeSpecificTags(productInfo?.prdtDesc);
|
||||||
|
}, [productInfo?.prdtDesc]);
|
||||||
|
|
||||||
// 왼쪽 화살표 키 이벤트 처리
|
// 왼쪽 화살표 키 이벤트 처리
|
||||||
const handleKeyDown = useCallback((ev) => {
|
const handleKeyDown = useCallback((ev) => {
|
||||||
if (ev.keyCode === 37) { // 왼쪽 화살표 키
|
if (ev.keyCode === 37) { // 왼쪽 화살표 키
|
||||||
@@ -34,6 +68,20 @@ export default function ProductDescription({ productInfo }) {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const descriptionClick = useCallback(() => {
|
||||||
|
dispatch(setShowPopup(Config.ACTIVE_POPUP.scrollPopup));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const _onClose = useCallback(()=>{
|
||||||
|
dispatch(setHidePopup());
|
||||||
|
// Restore focus to the description content after popup closes
|
||||||
|
setTimeout(() => {
|
||||||
|
Spotlight.focus('product-description-content');
|
||||||
|
}, 100);
|
||||||
|
},[dispatch])
|
||||||
|
|
||||||
// ProductDescription: Container 직접 사용 패턴
|
// ProductDescription: Container 직접 사용 패턴
|
||||||
// prdtDesc가 없으면 렌더링하지 않음
|
// prdtDesc가 없으면 렌더링하지 않음
|
||||||
if (!productInfo?.prdtDesc) {
|
if (!productInfo?.prdtDesc) {
|
||||||
@@ -41,6 +89,7 @@ export default function ProductDescription({ productInfo }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Container
|
<Container
|
||||||
className={css.descriptionContainer}
|
className={css.descriptionContainer}
|
||||||
spotlightId="product-description-container"
|
spotlightId="product-description-container"
|
||||||
@@ -61,7 +110,8 @@ export default function ProductDescription({ productInfo }) {
|
|||||||
<SpottableComponent
|
<SpottableComponent
|
||||||
className={css.descriptionWrapper}
|
className={css.descriptionWrapper}
|
||||||
spotlightId="product-description-content"
|
spotlightId="product-description-content"
|
||||||
onClick={() => console.log("[ProductDescription] Content clicked")}
|
// onClick={() => console.log("[ProductDescription] Content clicked")}
|
||||||
|
onClick={descriptionClick}
|
||||||
onFocus={() => console.log("[ProductDescription] Content focused")}
|
onFocus={() => console.log("[ProductDescription] Content focused")}
|
||||||
onBlur={() => console.log("[ProductDescription] Content blurred")}
|
onBlur={() => console.log("[ProductDescription] Content blurred")}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
@@ -72,5 +122,28 @@ export default function ProductDescription({ productInfo }) {
|
|||||||
/>
|
/>
|
||||||
</SpottableComponent>
|
</SpottableComponent>
|
||||||
</Container>
|
</Container>
|
||||||
|
{activePopup === Config.ACTIVE_POPUP.scrollPopup && (
|
||||||
|
<TNewPopUp
|
||||||
|
kind="scrollPopup"
|
||||||
|
open={popupVisible}
|
||||||
|
hasText
|
||||||
|
title={$L("DESCRIPTION")}
|
||||||
|
onClick={_onClose}
|
||||||
|
hasButton
|
||||||
|
button1Text={$L("OK")}
|
||||||
|
>
|
||||||
|
<TButtonScroller
|
||||||
|
boxHeight={460}
|
||||||
|
width={844}
|
||||||
|
kind={"figmaTermsPopup"}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css.scrollContainer}
|
||||||
|
dangerouslySetInnerHTML={{ __html: productDescriptionText }}
|
||||||
|
/>
|
||||||
|
</TButtonScroller>
|
||||||
|
</TNewPopUp>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,3 +51,8 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.scrollContainer {
|
||||||
|
padding: 31px;
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 1114px; // ProductDetail과 동일한 고정 크기
|
width: 1114px; // ProductDetail과 동일한 고정 크기
|
||||||
max-width: 1114px;
|
max-width: 1114px;
|
||||||
height: 740px; // ProductDetail과 동일한 고정 높이
|
height: 632px !important; // ProductDetail과 동일한 고정 높이
|
||||||
margin-bottom: 30px; // ProductDetail과 동일한 간격
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: rgba(0, 0, 0, 1);
|
background-color: rgba(0, 0, 0, 1);
|
||||||
border-radius: 12px;
|
border-radius: 12px 12px 0 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 6px; // 포커스 테두리를 위한 공간
|
padding: 6px; // 포커스 테두리를 위한 공간
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -80,7 +79,7 @@
|
|||||||
z-index: 23; // MediaPanel(z-index: 22)보다 위에 표시되어야 비디오 재생 중에도 포커스 테두리가 보임
|
z-index: 23; // MediaPanel(z-index: 22)보다 위에 표시되어야 비디오 재생 중에도 포커스 테두리가 보임
|
||||||
border: 6px solid @PRIMARY_COLOR_RED;
|
border: 6px solid @PRIMARY_COLOR_RED;
|
||||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 12px;
|
border-radius: 12px 12px 0px 0px;
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,15 +216,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
width: 100%;
|
width: calc(100% - 10px);
|
||||||
height: 54px;
|
height: 54px;
|
||||||
background: #000000;
|
background: #000000;
|
||||||
.flex(@justifyCenter:flex-start);
|
.flex(@justifyCenter:flex-start);
|
||||||
padding: 6px 18px 18px 18px;
|
padding: 6px 18px 18px 18px;
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
border-radius: 0 0 12px 12px;
|
border-radius: 0 0 12px 12px;
|
||||||
|
margin-bottom: 30px; // ProductDetail과 동일한 간격
|
||||||
.marquee {
|
.marquee {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ export default function ProductVideo({
|
|||||||
if (!canPlayVideo) return null;
|
if (!canPlayVideo) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<SpottableComponent
|
<SpottableComponent
|
||||||
className={css.videoContainer}
|
className={css.videoContainer}
|
||||||
onClick={handleVideoClick}
|
onClick={handleVideoClick}
|
||||||
@@ -330,12 +331,13 @@ export default function ProductVideo({
|
|||||||
<img src={playImg} alt="재생" />
|
<img src={playImg} alt="재생" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</SpottableComponent>
|
||||||
<div className={css.notice}>
|
<div className={css.notice}>
|
||||||
<Marquee className={css.marquee} marqueeOn="render">
|
<Marquee className={css.marquee} marqueeOn="render">
|
||||||
<img src={ic_warning} alt={disclaimer} />
|
<img src={ic_warning} alt={disclaimer} />
|
||||||
<span>{disclaimer}</span>
|
<span>{disclaimer}</span>
|
||||||
</Marquee>
|
</Marquee>
|
||||||
</div>
|
</div>
|
||||||
</SpottableComponent>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -26,17 +27,35 @@ export default function UserReviewDetail({
|
|||||||
onNext,
|
onNext,
|
||||||
className,
|
className,
|
||||||
}) {
|
}) {
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||||
|
|
||||||
|
// 새로운 리뷰가 로드될 때 이미지 인덱스 초기화
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentImageIndex(0);
|
||||||
|
}, [currentReview]);
|
||||||
|
|
||||||
|
const reviewImages = currentReview?.reviewImageList || [];
|
||||||
|
const hasMultipleImages = reviewImages.length > 1;
|
||||||
|
|
||||||
const handlePrevious = useCallback(() => {
|
const handlePrevious = useCallback(() => {
|
||||||
if (onPrevious && currentIndex > 0) {
|
// 이미지가 여러 개이고 현재 이미지가 첫 번째가 아니면 이미지만 변경
|
||||||
|
if (hasMultipleImages && currentImageIndex > 0) {
|
||||||
|
setCurrentImageIndex(prev => prev - 1);
|
||||||
|
} else if (onPrevious && currentIndex > 0) {
|
||||||
|
// 이미지가 첫 번째이면 이전 리뷰로 이동
|
||||||
onPrevious();
|
onPrevious();
|
||||||
}
|
}
|
||||||
}, [onPrevious, currentIndex]);
|
}, [onPrevious, currentIndex, hasMultipleImages, currentImageIndex]);
|
||||||
|
|
||||||
const handleNext = useCallback(() => {
|
const handleNext = useCallback(() => {
|
||||||
if (onNext && currentIndex < totalReviews - 1) {
|
// 이미지가 여러 개이고 현재 이미지가 마지막이 아니면 이미지만 변경
|
||||||
|
if (hasMultipleImages && currentImageIndex < reviewImages.length - 1) {
|
||||||
|
setCurrentImageIndex(prev => prev + 1);
|
||||||
|
} else if (onNext && currentIndex < totalReviews - 1) {
|
||||||
|
// 이미지가 마지막이면 다음 리뷰로 이동
|
||||||
onNext();
|
onNext();
|
||||||
}
|
}
|
||||||
}, [onNext, currentIndex, totalReviews]);
|
}, [onNext, currentIndex, totalReviews, hasMultipleImages, currentImageIndex, reviewImages.length]);
|
||||||
|
|
||||||
// 리뷰 데이터가 없을 때 처리
|
// 리뷰 데이터가 없을 때 처리
|
||||||
if (!currentReview) {
|
if (!currentReview) {
|
||||||
@@ -47,9 +66,7 @@ export default function UserReviewDetail({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewImage =
|
const reviewImage = reviewImages[currentImageIndex];
|
||||||
currentReview.reviewImageList && currentReview.reviewImageList[0];
|
|
||||||
const hasMultipleReviews = totalReviews > 1;
|
|
||||||
|
|
||||||
const formatDate = (dateStr) => {
|
const formatDate = (dateStr) => {
|
||||||
const [year, month, day] = dateStr.split("-");
|
const [year, month, day] = dateStr.split("-");
|
||||||
@@ -59,7 +76,7 @@ export default function UserReviewDetail({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Left Arrow - 이전 리뷰가 있을 때만 표시 */}
|
{/* Left Arrow - 이전 리뷰가 있을 때만 표시 */}
|
||||||
{hasMultipleReviews && currentIndex > 0 && (
|
{(hasMultipleImages || currentIndex > 0) && (
|
||||||
<SpottableButton
|
<SpottableButton
|
||||||
className={css.leftArrow}
|
className={css.leftArrow}
|
||||||
onClick={handlePrevious}
|
onClick={handlePrevious}
|
||||||
@@ -128,7 +145,7 @@ export default function UserReviewDetail({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Right Arrow - 다음 리뷰가 있을 때만 표시 */}
|
{/* Right Arrow - 다음 리뷰가 있을 때만 표시 */}
|
||||||
{hasMultipleReviews && currentIndex < totalReviews - 1 && (
|
{(hasMultipleImages || currentIndex < totalReviews - 1) && (
|
||||||
<SpottableButton
|
<SpottableButton
|
||||||
className={css.rightArrow}
|
className={css.rightArrow}
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
|
|||||||
@@ -1,21 +1,41 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import { Job } from '@enact/core/util';
|
import { Job } from '@enact/core/util';
|
||||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import { clearThemeDetail } from '../../../../actions/homeActions';
|
import { clearThemeDetail } from '../../../../actions/homeActions';
|
||||||
import { finishModalMediaForce } from '../../../../actions/mediaActions';
|
import { finishModalMediaForce } from '../../../../actions/mediaActions';
|
||||||
import { popPanel, pushPanel, updatePanel } from '../../../../actions/panelActions';
|
import {
|
||||||
|
popPanel,
|
||||||
|
pushPanel,
|
||||||
|
updatePanel,
|
||||||
|
} from '../../../../actions/panelActions';
|
||||||
import { finishVideoPreview } from '../../../../actions/playActions';
|
import { finishVideoPreview } from '../../../../actions/playActions';
|
||||||
import THeader from '../../../../components/THeader/THeader';
|
import THeader from '../../../../components/THeader/THeader';
|
||||||
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
|
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
|
||||||
import TVerticalPagenator from '../../../../components/TVerticalPagenator/TVerticalPagenator';
|
import TVerticalPagenator
|
||||||
import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
|
from '../../../../components/TVerticalPagenator/TVerticalPagenator';
|
||||||
|
import TVirtualGridList
|
||||||
|
from '../../../../components/TVirtualGridList/TVirtualGridList';
|
||||||
import useScrollTo from '../../../../hooks/useScrollTo';
|
import useScrollTo from '../../../../hooks/useScrollTo';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../../utils/Config';
|
||||||
import { $L } from '../../../../utils/helperMethods';
|
import { $L } from '../../../../utils/helperMethods';
|
||||||
import css from './YouMayAlsoLike.module.less';
|
import css from './YouMayAlsoLike.module.less';
|
||||||
|
|
||||||
@@ -158,7 +178,7 @@ export default function YouMayAlsoLike({
|
|||||||
// DetailPanel을 언마운트하지 않고 상품 정보만 업데이트
|
// DetailPanel을 언마운트하지 않고 상품 정보만 업데이트
|
||||||
// 이렇게 하면 백그라운드 비디오 제어 상태가 유지됨
|
// 이렇게 하면 백그라운드 비디오 제어 상태가 유지됨
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
pushPanel({
|
||||||
name: panel_names.DETAIL_PANEL,
|
name: panel_names.DETAIL_PANEL,
|
||||||
panelInfo: {
|
panelInfo: {
|
||||||
showNm: panelInfo?.showNm,
|
showNm: panelInfo?.showNm,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@import "../../../../../style/CommonStyle.module.less";
|
@import '../../../../../style/CommonStyle.module.less';
|
||||||
@import "../../../../../style/utils.module.less";
|
@import '../../../../../style/utils.module.less';
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 36px;
|
font-size: 30px;
|
||||||
color: @COLOR_WHITE;
|
color: @COLOR_WHITE;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.enrgLbImg {
|
.enrgLbImg {
|
||||||
width:62px;
|
width:70px;
|
||||||
border:4px solid transparent;
|
border:4px solid transparent;
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 4px solid @PRIMARY_COLOR_RED;
|
border: 4px solid @PRIMARY_COLOR_RED;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@import "../../../../../style/CommonStyle.module.less";
|
@import '../../../../../style/CommonStyle.module.less';
|
||||||
@import "../../../../../style/utils.module.less";
|
@import '../../../../../style/utils.module.less';
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 36px;
|
font-size: 30px;
|
||||||
color: @COLOR_WHITE;
|
color: @COLOR_WHITE;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ export default function ThemeContents({
|
|||||||
handleItemFocus?.(index);
|
handleItemFocus?.(index);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => Spotlight.focus(spotlightItemId)}
|
onMouseEnter={() => Spotlight.focus(spotlightItemId)}
|
||||||
|
euEnrgLblInfos={euEnrgLblInfos}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export default function ThemeItemCard({
|
|||||||
spotlightId,
|
spotlightId,
|
||||||
dataSpotlightDefault,
|
dataSpotlightDefault,
|
||||||
onFocused,
|
onFocused,
|
||||||
|
euEnrgLblInfos
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
@@ -78,35 +79,35 @@ export default function ThemeItemCard({
|
|||||||
const { originalPrice, discountedPrice, discountRate } =
|
const { originalPrice, discountedPrice, discountRate } =
|
||||||
usePriceInfo(priceInfo) || {};
|
usePriceInfo(priceInfo) || {};
|
||||||
|
|
||||||
const mockEnergyLabel = [
|
// const mockEnergyLabel = [
|
||||||
{
|
// {
|
||||||
"enrgLblExpsOrd": "0",
|
// "enrgLblExpsOrd": "0",
|
||||||
"enrgLblTpCd": "EL_TYPE_05",
|
// "enrgLblTpCd": "EL_TYPE_05",
|
||||||
"enrgLblCd": "MNLC",
|
// "enrgLblCd": "MNLC",
|
||||||
"enrgClasCd": "A",
|
// "enrgClasCd": "A",
|
||||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
||||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
"enrgLblExpsOrd": "0",
|
// "enrgLblExpsOrd": "0",
|
||||||
"enrgLblTpCd": "EL_TYPE_05",
|
// "enrgLblTpCd": "EL_TYPE_05",
|
||||||
"enrgLblCd": "MNLC",
|
// "enrgLblCd": "MNLC",
|
||||||
"enrgClasCd": "D",
|
// "enrgClasCd": "D",
|
||||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
"enrgLblExpsOrd": "0",
|
// "enrgLblExpsOrd": "0",
|
||||||
"enrgLblTpCd": "EL_TYPE_05",
|
// "enrgLblTpCd": "EL_TYPE_05",
|
||||||
"enrgLblCd": "MNLC",
|
// "enrgLblCd": "MNLC",
|
||||||
"enrgClasCd": "D",
|
// "enrgClasCd": "D",
|
||||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||||
}
|
// }
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const setEnactFitZIndex = (zIndexValue) => {
|
const setEnactFitZIndex = (zIndexValue) => {
|
||||||
const target = document.getElementById("floatLayer");
|
const target = document.getElementById("floatLayer");
|
||||||
@@ -211,9 +212,9 @@ export default function ThemeItemCard({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
{mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
{euEnrgLblInfos && euEnrgLblInfos.length > 0 && (
|
||||||
<div className={css.energyLabels}>
|
<div className={css.energyLabels}>
|
||||||
{mockEnergyLabel.map((label, labelIndex) => (
|
{euEnrgLblInfos.map((label, labelIndex) => (
|
||||||
<SpottableTemp
|
<SpottableTemp
|
||||||
key={labelIndex}
|
key={labelIndex}
|
||||||
spotlightDisabled={Boolean(!cursorVisible)}
|
spotlightDisabled={Boolean(!cursorVisible)}
|
||||||
|
|||||||
@@ -220,3 +220,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.energyImage {
|
||||||
|
max-height:800px;
|
||||||
|
}
|
||||||
@@ -93,9 +93,6 @@ export default function THeaderCustom({
|
|||||||
role="button"
|
role="button"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{type === "theme" && themeTitle && (
|
|
||||||
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: themeTitle }} />
|
|
||||||
)}
|
|
||||||
{kind ? (
|
{kind ? (
|
||||||
""
|
""
|
||||||
) : (
|
) : (
|
||||||
@@ -107,6 +104,9 @@ export default function THeaderCustom({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{type === "theme" && themeTitle && (
|
||||||
|
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: `[${themeTitle}]` }} />
|
||||||
|
)}
|
||||||
<Marquee
|
<Marquee
|
||||||
marqueeOn="render"
|
marqueeOn="render"
|
||||||
className={css.title}
|
className={css.title}
|
||||||
|
|||||||
@@ -61,4 +61,5 @@
|
|||||||
color: #eaeaea;
|
color: #eaeaea;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,26 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState, useMemo, forwardRef } from 'react';
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { off, on } from '@enact/core/dispatcher';
|
import {
|
||||||
|
off,
|
||||||
|
on,
|
||||||
|
} from '@enact/core/dispatcher';
|
||||||
import { Job } from '@enact/core/util';
|
import { Job } from '@enact/core/util';
|
||||||
import Scroller from '@enact/sandstone/Scroller';
|
import Scroller from '@enact/sandstone/Scroller';
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
|
|
||||||
import AutoScrollAreaDetail, { POSITION } from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
|
import AutoScrollAreaDetail, {
|
||||||
|
POSITION,
|
||||||
|
} from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
|
||||||
import css from './TScrollerDetail.module.less';
|
import css from './TScrollerDetail.module.less';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,7 +218,8 @@ const TScrollerDetail = forwardRef(
|
|||||||
onScrollStop={_onScrollStop}
|
onScrollStop={_onScrollStop}
|
||||||
onScroll={_onScroll}
|
onScroll={_onScroll}
|
||||||
scrollMode={scrollMode || 'translate'}
|
scrollMode={scrollMode || 'translate'}
|
||||||
focusableScrollbar={focusableScrollbar}
|
// focusableScrollbar={focusableScrollbar}
|
||||||
|
focusableScrollbar={false}
|
||||||
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
|
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
horizontalScrollbar={horizontalScrollbar}
|
horizontalScrollbar={horizontalScrollbar}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { Job } from "@enact/core/util";
|
import { Job } from "@enact/core/util";
|
||||||
import Spotlight from "@enact/spotlight";
|
import Spotlight from "@enact/spotlight";
|
||||||
|
|
||||||
import { updatePanel } from "../../../../../actions/panelActions";
|
import { pushPanel, updatePanel } from "../../../../../actions/panelActions";
|
||||||
import { startVideoPlayer } from "../../../../../actions/playActions";
|
// import { startVideoPlayer } from "../../../../../actions/playActions";
|
||||||
import TItemCard, {
|
import TItemCard, {
|
||||||
removeDotAndColon,
|
removeDotAndColon,
|
||||||
} from "../../../../../components/TItemCard/TItemCard";
|
} from "../../../../../components/TItemCard/TItemCard";
|
||||||
@@ -113,27 +113,36 @@ export default function RecommendedShowsProductList({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let y =
|
// 🆕 DetailPanel로 이동 (ShopByShow 방식)
|
||||||
index < 2
|
|
||||||
? 0
|
|
||||||
: index === 2
|
|
||||||
? scaleH(208)
|
|
||||||
: scaleH(index * 248 - 248 - 40);
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
startVideoPlayer({
|
pushPanel({
|
||||||
modal: false,
|
name: panel_names.DETAIL_PANEL,
|
||||||
patnrId,
|
panelInfo: { patnrId, prdtId },
|
||||||
prdtId,
|
|
||||||
showId,
|
|
||||||
shptmBanrTpNm: "VOD",
|
|
||||||
thumbnail: videoThumbnail,
|
|
||||||
targetId: "spotlightId-" + prdtId,
|
|
||||||
y,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔴 기존 PlayerPanel 로직 (주석처리)
|
||||||
|
// let y =
|
||||||
|
// index < 2
|
||||||
|
// ? 0
|
||||||
|
// : index === 2
|
||||||
|
// ? scaleH(208)
|
||||||
|
// : scaleH(index * 248 - 248 - 40);
|
||||||
|
//
|
||||||
|
// dispatch(
|
||||||
|
// startVideoPlayer({
|
||||||
|
// modal: false,
|
||||||
|
// patnrId,
|
||||||
|
// prdtId,
|
||||||
|
// showId,
|
||||||
|
// shptmBanrTpNm: "VOD",
|
||||||
|
// thumbnail: videoThumbnail,
|
||||||
|
// targetId: "spotlightId-" + prdtId,
|
||||||
|
// y,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
},
|
},
|
||||||
[catCd, dispatch, patnrId, showId, videoThumbnail]
|
[catCd, dispatch, patnrId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import Spotlight from '@enact/spotlight';
|
|||||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import { pushPanel, updatePanel, navigateFromBestSeller } from '../../../actions/panelActions';
|
import {
|
||||||
|
navigateFromBestSeller,
|
||||||
|
pushPanel,
|
||||||
|
updatePanel,
|
||||||
|
} from '../../../actions/panelActions';
|
||||||
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
|
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
|
||||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||||
import Tag from '../../../components/TItemCard/Tag';
|
import Tag from '../../../components/TItemCard/Tag';
|
||||||
@@ -15,13 +19,20 @@ import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
|||||||
import TScroller from '../../../components/TScroller/TScroller';
|
import TScroller from '../../../components/TScroller/TScroller';
|
||||||
import useScrollReset from '../../../hooks/useScrollReset';
|
import useScrollReset from '../../../hooks/useScrollReset';
|
||||||
import useScrollTo from '../../../hooks/useScrollTo';
|
import useScrollTo from '../../../hooks/useScrollTo';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../utils/Config';
|
||||||
import { $L, scaleW } from '../../../utils/helperMethods';
|
import { $L, scaleW } from '../../../utils/helperMethods';
|
||||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
import css from './BestSeller.module.less';
|
import css from './BestSeller.module.less';
|
||||||
|
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable('div');
|
||||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: 'last-focused' },
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
const BestSeller = ({
|
const BestSeller = ({
|
||||||
order,
|
order,
|
||||||
@@ -33,15 +44,26 @@ const BestSeller = ({
|
|||||||
shelfTitle,
|
shelfTitle,
|
||||||
}) => {
|
}) => {
|
||||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
|
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||||
|
scrollLeft,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
||||||
|
|
||||||
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
|
const bestSellerDatas = useSelector(
|
||||||
|
(state) => state.product.bestSellerData?.bestSeller
|
||||||
|
);
|
||||||
|
|
||||||
const bestSellerNewDatas = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
|
const bestSellerNewDatas = useSelector(
|
||||||
|
(state) => state.foryou?.recommendInfo?.recommendProduct
|
||||||
|
);
|
||||||
|
|
||||||
|
const { userNumber } = useSelector(
|
||||||
|
(state) => state.common.appStatus.loginUserData
|
||||||
|
);
|
||||||
|
|
||||||
const [drawChk, setDrawChk] = useState(false);
|
const [drawChk, setDrawChk] = useState(false);
|
||||||
const [firstChk, setFirstChk] = useState(0);
|
const [firstChk, setFirstChk] = useState(0);
|
||||||
@@ -51,11 +73,14 @@ const BestSeller = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBestInfos(
|
setBestInfos(
|
||||||
bestSellerNewDatas?.filter((item) => item.recommendTpCd === 'BESTSELLER') || [] // 기본값으로 빈 배열 설정
|
bestSellerNewDatas?.filter(
|
||||||
|
(item) => item.recommendTpCd === 'BESTSELLER'
|
||||||
|
) || [] // 기본값으로 빈 배열 설정
|
||||||
);
|
);
|
||||||
}, [bestSellerNewDatas]);
|
}, [bestSellerNewDatas]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (userNumber) {
|
||||||
if (!bestInfos || bestInfos.length === 0) {
|
if (!bestInfos || bestInfos.length === 0) {
|
||||||
const baseData =
|
const baseData =
|
||||||
bestSellerDatas?.map((item) => ({
|
bestSellerDatas?.map((item) => ({
|
||||||
@@ -72,7 +97,9 @@ const BestSeller = ({
|
|||||||
foryou: true,
|
foryou: true,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
const recommendedPrdtIds = new Set(
|
||||||
|
recommendedData.map((item) => item.prdtId)
|
||||||
|
);
|
||||||
|
|
||||||
const baseData =
|
const baseData =
|
||||||
bestSellerDatas?.map((item) => ({
|
bestSellerDatas?.map((item) => ({
|
||||||
@@ -81,7 +108,10 @@ const BestSeller = ({
|
|||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
setBestItemNewData(baseData);
|
setBestItemNewData(baseData);
|
||||||
}, [bestSellerDatas, bestInfos]);
|
} else {
|
||||||
|
setBestItemNewData(bestSellerDatas);
|
||||||
|
}
|
||||||
|
}, [bestSellerDatas, bestInfos, userNumber]);
|
||||||
|
|
||||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||||
|
|
||||||
@@ -144,7 +174,10 @@ const BestSeller = ({
|
|||||||
if (c) {
|
if (c) {
|
||||||
let cAriaLabel = c.getAttribute('aria-label');
|
let cAriaLabel = c.getAttribute('aria-label');
|
||||||
if (cAriaLabel) {
|
if (cAriaLabel) {
|
||||||
const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
|
const newcAriaLabel = cAriaLabel.replace(
|
||||||
|
'Best Seller, Heading 1,',
|
||||||
|
''
|
||||||
|
);
|
||||||
c.setAttribute('aria-label', newcAriaLabel);
|
c.setAttribute('aria-label', newcAriaLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ export default function HomeBanner({
|
|||||||
|
|
||||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||||
const panels = useSelector((state) => state.panels.panels);
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
|
const isDeepLinkEntry = useSelector(
|
||||||
|
(state) => state.home.homeInfo?.panelInfo?.isDeepLinkEntry
|
||||||
|
);
|
||||||
// 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리
|
// 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리
|
||||||
const focusHistory = useFocusHistory({
|
const focusHistory = useFocusHistory({
|
||||||
enableLogging: true,
|
enableLogging: true,
|
||||||
@@ -164,10 +167,11 @@ export default function HomeBanner({
|
|||||||
videoData = targetBannerData.bannerDetailInfos?.[0];
|
videoData = targetBannerData.bannerDetailInfos?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetailPanel이 떠 있는 동안에는 배너 자동 재생을 스킵 (PlayerPanel 모달 재설정 방지)
|
// 🔽 [251221] DetailPanel이나 DeepLink PlayerPanel이 떠 있으면 배너 자동 재생 스킵
|
||||||
const hasDetailPanel = panels.some((p) => p.name === panel_names.DETAIL_PANEL);
|
const hasDetailPanel = panels.some((p) => p.name === panel_names.DETAIL_PANEL);
|
||||||
|
const hasPlayerPanel = panels.some((p) => p.name === panel_names.PLAYER_PANEL);
|
||||||
|
|
||||||
if (!hasDetailPanel && videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) {
|
if (!hasDetailPanel && !hasPlayerPanel && !isDeepLinkEntry && videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) {
|
||||||
console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus);
|
console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus);
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -185,7 +189,7 @@ export default function HomeBanner({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [bannerDataList, defaultFocus, dispatch, panels]);
|
}, [bannerDataList, defaultFocus, dispatch, panels, isDeepLinkEntry]);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index, isHorizontal) => {
|
(index, isHorizontal) => {
|
||||||
|
|||||||
@@ -617,6 +617,15 @@ export default function RandomUnit({
|
|||||||
|
|
||||||
// 비디오 클릭
|
// 비디오 클릭
|
||||||
const videoClick = useCallback(() => {
|
const videoClick = useCallback(() => {
|
||||||
|
// 🔽 비디오가 다른 배너에서 modal=true로 이미 재생 중이면 클릭 무시
|
||||||
|
if (playerPanelInfo?.modal === true && currentVideoBannerId && currentVideoBannerId !== spotlightId) {
|
||||||
|
console.log('[RandomUnit] videoClick 무시: 다른 배너에서 modal=true로 재생 중', {
|
||||||
|
currentVideoBannerId,
|
||||||
|
clickedBannerId: spotlightId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lastFocusedTargetId = getContainerId(Spotlight.getCurrent());
|
const lastFocusedTargetId = getContainerId(Spotlight.getCurrent());
|
||||||
const currentSpot = Spotlight.getCurrent();
|
const currentSpot = Spotlight.getCurrent();
|
||||||
if (lastFocusedTargetId) {
|
if (lastFocusedTargetId) {
|
||||||
@@ -674,6 +683,7 @@ export default function RandomUnit({
|
|||||||
sendBannerLog,
|
sendBannerLog,
|
||||||
onBlur,
|
onBlur,
|
||||||
playerPanelInfo?.modal,
|
playerPanelInfo?.modal,
|
||||||
|
currentVideoBannerId,
|
||||||
dispatch,
|
dispatch,
|
||||||
handleStartVideo,
|
handleStartVideo,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
useDispatch,
|
|
||||||
useSelector,
|
|
||||||
} from 'react-redux';
|
|
||||||
import { applyMiddleware } from 'redux';
|
import { applyMiddleware } from 'redux';
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
@@ -23,11 +20,11 @@ import {
|
|||||||
import hsn from '../../../assets/images/bg/hsn_new.png';
|
import hsn from '../../../assets/images/bg/hsn_new.png';
|
||||||
import koreaKiosk from '../../../assets/images/bg/koreaKiosk_new.png';
|
import koreaKiosk from '../../../assets/images/bg/koreaKiosk_new.png';
|
||||||
import lgelectronics from '../../../assets/images/bg/lgelectronics_new.png';
|
import lgelectronics from '../../../assets/images/bg/lgelectronics_new.png';
|
||||||
|
import nbcu from '../../../assets/images/bg/nbcu_new.png';
|
||||||
import ontv4u from '../../../assets/images/bg/ontv4u_new.png';
|
import ontv4u from '../../../assets/images/bg/ontv4u_new.png';
|
||||||
import Pinkfong from '../../../assets/images/bg/Pinkfong_new.png';
|
import Pinkfong from '../../../assets/images/bg/Pinkfong_new.png';
|
||||||
import qvc from '../../../assets/images/bg/qvc_new.png';
|
import qvc from '../../../assets/images/bg/qvc_new.png';
|
||||||
import shoplc from '../../../assets/images/bg/shoplc_new.png';
|
import shoplc from '../../../assets/images/bg/shoplc_new.png';
|
||||||
import nbcu from '../../../assets/images/bg/nbcu_new.png';
|
|
||||||
import { types } from '../../actions/actionTypes';
|
import { types } from '../../actions/actionTypes';
|
||||||
import {
|
import {
|
||||||
changeAppStatus,
|
changeAppStatus,
|
||||||
@@ -45,14 +42,8 @@ import {
|
|||||||
getHomeMainContents,
|
getHomeMainContents,
|
||||||
updateHomeInfo,
|
updateHomeInfo,
|
||||||
} from '../../actions/homeActions';
|
} from '../../actions/homeActions';
|
||||||
import {
|
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
|
||||||
sendLogGNB,
|
import { getSubCategory, getTop20Show } from '../../actions/mainActions';
|
||||||
sendLogTotalRecommend,
|
|
||||||
} from '../../actions/logActions';
|
|
||||||
import {
|
|
||||||
getSubCategory,
|
|
||||||
getTop20Show,
|
|
||||||
} from '../../actions/mainActions';
|
|
||||||
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
||||||
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
|
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
|
||||||
import { updatePanel } from '../../actions/panelActions';
|
import { updatePanel } from '../../actions/panelActions';
|
||||||
@@ -69,8 +60,7 @@ import TButton, { TYPES } from '../../components/TButton/TButton';
|
|||||||
import TPanel from '../../components/TPanel/TPanel';
|
import TPanel from '../../components/TPanel/TPanel';
|
||||||
import TNewPopUp from '../../components/TPopUp/TNewPopUp';
|
import TNewPopUp from '../../components/TPopUp/TNewPopUp';
|
||||||
import TPopUp from '../../components/TPopUp/TPopUp';
|
import TPopUp from '../../components/TPopUp/TPopUp';
|
||||||
import TVerticalPagenator
|
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||||
from '../../components/TVerticalPagenator/TVerticalPagenator';
|
|
||||||
import useDebugKey from '../../hooks/useDebugKey';
|
import useDebugKey from '../../hooks/useDebugKey';
|
||||||
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
|
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
@@ -178,68 +168,113 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
// });
|
// });
|
||||||
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
|
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
|
||||||
const homeLayoutInfo = useSelector((state) => state.home.layoutData);
|
const homeLayoutInfo = useSelector((state) => state.home.layoutData);
|
||||||
const panelInfo = useSelector((state) => state.home.homeInfo?.panelInfo ?? {});
|
const panelInfo = useSelector(
|
||||||
|
(state) => state.home.homeInfo?.panelInfo ?? {}
|
||||||
|
);
|
||||||
const panels = useSelector((state) => state.panels.panels);
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
const webOSVersion = useSelector((state) => state.common.appStatus?.webOSVersion);
|
const webOSVersion = useSelector(
|
||||||
|
(state) => state.common.appStatus?.webOSVersion
|
||||||
|
);
|
||||||
const enterThroughGNB = useSelector((state) => state.home.enterThroughGNB);
|
const enterThroughGNB = useSelector((state) => state.home.enterThroughGNB);
|
||||||
const defaultFocus = useSelector((state) => state.home.defaultFocus);
|
const defaultFocus = useSelector((state) => state.home.defaultFocus);
|
||||||
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
|
const bannerDataList = useSelector(
|
||||||
|
(state) => state.home.bannerData?.bannerInfos
|
||||||
|
);
|
||||||
|
|
||||||
// ✅ PlayerPanel의 shouldShrinkTo1px 상태 추적
|
// ✅ PlayerPanel의 shouldShrinkTo1px 상태 추적
|
||||||
const playerPanelShouldShrink = useSelector((state) => {
|
const playerPanelShouldShrink = useSelector((state) => {
|
||||||
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
|
const playerPanel = state.panels.panels.find(
|
||||||
|
(p) => p.name === panel_names.PLAYER_PANEL
|
||||||
|
);
|
||||||
return playerPanel?.panelInfo?.shouldShrinkTo1px ?? false;
|
return playerPanel?.panelInfo?.shouldShrinkTo1px ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ PlayerPanel의 modal 상태 추적 (false → true 감지용)
|
// ✅ PlayerPanel의 modal 상태 추적 (false → true 감지용)
|
||||||
const playerModalState = useSelector((state) => {
|
const playerModalState = useSelector((state) => {
|
||||||
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
|
const playerPanel = state.panels.panels.find(
|
||||||
|
(p) => p.name === panel_names.PLAYER_PANEL
|
||||||
|
);
|
||||||
return playerPanel?.panelInfo?.modal ?? false;
|
return playerPanel?.panelInfo?.modal ?? false;
|
||||||
});
|
});
|
||||||
const prevPlayerModalStateRef = useRef(false);
|
const prevPlayerModalStateRef = useRef(false);
|
||||||
|
|
||||||
const categoryInfos = useSelector((state) => state.onSale.homeOnSaleData?.data?.categoryInfos);
|
const categoryInfos = useSelector(
|
||||||
|
(state) => state.onSale.homeOnSaleData?.data?.categoryInfos
|
||||||
|
);
|
||||||
|
|
||||||
const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
|
const categoryItemInfos = useSelector(
|
||||||
|
(state) => state.main.subCategoryData?.categoryItemInfos
|
||||||
|
);
|
||||||
|
|
||||||
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
|
const { popupVisible, activePopup } = useSelector(
|
||||||
|
(state) => state.common.popup
|
||||||
|
);
|
||||||
|
|
||||||
const eventPopInfosData = useSelector((state) => state.event.eventData.eventPopInfo);
|
const eventPopInfosData = useSelector(
|
||||||
|
(state) => state.event.eventData.eventPopInfo
|
||||||
|
);
|
||||||
const eventData = useSelector((state) => state.event.eventData);
|
const eventData = useSelector((state) => state.event.eventData);
|
||||||
const eventClickSuccess = useSelector((state) => state.event.eventClickSuccess);
|
const eventClickSuccess = useSelector(
|
||||||
const homeOnSaleInfos = useSelector((state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos);
|
(state) => state.event.eventClickSuccess
|
||||||
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
|
);
|
||||||
|
const homeOnSaleInfos = useSelector(
|
||||||
|
(state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos
|
||||||
|
);
|
||||||
|
const bestSellerDatas = useSelector(
|
||||||
|
(state) => state.product.bestSellerData?.bestSeller
|
||||||
|
);
|
||||||
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
||||||
const isDeepLink = useSelector((state) => state.common.deepLinkInfo.isDeepLink);
|
const isDeepLink = useSelector(
|
||||||
|
(state) => state.common.deepLinkInfo.isDeepLink
|
||||||
|
);
|
||||||
|
|
||||||
// 선택약관 관련 Redux 상태
|
// 선택약관 관련 Redux 상태
|
||||||
const termsData = useSelector((state) => state.home.termsData);
|
const termsData = useSelector((state) => state.home.termsData);
|
||||||
const termsIdMap = useSelector((state) => state.home.termsIdMap);
|
const termsIdMap = useSelector((state) => state.home.termsIdMap);
|
||||||
const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable);
|
const optionalTermsAvailable = useSelector(
|
||||||
|
(state) => state.home.optionalTermsAvailable
|
||||||
|
);
|
||||||
const optionalTermsData = useSelector((state) => {
|
const optionalTermsData = useSelector((state) => {
|
||||||
if (state.home.termsData && state.home.termsData.data && state.home.termsData.data.terms) {
|
if (
|
||||||
return state.home.termsData.data.terms.find((term) => term.trmsTpCd === 'MST00405');
|
state.home.termsData &&
|
||||||
|
state.home.termsData.data &&
|
||||||
|
state.home.termsData.data.terms
|
||||||
|
) {
|
||||||
|
return state.home.termsData.data.terms.find(
|
||||||
|
(term) => term.trmsTpCd === 'MST00405'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
const termsLoading = useSelector((state) => state.common.termsLoading);
|
const termsLoading = useSelector((state) => state.common.termsLoading);
|
||||||
const currentTermsFlag = useSelector((state) => state.common.termsFlag);
|
const currentTermsFlag = useSelector((state) => state.common.termsFlag);
|
||||||
const optionalTermsPopupFlow = useSelector((state) => state.common.optionalTermsPopupFlow);
|
const optionalTermsPopupFlow = useSelector(
|
||||||
|
(state) => state.common.optionalTermsPopupFlow
|
||||||
|
);
|
||||||
|
|
||||||
|
const { userNumber } = useSelector(
|
||||||
|
(state) => state.common.appStatus.loginUserData
|
||||||
|
);
|
||||||
|
|
||||||
const [btnDisabled, setBtnDisabled] = useState(true);
|
const [btnDisabled, setBtnDisabled] = useState(true);
|
||||||
const [arrowBottom, setArrowBottom] = useState(true);
|
const [arrowBottom, setArrowBottom] = useState(true);
|
||||||
const [firstSpot, setFirstSpot] = useState(false);
|
const [firstSpot, setFirstSpot] = useState(false);
|
||||||
const [eventPopOpen, setEventPopOpen] = useState(false);
|
const [eventPopOpen, setEventPopOpen] = useState(false);
|
||||||
const [nowShelf, setNowShelf] = useState(panelInfo.nowShelf);
|
const [nowShelf, setNowShelf] = useState(panelInfo.nowShelf);
|
||||||
const [firstLgCatCd, setFirstLgCatCd] = useState(panelInfo.currentCatCd ?? null);
|
const [firstLgCatCd, setFirstLgCatCd] = useState(
|
||||||
|
panelInfo.currentCatCd ?? null
|
||||||
|
);
|
||||||
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
|
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
|
||||||
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
|
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
|
||||||
// 선택약관 팝업 상태
|
// 선택약관 팝업 상태
|
||||||
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
|
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] =
|
||||||
|
useState(false);
|
||||||
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
|
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
|
||||||
const [optionalTermsAgreed, setOptionalTermsAgreed] = useState(false);
|
const [optionalTermsAgreed, setOptionalTermsAgreed] = useState(false);
|
||||||
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
|
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
|
||||||
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo.focusedContainerId);
|
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||||
|
panelInfo.focusedContainerId
|
||||||
|
);
|
||||||
// DetailPanel 진입 시 포커스 대상 저장
|
// DetailPanel 진입 시 포커스 대상 저장
|
||||||
const lastFocusedTargetRef = useRef(panelInfo.lastFocusedTargetId || null);
|
const lastFocusedTargetRef = useRef(panelInfo.lastFocusedTargetId || null);
|
||||||
|
|
||||||
@@ -254,7 +289,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevIsOnTopRef.current && !isOnTop) {
|
if (prevIsOnTopRef.current && !isOnTop) {
|
||||||
const current = Spotlight.getCurrent();
|
const current = Spotlight.getCurrent();
|
||||||
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
|
const tBody = document.querySelector(
|
||||||
|
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`
|
||||||
|
);
|
||||||
|
|
||||||
if (current && tBody && tBody.contains(current)) {
|
if (current && tBody && tBody.contains(current)) {
|
||||||
const targetId = current.getAttribute('data-spotlight-id');
|
const targetId = current.getAttribute('data-spotlight-id');
|
||||||
@@ -286,7 +323,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
ImagePreloader.preloadAllImages(BACKGROUND_IMAGES)
|
ImagePreloader.preloadAllImages(BACKGROUND_IMAGES)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
const successCount = results.filter((r) => r !== null).length;
|
const successCount = results.filter((r) => r !== null).length;
|
||||||
dlog(`[HomePanel] Background images preloaded: ${successCount}/${results.length} images`);
|
dlog(
|
||||||
|
`[HomePanel] Background images preloaded: ${successCount}/${results.length} images`
|
||||||
|
);
|
||||||
|
|
||||||
// 프리로딩 통계 정보 로깅 (디버깅용)
|
// 프리로딩 통계 정보 로깅 (디버깅용)
|
||||||
const stats = ImagePreloader.getStats();
|
const stats = ImagePreloader.getStats();
|
||||||
@@ -312,7 +351,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
|
|
||||||
const sortedHomeLayoutInfo = useMemo(() => {
|
const sortedHomeLayoutInfo = useMemo(() => {
|
||||||
if (homeLayoutInfo && homeLayoutInfo.homeLayoutInfo) {
|
if (homeLayoutInfo && homeLayoutInfo.homeLayoutInfo) {
|
||||||
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort((x, y) => x.expsOrd - y.expsOrd);
|
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort(
|
||||||
|
(x, y) => x.expsOrd - y.expsOrd
|
||||||
|
);
|
||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -337,7 +378,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
const expandAttemptRef = useRef(0); // 복구 시도 횟수
|
const expandAttemptRef = useRef(0); // 복구 시도 횟수
|
||||||
|
|
||||||
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
||||||
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
|
const isVideoTransitionLocked = useSelector(
|
||||||
|
(state) => state.home.videoTransitionLocked
|
||||||
|
);
|
||||||
|
|
||||||
// 선택약관 동의 핸들러
|
// 선택약관 동의 핸들러
|
||||||
const handleOptionalAgree = useCallback(() => {
|
const handleOptionalAgree = useCallback(() => {
|
||||||
@@ -417,7 +460,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Spotlight.focus('home_tbody');
|
Spotlight.focus('home_tbody');
|
||||||
},100)
|
}, 100);
|
||||||
}, [handleOptionalAgree, dispatch, currentTermsFlag]);
|
}, [handleOptionalAgree, dispatch, currentTermsFlag]);
|
||||||
|
|
||||||
const handleOptionalDeclineClick = useCallback(() => {
|
const handleOptionalDeclineClick = useCallback(() => {
|
||||||
@@ -428,7 +471,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
setIsOptionalConfirmVisible(false);
|
setIsOptionalConfirmVisible(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Spotlight.focus('home_tbody');
|
Spotlight.focus('home_tbody');
|
||||||
},100)
|
}, 100);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleTermsPopupClosed = useCallback(() => {
|
const handleTermsPopupClosed = useCallback(() => {
|
||||||
@@ -499,7 +542,12 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]);
|
}, [
|
||||||
|
shouldShowOptionalTermsPopup,
|
||||||
|
termsLoading,
|
||||||
|
isOptionalConfirmVisible,
|
||||||
|
dispatch,
|
||||||
|
]);
|
||||||
|
|
||||||
const onCancel = useCallback(() => {
|
const onCancel = useCallback(() => {
|
||||||
const currentSpot = Spotlight.getCurrent();
|
const currentSpot = Spotlight.getCurrent();
|
||||||
@@ -550,7 +598,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
const containerId = sortedHomeLayoutInfo[0].shptmApphmDspyOptCd;
|
const containerId = sortedHomeLayoutInfo[0].shptmApphmDspyOptCd;
|
||||||
const navigableEls = getContainerNavigableElements(containerId);
|
const navigableEls = getContainerNavigableElements(containerId);
|
||||||
const navigableIds = navigableEls.filter((el) => typeof el === 'string');
|
const navigableIds = navigableEls.filter((el) => typeof el === 'string');
|
||||||
const target = containerId === TEMPLATE_CODE_CONF.TOP ? 'banner0' : containerId;
|
const target =
|
||||||
|
containerId === TEMPLATE_CODE_CONF.TOP ? 'banner0' : containerId;
|
||||||
|
|
||||||
if (navigableIds.length > 0) {
|
if (navigableIds.length > 0) {
|
||||||
setContainerLastFocusedElement(null, navigableIds);
|
setContainerLastFocusedElement(null, navigableIds);
|
||||||
@@ -626,7 +675,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
<HomeBanner
|
<HomeBanner
|
||||||
key={el.shptmApphmDspyOptCd}
|
key={el.shptmApphmDspyOptCd}
|
||||||
spotlightId={el.shptmApphmDspyOptCd}
|
spotlightId={el.shptmApphmDspyOptCd}
|
||||||
firstSpot={!panelInfo.focusedContainerId && !panelInfo.currentSpot}
|
firstSpot={
|
||||||
|
!panelInfo.focusedContainerId && !panelInfo.currentSpot
|
||||||
|
}
|
||||||
className={css.homeBannerWrap}
|
className={css.homeBannerWrap}
|
||||||
handleShelfFocus={handleItemFocus(
|
handleShelfFocus={handleItemFocus(
|
||||||
el.shptmApphmDspyOptCd,
|
el.shptmApphmDspyOptCd,
|
||||||
@@ -718,7 +769,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
} else break;
|
} else break;
|
||||||
}
|
}
|
||||||
case TEMPLATE_CODE_CONF.PICK_FOR_YOU: {
|
case TEMPLATE_CODE_CONF.PICK_FOR_YOU: {
|
||||||
if (bestSellerDatas && bestSellerDatas.length > 0) {
|
if (userNumber) {
|
||||||
return (
|
return (
|
||||||
<PickedForYou
|
<PickedForYou
|
||||||
key={el.shptmApphmDspyOptCd}
|
key={el.shptmApphmDspyOptCd}
|
||||||
@@ -737,7 +788,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
{loadingComplete && sortedHomeLayoutInfo && sortedHomeLayoutInfo.length > 0 && (
|
{loadingComplete &&
|
||||||
|
sortedHomeLayoutInfo &&
|
||||||
|
sortedHomeLayoutInfo.length > 0 && (
|
||||||
<TButton
|
<TButton
|
||||||
className={css.tButton}
|
className={css.tButton}
|
||||||
onClick={handleTopButtonClick}
|
onClick={handleTopButtonClick}
|
||||||
@@ -914,7 +967,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
// console.log('[HomePanel] playVideo 호출 완료');
|
// console.log('[HomePanel] playVideo 호출 완료');
|
||||||
|
|
||||||
if (isDeepLink || (!panels.length && !panelInfo.focusedContainerId)) {
|
if (isDeepLink || (!panels.length && !panelInfo.focusedContainerId)) {
|
||||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
dispatch(
|
||||||
|
changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })
|
||||||
|
);
|
||||||
dispatch(getHomeMainContents());
|
dispatch(getHomeMainContents());
|
||||||
dispatch(getHomeLayout());
|
dispatch(getHomeLayout());
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -1071,6 +1126,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
const detailPanelClosedTime = useSelector(
|
const detailPanelClosedTime = useSelector(
|
||||||
(state) => state.home.homeInfo?.panelInfo?.detailPanelClosedAt
|
(state) => state.home.homeInfo?.panelInfo?.detailPanelClosedAt
|
||||||
);
|
);
|
||||||
|
const isDeepLinkEntry = useSelector(
|
||||||
|
(state) => state.home.homeInfo?.panelInfo?.isDeepLinkEntry
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (detailPanelClosed && isOnTop) {
|
if (detailPanelClosed && isOnTop) {
|
||||||
@@ -1082,9 +1140,18 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
// console.log('[HomePanel] *** videoPlayIntentRef.current:', videoPlayIntentRef.current);
|
// console.log('[HomePanel] *** videoPlayIntentRef.current:', videoPlayIntentRef.current);
|
||||||
// console.log('[HomePanel] *** lastPlayedBannerIdRef.current:', lastPlayedBannerIdRef.current);
|
// console.log('[HomePanel] *** lastPlayedBannerIdRef.current:', lastPlayedBannerIdRef.current);
|
||||||
|
|
||||||
|
// 🔽 [251221] DeepLink로 PlayerPanel이 진입한 경우 자동 재생 스킵
|
||||||
|
// (플래그 리셋은 PlayerPanel cleanup에서 처리하므로 여기서는 스킵만)
|
||||||
|
if (isDeepLinkEntry) {
|
||||||
|
dlog('[HomePanel] *** [DeepLink] isDeepLinkEntry=true 감지 - 자동 재생 스킵');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 🔽 videoPlayIntentRef가 null인 경우: 비디오 재생 가능한 첫 번째 배너 찾기
|
// 🔽 videoPlayIntentRef가 null인 경우: 비디오 재생 가능한 첫 번째 배너 찾기
|
||||||
if (!videoPlayIntentRef.current && bannerDataList) {
|
if (!videoPlayIntentRef.current && bannerDataList) {
|
||||||
dlog('[HomePanel] *** videoPlayIntentRef가 null - 첫 번째 비디오 배너 검색');
|
dlog(
|
||||||
|
'[HomePanel] *** videoPlayIntentRef가 null - 첫 번째 비디오 배너 검색'
|
||||||
|
);
|
||||||
|
|
||||||
// HomeBanner.jsx의 defaultFocus 계산 로직과 동일
|
// HomeBanner.jsx의 defaultFocus 계산 로직과 동일
|
||||||
let targetIndex = 0;
|
let targetIndex = 0;
|
||||||
@@ -1138,7 +1205,10 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
lastPlayedBannerIdRef.current = bannerId;
|
lastPlayedBannerIdRef.current = bannerId;
|
||||||
dlog('[HomePanel] *** videoPlayIntentRef 설정 완료:', videoPlayIntentRef.current);
|
dlog(
|
||||||
|
'[HomePanel] *** videoPlayIntentRef 설정 완료:',
|
||||||
|
videoPlayIntentRef.current
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
dlog('[HomePanel] *** ⚠️ 비디오 재생 가능한 배너를 찾지 못함');
|
dlog('[HomePanel] *** ⚠️ 비디오 재생 가능한 배너를 찾지 못함');
|
||||||
}
|
}
|
||||||
@@ -1181,7 +1251,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
|
|
||||||
// 🔽 DetailPanel에서 돌아온 뒤 포커스를 마지막 포커스 대상에 복원
|
// 🔽 DetailPanel에서 돌아온 뒤 포커스를 마지막 포커스 대상에 복원
|
||||||
dlog('[HomePanel] *** 🎯 Focus 복원 준비');
|
dlog('[HomePanel] *** 🎯 Focus 복원 준비');
|
||||||
const targetFocusId = panelInfo.lastFocusedTargetId || lastFocusedTargetRef.current;
|
const targetFocusId =
|
||||||
|
panelInfo.lastFocusedTargetId || lastFocusedTargetRef.current;
|
||||||
dlog(
|
dlog(
|
||||||
'[HomePanel] *** 📍 targetFocusId:',
|
'[HomePanel] *** 📍 targetFocusId:',
|
||||||
targetFocusId,
|
targetFocusId,
|
||||||
@@ -1219,7 +1290,7 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [detailPanelClosed, isOnTop, bannerDataList, dispatch]);
|
}, [detailPanelClosed, isOnTop, bannerDataList, isDeepLinkEntry, dispatch]);
|
||||||
// =======
|
// =======
|
||||||
// const justCameBack = !prevIsOnTopRef.current && isOnTop;
|
// const justCameBack = !prevIsOnTopRef.current && isOnTop;
|
||||||
|
|
||||||
@@ -1250,7 +1321,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
targetSpotlightCateNm = c.getAttribute('data-catcd-nm');
|
targetSpotlightCateNm = c.getAttribute('data-catcd-nm');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
|
const tBody = document.querySelector(
|
||||||
|
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`
|
||||||
|
);
|
||||||
const currentSpot = c && tBody.contains(c) ? targetSpotlightId : null;
|
const currentSpot = c && tBody.contains(c) ? targetSpotlightId : null;
|
||||||
|
|
||||||
dispatch(checkEnterThroughGNB(false));
|
dispatch(checkEnterThroughGNB(false));
|
||||||
@@ -1263,7 +1336,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
currentCateName: targetSpotlightCateNm,
|
currentCateName: targetSpotlightCateNm,
|
||||||
// <<<<<<< HEAD
|
// <<<<<<< HEAD
|
||||||
focusedContainerId: focusedContainerIdRef.current,
|
focusedContainerId: focusedContainerIdRef.current,
|
||||||
lastFocusedTargetId: lastFocusedTargetRef.current || panelInfo.lastFocusedTargetId,
|
lastFocusedTargetId:
|
||||||
|
lastFocusedTargetRef.current || panelInfo.lastFocusedTargetId,
|
||||||
// =======
|
// =======
|
||||||
// focusedContainerId: focusedContainerId,
|
// focusedContainerId: focusedContainerId,
|
||||||
// >>>>>>> gitlab/develop
|
// >>>>>>> gitlab/develop
|
||||||
@@ -1339,7 +1413,9 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
<>
|
<>
|
||||||
{/* HomePanel용 메모리 상주 그라데이션 배경 */}
|
{/* HomePanel용 메모리 상주 그라데이션 배경 */}
|
||||||
<div
|
<div
|
||||||
className={classNames(css.gradientBackground, { [css.visible]: showGradientBackground })}
|
className={classNames(css.gradientBackground, {
|
||||||
|
[css.visible]: showGradientBackground,
|
||||||
|
})}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -1369,7 +1445,10 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{arrowBottom && (
|
{arrowBottom && (
|
||||||
<p className={classNames(css.arrow, css.arrowBottom)} onClick={handleArrowClick} />
|
<p
|
||||||
|
className={classNames(css.arrow, css.arrowBottom)}
|
||||||
|
onClick={handleArrowClick}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activePopup === ACTIVE_POPUP.exitPopup && (
|
{activePopup === ACTIVE_POPUP.exitPopup && (
|
||||||
@@ -1386,9 +1465,8 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
|
|||||||
text={$L('Are you sure you want to exit Shop Time?')}
|
text={$L('Are you sure you want to exit Shop Time?')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(activePopup === ACTIVE_POPUP.eventPopup || activePopup === ACTIVE_POPUP.smsPopup) && (
|
{(activePopup === ACTIVE_POPUP.eventPopup ||
|
||||||
<EventPopUpBanner />
|
activePopup === ACTIVE_POPUP.smsPopup) && <EventPopUpBanner />}
|
||||||
)}
|
|
||||||
{/* 선택약관 동의 팝업 */}
|
{/* 선택약관 동의 팝업 */}
|
||||||
<OptionalConfirm
|
<OptionalConfirm
|
||||||
open={isOptionalConfirmVisible}
|
open={isOptionalConfirmVisible}
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
import React, {
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import {
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
useDispatch,
|
|
||||||
useSelector,
|
|
||||||
} from 'react-redux';
|
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
import {
|
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
SpotlightContainerDecorator,
|
|
||||||
} from '@enact/spotlight/SpotlightContainerDecorator';
|
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
import { getContainerId } from '@enact/spotlight/src/container';
|
import { getContainerId } from '@enact/spotlight/src/container';
|
||||||
|
|
||||||
import { updateHomeInfo } from '../../../actions/homeActions';
|
import { updateHomeInfo } from '../../../actions/homeActions';
|
||||||
import { pushPanel, popPanel } from '../../../actions/panelActions';
|
import { popPanel, pushPanel } from '../../../actions/panelActions';
|
||||||
import { startVideoPlayer } from '../../../actions/playActions';
|
import { startVideoPlayer } from '../../../actions/playActions';
|
||||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||||
import Tag from '../../../components/TItemCard/Tag';
|
import Tag from '../../../components/TItemCard/Tag';
|
||||||
@@ -35,18 +25,15 @@ import {
|
|||||||
LOG_MESSAGE_ID,
|
LOG_MESSAGE_ID,
|
||||||
panel_names,
|
panel_names,
|
||||||
} from '../../../utils/Config';
|
} from '../../../utils/Config';
|
||||||
import {
|
import { $L, scaleW } from '../../../utils/helperMethods';
|
||||||
$L,
|
|
||||||
scaleW,
|
|
||||||
} from '../../../utils/helperMethods';
|
|
||||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
import { TEMPLATE_CODE_CONF } from '../HomePanel';
|
import { TEMPLATE_CODE_CONF } from '../HomePanel';
|
||||||
import css from '../PopularShow/PopularShow.module.less';
|
import css from '../PopularShow/PopularShow.module.less';
|
||||||
|
|
||||||
const SpottableComponent = Spottable("div");
|
const SpottableComponent = Spottable('div');
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator(
|
||||||
{ enterTo: "last-focused" },
|
{ enterTo: 'last-focused' },
|
||||||
"div"
|
'div'
|
||||||
);
|
);
|
||||||
|
|
||||||
const PopularShow = ({
|
const PopularShow = ({
|
||||||
@@ -72,9 +59,13 @@ const PopularShow = ({
|
|||||||
const panels = useSelector((state) => state.panels.panels);
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
|
|
||||||
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
||||||
const recommendInfo = useSelector((state) => state.foryou?.recommendInfo?.recommendShow);
|
const recommendInfo = useSelector(
|
||||||
|
(state) => state.foryou?.recommendInfo?.recommendShow
|
||||||
|
);
|
||||||
|
|
||||||
|
const { userNumber } = useSelector(
|
||||||
|
(state) => state.common.appStatus.loginUserData
|
||||||
|
);
|
||||||
|
|
||||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||||
const [drawChk, setDrawChk] = useState(false);
|
const [drawChk, setDrawChk] = useState(false);
|
||||||
@@ -89,15 +80,16 @@ const PopularShow = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowInfos(
|
setShowInfos(
|
||||||
recommendInfo?.filter(
|
recommendInfo?.filter((item) => item.recommendTpCd === 'POPULARSHOW') ||
|
||||||
(item) => item.recommendTpCd === "POPULARSHOW"
|
[]
|
||||||
) || []
|
);
|
||||||
)
|
}, [recommendInfo]);
|
||||||
},[recommendInfo])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (userNumber) {
|
||||||
if (!showInfos || showInfos.length === 0) {
|
if (!showInfos || showInfos.length === 0) {
|
||||||
const baseData = topInfos?.map((item) => ({
|
const baseData =
|
||||||
|
topInfos?.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
foryou: false,
|
foryou: false,
|
||||||
})) || [];
|
})) || [];
|
||||||
@@ -105,23 +97,27 @@ const PopularShow = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recommendedData = showInfos[0].showInfos?.map((item) => ({
|
const recommendedData =
|
||||||
|
showInfos[0].showInfos?.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
foryou: true,
|
foryou: true,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const recommendedPrdtIds = new Set(recommendedData?.map(item => item.showId));
|
const recommendedPrdtIds = new Set(
|
||||||
|
recommendedData?.map((item) => item.showId)
|
||||||
|
);
|
||||||
|
|
||||||
const baseData = topInfos?.map((item) => ({
|
const baseData =
|
||||||
|
topInfos?.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
foryou: recommendedPrdtIds.has(item.showId),
|
foryou: recommendedPrdtIds.has(item.showId),
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
setShowNewInfos([...baseData]);
|
setShowNewInfos([...baseData]);
|
||||||
|
} else {
|
||||||
}, [topInfos, showInfos]);
|
setShowNewInfos(topInfos);
|
||||||
|
}
|
||||||
|
}, [topInfos, showInfos, userNumber]);
|
||||||
|
|
||||||
const handleCardClick = useCallback(
|
const handleCardClick = useCallback(
|
||||||
(patnrId, showId, catCd, showUrl) => () => {
|
(patnrId, showId, catCd, showUrl) => () => {
|
||||||
@@ -135,7 +131,7 @@ const PopularShow = ({
|
|||||||
startVideoPlayer({
|
startVideoPlayer({
|
||||||
showId,
|
showId,
|
||||||
patnrId,
|
patnrId,
|
||||||
shptmBanrTpNm: "VOD",
|
shptmBanrTpNm: 'VOD',
|
||||||
lgCatCd: catCd,
|
lgCatCd: catCd,
|
||||||
modal: false,
|
modal: false,
|
||||||
showUrl: showUrl,
|
showUrl: showUrl,
|
||||||
@@ -151,7 +147,7 @@ const PopularShow = ({
|
|||||||
{
|
{
|
||||||
name: panel_names.TRENDING_NOW_PANEL,
|
name: panel_names.TRENDING_NOW_PANEL,
|
||||||
panelInfo: {
|
panelInfo: {
|
||||||
pageName: "PS",
|
pageName: 'PS',
|
||||||
focusedContainerId: SpotlightIds.TRENDING_NOW_POPULAR_SHOW,
|
focusedContainerId: SpotlightIds.TRENDING_NOW_POPULAR_SHOW,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -179,23 +175,23 @@ const PopularShow = ({
|
|||||||
|
|
||||||
if (firstChk === 0 && itemIndex === 0) {
|
if (firstChk === 0 && itemIndex === 0) {
|
||||||
const c = Spotlight.getCurrent();
|
const c = Spotlight.getCurrent();
|
||||||
const getAriaLabel = c.getAttribute("aria-label");
|
const getAriaLabel = c.getAttribute('aria-label');
|
||||||
if (c) {
|
if (c) {
|
||||||
let cAriaLabel = c.getAttribute("aria-label");
|
let cAriaLabel = c.getAttribute('aria-label');
|
||||||
cAriaLabel = "POPULAR SHOW, Heading 1," + cAriaLabel;
|
cAriaLabel = 'POPULAR SHOW, Heading 1,' + cAriaLabel;
|
||||||
c.setAttribute("aria-label", cAriaLabel);
|
c.setAttribute('aria-label', cAriaLabel);
|
||||||
}
|
}
|
||||||
setFirstChk(1);
|
setFirstChk(1);
|
||||||
} else if (firstChk === 1 && itemIndex === 0) {
|
} else if (firstChk === 1 && itemIndex === 0) {
|
||||||
const c = Spotlight.getCurrent();
|
const c = Spotlight.getCurrent();
|
||||||
if (c) {
|
if (c) {
|
||||||
let cAriaLabel = c.getAttribute("aria-label");
|
let cAriaLabel = c.getAttribute('aria-label');
|
||||||
if (cAriaLabel) {
|
if (cAriaLabel) {
|
||||||
const newcAriaLabel = cAriaLabel.replace(
|
const newcAriaLabel = cAriaLabel.replace(
|
||||||
"POPULAR SHOW, Heading 1,",
|
'POPULAR SHOW, Heading 1,',
|
||||||
""
|
''
|
||||||
);
|
);
|
||||||
c.setAttribute("aria-label", newcAriaLabel);
|
c.setAttribute('aria-label', newcAriaLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -246,7 +242,7 @@ const PopularShow = ({
|
|||||||
>
|
>
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
className={css.subTitle}
|
className={css.subTitle}
|
||||||
title={$L("POPULAR SHOW")}
|
title={$L('POPULAR SHOW')}
|
||||||
data-title-index="homePopularShow"
|
data-title-index="homePopularShow"
|
||||||
label="POPULAR SHOW"
|
label="POPULAR SHOW"
|
||||||
/>
|
/>
|
||||||
@@ -293,8 +289,8 @@ const PopularShow = ({
|
|||||||
showId={showId}
|
showId={showId}
|
||||||
showTitle={showNm}
|
showTitle={showNm}
|
||||||
imageSource={
|
imageSource={
|
||||||
(thumbnailUrl && thumbnailUrl960) ?
|
thumbnailUrl && thumbnailUrl960
|
||||||
thumbnailUrl !== thumbnailUrl960
|
? thumbnailUrl !== thumbnailUrl960
|
||||||
? thumbnailUrl960
|
? thumbnailUrl960
|
||||||
: thumbnailUrl
|
: thumbnailUrl
|
||||||
: thumbnailUrl
|
: thumbnailUrl
|
||||||
@@ -304,7 +300,7 @@ const PopularShow = ({
|
|||||||
nonPosition={true}
|
nonPosition={true}
|
||||||
type={TYPES.videoShow}
|
type={TYPES.videoShow}
|
||||||
imgType={
|
imgType={
|
||||||
vtctpYn !== "Y"
|
vtctpYn !== 'Y'
|
||||||
? IMAGETYPES.imgHorizontal
|
? IMAGETYPES.imgHorizontal
|
||||||
: IMAGETYPES.imgVertical
|
: IMAGETYPES.imgVertical
|
||||||
}
|
}
|
||||||
@@ -313,11 +309,11 @@ const PopularShow = ({
|
|||||||
onFocus={handleFocus(itemIndex)}
|
onFocus={handleFocus(itemIndex)}
|
||||||
onBlur={handleBlur(itemIndex)}
|
onBlur={handleBlur(itemIndex)}
|
||||||
onClick={handleCardClick(patnrId, showId, catCd, showUrl)}
|
onClick={handleCardClick(patnrId, showId, catCd, showUrl)}
|
||||||
firstLabel={patncNm + " "}
|
firstLabel={patncNm + ' '}
|
||||||
label={itemIndex * 1 + 1 + " of " + showNewInfos.length}
|
label={itemIndex * 1 + 1 + ' of ' + showNewInfos.length}
|
||||||
lastLabel=" go to detail, button"
|
lastLabel=" go to detail, button"
|
||||||
>
|
>
|
||||||
{foryou === true && <Tag text={"For You"} />}
|
{foryou === true && <Tag text={'For You'} />}
|
||||||
</TItemCardNew>
|
</TItemCardNew>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -329,7 +325,7 @@ const PopularShow = ({
|
|||||||
className={css.displayBox}
|
className={css.displayBox}
|
||||||
onClick={handleMoreCardClick}
|
onClick={handleMoreCardClick}
|
||||||
onFocus={_handleItemFocus}
|
onFocus={_handleItemFocus}
|
||||||
spotlightId={"home-popularshow-more-btn"}
|
spotlightId={'home-popularshow-more-btn'}
|
||||||
></SpottableComponent>
|
></SpottableComponent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,21 +8,32 @@ import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
|||||||
|
|
||||||
import { sendLogCuration } from '../../../actions/logActions';
|
import { sendLogCuration } from '../../../actions/logActions';
|
||||||
import { getSubCategory } from '../../../actions/mainActions';
|
import { getSubCategory } from '../../../actions/mainActions';
|
||||||
import { pushPanel, navigateFromSubCategory } from '../../../actions/panelActions';
|
import {
|
||||||
|
navigateFromSubCategory,
|
||||||
|
pushPanel,
|
||||||
|
} from '../../../actions/panelActions';
|
||||||
import Tag from '../../../components/TItemCard/Tag';
|
import Tag from '../../../components/TItemCard/Tag';
|
||||||
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||||
import TScroller from '../../../components/TScroller/TScroller';
|
import TScroller from '../../../components/TScroller/TScroller';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useScrollReset from '../../../hooks/useScrollReset';
|
import useScrollReset from '../../../hooks/useScrollReset';
|
||||||
import useScrollTo from '../../../hooks/useScrollTo';
|
import useScrollTo from '../../../hooks/useScrollTo';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, LOG_TP_NO, panel_names } from '../../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
LOG_TP_NO,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../utils/Config';
|
||||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
import CategoryNav from '../../HomePanel/SubCategory/CategoryNav/CategoryNav';
|
import CategoryNav from '../../HomePanel/SubCategory/CategoryNav/CategoryNav';
|
||||||
import css from '../../HomePanel/SubCategory/SubCategory.module.less';
|
import css from '../../HomePanel/SubCategory/SubCategory.module.less';
|
||||||
|
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable('div');
|
||||||
const Container = SpotlightContainerDecorator({ enterTo: null }, 'div');
|
const Container = SpotlightContainerDecorator({ enterTo: null }, 'div');
|
||||||
const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const ContainerBasic = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: 'last-focused' },
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
const getExpsOrdByLgCatCd = (array, value) => {
|
const getExpsOrdByLgCatCd = (array, value) => {
|
||||||
const expsOrd = array.findIndex(({ lgCatCd }) => value === lgCatCd) + 1;
|
const expsOrd = array.findIndex(({ lgCatCd }) => value === lgCatCd) + 1;
|
||||||
@@ -40,12 +51,24 @@ export default memo(function SubCategory({
|
|||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, false);
|
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||||
|
scrollLeft,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const categoryInfos = useSelector((state) => state.home.menuData?.data?.homeCategory);
|
const categoryInfos = useSelector(
|
||||||
const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
|
(state) => state.home.menuData?.data?.homeCategory
|
||||||
|
);
|
||||||
|
const categoryItemInfos = useSelector(
|
||||||
|
(state) => state.main.subCategoryData?.categoryItemInfos
|
||||||
|
);
|
||||||
|
|
||||||
const foruItemInfos = useSelector((state) => state.main.recommendProduct[0]?.productInfos);
|
const foruItemInfos = useSelector(
|
||||||
|
(state) => state.main.recommendProduct[0]?.productInfos
|
||||||
|
);
|
||||||
|
const { userNumber } = useSelector(
|
||||||
|
(state) => state.common.appStatus.loginUserData
|
||||||
|
);
|
||||||
|
|
||||||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||||
|
|
||||||
@@ -207,6 +230,7 @@ export default memo(function SubCategory({
|
|||||||
}, [handleShelfFocus]);
|
}, [handleShelfFocus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (userNumber) {
|
||||||
if (!foruItemInfos || foruItemInfos.length === 0) {
|
if (!foruItemInfos || foruItemInfos.length === 0) {
|
||||||
const baseData =
|
const baseData =
|
||||||
categoryItemInfos?.subCatItemList?.map((item) => ({
|
categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||||
@@ -223,7 +247,9 @@ export default memo(function SubCategory({
|
|||||||
foryou: true,
|
foryou: true,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
const recommendedPrdtIds = new Set(
|
||||||
|
recommendedData.map((item) => item.prdtId)
|
||||||
|
);
|
||||||
|
|
||||||
const baseData =
|
const baseData =
|
||||||
categoryItemInfos?.subCatItemList?.map((item) => ({
|
categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||||
@@ -232,10 +258,17 @@ export default memo(function SubCategory({
|
|||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
setCategoryItemNewData([...baseData]);
|
setCategoryItemNewData([...baseData]);
|
||||||
}, [categoryItemInfos?.subCatItemList, foruItemInfos]);
|
} else {
|
||||||
|
setCategoryItemNewData(categoryItemInfos?.subCatItemList);
|
||||||
|
}
|
||||||
|
}, [categoryItemInfos?.subCatItemList, foruItemInfos, userNumber]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container spotlightId={spotlightId} data-wheel-point onFocus={_handleShelfFocus}>
|
<Container
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
data-wheel-point
|
||||||
|
onFocus={_handleShelfFocus}
|
||||||
|
>
|
||||||
<CategoryNav
|
<CategoryNav
|
||||||
categoryInfos={categoryInfos}
|
categoryInfos={categoryInfos}
|
||||||
currentCategoryCode={currentLgCatCd}
|
currentCategoryCode={currentLgCatCd}
|
||||||
@@ -293,7 +326,12 @@ export default memo(function SubCategory({
|
|||||||
offerInfo={offerInfo}
|
offerInfo={offerInfo}
|
||||||
data-catcd-num={currentLgCatCd}
|
data-catcd-num={currentLgCatCd}
|
||||||
data-catcd-nm={currentLgCatNm}
|
data-catcd-nm={currentLgCatNm}
|
||||||
label={itemIndex * 1 + 1 + ' of ' + (categoryItemNewData?.length || 0)}
|
label={
|
||||||
|
itemIndex * 1 +
|
||||||
|
1 +
|
||||||
|
' of ' +
|
||||||
|
(categoryItemNewData?.length || 0)
|
||||||
|
}
|
||||||
lastLabel=" go to detail, button"
|
lastLabel=" go to detail, button"
|
||||||
euEnrgLblInfos={euEnrgLblInfos}
|
euEnrgLblInfos={euEnrgLblInfos}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -209,6 +209,9 @@ export default function MainView({ className, initService }) {
|
|||||||
const hasFeaturedBrandsPanel = panels.some(
|
const hasFeaturedBrandsPanel = panels.some(
|
||||||
(panel) => panel?.name === Config.panel_names.FEATURED_BRANDS_PANEL
|
(panel) => panel?.name === Config.panel_names.FEATURED_BRANDS_PANEL
|
||||||
);
|
);
|
||||||
|
const hasTrendingNowPanel = panels.some(
|
||||||
|
(panel) => panel?.name === Config.panel_names.TRENDING_NOW_PANEL
|
||||||
|
);
|
||||||
// 단독 패널 체크 - CheckOutPanel, CartPanel 등 단독으로 렌더링되어야 하는 패널들
|
// 단독 패널 체크 - CheckOutPanel, CartPanel 등 단독으로 렌더링되어야 하는 패널들
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
console.log(`[PANEL_MainView] 🔍 Top panel name: ${topPanel?.name}`);
|
console.log(`[PANEL_MainView] 🔍 Top panel name: ${topPanel?.name}`);
|
||||||
@@ -250,7 +253,11 @@ export default function MainView({ className, initService }) {
|
|||||||
'[MainView] Rendering 3-layer structure: PlayerPanel + DetailPanel + MediaPanel'
|
'[MainView] Rendering 3-layer structure: PlayerPanel + DetailPanel + MediaPanel'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
renderingPanels = hasFeaturedBrandsPanel ? panels.slice(-4) : panels.slice(-3);
|
if (hasFeaturedBrandsPanel || hasTrendingNowPanel) {
|
||||||
|
renderingPanels = panels.slice(-4);
|
||||||
|
} else {
|
||||||
|
renderingPanels = panels.slice(-3);
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL ||
|
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL_NEW ||
|
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL_NEW ||
|
||||||
@@ -258,7 +265,11 @@ export default function MainView({ className, initService }) {
|
|||||||
panels[panels.length - 2]?.name === Config.panel_names.PLAYER_PANEL ||
|
panels[panels.length - 2]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||||
panels[panels.length - 2]?.name === Config.panel_names.MEDIA_PANEL
|
panels[panels.length - 2]?.name === Config.panel_names.MEDIA_PANEL
|
||||||
) {
|
) {
|
||||||
renderingPanels = hasFeaturedBrandsPanel ? panels.slice(-3) : panels.slice(-2);
|
if (hasFeaturedBrandsPanel || hasTrendingNowPanel) {
|
||||||
|
renderingPanels = panels.slice(-3);
|
||||||
|
} else {
|
||||||
|
renderingPanels = panels.slice(-2);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
renderingPanels = panels.slice(-1);
|
renderingPanels = panels.slice(-1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ const MediaPanel = React.forwardRef(
|
|||||||
const onEnded = useCallback(
|
const onEnded = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
debugLog('[MediaPanel] Video ended');
|
debugLog('[MediaPanel] Video ended');
|
||||||
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] MediaPanel onEnded triggered - will pop after 1500ms');
|
||||||
// continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리
|
// continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리
|
||||||
// onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음
|
// onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음
|
||||||
Spotlight.pause();
|
Spotlight.pause();
|
||||||
@@ -402,6 +403,7 @@ const MediaPanel = React.forwardRef(
|
|||||||
|
|
||||||
// ✅ 새로운 타이머 저장 (cleanup 시 정리용)
|
// ✅ 새로운 타이머 저장 (cleanup 시 정리용)
|
||||||
onEndedTimerRef.current = setTimeout(() => {
|
onEndedTimerRef.current = setTimeout(() => {
|
||||||
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] Executing popPanel(MEDIA_PANEL) after 1500ms');
|
||||||
Spotlight.resume();
|
Spotlight.resume();
|
||||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||||
onEndedTimerRef.current = null;
|
onEndedTimerRef.current = null;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
video {
|
video {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
object-fit: contain; /* 비율 유지하면서 컨테이너 안에 맞춤 */
|
object-fit: contain; /* 높이 기준으로 맞추고 좌우는 잘림 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,36 @@ const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 수집 함수
|
||||||
|
const collectProductScrollPosition = () => {
|
||||||
|
const scrollContainer = document.querySelector('[data-spotlight-id="main-content-scroller"]');
|
||||||
|
|
||||||
|
if (scrollContainer) {
|
||||||
|
const { top, left } = scrollContainer.getBoundingClientRect();
|
||||||
|
const position = {
|
||||||
|
top: Math.round(top),
|
||||||
|
left: Math.round(left)
|
||||||
|
};
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 검증 함수
|
||||||
|
const isProductScrollPositionValid = (savedPosition, currentPosition) => {
|
||||||
|
if (!savedPosition || !currentPosition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tolerance = 1; // 1px 오차 범위
|
||||||
|
const isMatching =
|
||||||
|
Math.abs(currentPosition.top - savedPosition.top) <= tolerance &&
|
||||||
|
Math.abs(currentPosition.left - savedPosition.left) <= tolerance;
|
||||||
|
|
||||||
|
return isMatching;
|
||||||
|
};
|
||||||
|
|
||||||
const getLogTpNo = (type, nowMenu) => {
|
const getLogTpNo = (type, nowMenu) => {
|
||||||
if (type === 'LIVE') {
|
if (type === 'LIVE') {
|
||||||
switch (nowMenu) {
|
switch (nowMenu) {
|
||||||
@@ -912,7 +942,7 @@ const MediaPanel = React.forwardRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!panelInfo.modal) {
|
if (!panelInfo.modal) {
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||||
dispatch(changeAppStatus({ cursorVisible: false }));
|
dispatch(changeAppStatus({ cursorVisible: false }));
|
||||||
document?.dispatchEvent?.(new CustomEvent('detailpanel-scroll-reset'));
|
document?.dispatchEvent?.(new CustomEvent('detailpanel-scroll-reset'));
|
||||||
|
|
||||||
@@ -937,7 +967,7 @@ const MediaPanel = React.forwardRef(
|
|||||||
// 패널이 2개 존재할때만 popPanel 진행
|
// 패널이 2개 존재할때만 popPanel 진행
|
||||||
// fullscreen 전환 중이면 popPanel하지 않음
|
// fullscreen 전환 중이면 popPanel하지 않음
|
||||||
if (panelInfo.modal && !isOnTop && !isTransitioningToFullscreen.current) {
|
if (panelInfo.modal && !isOnTop && !isTransitioningToFullscreen.current) {
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||||
} else {
|
} else {
|
||||||
Spotlight.focus('tbody');
|
Spotlight.focus('tbody');
|
||||||
}
|
}
|
||||||
@@ -1107,6 +1137,26 @@ const MediaPanel = React.forwardRef(
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const enterFullscreen = useCallback(() => {
|
const enterFullscreen = useCallback(() => {
|
||||||
|
// 🔽 [251217] ProductAllSection 스크롤 위치 검증
|
||||||
|
// DetailPanel에서 ProductAllSection의 초기 위치와 현재 위치를 비교하여 검증
|
||||||
|
const savedPosition = window.productScrollPositionOnMount;
|
||||||
|
const currentPosition = collectProductScrollPosition();
|
||||||
|
|
||||||
|
if (savedPosition && currentPosition) {
|
||||||
|
const isValid = isProductScrollPositionValid(savedPosition, currentPosition);
|
||||||
|
if (!isValid) {
|
||||||
|
console.warn('[MediaPanel] ⚠️ ProductAllSection 스크롤 위치 검증 실패 - 전체화면 재생 가능하지만 경고 표시', {
|
||||||
|
savedPosition,
|
||||||
|
currentPosition,
|
||||||
|
});
|
||||||
|
// 사용자 피드백: 위치가 일치하지 않음을 콘솔에 기록 (비디오는 계속 재생)
|
||||||
|
} else {
|
||||||
|
console.log('[MediaPanel] ✅ ProductAllSection 스크롤 위치 검증 성공');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[MediaPanel] ⚠️ ProductAllSection 위치 정보 없음 - 검증 스킵');
|
||||||
|
}
|
||||||
|
|
||||||
isTransitioningToFullscreen.current = true;
|
isTransitioningToFullscreen.current = true;
|
||||||
dispatch(switchMediaToFullscreen());
|
dispatch(switchMediaToFullscreen());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@@ -1576,7 +1626,7 @@ const MediaPanel = React.forwardRef(
|
|||||||
) {
|
) {
|
||||||
// case: Featured Brands
|
// case: Featured Brands
|
||||||
if (panelInfo?.sourcePanel === panel_names.FEATURED_BRANDS_PANEL) {
|
if (panelInfo?.sourcePanel === panel_names.FEATURED_BRANDS_PANEL) {
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -2043,19 +2093,25 @@ const MediaPanel = React.forwardRef(
|
|||||||
(e) => {
|
(e) => {
|
||||||
const currentInfo = panelInfoRef.current;
|
const currentInfo = panelInfoRef.current;
|
||||||
|
|
||||||
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] onEnded triggered - shptmBanrTpNm:', currentInfo?.shptmBanrTpNm);
|
||||||
|
|
||||||
// MEDIA: 기존 동작 유지 (배경 복원 없이 즉시 pop)
|
// MEDIA: 기존 동작 유지 (배경 복원 없이 즉시 pop)
|
||||||
if (currentInfo.shptmBanrTpNm === 'MEDIA') {
|
if (currentInfo.shptmBanrTpNm === 'MEDIA') {
|
||||||
console.log('[MediaPanel] 🚫 Skipping background restoration for ended media');
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] MEDIA type - popPanel will be called');
|
||||||
Spotlight.pause();
|
Spotlight.pause();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] setTimeout fired - dispatching popPanel(MEDIA_PANEL)');
|
||||||
Spotlight.resume();
|
Spotlight.resume();
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||||
}, VIDEO_END_ACTION_DELAY);
|
}, VIDEO_END_ACTION_DELAY);
|
||||||
|
e?.stopPropagation();
|
||||||
|
e?.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// VOD: modal 여부에 따라 동작 분리
|
// VOD: modal 여부에 따라 동작 분리
|
||||||
if (currentInfo.shptmBanrTpNm === 'VOD') {
|
if (currentInfo.shptmBanrTpNm === 'VOD') {
|
||||||
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] VOD type - popPanel will be called');
|
||||||
Spotlight.pause();
|
Spotlight.pause();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
stopExternalPlayer();
|
stopExternalPlayer();
|
||||||
@@ -2074,6 +2130,8 @@ const MediaPanel = React.forwardRef(
|
|||||||
e?.preventDefault();
|
e?.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[🔥UNIQUE_MEDIA_ENDED🔥] Unknown shptmBanrTpNm - no action taken');
|
||||||
},
|
},
|
||||||
[dispatch, focusBackButtonOrFallback, stopExternalPlayer]
|
[dispatch, focusBackButtonOrFallback, stopExternalPlayer]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -167,8 +167,8 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: 4px solid @PRIMARY_COLOR_RED;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 0 22px @PRIMARY_COLOR_RED;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,8 +298,8 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: 4px solid @PRIMARY_COLOR_RED;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 0 22px @PRIMARY_COLOR_RED;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,464 @@
|
|||||||
import React from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import PlayerOverlayContents from './PlayerOverlayContents';
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
function MediaOverlayContents(props) {
|
import Spotlight from '@enact/spotlight';
|
||||||
return <PlayerOverlayContents {...props} forceShowMediaOverlay />;
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
import Marquee from '@enact/ui/Marquee';
|
||||||
|
|
||||||
|
import defaultLogoImg
|
||||||
|
from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
||||||
|
import { setShowPopup } from '../../../actions/commonActions';
|
||||||
|
import CustomImage from '../../../components/CustomImage/CustomImage';
|
||||||
|
import { ACTIVE_POPUP } from '../../../utils/Config';
|
||||||
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
|
import PlayerTabButton from '../PlayerTabContents/TabButton/PlayerTabButton';
|
||||||
|
import css from './MediaOverlayContents.module.less';
|
||||||
|
|
||||||
|
const SpottableBtn = Spottable('button');
|
||||||
|
|
||||||
|
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||||
|
|
||||||
|
function MediaOverlayContents({
|
||||||
|
type,
|
||||||
|
onClick,
|
||||||
|
panelInfo,
|
||||||
|
disclaimer,
|
||||||
|
playListInfo,
|
||||||
|
captionEnable,
|
||||||
|
selectedIndex,
|
||||||
|
setIsSubtitleActive,
|
||||||
|
videoVerticalVisible,
|
||||||
|
sideContentsVisible,
|
||||||
|
setSideContentsVisible,
|
||||||
|
belowContentsVisible,
|
||||||
|
handleIndicatorUpClick,
|
||||||
|
handleIndicatorDownClick,
|
||||||
|
tabContainerVersion,
|
||||||
|
tabIndexV2,
|
||||||
|
}) {
|
||||||
|
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const onClickBack = (ev) => {
|
||||||
|
// TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지
|
||||||
|
// (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음)
|
||||||
|
if (tabContainerVersion === 2 && belowContentsVisible) {
|
||||||
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MediaOverlayContents;
|
if (onClick) {
|
||||||
|
onClick(ev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const backBtnRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (type === 'MEDIA' && !panelInfo.modal && backBtnRef.current) {
|
||||||
|
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||||
|
}
|
||||||
|
}, [type, panelInfo.modal, backBtnRef]);
|
||||||
|
|
||||||
|
const handleSubtitleOnClick = useCallback(() => {
|
||||||
|
if (!captionEnable) {
|
||||||
|
return dispatch(setShowPopup(ACTIVE_POPUP.alertPopup));
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubtitleActive((prev) => !prev);
|
||||||
|
}, [dispatch, captionEnable, setIsSubtitleActive]);
|
||||||
|
|
||||||
|
const patncLogoPath = useMemo(() => {
|
||||||
|
let logo = playListInfo[selectedIndex]?.patncLogoPath;
|
||||||
|
if (type === 'MEDIA') {
|
||||||
|
logo = panelInfo?.patncLogoPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return logo;
|
||||||
|
}, [playListInfo, selectedIndex, panelInfo, type]);
|
||||||
|
|
||||||
|
const partnerName = useMemo(() => {
|
||||||
|
let name = playListInfo[selectedIndex]?.patncNm;
|
||||||
|
if (type === 'MEDIA') {
|
||||||
|
name = panelInfo?.patncNm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}, [playListInfo, selectedIndex, panelInfo, type]);
|
||||||
|
|
||||||
|
const showName = useMemo(() => {
|
||||||
|
let name = playListInfo[selectedIndex]?.showNm;
|
||||||
|
if (type === 'MEDIA') {
|
||||||
|
name = panelInfo?.showNm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name ? name.replace(/<br\s*\/?>/gi, ' ') : '';
|
||||||
|
}, [playListInfo, selectedIndex, panelInfo, type]);
|
||||||
|
|
||||||
|
const onSpotlightMoveTabButton = useCallback((e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSpotlightMoveMediaButton = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// TabContainerV2의 tabIndex=2일 때 하단 버튼들로 포커스 이동
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
if (Spotlight.focus('live-channel-next-button')) return;
|
||||||
|
if (Spotlight.focus('below-tab-shop-now-button')) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'LIVE') {
|
||||||
|
return Spotlight.focus('videoIndicator-down-button');
|
||||||
|
}
|
||||||
|
return Spotlight.focus(SpotlightIds.PLAYER_PLAY_BUTTON);
|
||||||
|
},
|
||||||
|
[type, tabContainerVersion, tabIndexV2]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSpotlightMoveSubtitleButton = useCallback(
|
||||||
|
() => {
|
||||||
|
// 1. 먼저 자막 버튼으로 포커스 시도
|
||||||
|
if (Spotlight.focus('player-subtitlebutton')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TabContainerV2의 tabIndex=2일 때 TabContainerV2 버튼들로 포커스 이동
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
let focusSuccessful = false;
|
||||||
|
|
||||||
|
// 먼저 LiveChannelNext 버튼으로 시도
|
||||||
|
if (Spotlight.focus('live-channel-next-button')) {
|
||||||
|
focusSuccessful = true;
|
||||||
|
}
|
||||||
|
// 실패하면 ShopNowButton으로 시도
|
||||||
|
else if (Spotlight.focus('below-tab-shop-now-button')) {
|
||||||
|
focusSuccessful = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusSuccessful) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기본 동작: 자막 버튼으로 포커스
|
||||||
|
return Spotlight.focus('player-subtitlebutton');
|
||||||
|
},
|
||||||
|
[tabContainerVersion, tabIndexV2]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSpotlightMoveSlider = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (type === 'VOD') {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[type]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSpotlightMoveSideTab = useCallback((e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
Spotlight.focus('tab-0');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSpotlightMoveBelowTab = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
console.log(`[onSpotlightMoveBelowTab] tabIndexV2: ${tabIndexV2}`);
|
||||||
|
|
||||||
|
// tabIndexV2에 따라 다른 버튼으로 포커스 이동
|
||||||
|
if (tabIndexV2 === 0) {
|
||||||
|
// ShopNow 탭: Close 버튼으로
|
||||||
|
// Spotlight.focus('below-tab-close-button');
|
||||||
|
const result = Spotlight.focus('shownow_close_button');
|
||||||
|
console.log(`[onSpotlightMoveBelowTab] tabIndexV2=0, focus result:`, result);
|
||||||
|
} else if (tabIndexV2 === 1) {
|
||||||
|
// LIVE CHANNEL 탭: LIVE CHANNEL 버튼으로
|
||||||
|
const result = Spotlight.focus('below-tab-live-channel-button');
|
||||||
|
console.log(`[onSpotlightMoveBelowTab] tabIndexV2=1, focus result:`, result);
|
||||||
|
} else if (tabIndexV2 === 2) {
|
||||||
|
// ShopNowButton: ShopNowButton으로
|
||||||
|
const result = Spotlight.focus('below-tab-shop-now-button');
|
||||||
|
console.log(`[onSpotlightMoveBelowTab] tabIndexV2=2, focus result:`, result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[tabIndexV2]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Back Button arrow down 전용 핸들러 - tabIndex에 따라 다른 포커스
|
||||||
|
const handleBackButtonDown = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (tabContainerVersion === 2 && belowContentsVisible) {
|
||||||
|
if (tabIndexV2 === 0) {
|
||||||
|
// tabIndexV2가 0일 때 ShopNow 닫기 버튼으로 포커스
|
||||||
|
const result = Spotlight.focus('shownow_close_button');
|
||||||
|
} else if (tabIndexV2 === 1) {
|
||||||
|
// tabIndexV2가 1일 때 below-tab-live-channel-button으로 포커스
|
||||||
|
Spotlight.focus('below-tab-live-channel-button');
|
||||||
|
} else if (tabIndexV2 === 2) {
|
||||||
|
// tabIndexV2가 2일 때 LiveChannelNext로 포커스
|
||||||
|
Spotlight.focus('live-channel-next-button');
|
||||||
|
} else {
|
||||||
|
// 그 외에는 기존 로직 사용
|
||||||
|
onSpotlightMoveMediaButton(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onSpotlightMoveMediaButton(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[tabContainerVersion, belowContentsVisible, tabIndexV2, onSpotlightMoveMediaButton]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSpotlightMoveBackButton = useCallback(() => {
|
||||||
|
return Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOverlayKeyDownCapture = useCallback(
|
||||||
|
(ev) => {
|
||||||
|
const currentId = Spotlight.getCurrent()?.getAttribute('data-spotlight-id');
|
||||||
|
if (ev.keyCode === 40 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
return handleBackButtonDown(ev);
|
||||||
|
}
|
||||||
|
if (ev.keyCode === 39 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
return onSpotlightMoveSubtitleButton(ev);
|
||||||
|
}
|
||||||
|
if (ev.keyCode === 37 && currentId === SpotlightIds.PLAYER_SUBTITLE_BUTTON) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
return onSpotlightMoveBackButton(ev);
|
||||||
|
}
|
||||||
|
if (ev.keyCode === 38 && currentId === SpotlightIds.PLAYER_PLAY_BUTTON) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
return onSpotlightMoveBackButton();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onSpotlightMoveBackButton, onSpotlightMoveMediaButton, onSpotlightMoveSubtitleButton, handleBackButtonDown]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentSideButtonStatus = useMemo(() => {
|
||||||
|
if (
|
||||||
|
!panelInfo?.modal &&
|
||||||
|
!sideContentsVisible &&
|
||||||
|
tabContainerVersion === 1
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [panelInfo, sideContentsVisible, tabContainerVersion]);
|
||||||
|
|
||||||
|
const noLiveContentsVisible = useMemo(() => {
|
||||||
|
if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noShowIdCount = playListInfo.filter((item) => !item.showId).length;
|
||||||
|
if (playListInfo.length - 1 === noShowIdCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [playListInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container className={css.overlayContainer} onKeyDownCapture={handleOverlayKeyDownCapture}>
|
||||||
|
{/* 251118 임시로 unvisible */}
|
||||||
|
{/* {playListInfo.length > 1 && noLiveContentsVisible && (
|
||||||
|
<>
|
||||||
|
<div className={css.indicatorUpButton}>
|
||||||
|
<SpottableBtn
|
||||||
|
onClick={handleIndicatorUpClick}
|
||||||
|
spotlightId="videoIndicator-up-button"
|
||||||
|
onSpotlightRight={
|
||||||
|
videoVerticalVisible
|
||||||
|
? onSpotlightMoveSideTab
|
||||||
|
: tabContainerVersion === 1
|
||||||
|
? onSpotlightMoveTabButton
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onSpotlightDown={
|
||||||
|
tabContainerVersion === 2 && belowContentsVisible
|
||||||
|
? onSpotlightMoveBelowTab
|
||||||
|
: onSpotlightMoveSlider
|
||||||
|
}
|
||||||
|
aria-label="Previous channel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={css.indicatorDownButton}>
|
||||||
|
<SpottableBtn
|
||||||
|
onClick={handleIndicatorDownClick}
|
||||||
|
spotlightId="videoIndicator-down-button"
|
||||||
|
onSpotlightLeft={onSpotlightMoveSlider}
|
||||||
|
onSpotlightUp={onSpotlightMoveSlider}
|
||||||
|
onSpotlightRight={
|
||||||
|
videoVerticalVisible
|
||||||
|
? onSpotlightMoveSideTab
|
||||||
|
: tabContainerVersion === 1
|
||||||
|
? onSpotlightMoveTabButton
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onSpotlightDown={
|
||||||
|
tabContainerVersion === 2 && belowContentsVisible
|
||||||
|
? onSpotlightMoveBelowTab
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
aria-label="Next channel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{currentSideButtonStatus && !videoVerticalVisible && (
|
||||||
|
<PlayerTabButton
|
||||||
|
setSideContentsVisible={setSideContentsVisible}
|
||||||
|
sideContentsVisible={sideContentsVisible}
|
||||||
|
onSpotlightLeft={
|
||||||
|
playListInfo.length < 2 && onSpotlightMoveBackButton
|
||||||
|
}
|
||||||
|
videoType={type}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cntry_cd === 'US' && (
|
||||||
|
<div className={css.videoButtonContainer}>
|
||||||
|
<SpottableBtn
|
||||||
|
className={classNames(
|
||||||
|
css.subtitleButton,
|
||||||
|
videoVerticalVisible && css.videoVericalSubtitleButton
|
||||||
|
)}
|
||||||
|
onClick={handleSubtitleOnClick}
|
||||||
|
spotlightId="player-subtitlebutton"
|
||||||
|
onSpotlightUp={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// tabIndexV2가 2일 때만 ShopNowButton으로 포커스
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
Spotlight.focus('below-tab-shop-now-button');
|
||||||
|
} else {
|
||||||
|
onSpotlightMoveBackButton();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSpotlightLeft={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
Spotlight.focus('live-channel-next-button');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSpotlightRight={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
Spotlight.focus('below-tab-shop-now-button');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSpotlightDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// tabIndexV2가 2일 때만 ShopNowButton으로 포커스
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
Spotlight.focus('live-channel-next-button');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label="Caption"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={css.overlayHeader}>
|
||||||
|
<SpottableBtn
|
||||||
|
onClick={onClickBack}
|
||||||
|
className={css.backIcon}
|
||||||
|
spotlightId="player-back-button"
|
||||||
|
onSpotlightDown={handleBackButtonDown}
|
||||||
|
onSpotlightRight={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// tabIndexV2가 2일 때만 ShopNowButton으로 포커스
|
||||||
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
|
Spotlight.focus('below-tab-shop-now-button');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSpotlightUp={onSpotlightMoveSubtitleButton}
|
||||||
|
aria-label="Video Player Close"
|
||||||
|
ref={backBtnRef}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classNames(type === 'LIVE' && css.liveIcon)}
|
||||||
|
aria-label={type === 'LIVE' && 'Live Icon'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{partnerName && (
|
||||||
|
<CustomImage
|
||||||
|
src={patncLogoPath}
|
||||||
|
fallbackSrc={defaultLogoImg}
|
||||||
|
alt={partnerName}
|
||||||
|
aria-label={partnerName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h2 className={css.patnerName}>{partnerName}</h2>
|
||||||
|
|
||||||
|
{!panelInfo?.modal && (
|
||||||
|
<Marquee
|
||||||
|
className={classNames(css.title, videoVerticalVisible && css.videoVerticalMarquee)}
|
||||||
|
marqueeOn="render"
|
||||||
|
>
|
||||||
|
{showName}
|
||||||
|
</Marquee>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
{type === 'VOD' && disclaimer && (
|
||||||
|
<div className={css.disclaimer}>
|
||||||
|
<span className={css.icon} />
|
||||||
|
<h3 aria-label={disclaimer}>{disclaimer}</h3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsAreEqual = (prev, next) => {
|
||||||
|
return (
|
||||||
|
prev.type === next.type &&
|
||||||
|
prev.panelInfo?.showId === next.panelInfo?.showId &&
|
||||||
|
prev.disclaimer === next.disclaimer &&
|
||||||
|
prev.playListInfo === next.playListInfo &&
|
||||||
|
prev.captionEnable === next.captionEnable &&
|
||||||
|
prev.selectedIndex === next.selectedIndex &&
|
||||||
|
prev.videoVerticalVisible === next.videoVerticalVisible &&
|
||||||
|
prev.sideContentsVisible === next.sideContentsVisible &&
|
||||||
|
prev.belowContentsVisible === next.belowContentsVisible &&
|
||||||
|
prev.tabContainerVersion === next.tabContainerVersion &&
|
||||||
|
prev.tabIndexV2 === next.tabIndexV2
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(MediaOverlayContents, propsAreEqual);
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
@import "../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.overlayContainer {
|
||||||
|
.position(@position: absolute, @top: 77px, @right: 0, @bottom: 0, @left: 60px);
|
||||||
|
display: flex;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
.videoButtonContainer {
|
||||||
|
.subtitleButton {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background-image: url("../../../../assets/images/btn/btn-video-cc-nor@3x.png");
|
||||||
|
background-size: cover;
|
||||||
|
position: absolute;
|
||||||
|
right: 300px;
|
||||||
|
top: 680px;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&.videoVericalSubtitleButton {
|
||||||
|
position: absolute;
|
||||||
|
right: 680px;
|
||||||
|
top: 850px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: rgba(199, 8, 80, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.indicatorUpButton {
|
||||||
|
position: absolute;
|
||||||
|
top: -77px;
|
||||||
|
left: 880px;
|
||||||
|
z-index: 10;
|
||||||
|
.size(@w: 144px, @h: 48px);
|
||||||
|
> button {
|
||||||
|
.size(@w: 144px, @h: 48px);
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../../../assets/images/btn/btn-wh-arrow-top-nor.svg");
|
||||||
|
&:focus {
|
||||||
|
background-color: @PRIMARY_COLOR_RED;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.indicatorDownButton {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -986px;
|
||||||
|
left: 880px;
|
||||||
|
z-index: 10;
|
||||||
|
.size(@w: 144px, @h: 48px);
|
||||||
|
> button {
|
||||||
|
.size(@w: 144px, @h: 48px);
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../../../assets/images/btn/btn-wh-arrow-down-nor.svg");
|
||||||
|
&:focus {
|
||||||
|
background-color: @PRIMARY_COLOR_RED;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlayHeader {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.backIcon {
|
||||||
|
.size(@w: 60px, @h: 60px);
|
||||||
|
background-size: 60px 60px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../../../assets/images/btn/btn-60-wh-back-nor@3x.png");
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-image: url("../../../../assets/images/btn/btn-60-wh-back-foc@3x.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.liveIcon {
|
||||||
|
.size(@w: 108px, @h: 48px);
|
||||||
|
margin: 6px 0 6px 30px;
|
||||||
|
background-size: 108px 48px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("../../../../assets/images/tag-liveshow.png");
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
> img {
|
||||||
|
.size(@w: auto, @h: 60px);
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patnerName {
|
||||||
|
font-size: 44px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fcfcfc;
|
||||||
|
margin-left: 14px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 1200px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 44px;
|
||||||
|
color: #fcfcfc;
|
||||||
|
margin-left: 35px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.videoVerticalTitle {
|
||||||
|
.size(@w: 950px , @h: 60px);
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
padding-top: 10px;
|
||||||
|
.size(@w: 940px , @h: 60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// .marquee {
|
||||||
|
// &.videoVerticalMarquee {
|
||||||
|
// width: 950px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer {
|
||||||
|
.size(@w: 1800px , @h: 54px);
|
||||||
|
display: flex;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
top: 140px;
|
||||||
|
left: 60px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
.size(@w: 18px , @h: 18px);
|
||||||
|
background-image: url("../../../../assets/images/icons/ic-alert-20@3x.png");
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
margin: 0 12px 0 20px;
|
||||||
|
}
|
||||||
|
> h3 {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -390,7 +390,7 @@ function PlayerOverlayContents({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
||||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||||
Spotlight.focus('live-channel-next-button');
|
Spotlight.focus('below-tab-shop-now-button');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSpotlightDown={(e) => {
|
onSpotlightDown={(e) => {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
background-image: url("../../../../assets/images/btn/btn-video-cc-nor@3x.png");
|
background-image: url("../../../../assets/images/btn/btn-video-cc-nor@3x.png");
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 60px;
|
right: 300px;
|
||||||
top: 800px;
|
top: 680px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
&.videoVericalSubtitleButton {
|
&.videoVericalSubtitleButton {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
getMainLiveShow,
|
getMainLiveShow,
|
||||||
getMainLiveShowNowProduct,
|
getMainLiveShowNowProduct,
|
||||||
} from '../../actions/mainActions';
|
} from '../../actions/mainActions';
|
||||||
|
import { updateHomeInfo } from '../../actions/homeActions';
|
||||||
import * as PanelActions from '../../actions/panelActions';
|
import * as PanelActions from '../../actions/panelActions';
|
||||||
import { updatePanel } from '../../actions/panelActions';
|
import { updatePanel } from '../../actions/panelActions';
|
||||||
import {
|
import {
|
||||||
@@ -90,6 +91,51 @@ const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 배너 위치 수집 함수 (top, left만 저장)
|
||||||
|
const collectBannerPositions = () => {
|
||||||
|
const positions = [];
|
||||||
|
|
||||||
|
// banner0, banner1 등의 배너 위치 수집
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const bannerId = `banner${i}`;
|
||||||
|
const node = document.querySelector(`[data-spotlight-id="${bannerId}"]`);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
const { top, left } = node.getBoundingClientRect();
|
||||||
|
positions.push({
|
||||||
|
bannerId,
|
||||||
|
position: { top: Math.round(top), left: Math.round(left) }
|
||||||
|
});
|
||||||
|
dlog(`[PlayerPanel] 배너 위치 수집: ${bannerId}`, { top: Math.round(top), left: Math.round(left) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 위치 검증 함수 (오차 범위: 1px)
|
||||||
|
const isPositionMatching = (bannerPositions, bannerId, currentPosition) => {
|
||||||
|
const validPosition = bannerPositions.find(p => p.bannerId === bannerId);
|
||||||
|
|
||||||
|
if (!validPosition) {
|
||||||
|
dlog(`[PlayerPanel] 배너 위치 검증 실패: ${bannerId} 배너를 찾을 수 없음`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tolerance = 1; // 1px 오차 범위
|
||||||
|
const isMatching =
|
||||||
|
Math.abs(currentPosition.top - validPosition.position.top) <= tolerance &&
|
||||||
|
Math.abs(currentPosition.left - validPosition.position.left) <= tolerance;
|
||||||
|
|
||||||
|
dlog(`[PlayerPanel] 배너 위치 검증: ${bannerId}`, {
|
||||||
|
expected: validPosition.position,
|
||||||
|
current: currentPosition,
|
||||||
|
matching: isMatching
|
||||||
|
});
|
||||||
|
|
||||||
|
return isMatching;
|
||||||
|
};
|
||||||
|
|
||||||
const getLogTpNo = (type, nowMenu) => {
|
const getLogTpNo = (type, nowMenu) => {
|
||||||
if (type === 'LIVE') {
|
if (type === 'LIVE') {
|
||||||
switch (nowMenu) {
|
switch (nowMenu) {
|
||||||
@@ -218,6 +264,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
const [tabIndexV2, setTabIndexV2] = USE_STATE('tabIndexV2', 1); // 0: ShopNow, 1: LiveChannel, 2: ShopNowButton
|
const [tabIndexV2, setTabIndexV2] = USE_STATE('tabIndexV2', 1); // 0: ShopNow, 1: LiveChannel, 2: ShopNowButton
|
||||||
const [tabContainerVersion, setTabContainerVersion] = USE_STATE('tabContainerVersion', 2); // 1: TabContainer (우측), 2: TabContainerV2 (하단)
|
const [tabContainerVersion, setTabContainerVersion] = USE_STATE('tabContainerVersion', 2); // 1: TabContainer (우측), 2: TabContainerV2 (하단)
|
||||||
const [isModalClosed, setIsModalClosed] = USE_STATE('isModalClosed', true); // 모달이 false 상태인지 나타내는 플래그
|
const [isModalClosed, setIsModalClosed] = USE_STATE('isModalClosed', true); // 모달이 false 상태인지 나타내는 플래그
|
||||||
|
const [validBannerPositions, setValidBannerPositions] = USE_STATE('validBannerPositions', []); // 유효한 배너 위치 (top, left)
|
||||||
|
|
||||||
const panels = USE_SELECTOR('panels', (state) => state.panels.panels);
|
const panels = USE_SELECTOR('panels', (state) => state.panels.panels);
|
||||||
const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
||||||
@@ -407,6 +454,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
lastFocusedTargetId: panelInfo.lastFocusedTargetId,
|
lastFocusedTargetId: panelInfo.lastFocusedTargetId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TabContainerV2의 tabIndex를 ShopNowContents(0)로 리셋
|
||||||
|
dlog('[PlayerPanel] 📑 TabContainerV2 tabIndex를 ShopNowContents(0)로 리셋');
|
||||||
|
setTabIndexV2(0);
|
||||||
|
|
||||||
// 포커스 복원 로직 추가 (1000ms 지연)
|
// 포커스 복원 로직 추가 (1000ms 지연)
|
||||||
if (panelInfo.lastFocusedTargetId) {
|
if (panelInfo.lastFocusedTargetId) {
|
||||||
dlog(
|
dlog(
|
||||||
@@ -431,6 +482,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - detailPanelClosed reset');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -503,10 +555,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
// PanelInfo 상태 변화 모니터링 useEffect (isPaused가 실제로 변경될 때만)
|
// PanelInfo 상태 변화 모니터링 useEffect (isPaused가 실제로 변경될 때만)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isOnTop = panel_names.HOME_PANEL === topPanel?.name;
|
|
||||||
const isPausedChanged = previousPanelInfo.current?.isPaused !== panelInfo?.isPaused;
|
const isPausedChanged = previousPanelInfo.current?.isPaused !== panelInfo?.isPaused;
|
||||||
|
|
||||||
if (isOnTop && panelInfo?.isPaused !== undefined && isPausedChanged) {
|
// isOnTop 여부와 관계없이 isPaused 변경을 감지하여 비디오 제어
|
||||||
|
if (panelInfo?.isPaused !== undefined && isPausedChanged) {
|
||||||
// 상태 변경 시에만 디버깅 로그 출력
|
// 상태 변경 시에만 디버깅 로그 출력
|
||||||
dlog('🔍 [PlayerPanel] PanelInfo isPaused changed', {
|
dlog('🔍 [PlayerPanel] PanelInfo isPaused changed', {
|
||||||
previousIsPaused: previousPanelInfo.current?.isPaused,
|
previousIsPaused: previousPanelInfo.current?.isPaused,
|
||||||
@@ -539,7 +591,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
}
|
}
|
||||||
|
|
||||||
previousPanelInfo.current = panelInfo;
|
previousPanelInfo.current = panelInfo;
|
||||||
}, [panelInfo?.isPaused, topPanel?.name, currentPlayingUrl]);
|
}, [panelInfo?.isPaused, currentPlayingUrl]);
|
||||||
|
|
||||||
// VideoPlayer 인스턴스 및 소스 변경 모니터링 (중요 변화만)
|
// VideoPlayer 인스턴스 및 소스 변경 모니터링 (중요 변화만)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -647,6 +699,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentLiveShowInfo && Object.keys(currentLiveShowInfo).length > 0) {
|
if (currentLiveShowInfo && Object.keys(currentLiveShowInfo).length > 0) {
|
||||||
if (currentLiveShowInfo.showId !== panelInfo?.showId) {
|
if (currentLiveShowInfo.showId !== panelInfo?.showId) {
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - LIVE showId update');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -1146,6 +1199,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
videoPlayer.current?.hideControls();
|
videoPlayer.current?.hideControls();
|
||||||
setSelectedIndex(backupInitialIndex);
|
setSelectedIndex(backupInitialIndex);
|
||||||
if (panelInfo.shptmBanrTpNm === 'MEDIA') {
|
if (panelInfo.shptmBanrTpNm === 'MEDIA') {
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - DETAIL_PANEL launchedFromPlayer false');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.DETAIL_PANEL,
|
name: panel_names.DETAIL_PANEL,
|
||||||
@@ -1163,6 +1217,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
if (!panelInfo.modal) {
|
if (!panelInfo.modal) {
|
||||||
console.log('[PlayerPanel] popPanel - closeButtonHandler');
|
console.log('[PlayerPanel] popPanel - closeButtonHandler');
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel());
|
||||||
|
// 🔽 [251221] cleanup useEffect에서 isDeepLinkEntry 리셋 처리
|
||||||
dispatch(changeAppStatus({ cursorVisible: false }));
|
dispatch(changeAppStatus({ cursorVisible: false }));
|
||||||
|
|
||||||
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
||||||
@@ -1193,12 +1248,23 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
return () => {
|
return () => {
|
||||||
// 패널이 2개 존재할때만 popPanel 진행
|
// 패널이 2개 존재할때만 popPanel 진행
|
||||||
// 현재 스택의 top이 PlayerPanel일 때만 pop 수행 (다른 패널이 올라온 상태에서 오작동 방지)
|
// 현재 스택의 top이 PlayerPanel일 때만 pop 수행 (다른 패널이 올라온 상태에서 오작동 방지)
|
||||||
console.log('[PP-TRACE] cleanup start', {
|
// console.log('[PP-TRACE] cleanup start', {
|
||||||
modal: panelInfo.modal,
|
// modal: panelInfo.modal,
|
||||||
isOnTop,
|
// isOnTop,
|
||||||
topPanel: panels[panels.length - 1]?.name,
|
// topPanel: panels[panels.length - 1]?.name,
|
||||||
stack: panels.map((p) => p.name),
|
// stack: panels.map((p) => p.name),
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
// 🔽 [251221] PlayerPanel unmount 시 DeepLink 플래그 리셋
|
||||||
|
dispatch(
|
||||||
|
updateHomeInfo({
|
||||||
|
name: panel_names.HOME_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
isDeepLinkEntry: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const topPanelName = panels[panels.length - 1]?.name;
|
const topPanelName = panels[panels.length - 1]?.name;
|
||||||
if (
|
if (
|
||||||
panelInfo.modal &&
|
panelInfo.modal &&
|
||||||
@@ -1206,13 +1272,13 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
topPanelName === panel_names.PLAYER_PANEL &&
|
topPanelName === panel_names.PLAYER_PANEL &&
|
||||||
panels.length === 1 // 다른 패널 존재 시 pop 금지 (DetailPanel 제거 방지)
|
panels.length === 1 // 다른 패널 존재 시 pop 금지 (DetailPanel 제거 방지)
|
||||||
) {
|
) {
|
||||||
console.log('[PP-TRACE] popPanel - useEffect cleanup (top is PlayerPanel)');
|
// console.log('[PP-TRACE] popPanel - useEffect cleanup (top is PlayerPanel)');
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel());
|
||||||
} else {
|
} else {
|
||||||
Spotlight.focus('tbody');
|
Spotlight.focus('tbody');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [panelInfo?.modal, isOnTop, panels]);
|
}, [panelInfo?.modal, isOnTop, panels, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {
|
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {
|
||||||
@@ -1309,10 +1375,21 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
// 최상단 패널이 DetailPanel이고 PlayerPanel에서 진입했는지 확인
|
// 최상단 패널이 DetailPanel이고 PlayerPanel에서 진입했는지 확인
|
||||||
const isTopPanelDetailFromPlayer = useMemo(() => {
|
const isTopPanelDetailFromPlayer = useMemo(() => {
|
||||||
const result =
|
let result =
|
||||||
topPanel?.name === panel_names.DETAIL_PANEL &&
|
topPanel?.name === panel_names.DETAIL_PANEL &&
|
||||||
topPanel?.panelInfo?.launchedFromPlayer === true;
|
topPanel?.panelInfo?.launchedFromPlayer === true;
|
||||||
|
|
||||||
|
// MediaPanel이 최상단에 있고 그 아래가 DetailPanel인 경우도 체크
|
||||||
|
if (!result && topPanel?.name === panel_names.MEDIA_PANEL) {
|
||||||
|
const prevPanel = panels[panels.length - 2];
|
||||||
|
if (
|
||||||
|
prevPanel?.name === panel_names.DETAIL_PANEL &&
|
||||||
|
prevPanel?.panelInfo?.launchedFromPlayer === true
|
||||||
|
) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔍 DetailPanel 상태 변화 로깅
|
// 🔍 DetailPanel 상태 변화 로깅
|
||||||
if (result) {
|
if (result) {
|
||||||
dlog('🎬 [PlayerPanel] DetailPanel is now on top (from Player)', {
|
dlog('🎬 [PlayerPanel] DetailPanel is now on top (from Player)', {
|
||||||
@@ -1768,6 +1845,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
if (watchIntervalLive.current) clearInterval(watchIntervalLive.current);
|
if (watchIntervalLive.current) clearInterval(watchIntervalLive.current);
|
||||||
if (watchIntervalVod.current) clearInterval(watchIntervalVod.current);
|
if (watchIntervalVod.current) clearInterval(watchIntervalVod.current);
|
||||||
if (watchIntervalMedia.current) clearInterval(watchIntervalMedia.current);
|
if (watchIntervalMedia.current) clearInterval(watchIntervalMedia.current);
|
||||||
|
if (activityCheckIntervalRef.current) clearInterval(activityCheckIntervalRef.current);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -1858,6 +1936,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
dlog('[PlayerPanel] Condition 2.5: Removing skipFlag after DOM render');
|
dlog('[PlayerPanel] Condition 2.5: Removing skipFlag after DOM render');
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - skipModalStyleRecalculation remove');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -1898,12 +1977,37 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
scale = width / window.innerWidth;
|
scale = width / window.innerWidth;
|
||||||
setModalScale(scale);
|
setModalScale(scale);
|
||||||
}
|
}
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - modalStyle and scale update');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
panelInfo: { modalStyle: modalStyle, modalScale: scale },
|
panelInfo: { modalStyle: modalStyle, modalScale: scale },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔽 배너 위치 수집 (초기 로드 시에만 실행)
|
||||||
|
if (validBannerPositions.length === 0) {
|
||||||
|
const positions = collectBannerPositions();
|
||||||
|
if (positions.length > 0) {
|
||||||
|
setValidBannerPositions(positions);
|
||||||
|
dlog('[PlayerPanel] ✅ 배너 위치 초기 수집 완료:', positions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔽 배너 위치 검증 (위치가 맞지 않으면 비디오 재생 중단)
|
||||||
|
if (validBannerPositions.length > 0) {
|
||||||
|
const currentPosition = { top: Math.round(top), left: Math.round(left) };
|
||||||
|
const isValidPosition = isPositionMatching(validBannerPositions, panelInfo.modalContainerId, currentPosition);
|
||||||
|
|
||||||
|
if (!isValidPosition) {
|
||||||
|
dlog('[PlayerPanel] ⚠️ 배너 위치 검증 실패 - 비디오 재생 중단', {
|
||||||
|
bannerId: panelInfo.modalContainerId,
|
||||||
|
currentPosition,
|
||||||
|
validBannerPositions
|
||||||
|
});
|
||||||
|
return; // 비디오 재생 중단
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dlog('[PlayerPanel] Condition 1: Node not found, using saved modalStyle');
|
dlog('[PlayerPanel] Condition 1: Node not found, using saved modalStyle');
|
||||||
setModalStyle(panelInfo.modalStyle);
|
setModalStyle(panelInfo.modalStyle);
|
||||||
@@ -2223,6 +2327,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
);
|
);
|
||||||
Spotlight.focus('playVideoShopNowBox');
|
Spotlight.focus('playVideoShopNowBox');
|
||||||
} else {
|
} else {
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - handleIndicatorDownClick');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -2241,7 +2346,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
setPrevChannelIndex(selectedIndex);
|
setPrevChannelIndex(selectedIndex);
|
||||||
}
|
}
|
||||||
setSideContentsVisible(true);
|
setSideContentsVisible(true);
|
||||||
}, [dispatch, playListInfo, selectedIndex, sideContentsVisible, initialEnter]);
|
}, [dispatch, playListInfo, selectedIndex, sideContentsVisible, initialEnter, tabContainerVersion, tabIndexV2]);
|
||||||
|
|
||||||
const handleIndicatorUpClick = useCallback(() => {
|
const handleIndicatorUpClick = useCallback(() => {
|
||||||
if (!initialEnter) {
|
if (!initialEnter) {
|
||||||
@@ -2270,6 +2375,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
);
|
);
|
||||||
Spotlight.focus('playVideoShopNowBox');
|
Spotlight.focus('playVideoShopNowBox');
|
||||||
} else {
|
} else {
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - handleIndicatorUpClick');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -2288,7 +2394,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
setPrevChannelIndex(selectedIndex);
|
setPrevChannelIndex(selectedIndex);
|
||||||
}
|
}
|
||||||
setSideContentsVisible(true);
|
setSideContentsVisible(true);
|
||||||
}, [dispatch, playListInfo, selectedIndex, sideContentsVisible, initialEnter]);
|
}, [dispatch, playListInfo, selectedIndex, sideContentsVisible, initialEnter, tabContainerVersion, tabIndexV2]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (panelInfo.shptmBanrTpNm === 'VOD' && panelInfo.patnrId && panelInfo.showId) {
|
if (panelInfo.shptmBanrTpNm === 'VOD' && panelInfo.patnrId && panelInfo.showId) {
|
||||||
@@ -2389,6 +2495,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
const onEnded = useCallback(
|
const onEnded = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') {
|
if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') {
|
||||||
|
console.log('[PANEL] PlayerPanel updatePanel - DETAIL_PANEL video ended');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.DETAIL_PANEL,
|
name: panel_names.DETAIL_PANEL,
|
||||||
@@ -2429,14 +2536,105 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onKeyDown = (ev) => {
|
const onKeyDown = (ev) => {
|
||||||
if (ev.keyCode === 34) {
|
// tabIndex === 1 (LiveChannelContents 표시)이고 비디오 배너에 포커스가 있는 경우
|
||||||
|
const currentFocused = Spotlight.getCurrent();
|
||||||
|
const spotlightId = currentFocused?.getAttribute('data-spotlight-id');
|
||||||
|
const isVideoItemFocused = spotlightId?.startsWith('tabChannel-video');
|
||||||
|
|
||||||
|
// LiveChannelContents의 비디오 배너에 포커스가 있는 경우: PageUp/PageDown을 좌우 이동으로 변환
|
||||||
|
if (tabIndexV2 === 1 && isVideoItemFocused) {
|
||||||
|
// DOM에서 실제로 렌더링된 모든 비디오 배너 찾기 (가상화 대응)
|
||||||
|
const allVideoBanners = Array.from(
|
||||||
|
document.querySelectorAll('[data-spotlight-id^="tabChannel-video-"]')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (allVideoBanners.length > 0) {
|
||||||
|
// 현재 포커스된 배너의 인덱스 찾기
|
||||||
|
const currentBannerIndex = allVideoBanners.findIndex(
|
||||||
|
(el) => el === currentFocused
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentBannerIndex !== -1) {
|
||||||
|
if (ev.keyCode === 34) { // PageDown -> 오른쪽 배너로 포커스 이동
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
// DOM에 렌더링된 다음 배너로 이동 (마지막이면 무시 또는 첫 번째로)
|
||||||
|
if (currentBannerIndex < allVideoBanners.length - 1) {
|
||||||
|
// 다음 배너가 DOM에 있으면 이동
|
||||||
|
const nextBanner = allVideoBanners[currentBannerIndex + 1];
|
||||||
|
const nextSpotlightId = nextBanner.getAttribute('data-spotlight-id');
|
||||||
|
|
||||||
|
dlog('[PlayerPanel] 🎯 PageDown (비디오 배너) -> 오른쪽으로 이동', {
|
||||||
|
current: spotlightId,
|
||||||
|
next: nextSpotlightId,
|
||||||
|
currentBannerIndex,
|
||||||
|
totalVisibleBanners: allVideoBanners.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
Spotlight.focus(nextSpotlightId);
|
||||||
|
} else {
|
||||||
|
// 마지막 배너면 첫 번째로 이동 시도 (DOM에 있으면)
|
||||||
|
const firstBanner = allVideoBanners[0];
|
||||||
|
const firstSpotlightId = firstBanner.getAttribute('data-spotlight-id');
|
||||||
|
|
||||||
|
dlog('[PlayerPanel] 🎯 PageDown (마지막 배너) -> 첫 번째 배너로 이동 시도', {
|
||||||
|
current: spotlightId,
|
||||||
|
next: firstSpotlightId,
|
||||||
|
isWrapAround: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Spotlight.focus(firstSpotlightId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (ev.keyCode === 33) { // PageUp -> 왼쪽 배너로 포커스 이동
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
// DOM에 렌더링된 이전 배너로 이동 (첫 번째면 무시 또는 마지막으로)
|
||||||
|
if (currentBannerIndex > 0) {
|
||||||
|
// 이전 배너가 DOM에 있으면 이동
|
||||||
|
const prevBanner = allVideoBanners[currentBannerIndex - 1];
|
||||||
|
const prevSpotlightId = prevBanner.getAttribute('data-spotlight-id');
|
||||||
|
|
||||||
|
dlog('[PlayerPanel] 🎯 PageUp (비디오 배너) -> 왼쪽으로 이동', {
|
||||||
|
current: spotlightId,
|
||||||
|
prev: prevSpotlightId,
|
||||||
|
currentBannerIndex,
|
||||||
|
totalVisibleBanners: allVideoBanners.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
Spotlight.focus(prevSpotlightId);
|
||||||
|
} else {
|
||||||
|
// 첫 번째 배너면 마지막으로 이동 시도 (DOM에 있으면)
|
||||||
|
const lastBanner = allVideoBanners[allVideoBanners.length - 1];
|
||||||
|
const lastSpotlightId = lastBanner.getAttribute('data-spotlight-id');
|
||||||
|
|
||||||
|
dlog('[PlayerPanel] 🎯 PageUp (첫 번째 배너) -> 마지막 배너로 이동 시도', {
|
||||||
|
current: spotlightId,
|
||||||
|
prev: lastSpotlightId,
|
||||||
|
isWrapAround: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Spotlight.focus(lastSpotlightId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 로직: LiveChannelButton 또는 다른 경우에는 상/하 이동
|
||||||
|
if (ev.keyCode === 34) { // PageDown
|
||||||
handleIndicatorDownClick();
|
handleIndicatorDownClick();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
} else if (ev.keyCode === 33) {
|
dlog('[PlayerPanel] 📺 PageDown (버튼 또는 다른 경우) -> 다음 비디오');
|
||||||
|
} else if (ev.keyCode === 33) { // PageUp
|
||||||
handleIndicatorUpClick();
|
handleIndicatorUpClick();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
dlog('[PlayerPanel] 📺 PageUp (버튼 또는 다른 경우) -> 이전 비디오');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2447,6 +2645,11 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
const timerIdTabAutoAdvance = useRef(null);
|
const timerIdTabAutoAdvance = useRef(null);
|
||||||
const prevTabIndexV2 = useRef(null);
|
const prevTabIndexV2 = useRef(null);
|
||||||
|
|
||||||
|
// Activity Check for tabIndex auto-advance
|
||||||
|
const lastActivityTimeRef = useRef(Date.now());
|
||||||
|
const activityCheckIntervalRef = useRef(null);
|
||||||
|
const ACTIVITY_TIMEOUT = 1000; // 1초 동안 활동이 없으면 타이머 진행
|
||||||
|
|
||||||
const showSideContents = useMemo(() => {
|
const showSideContents = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
sideContentsVisible &&
|
sideContentsVisible &&
|
||||||
@@ -2558,17 +2761,62 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
timerIdTabAutoAdvance.current = null;
|
timerIdTabAutoAdvance.current = null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Activity 감지 함수
|
||||||
|
const onActivityDetected = useCallback(() => {
|
||||||
|
lastActivityTimeRef.current = Date.now();
|
||||||
|
dlog('[PlayerPanel] 🎯 Activity detected - timer will be delayed', {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Activity 여부를 확인하는 함수 (1초 타임아웃 체크)
|
||||||
|
const isInactive = useCallback(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSinceLastActivity = now - lastActivityTimeRef.current;
|
||||||
|
return timeSinceLastActivity > ACTIVITY_TIMEOUT;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const resetTimerTabAutoAdvance = useCallback(
|
const resetTimerTabAutoAdvance = useCallback(
|
||||||
(timeout) => {
|
(timeout) => {
|
||||||
if (timerIdTabAutoAdvance.current) {
|
if (timerIdTabAutoAdvance.current) {
|
||||||
clearTimerTabAutoAdvance();
|
clearTimerTabAutoAdvance();
|
||||||
}
|
}
|
||||||
|
|
||||||
timerIdTabAutoAdvance.current = setTimeout(() => {
|
// Activity check interval 설정 (매 100ms마다 체크)
|
||||||
|
if (activityCheckIntervalRef.current) {
|
||||||
|
clearInterval(activityCheckIntervalRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsedTime = 0;
|
||||||
|
|
||||||
|
activityCheckIntervalRef.current = setInterval(() => {
|
||||||
|
// 활동이 없을 때만 경과 시간 증가
|
||||||
|
if (isInactive()) {
|
||||||
|
elapsedTime += 100;
|
||||||
|
dlog('[PlayerPanel] ⏱️ TabIndex auto-advance: inactive', {
|
||||||
|
elapsedTime,
|
||||||
|
requiredTime: timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 필요한 시간만큼 경과했으면 타이머 실행
|
||||||
|
if (elapsedTime >= timeout) {
|
||||||
|
dlog('[PlayerPanel] ✅ TabIndex auto-advance executing - setTabIndexV2(2)', {
|
||||||
|
totalElapsed: elapsedTime,
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
|
clearInterval(activityCheckIntervalRef.current);
|
||||||
setTabIndexV2(2);
|
setTabIndexV2(2);
|
||||||
}, timeout);
|
}
|
||||||
|
} else {
|
||||||
|
// 활동이 감지되면 경과 시간 리셋
|
||||||
|
dlog('[PlayerPanel] 🔄 Activity detected - resetting elapsed time', {
|
||||||
|
previousElapsed: elapsedTime,
|
||||||
|
});
|
||||||
|
elapsedTime = 0;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
},
|
},
|
||||||
[clearTimerTabAutoAdvance]
|
[clearTimerTabAutoAdvance, isInactive]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Redux로 오버레이 숨김
|
// Redux로 오버레이 숨김
|
||||||
@@ -2593,6 +2841,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
if (timerIdTabAutoAdvance.current) {
|
if (timerIdTabAutoAdvance.current) {
|
||||||
clearTimerTabAutoAdvance();
|
clearTimerTabAutoAdvance();
|
||||||
}
|
}
|
||||||
|
if (activityCheckIntervalRef.current) {
|
||||||
|
clearInterval(activityCheckIntervalRef.current);
|
||||||
|
activityCheckIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(resetPlayerOverlays());
|
dispatch(resetPlayerOverlays());
|
||||||
}
|
}
|
||||||
@@ -2807,6 +3059,53 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
panelInfo?.modal,
|
panelInfo?.modal,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// PageUp/PageDown으로 비디오 변경 시 현재 재생 배너로 포커스 이동
|
||||||
|
useEffect(() => {
|
||||||
|
if (tabContainerVersion === 2 &&
|
||||||
|
tabIndexV2 === 1 &&
|
||||||
|
panelInfo?.isIndicatorByClick &&
|
||||||
|
selectedIndex !== null &&
|
||||||
|
selectedIndex >= 0) {
|
||||||
|
|
||||||
|
dlog('[PlayerPanel] 🎯 PageUp/PageDown 후 포커스 이동 준비', {
|
||||||
|
selectedIndex,
|
||||||
|
tabContainerVersion,
|
||||||
|
tabIndexV2,
|
||||||
|
isIndicatorByClick: panelInfo.isIndicatorByClick
|
||||||
|
});
|
||||||
|
|
||||||
|
const bannerSpotlightId = `banner${selectedIndex}`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
dlog('[PlayerPanel] 🔍 포커스 이동 시도:', bannerSpotlightId);
|
||||||
|
|
||||||
|
const bannerElement = document.querySelector(`[data-spotlight-id="${bannerSpotlightId}"]`);
|
||||||
|
|
||||||
|
if (bannerElement) {
|
||||||
|
dlog('[PlayerPanel] ✅ 배너 요소 찾음, 포커스 이동 실행');
|
||||||
|
Spotlight.focus(bannerElement);
|
||||||
|
} else {
|
||||||
|
dlog('[PlayerPanel] ⚠️ 배너 요소 찾지 못함:', bannerSpotlightId);
|
||||||
|
// 모든 배너 요소 목록 출력
|
||||||
|
const allBanners = document.querySelectorAll('[data-spotlight-id^="banner"]');
|
||||||
|
dlog('[PlayerPanel] 🔍 사용 가능한 배너 목록:',
|
||||||
|
Array.from(allBanners).map(el => el.getAttribute('data-spotlight-id'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 플래그 리셋
|
||||||
|
dispatch(
|
||||||
|
updatePanel({
|
||||||
|
name: panel_names.PLAYER_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
isIndicatorByClick: false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, 200); // DOM 업데이트 대기
|
||||||
|
}
|
||||||
|
}, [selectedIndex, tabContainerVersion, tabIndexV2, panelInfo?.isIndicatorByClick, dispatch]);
|
||||||
|
|
||||||
// TabIndex 1 자동 다음 단계로 이동
|
// TabIndex 1 자동 다음 단계로 이동
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// tabIndex === 1일 때만 실행
|
// tabIndex === 1일 때만 실행
|
||||||
@@ -2833,6 +3132,31 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
clearTimerTabAutoAdvance,
|
clearTimerTabAutoAdvance,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Activity detection for tabIndex auto-advance (mousemove, keydown, click)
|
||||||
|
useEffect(() => {
|
||||||
|
// tabIndex === 1일 때만 Activity 감지 활성화
|
||||||
|
if (tabIndexV2 !== 1 || !belowContentsVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dlog('[PlayerPanel] 🎙️ Activity listener registered for tabIndex=1');
|
||||||
|
|
||||||
|
const handleMouseMove = onActivityDetected;
|
||||||
|
const handleKeyDown = onActivityDetected;
|
||||||
|
const handleClick = onActivityDetected;
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
document.addEventListener('click', handleClick);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dlog('[PlayerPanel] 🎙️ Activity listener unregistered');
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
document.removeEventListener('click', handleClick);
|
||||||
|
};
|
||||||
|
}, [tabIndexV2, belowContentsVisible, onActivityDetected]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const videoContainer = document.querySelector(`.${css.videoContainer}`);
|
const videoContainer = document.querySelector(`.${css.videoContainer}`);
|
||||||
|
|
||||||
|
|||||||
@@ -4,36 +4,26 @@ import { useDispatch } from 'react-redux';
|
|||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
|
|
||||||
// <<<<<<< HEAD
|
import { sendLogTotalRecommend } from '../../../../actions/logActions';
|
||||||
import { updatePanel } from '../../../../actions/panelActions';
|
import { updatePanel } from '../../../../actions/panelActions';
|
||||||
import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
|
import TScrollerLiveChannel from './TScrollerLiveChannel';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MENU,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../../utils/Config';
|
||||||
import { $L } from '../../../../utils/helperMethods';
|
import { $L } from '../../../../utils/helperMethods';
|
||||||
import PlayerItemCard, { TYPES } from '../../PlayerItemCard/PlayerItemCard';
|
import PlayerItemCard, { TYPES } from '../../PlayerItemCard/PlayerItemCard';
|
||||||
import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
|
import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
|
||||||
import css from './LiveChannelContents.module.less';
|
import css from './LiveChannelContents.module.less';
|
||||||
import cssV2 from './LiveChannelContents.v2.module.less';
|
import cssV2 from './LiveChannelContents.v2.module.less';
|
||||||
import { sendLogTotalRecommend } from '../../../../actions/logActions';
|
|
||||||
// =======
|
|
||||||
// import { updatePanel } from "../../../../actions/panelActions";
|
|
||||||
// import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList";
|
|
||||||
// import {
|
|
||||||
// LOG_CONTEXT_NAME,
|
|
||||||
// LOG_MENU,
|
|
||||||
// LOG_MESSAGE_ID,
|
|
||||||
// panel_names,
|
|
||||||
// } from "../../../../utils/Config";
|
|
||||||
// import { $L } from "../../../../utils/helperMethods";
|
|
||||||
// import PlayerItemCard, { TYPES } from "../../PlayerItemCard/PlayerItemCard";
|
|
||||||
// import ListEmptyContents from "../TabContents/ListEmptyContents/ListEmptyContents";
|
|
||||||
// import css from "./LiveChannelContents.module.less";
|
|
||||||
// import { sendLogTotalRecommend } from "../../../../actions/logActions";
|
|
||||||
// >>>>>>> gitlab/develop
|
|
||||||
|
|
||||||
export default function LiveChannelContents({
|
export default function LiveChannelContents({
|
||||||
liveInfos,
|
liveInfos,
|
||||||
currentTime,
|
currentTime,
|
||||||
setSelectedIndex,
|
setSelectedIndex,
|
||||||
|
selectedIndex,
|
||||||
videoVerticalVisible,
|
videoVerticalVisible,
|
||||||
currentVideoShowId,
|
currentVideoShowId,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
@@ -77,6 +67,19 @@ export default function LiveChannelContents({
|
|||||||
}
|
}
|
||||||
}, [isFilteredByPatnr19]);
|
}, [isFilteredByPatnr19]);
|
||||||
|
|
||||||
|
// currentVideoShowId 변경 시 해당 배너가 보이도록 스크롤
|
||||||
|
// (LiveChannelButton에서 PageUp/PageDown으로 동영상 변경 시)
|
||||||
|
// currentVideoShowId 기반으로 스크롤하면 포커스 이동 없이 배너만 화면에 보임
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentVideoShowId && liveInfos && liveInfos.length > 0 && scrollToRef.current) {
|
||||||
|
// currentVideoShowId와 일치하는 배너의 인덱스 찾기
|
||||||
|
const index = liveInfos.findIndex((item) => item.showId === currentVideoShowId);
|
||||||
|
if (index !== -1) {
|
||||||
|
scrollToRef.current({ index, animate: true, focus: false, alignToStart: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentVideoShowId, liveInfos]);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ index, ...rest }) => {
|
({ index, ...rest }) => {
|
||||||
const {
|
const {
|
||||||
@@ -181,6 +184,7 @@ export default function LiveChannelContents({
|
|||||||
startDt={strtDt}
|
startDt={strtDt}
|
||||||
endDt={endDt}
|
endDt={endDt}
|
||||||
currentTime={currentTime}
|
currentTime={currentTime}
|
||||||
|
currentVideoVisible={currentVideoShowId === liveInfos[index].showId}
|
||||||
// <<<<<<< HEAD
|
// <<<<<<< HEAD
|
||||||
version={version}
|
version={version}
|
||||||
// =======
|
// =======
|
||||||
@@ -189,7 +193,15 @@ export default function LiveChannelContents({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[liveInfos, currentTime, currentVideoShowId, isClickBlocked, dispatch, handleFocus, version]
|
[
|
||||||
|
liveInfos,
|
||||||
|
currentTime,
|
||||||
|
currentVideoShowId,
|
||||||
|
isClickBlocked,
|
||||||
|
dispatch,
|
||||||
|
handleFocus,
|
||||||
|
version,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const containerClass = version === 2 ? cssV2.container : css.container;
|
const containerClass = version === 2 ? cssV2.container : css.container;
|
||||||
@@ -207,7 +219,7 @@ export default function LiveChannelContents({
|
|||||||
<>
|
<>
|
||||||
<div className={containerClass}>
|
<div className={containerClass}>
|
||||||
{liveInfos && liveInfos.length > 0 ? (
|
{liveInfos && liveInfos.length > 0 ? (
|
||||||
<TVirtualGridList
|
<TScrollerLiveChannel
|
||||||
cbScrollTo={handleScrollTo}
|
cbScrollTo={handleScrollTo}
|
||||||
dataSize={liveInfos.length}
|
dataSize={liveInfos.length}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
@@ -215,7 +227,6 @@ export default function LiveChannelContents({
|
|||||||
itemWidth={version === 2 ? 470 : videoVerticalVisible ? 540 : 600}
|
itemWidth={version === 2 ? 470 : videoVerticalVisible ? 540 : 600}
|
||||||
itemHeight={version === 2 ? 155 : 236}
|
itemHeight={version === 2 ? 155 : 236}
|
||||||
spacing={version === 2 ? 30 : 12}
|
spacing={version === 2 ? 30 : 12}
|
||||||
noScrollByWheel={false}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ListEmptyContents tabIndex={tabIndex} />
|
<ListEmptyContents tabIndex={tabIndex} />
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { scaleH, scaleW } from '../../../../utils/helperMethods';
|
||||||
|
import css from './TScrollerLiveChannel.module.less';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TScrollerLiveChannel - Live Channel용 간단한 스크롤 컴포넌트
|
||||||
|
*
|
||||||
|
* TVirtualGridList의 가상화 대신 모든 아이템을 DOM에 렌더링
|
||||||
|
* 20개 미만의 아이템에 최적화되어 있음
|
||||||
|
*
|
||||||
|
* @param {number} dataSize - 아이템 개수
|
||||||
|
* @param {string} direction - 'horizontal' 또는 'vertical'
|
||||||
|
* @param {function} renderItem - 아이템 렌더링 함수 ({ index })
|
||||||
|
* @param {number} itemWidth - 아이템 너비
|
||||||
|
* @param {number} itemHeight - 아이템 높이
|
||||||
|
* @param {number} spacing - 아이템 간 간격
|
||||||
|
* @param {function} cbScrollTo - 스크롤 함수를 받을 콜백
|
||||||
|
* @param {string} className - 추가 CSS 클래스
|
||||||
|
* @param {string} spotlightId - Spotlight 포커스 ID prefix
|
||||||
|
*/
|
||||||
|
export default function TScrollerLiveChannel({
|
||||||
|
dataSize,
|
||||||
|
direction = 'horizontal',
|
||||||
|
renderItem,
|
||||||
|
itemWidth,
|
||||||
|
itemHeight,
|
||||||
|
spacing,
|
||||||
|
cbScrollTo,
|
||||||
|
className,
|
||||||
|
spotlightId,
|
||||||
|
}) {
|
||||||
|
const scrollContainerRef = useRef(null);
|
||||||
|
const itemsRef = useRef([]);
|
||||||
|
|
||||||
|
// 스크롤 컨테이너 크기 계산
|
||||||
|
const containerStyle = useMemo(() => {
|
||||||
|
if (direction === 'horizontal') {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
overflowX: 'auto',
|
||||||
|
overflowY: 'hidden',
|
||||||
|
width: '100%',
|
||||||
|
height: scaleH(itemHeight),
|
||||||
|
alignItems: 'center',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [direction, itemHeight]);
|
||||||
|
|
||||||
|
// 아이템 래퍼 스타일 계산
|
||||||
|
const itemsWrapperStyle = useMemo(() => {
|
||||||
|
if (direction === 'horizontal') {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: scaleW(spacing),
|
||||||
|
padding: `0 ${scaleW(spacing)}px`,
|
||||||
|
alignItems: 'center',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: scaleH(spacing),
|
||||||
|
padding: `${scaleH(spacing)}px 0`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [direction, spacing]);
|
||||||
|
|
||||||
|
// 스크롤 함수 생성
|
||||||
|
const scrollToIndex = useCallback(
|
||||||
|
(index, options = {}) => {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
if (!container || !itemsRef.current[index]) return;
|
||||||
|
|
||||||
|
const item = itemsRef.current[index];
|
||||||
|
const { animate = true, alignToStart = false } = options;
|
||||||
|
|
||||||
|
if (direction === 'horizontal') {
|
||||||
|
// 수평 스크롤: 스크롤 가능 여부 판단
|
||||||
|
const isScrollable = container.scrollWidth > container.clientWidth;
|
||||||
|
|
||||||
|
let scrollLeft = 0;
|
||||||
|
|
||||||
|
if (alignToStart && isScrollable) {
|
||||||
|
// 첫 번째 위치로 스크롤
|
||||||
|
scrollLeft = item.offsetLeft - scaleW(spacing);
|
||||||
|
} else if (!alignToStart) {
|
||||||
|
// 기존 로직: 현재 아이템 + 다음 아이템까지 보이도록
|
||||||
|
const itemLeft = item.offsetLeft;
|
||||||
|
const itemWidth = item.offsetWidth;
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
|
||||||
|
const nextItem = itemsRef.current[index + 1];
|
||||||
|
scrollLeft = itemLeft - scaleW(spacing);
|
||||||
|
|
||||||
|
if (nextItem) {
|
||||||
|
const nextItemLeft = nextItem.offsetLeft;
|
||||||
|
const nextItemWidth = nextItem.offsetWidth;
|
||||||
|
const targetScrollLeft = nextItemLeft + nextItemWidth - containerWidth + scaleW(spacing);
|
||||||
|
|
||||||
|
scrollLeft = Math.min(scrollLeft, targetScrollLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 음수 스크롤 방지
|
||||||
|
scrollLeft = Math.max(0, scrollLeft);
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
container.scrollTo({
|
||||||
|
left: scrollLeft,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
container.scrollLeft = scrollLeft;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 수직 스크롤: 스크롤 가능 여부 판단
|
||||||
|
const isScrollable = container.scrollHeight > container.clientHeight;
|
||||||
|
|
||||||
|
let scrollTop = 0;
|
||||||
|
|
||||||
|
if (alignToStart && isScrollable) {
|
||||||
|
// 첫 번째 위치로 스크롤
|
||||||
|
scrollTop = item.offsetTop - scaleH(spacing);
|
||||||
|
} else if (!alignToStart) {
|
||||||
|
// 기존 로직: 현재 아이템 + 다음 아이템까지 보이도록
|
||||||
|
const itemTop = item.offsetTop;
|
||||||
|
const itemHeight = item.offsetHeight;
|
||||||
|
const containerHeight = container.clientHeight;
|
||||||
|
|
||||||
|
const nextItem = itemsRef.current[index + 1];
|
||||||
|
scrollTop = itemTop - scaleH(spacing);
|
||||||
|
|
||||||
|
if (nextItem) {
|
||||||
|
const nextItemTop = nextItem.offsetTop;
|
||||||
|
const nextItemHeight = nextItem.offsetHeight;
|
||||||
|
const targetScrollTop = nextItemTop + nextItemHeight - containerHeight + scaleH(spacing);
|
||||||
|
|
||||||
|
scrollTop = Math.min(scrollTop, targetScrollTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 음수 스크롤 방지
|
||||||
|
scrollTop = Math.max(0, scrollTop);
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
container.scrollTo({
|
||||||
|
top: scrollTop,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
container.scrollTop = scrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[direction, spacing]
|
||||||
|
);
|
||||||
|
|
||||||
|
// TVirtualGridList와 호환되는 콜백 인터페이스 제공
|
||||||
|
useEffect(() => {
|
||||||
|
if (cbScrollTo) {
|
||||||
|
cbScrollTo((options) => {
|
||||||
|
const { index, animate = true, focus = true, alignToStart = false } = options;
|
||||||
|
if (typeof index === 'number' && index >= 0 && index < dataSize) {
|
||||||
|
scrollToIndex(index, { animate, alignToStart });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [cbScrollTo, scrollToIndex, dataSize]);
|
||||||
|
|
||||||
|
// 아이템 ref 할당 함수
|
||||||
|
const setItemRef = useCallback((el, index) => {
|
||||||
|
if (el) {
|
||||||
|
itemsRef.current[index] = el;
|
||||||
|
} else {
|
||||||
|
delete itemsRef.current[index];
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 포커스된 아이템을 화면에 완전히 보이도록 스크롤
|
||||||
|
const handleItemFocus = useCallback(
|
||||||
|
(index) => {
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
const item = itemsRef.current[index];
|
||||||
|
|
||||||
|
if (!container || !item) return;
|
||||||
|
|
||||||
|
if (direction === 'horizontal') {
|
||||||
|
const itemLeft = item.offsetLeft;
|
||||||
|
const itemWidth = item.offsetWidth;
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
const containerScrollLeft = container.scrollLeft;
|
||||||
|
|
||||||
|
// 아이템이 완전히 보이는지 확인
|
||||||
|
const itemRight = itemLeft + itemWidth;
|
||||||
|
const containerRight = containerScrollLeft + containerWidth;
|
||||||
|
|
||||||
|
// 아이템이 왼쪽으로 밖에 나가 있으면 왼쪽 끝에 맞춤
|
||||||
|
if (itemLeft < containerScrollLeft) {
|
||||||
|
container.scrollLeft = itemLeft - scaleW(spacing);
|
||||||
|
}
|
||||||
|
// 아이템이 오른쪽으로 밖에 나가 있으면 오른쪽 끝에 맞춤
|
||||||
|
else if (itemRight > containerRight) {
|
||||||
|
container.scrollLeft = itemRight - containerWidth + scaleW(spacing);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const itemTop = item.offsetTop;
|
||||||
|
const itemHeight = item.offsetHeight;
|
||||||
|
const containerHeight = container.clientHeight;
|
||||||
|
const containerScrollTop = container.scrollTop;
|
||||||
|
|
||||||
|
// 아이템이 완전히 보이는지 확인
|
||||||
|
const itemBottom = itemTop + itemHeight;
|
||||||
|
const containerBottom = containerScrollTop + containerHeight;
|
||||||
|
|
||||||
|
// 아이템이 위로 밖에 나가 있으면 위쪽 끝에 맞춤
|
||||||
|
if (itemTop < containerScrollTop) {
|
||||||
|
container.scrollTop = itemTop - scaleH(spacing);
|
||||||
|
}
|
||||||
|
// 아이템이 아래로 밖에 나가 있으면 아래쪽 끝에 맞춤
|
||||||
|
else if (itemBottom > containerBottom) {
|
||||||
|
container.scrollTop = itemBottom - containerHeight + scaleH(spacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[direction, spacing]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(css.tScrollerLiveChannelContainer, className)}
|
||||||
|
style={containerStyle}
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
>
|
||||||
|
<div className={css.itemsWrapper} style={itemsWrapperStyle}>
|
||||||
|
{Array.from({ length: dataSize }).map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={`item-${index}`}
|
||||||
|
ref={(el) => setItemRef(el, index)}
|
||||||
|
className={css.item}
|
||||||
|
style={{
|
||||||
|
width: direction === 'horizontal' ? scaleW(itemWidth) : 'auto',
|
||||||
|
height: direction === 'horizontal' ? 'auto' : scaleH(itemHeight),
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
onFocus={() => handleItemFocus(index)}
|
||||||
|
>
|
||||||
|
{renderItem({ index })}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
.tScrollerLiveChannelContainer {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// 스크롤바 스타일
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemsWrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
// 포커스 상태 처리
|
||||||
|
&:focus-within {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,13 @@ import { compose } from 'ramda/src/compose';
|
|||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
import { Marquee, MarqueeController } from '@enact/ui/Marquee';
|
import {
|
||||||
|
Marquee,
|
||||||
|
MarqueeController,
|
||||||
|
} from '@enact/ui/Marquee';
|
||||||
|
|
||||||
import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
|
import icon_arrow_dwon
|
||||||
|
from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
|
||||||
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||||
import { SpotlightIds } from '../../../../utils/SpotlightIds';
|
import { SpotlightIds } from '../../../../utils/SpotlightIds';
|
||||||
import css from './LiveChannelNext.module.less';
|
import css from './LiveChannelNext.module.less';
|
||||||
@@ -18,7 +22,7 @@ export default function LiveChannelNext({
|
|||||||
channelLogo,
|
channelLogo,
|
||||||
channelName = 'ShopLC',
|
channelName = 'ShopLC',
|
||||||
programName = 'Sandal Black...',
|
programName = 'Sandal Black...',
|
||||||
backgroundColor = 'linear-gradient(180deg, #284998 0%, #06B0EE 100%)',
|
backgroundColor = 'transparent',
|
||||||
onClick,
|
onClick,
|
||||||
onFocus,
|
onFocus,
|
||||||
spotlightId = 'live-channel-next-button',
|
spotlightId = 'live-channel-next-button',
|
||||||
@@ -54,7 +58,6 @@ export default function LiveChannelNext({
|
|||||||
<div className={css.logoWrapper}>
|
<div className={css.logoWrapper}>
|
||||||
<div
|
<div
|
||||||
className={css.logoBackground}
|
className={css.logoBackground}
|
||||||
style={{ background: backgroundColor }}
|
|
||||||
>
|
>
|
||||||
{channelLogo ? (
|
{channelLogo ? (
|
||||||
<CustomImage
|
<CustomImage
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
height: 72px;
|
height: 72px;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logoBackground {
|
.logoBackground {
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logoImage {
|
.logoImage {
|
||||||
@@ -60,6 +62,7 @@
|
|||||||
&.qvcLogoImg {
|
&.qvcLogoImg {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,19 @@ export default function ShopNowButton({ onClick }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSpotlightDown = (e) => {
|
const handleSpotlightDown = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
// tabIndexV2가 2일 때만 CC 버튼으로 내려가기
|
||||||
|
Spotlight.focus('live-channel-next-button');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSpotlightLeft = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// tabIndexV2가 2일 때만 CC 버튼으로 내려가기
|
// tabIndexV2가 2일 때만 CC 버튼으로 내려가기
|
||||||
Spotlight.focus('player-subtitlebutton');
|
Spotlight.focus('player-subtitlebutton');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={css.container}>
|
<div className={css.container}>
|
||||||
<SpottableDiv
|
<SpottableDiv
|
||||||
@@ -31,6 +39,7 @@ export default function ShopNowButton({ onClick }) {
|
|||||||
spotlightId="below-tab-shop-now-button"
|
spotlightId="below-tab-shop-now-button"
|
||||||
onSpotlightUp={handleSpotlightUp}
|
onSpotlightUp={handleSpotlightUp}
|
||||||
onSpotlightDown={handleSpotlightDown}
|
onSpotlightDown={handleSpotlightDown}
|
||||||
|
onSpotlightLeft={handleSpotlightLeft}
|
||||||
>
|
>
|
||||||
<span className={css.buttonText}>SHOP NOW</span>
|
<span className={css.buttonText}>SHOP NOW</span>
|
||||||
</SpottableDiv>
|
</SpottableDiv>
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export default function TabContainerV2({
|
|||||||
// tabIndex = 2 (ShopNowButton)
|
// tabIndex = 2 (ShopNowButton)
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
Spotlight.focus('below-tab-shop-now-button');
|
Spotlight.focus('below-tab-shop-now-button');
|
||||||
}, 100);
|
}, 10);
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
// src/views/SearchPanel/SearchPanel.new.jsx
|
// src/views/SearchPanel/SearchPanel.new.jsx
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
@@ -33,7 +39,9 @@ import {
|
|||||||
// showWarningToast,
|
// showWarningToast,
|
||||||
// } from '../../actions/toastActions';
|
// } from '../../actions/toastActions';
|
||||||
import TBody from '../../components/TBody/TBody';
|
import TBody from '../../components/TBody/TBody';
|
||||||
import TItemCardNew, { removeDotAndColon } from '../../components/TItemCard/TItemCard.new';
|
import TItemCardNew, {
|
||||||
|
removeDotAndColon,
|
||||||
|
} from '../../components/TItemCard/TItemCard.new';
|
||||||
import TPanel from '../../components/TPanel/TPanel';
|
import TPanel from '../../components/TPanel/TPanel';
|
||||||
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
|
||||||
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
|
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
|
||||||
@@ -41,15 +49,22 @@ import usePanelHistory from '../../hooks/usePanelHistory/usePanelHistory';
|
|||||||
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
|
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
import { useSearchHistory } from '../../hooks/useSearchHistory';
|
import { useSearchHistory } from '../../hooks/useSearchHistory';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MENU,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../utils/Config';
|
||||||
|
import { createDebugHelpers } from '../../utils/debug';
|
||||||
import NoSearchResults from './NoSearchResults/NoSearchResults';
|
import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||||
// import NoSearchResults from './NoSearchResults/NoSearchResults';
|
// import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||||
import SearchInputOverlay from './SearchInputOverlay';
|
import SearchInputOverlay from './SearchInputOverlay';
|
||||||
import css from './SearchPanel.new.module.less';
|
import css from './SearchPanel.new.module.less';
|
||||||
import SearchResultsNew from './SearchResults.new.v2';
|
import SearchResultsNew from './SearchResults.new.v2';
|
||||||
import TInputSimple, { ICONS, KINDS } from './TInput/TInputSimple';
|
import TInputSimple, { ICONS, KINDS } from './TInput/TInputSimple';
|
||||||
import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
|
import VoiceInputOverlay, {
|
||||||
import { createDebugHelpers } from '../../utils/debug';
|
VOICE_MODES,
|
||||||
|
} from './VoiceInputOverlay/VoiceInputOverlay';
|
||||||
|
|
||||||
// 디버그 헬퍼 설정
|
// 디버그 헬퍼 설정
|
||||||
const DEBUG_MODE = false;
|
const DEBUG_MODE = false;
|
||||||
@@ -75,13 +90,22 @@ export const SEARCH_PANEL_MODES = {
|
|||||||
VOICE_RESULT: 'voice_result', // 음성 검색 결과 표시
|
VOICE_RESULT: 'voice_result', // 음성 검색 결과 표시
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const ContainerBasic = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: 'last-focused' },
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
// 검색 입력 영역 컨테이너
|
// 검색 입력 영역 컨테이너
|
||||||
const InputContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const InputContainer = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: 'last-focused' },
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
// 콘텐츠 섹션 컨테이너
|
// 콘텐츠 섹션 컨테이너
|
||||||
const SectionContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const SectionContainer = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: 'last-focused' },
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
// 메모리 누수 방지를 위한 안전한 이미지 컴포넌트 (컴포넌트 외부로 이동)
|
// 메모리 누수 방지를 위한 안전한 이미지 컴포넌트 (컴포넌트 외부로 이동)
|
||||||
const SafeImageComponent = ({ src, alt, className, ...props }) => {
|
const SafeImageComponent = ({ src, alt, className, ...props }) => {
|
||||||
@@ -118,7 +142,9 @@ const SafeImageComponent = ({ src, alt, className, ...props }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <img ref={imgRef} src={src} alt={alt} className={className} {...props} />;
|
return (
|
||||||
|
<img ref={imgRef} src={src} alt={alt} className={className} {...props} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 9;
|
const ITEMS_PER_PAGE = 9;
|
||||||
@@ -152,22 +178,36 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 0hun: 패널 전역 상태
|
// 0hun: 패널 전역 상태
|
||||||
const panels = useSelector((state) => state.panels.panels);
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
// 0hun: 음성 검색 결과에 대한 전역 상태
|
// 0hun: 음성 검색 결과에 대한 전역 상태
|
||||||
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
|
const shopperHouseData = useSelector(
|
||||||
const shopperHouseError = useSelector((state) => state.search.shopperHouseError);
|
(state) => state.search.shopperHouseData
|
||||||
|
);
|
||||||
|
const shopperHouseError = useSelector(
|
||||||
|
(state) => state.search.shopperHouseError
|
||||||
|
);
|
||||||
// 0hun: 음성 검색 searchId (Redux에서 별도 관리)
|
// 0hun: 음성 검색 searchId (Redux에서 별도 관리)
|
||||||
const shopperHouseSearchId = useSelector((state) => state.search.shopperHouseSearchId);
|
const shopperHouseSearchId = useSelector(
|
||||||
|
(state) => state.search.shopperHouseSearchId
|
||||||
|
);
|
||||||
// 0hun: 음성 검색 relativeQueries (Redux에서 별도 관리)
|
// 0hun: 음성 검색 relativeQueries (Redux에서 별도 관리)
|
||||||
const shopperHouseRelativeQueries = useSelector(
|
const shopperHouseRelativeQueries = useSelector(
|
||||||
(state) => state.search.shopperHouseRelativeQueries
|
(state) => state.search.shopperHouseRelativeQueries
|
||||||
);
|
);
|
||||||
// 🔄 이전 shopperHouseData (sortingType 변경 시 사용)
|
// 🔄 이전 shopperHouseData (sortingType 변경 시 사용)
|
||||||
const preShopperHouseData = useSelector((state) => state.search.preShopperHouseData);
|
const preShopperHouseData = useSelector(
|
||||||
|
(state) => state.search.preShopperHouseData
|
||||||
|
);
|
||||||
// 0hun: 검색 메인, Hot Picks for you 영역에 대한 전역 상태 값
|
// 0hun: 검색 메인, Hot Picks for you 영역에 대한 전역 상태 값
|
||||||
const hotPicksForYou = useSelector((state) => state.search.searchMainData.hotPicksForYou);
|
const hotPicksForYou = useSelector(
|
||||||
|
(state) => state.search.searchMainData.hotPicksForYou
|
||||||
|
);
|
||||||
// 0hun: 검색 메인, Popular Brands 영역에 대한 전역 상태 값
|
// 0hun: 검색 메인, Popular Brands 영역에 대한 전역 상태 값
|
||||||
const popularBrands = useSelector((state) => state.search.searchMainData.popularBrands);
|
const popularBrands = useSelector(
|
||||||
|
(state) => state.search.searchMainData.popularBrands
|
||||||
|
);
|
||||||
// 0hun: 검색 메인, Top Searchs 영역에 대한 전역 상태 값
|
// 0hun: 검색 메인, Top Searchs 영역에 대한 전역 상태 값
|
||||||
const topSearchs = useSelector((state) => state.search.searchMainData.topSearchs);
|
const topSearchs = useSelector(
|
||||||
|
(state) => state.search.searchMainData.topSearchs
|
||||||
|
);
|
||||||
// jhun: 검색 메인, Today Deals 영역에 대한 전역 상태 값
|
// jhun: 검색 메인, Today Deals 영역에 대한 전역 상태 값
|
||||||
const tsvInfo = useSelector((state) => state.search.searchMainData.tsvInfo);
|
const tsvInfo = useSelector((state) => state.search.searchMainData.tsvInfo);
|
||||||
|
|
||||||
@@ -177,7 +217,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 0hun: 초기 포커스 유무를 나타내는 Boolean 상태
|
// 0hun: 초기 포커스 유무를 나타내는 Boolean 상태
|
||||||
const [firstSpot, setFirstSpot] = useState(false);
|
const [firstSpot, setFirstSpot] = useState(false);
|
||||||
// 0hun: 검색어 상태
|
// 0hun: 검색어 상태
|
||||||
const [searchQuery, setSearchQuery] = useState(panelInfo.searchVal ? panelInfo.searchVal : null);
|
const [searchQuery, setSearchQuery] = useState(
|
||||||
|
panelInfo.searchVal ? panelInfo.searchVal : null
|
||||||
|
);
|
||||||
// 0hun: 검색 컨테이너 포커스 position 상태 값
|
// 0hun: 검색 컨테이너 포커스 position 상태 값
|
||||||
const [position, setPosition] = useState(null);
|
const [position, setPosition] = useState(null);
|
||||||
// 0hun: 가상 키보드 Display 유무 Boolean 값 (주석: 현재 VirtualKeyboardContainer가 비활성화됨)
|
// 0hun: 가상 키보드 Display 유무 Boolean 값 (주석: 현재 VirtualKeyboardContainer가 비활성화됨)
|
||||||
@@ -191,14 +233,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// ✨ [Phase 3] TInput의 입력 모드 상태 제거 (더 이상 InputField가 없으므로 불필요)
|
// ✨ [Phase 3] TInput의 입력 모드 상태 제거 (더 이상 InputField가 없으므로 불필요)
|
||||||
// const [isInputModeActive, setIsInputModeActive] = useState(false);
|
// const [isInputModeActive, setIsInputModeActive] = useState(false);
|
||||||
// 0hun: 현재 포커스된 container의 spotlightId를 관리하는 상태 값
|
// 0hun: 현재 포커스된 container의 spotlightId를 관리하는 상태 값
|
||||||
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo?.focusedContainerId);
|
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||||
|
panelInfo?.focusedContainerId
|
||||||
|
);
|
||||||
|
|
||||||
// ✨ [Phase 1] SearchPanel의 현재 모드 상태 (VoiceInputOverlay의 VOICE_MODES와 동일한 개념)
|
// ✨ [Phase 1] SearchPanel의 현재 모드 상태 (VoiceInputOverlay의 VOICE_MODES와 동일한 개념)
|
||||||
const [currentMode, setCurrentMode] = useState(SEARCH_PANEL_MODES.INITIAL);
|
const [currentMode, setCurrentMode] = useState(SEARCH_PANEL_MODES.INITIAL);
|
||||||
const [isShopperHousePending, setIsShopperHousePending] = useState(false);
|
const [isShopperHousePending, setIsShopperHousePending] = useState(false);
|
||||||
const [voiceOverlayMode, setVoiceOverlayMode] = useState(VOICE_MODES.PROMPT);
|
const [voiceOverlayMode, setVoiceOverlayMode] = useState(VOICE_MODES.PROMPT);
|
||||||
const [voiceOverlayResponseText, setVoiceOverlayResponseText] = useState('');
|
const [voiceOverlayResponseText, setVoiceOverlayResponseText] = useState('');
|
||||||
const [isVoiceOverlayBubbleSearch, setIsVoiceOverlayBubbleSearch] = useState(false);
|
const [isVoiceOverlayBubbleSearch, setIsVoiceOverlayBubbleSearch] =
|
||||||
|
useState(false);
|
||||||
const [shouldFocusVoiceResult, setShouldFocusVoiceResult] = useState(false);
|
const [shouldFocusVoiceResult, setShouldFocusVoiceResult] = useState(false);
|
||||||
|
|
||||||
// 🎯 HowAboutThese 포커스 관리 - 검색 입력 영역 포커스 감지용 상태
|
// 🎯 HowAboutThese 포커스 관리 - 검색 입력 영역 포커스 감지용 상태
|
||||||
@@ -285,11 +330,16 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
const unifiedFocusTimerRef = useRef(null);
|
const unifiedFocusTimerRef = useRef(null);
|
||||||
|
|
||||||
// ShopperHouse 에러 팝업 상태 가져오기
|
// ShopperHouse 에러 팝업 상태 가져오기
|
||||||
const shopperHouseErrorPopup = useSelector((state) => state.search.shopperHouseErrorPopup);
|
const shopperHouseErrorPopup = useSelector(
|
||||||
|
(state) => state.search.shopperHouseErrorPopup
|
||||||
|
);
|
||||||
|
|
||||||
// API 실패 시 fallback reference 초기화
|
// API 실패 시 fallback reference 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shopperHouseErrorPopup?.visible && shopperHouseErrorPopup?.type === 'API_FAILURE') {
|
if (
|
||||||
|
shopperHouseErrorPopup?.visible &&
|
||||||
|
shopperHouseErrorPopup?.type === 'API_FAILURE'
|
||||||
|
) {
|
||||||
dlog('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화');
|
dlog('[SearchPanel] 🧹 API 실패 감지 - fallbackShopperHouseData 초기화');
|
||||||
shopperHouseDataRef.current = null;
|
shopperHouseDataRef.current = null;
|
||||||
}
|
}
|
||||||
@@ -307,8 +357,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
/**
|
/**
|
||||||
* useSearchHistory Hook 적용
|
* useSearchHistory Hook 적용
|
||||||
*/
|
*/
|
||||||
const { normalSearches, addNormalSearch, refreshHistory, executeSearchFromHistory } =
|
const {
|
||||||
useSearchHistory();
|
normalSearches,
|
||||||
|
addNormalSearch,
|
||||||
|
refreshHistory,
|
||||||
|
executeSearchFromHistory,
|
||||||
|
} = useSearchHistory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🎯 [DetailPanel 복귀 감지] usePanelHistory Hook 적용
|
* 🎯 [DetailPanel 복귀 감지] usePanelHistory Hook 적용
|
||||||
@@ -422,7 +476,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
// ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시
|
// ✨ [Phase 4] Enter/OK 키 처리 - SearchInputOverlay 표시
|
||||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||||
dlog('[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈');
|
dlog(
|
||||||
|
'[DEBUG] [SearchPanel] TInputSimple에서 Enter/OK 키 감지 → SearchInputOverlay 오픈'
|
||||||
|
);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료
|
// ✨ [Phase 6] SearchInputOverlay 오픈 후 자동으로 입력 준비 완료
|
||||||
@@ -450,7 +506,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
];
|
];
|
||||||
if (arrowKeys.includes(e.key)) {
|
if (arrowKeys.includes(e.key)) {
|
||||||
// 입력 필드가 비어있고 왼쪽 화살표인 경우에만 방지
|
// 입력 필드가 비어있고 왼쪽 화살표인 경우에만 방지
|
||||||
if (position === 0 && (e.key === 'Left' || e.key === 'ArrowLeft') && !searchQuery) {
|
if (
|
||||||
|
position === 0 &&
|
||||||
|
(e.key === 'Left' || e.key === 'ArrowLeft') &&
|
||||||
|
!searchQuery
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -461,7 +521,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// DOM 쿼리 최적화: 캐싱된 input element 사용
|
// DOM 쿼리 최적화: 캐싱된 input element 사용
|
||||||
const input =
|
const input =
|
||||||
inputElementRef.current ||
|
inputElementRef.current ||
|
||||||
document.querySelector(`[data-spotlight-id="input-field-box"] > input`);
|
document.querySelector(
|
||||||
|
`[data-spotlight-id="input-field-box"] > input`
|
||||||
|
);
|
||||||
if (input) {
|
if (input) {
|
||||||
inputElementRef.current = input; // 캐싱
|
inputElementRef.current = input; // 캐싱
|
||||||
if (position === input.value.length) {
|
if (position === input.value.length) {
|
||||||
@@ -658,7 +720,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
'[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)'
|
'[DEBUG]-VOICE_RESULT: Clearing ShopperHouse data (searchId will be preserved for 2nd search)'
|
||||||
);
|
);
|
||||||
dlog('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT');
|
dlog('[VoiceInput]-SearchPanel-onCancel-VOICE_RESULT');
|
||||||
dlog('[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출');
|
dlog(
|
||||||
|
'[VoiceInput] 🧹 VOICE_RESULT 모드에서 ESC 누름 - clearShopperHouseData 호출'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨
|
// 🎯 [포커스 로직 통합] 포커스는 상태 변경에 의해 자동으로 처리됨
|
||||||
setIsShopperHousePending(false);
|
setIsShopperHousePending(false);
|
||||||
@@ -816,7 +880,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
if (isReturningFromDetailPanel) {
|
if (isReturningFromDetailPanel) {
|
||||||
const currentSpot = currentPanel?.panelInfo?.currentSpot;
|
const currentSpot = currentPanel?.panelInfo?.currentSpot;
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
dlog(
|
||||||
|
'[Focus] usePanelHistory로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'
|
||||||
|
);
|
||||||
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', {
|
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (usePanelHistory)', {
|
||||||
currentSpot,
|
currentSpot,
|
||||||
mode: currentMode,
|
mode: currentMode,
|
||||||
@@ -860,7 +926,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
) {
|
) {
|
||||||
const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop;
|
const usedHistoryOnTop = currentIsOnTop && isOnTopChange?.becameOnTop;
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동');
|
dlog(
|
||||||
|
'[Focus] 개선된 방식으로 DetailPanel 복귀 감지 - 이전 상품으로 포커스 이동'
|
||||||
|
);
|
||||||
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', {
|
dlog('[FOCUS] 🎯 Scenario: DETAIL_PANEL_RETURN (improved fallback)', {
|
||||||
currentSpot: panelInfo.currentSpot,
|
currentSpot: panelInfo.currentSpot,
|
||||||
mode: currentMode,
|
mode: currentMode,
|
||||||
@@ -928,7 +996,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
prevMode: currentModeRef.current,
|
prevMode: currentModeRef.current,
|
||||||
nextMode: currentMode,
|
nextMode: currentMode,
|
||||||
isOnTopChanged: isOnTop !== isOnTopRef.current,
|
isOnTopChanged: isOnTop !== isOnTopRef.current,
|
||||||
modeChanged: currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT,
|
modeChanged:
|
||||||
|
currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT,
|
||||||
dataChanged: shopperHouseDataRef.current !== shopperHouseData,
|
dataChanged: shopperHouseDataRef.current !== shopperHouseData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -946,7 +1015,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지
|
// 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지
|
||||||
if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
|
if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리');
|
dlog(
|
||||||
|
'[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return 'NEW_SEARCH_LOADED';
|
return 'NEW_SEARCH_LOADED';
|
||||||
}
|
}
|
||||||
@@ -1009,7 +1080,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
let currentSpot = null;
|
let currentSpot = null;
|
||||||
|
|
||||||
// 1. usePanelHistory의 currentSpot 우선 사용
|
// 1. usePanelHistory의 currentSpot 우선 사용
|
||||||
if (isReturningFromDetailPanel && currentPanel?.panelInfo?.currentSpot) {
|
if (
|
||||||
|
isReturningFromDetailPanel &&
|
||||||
|
currentPanel?.panelInfo?.currentSpot
|
||||||
|
) {
|
||||||
currentSpot = currentPanel.panelInfo.currentSpot;
|
currentSpot = currentPanel.panelInfo.currentSpot;
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot);
|
dlog('[FOCUS] 🎯 usePanelHistory currentSpot 사용:', currentSpot);
|
||||||
@@ -1019,13 +1093,19 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
else if (panelInfo?.currentSpot) {
|
else if (panelInfo?.currentSpot) {
|
||||||
currentSpot = panelInfo.currentSpot;
|
currentSpot = panelInfo.currentSpot;
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:', currentSpot);
|
dlog(
|
||||||
|
'[FOCUS] 🔄 fallback으로 panelInfo.currentSpot 사용:',
|
||||||
|
currentSpot
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSpot && currentSpot.startsWith('searchItemContents')) {
|
if (currentSpot && currentSpot.startsWith('searchItemContents')) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:', currentSpot);
|
dlog(
|
||||||
|
'[FOCUS] 🎯 DETAIL_PANEL_RETURN: 이전 상품으로 포커스 복원:',
|
||||||
|
currentSpot
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return currentSpot;
|
return currentSpot;
|
||||||
} else {
|
} else {
|
||||||
@@ -1066,7 +1146,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고
|
// SearchInputOverlay에서 검색을 실행하면 isSearchOverlayVisible이 false로 설정되고
|
||||||
// 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생
|
// 동시에 검색 결과에 따라 모드가 변경되므로, 이 케이스는 검색어 선택 후 닫을 때만 발생
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스');
|
dlog(
|
||||||
|
'[FOCUS] 🎯 Scenario: SEARCH_OVERLAY_CLOSED - TInputSimple으로 포커스'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return SPOTLIGHT_IDS.SEARCH_INPUT_BOX;
|
return SPOTLIGHT_IDS.SEARCH_INPUT_BOX;
|
||||||
|
|
||||||
@@ -1290,17 +1372,27 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<div className={css.productImageWrapper}>
|
<div className={css.productImageWrapper}>
|
||||||
<SafeImage src={bgImgPath} alt={curationNm} className={css.productImage} />
|
<SafeImage
|
||||||
|
src={bgImgPath}
|
||||||
|
alt={curationNm}
|
||||||
|
className={css.productImage}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={css.productInfo}>
|
<div className={css.productInfo}>
|
||||||
{showBrandLogo && (
|
{showBrandLogo && (
|
||||||
<div className={css.productBrandWrapper}>
|
<div className={css.productBrandWrapper}>
|
||||||
<SafeImage src={patncLogoPath} alt={patncNm} className={css.brandLogo} />
|
<SafeImage
|
||||||
|
src={patncLogoPath}
|
||||||
|
alt={patncNm}
|
||||||
|
className={css.brandLogo}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={css.productDetails}>
|
<div className={css.productDetails}>
|
||||||
{showBrandName && <div className={css.brandName}>{patncNm}</div>}
|
{showBrandName && <div className={css.brandName}>{patncNm}</div>}
|
||||||
{showProductTitle && <div className={css.productTitle}>{curationNm}</div>}
|
{showProductTitle && (
|
||||||
|
<div className={css.productTitle}>{curationNm}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SpottableProduct>
|
</SpottableProduct>
|
||||||
@@ -1323,7 +1415,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
const renderTsvItem = useCallback(
|
const renderTsvItem = useCallback(
|
||||||
({ index, ...rest }) => {
|
({ index, ...rest }) => {
|
||||||
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } = tsvInfo[index];
|
const { offerInfo, prdtId, imgUrl, patnrId, prdtNm, priceInfo } =
|
||||||
|
tsvInfo[index];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TItemCardNew
|
<TItemCardNew
|
||||||
@@ -1336,7 +1429,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
priceInfo={priceInfo}
|
priceInfo={priceInfo}
|
||||||
productId={prdtId}
|
productId={prdtId}
|
||||||
productName={prdtNm}
|
productName={prdtNm}
|
||||||
spotlightId={'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)}
|
spotlightId={
|
||||||
|
'searchMain-tsvInfo-spotlightId-' + removeDotAndColon(prdtId)
|
||||||
|
}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1603,10 +1698,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드
|
// 우선순위 2: 음성 검색 결과가 있으면 VOICE_RESULT 모드
|
||||||
else if (shopperHouseData || isShopperHousePending) {
|
else if (shopperHouseData || isShopperHousePending) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT', {
|
dlog(
|
||||||
|
'[DEBUG]-MODE: shopperHouseData EXISTS or pending → VOICE_RESULT',
|
||||||
|
{
|
||||||
hasData: !!shopperHouseData,
|
hasData: !!shopperHouseData,
|
||||||
isPending: isShopperHousePending,
|
isPending: isShopperHousePending,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextMode = SEARCH_PANEL_MODES.VOICE_RESULT;
|
nextMode = SEARCH_PANEL_MODES.VOICE_RESULT;
|
||||||
}
|
}
|
||||||
@@ -1640,7 +1738,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 모드가 변경되었을 때만 업데이트
|
// 모드가 변경되었을 때만 업데이트
|
||||||
if (nextMode !== currentMode) {
|
if (nextMode !== currentMode) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog(`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`, {
|
dlog(
|
||||||
|
`[DEBUG]-VOICE_RESULT 🔀 Mode changed: ${currentMode} → ${nextMode}`,
|
||||||
|
{
|
||||||
isVoiceOverlayVisible,
|
isVoiceOverlayVisible,
|
||||||
shopperHouseData: !!shopperHouseData,
|
shopperHouseData: !!shopperHouseData,
|
||||||
isShopperHousePending,
|
isShopperHousePending,
|
||||||
@@ -1653,7 +1753,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
),
|
),
|
||||||
isSearchOverlayVisible,
|
isSearchOverlayVisible,
|
||||||
inputFocus,
|
inputFocus,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setCurrentMode(nextMode);
|
setCurrentMode(nextMode);
|
||||||
} else {
|
} else {
|
||||||
@@ -1779,10 +1880,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지
|
// NEW_SEARCH_LOADED: 음성 검색 결과 로드 시 VoiceInputOverlay와 충돌 방지
|
||||||
// 다른 시나리오에서는 기존과 같은 지연 시간 (100ms)
|
// 다른 시나리오에서는 기존과 같은 지연 시간 (100ms)
|
||||||
const focusDelay =
|
const focusDelay =
|
||||||
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED' ? 50 : 100;
|
scenario === 'DETAIL_PANEL_RETURN' || scenario === 'NEW_SEARCH_LOADED'
|
||||||
|
? 50
|
||||||
|
: 100;
|
||||||
|
|
||||||
unifiedFocusTimerRef.current = setTimeout(() => {
|
unifiedFocusTimerRef.current = setTimeout(() => {
|
||||||
const targetElement = document.querySelector(`[data-spotlight-id="${targetId}"]`);
|
const targetElement = document.querySelector(
|
||||||
|
`[data-spotlight-id="${targetId}"]`
|
||||||
|
);
|
||||||
|
|
||||||
if (targetElement || targetId === SPOTLIGHT_IDS.SEARCH_INPUT_BOX) {
|
if (targetElement || targetId === SPOTLIGHT_IDS.SEARCH_INPUT_BOX) {
|
||||||
Spotlight.focus(targetId);
|
Spotlight.focus(targetId);
|
||||||
@@ -1805,9 +1910,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🎯 DETAIL_PANEL_RETURN에서 요소를 찾지 못하면 fallback으로 첫 번째 상품 시도
|
// 🎯 DETAIL_PANEL_RETURN에서 요소를 찾지 못하면 fallback으로 첫 번째 상품 시도
|
||||||
if (scenario === 'DETAIL_PANEL_RETURN' && targetId.startsWith('searchItemContents')) {
|
if (
|
||||||
|
scenario === 'DETAIL_PANEL_RETURN' &&
|
||||||
|
targetId.startsWith('searchItemContents')
|
||||||
|
) {
|
||||||
const fallbackTarget = 'searchItemContents0';
|
const fallbackTarget = 'searchItemContents0';
|
||||||
const fallbackElement = document.querySelector(`[data-spotlight-id="${fallbackTarget}"]`);
|
const fallbackElement = document.querySelector(
|
||||||
|
`[data-spotlight-id="${fallbackTarget}"]`
|
||||||
|
);
|
||||||
if (fallbackElement) {
|
if (fallbackElement) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog(
|
dlog(
|
||||||
@@ -1825,10 +1935,15 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
// 🎯 [NEW_SEARCH_LOADED] 1초 후 다시 첫 번째 아이템으로 포커스 이동
|
// 🎯 [NEW_SEARCH_LOADED] 1초 후 다시 첫 번째 아이템으로 포커스 이동
|
||||||
// TInputSimple과 Mic Icon의 포커스 충돌 해결을 위해
|
// TInputSimple과 Mic Icon의 포커스 충돌 해결을 위해
|
||||||
if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') {
|
if (
|
||||||
|
scenario === 'NEW_SEARCH_LOADED' &&
|
||||||
|
targetId === 'searchItemContents0'
|
||||||
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동');
|
dlog(
|
||||||
|
'[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Spotlight.focus('searchItemContents0');
|
Spotlight.focus('searchItemContents0');
|
||||||
}, 500); // 0.5초 후
|
}, 500); // 0.5초 후
|
||||||
@@ -1866,7 +1981,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
isVoiceOverlayVisibleRef.current = isVoiceOverlayVisible;
|
isVoiceOverlayVisibleRef.current = isVoiceOverlayVisible;
|
||||||
isSearchOverlayVisibleRef.current = isSearchOverlayVisible;
|
isSearchOverlayVisibleRef.current = isSearchOverlayVisible;
|
||||||
currentModeRef.current = currentMode;
|
currentModeRef.current = currentMode;
|
||||||
}, [shopperHouseData, searchDatas, isVoiceOverlayVisible, isSearchOverlayVisible, currentMode]);
|
}, [
|
||||||
|
shopperHouseData,
|
||||||
|
searchDatas,
|
||||||
|
isVoiceOverlayVisible,
|
||||||
|
isSearchOverlayVisible,
|
||||||
|
currentMode,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🎯 SearchInputOverlay 닫힘 후 포커스 관리
|
* 🎯 SearchInputOverlay 닫힘 후 포커스 관리
|
||||||
@@ -1889,10 +2010,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행', {
|
dlog(
|
||||||
|
'[FOCUS] 🎯 SearchInputOverlay 닫힘 후 포커스 관리 useEffect 실행',
|
||||||
|
{
|
||||||
shouldFocusSearchInput,
|
shouldFocusSearchInput,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 500ms 후 TInputSimple에 포커스 이동
|
// 500ms 후 TInputSimple에 포커스 이동
|
||||||
@@ -1903,10 +2027,13 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동', {
|
dlog(
|
||||||
|
'[FOCUS] ⏰ 500ms 타이머 콜백 실행 - TInputSimple으로 포커스 이동',
|
||||||
|
{
|
||||||
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
targetId: SPOTLIGHT_IDS.SEARCH_INPUT_BOX,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dlog('[DEBUG] Spotlight.focus() 호출 직전', {
|
dlog('[DEBUG] Spotlight.focus() 호출 직전', {
|
||||||
@@ -1950,9 +2077,12 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
});
|
});
|
||||||
if (focusTimer) {
|
if (focusTimer) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
dlog('[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리', {
|
dlog(
|
||||||
|
'[FOCUS] 🧹 SearchInputOverlay 포커스 관리 useEffect cleanup - 타이머 정리',
|
||||||
|
{
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
clearTimeout(focusTimer);
|
clearTimeout(focusTimer);
|
||||||
}
|
}
|
||||||
@@ -2053,12 +2183,18 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
}, [currentMode, isOnTop, refreshHistory]);
|
}, [currentMode, isOnTop, refreshHistory]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TPanel className={css.container} handleCancel={onCancel} spotlightId={spotlightId}>
|
<TPanel
|
||||||
|
className={css.container}
|
||||||
|
handleCancel={onCancel}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
>
|
||||||
{/* ✨ [Phase 2] spotlightDisabled를 currentMode로 제어 */}
|
{/* ✨ [Phase 2] spotlightDisabled를 currentMode로 제어 */}
|
||||||
<TBody
|
<TBody
|
||||||
className={css.tBody}
|
className={css.tBody}
|
||||||
scrollable
|
scrollable
|
||||||
spotlightDisabled={!isOnTop || currentMode === SEARCH_PANEL_MODES.SEARCH_INPUT}
|
spotlightDisabled={
|
||||||
|
!isOnTop || currentMode === SEARCH_PANEL_MODES.SEARCH_INPUT
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ContainerBasic>
|
<ContainerBasic>
|
||||||
{isOnTop && (
|
{isOnTop && (
|
||||||
@@ -2078,7 +2214,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
css.inputContainer,
|
css.inputContainer,
|
||||||
inputFocus === true && css.inputFocus,
|
inputFocus === true && css.inputFocus,
|
||||||
searchDatas && css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
|
searchDatas &&
|
||||||
|
css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
|
||||||
(currentMode === SEARCH_PANEL_MODES.VOICE_INPUT ||
|
(currentMode === SEARCH_PANEL_MODES.VOICE_INPUT ||
|
||||||
currentMode === SEARCH_PANEL_MODES.INPUT_FOCUSED) &&
|
currentMode === SEARCH_PANEL_MODES.INPUT_FOCUSED) &&
|
||||||
css.hidden
|
css.hidden
|
||||||
@@ -2129,7 +2266,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
placeholder="Search products or brands"
|
placeholder="Search products or brands"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SpottableMicButton
|
{/* <SpottableMicButton
|
||||||
className={css.microphoneButton}
|
className={css.microphoneButton}
|
||||||
onClick={onClickMic}
|
onClick={onClickMic}
|
||||||
onFocus={onFocusMic}
|
onFocus={onFocusMic}
|
||||||
@@ -2148,7 +2285,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
<div className={css.microphoneCircle}>
|
<div className={css.microphoneCircle}>
|
||||||
<SafeImage src={micIcon} alt="Microphone" className={css.microphoneIcon} />
|
<SafeImage src={micIcon} alt="Microphone" className={css.microphoneIcon} />
|
||||||
</div>
|
</div>
|
||||||
</SpottableMicButton>
|
</SpottableMicButton> */}
|
||||||
</div>
|
</div>
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
|
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ const TrendingNowPanel = ({ panelInfo, spotlightId, isOnTop, ...rest }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={css.trendingNowWrap}>
|
<div className={css.trendingNowWrap}>
|
||||||
{selectedIndex >= 1 && showButton && (
|
{isOnTop && selectedIndex >= 1 && showButton && (
|
||||||
<TButton
|
<TButton
|
||||||
className={classNames(css.button, css.prevBtn)}
|
className={classNames(css.button, css.prevBtn)}
|
||||||
onClick={handleIndicatorClick("prev")}
|
onClick={handleIndicatorClick("prev")}
|
||||||
@@ -411,7 +411,7 @@ const TrendingNowPanel = ({ panelInfo, spotlightId, isOnTop, ...rest }) => {
|
|||||||
</TVerticalPagenator>
|
</TVerticalPagenator>
|
||||||
</TBody>
|
</TBody>
|
||||||
</TPanel>
|
</TPanel>
|
||||||
{topInfos &&
|
{isOnTop && topInfos &&
|
||||||
topInfos?.length > 0 &&
|
topInfos?.length > 0 &&
|
||||||
selectedIndex !== topInfos?.length - 1 &&
|
selectedIndex !== topInfos?.length - 1 &&
|
||||||
showButton && (
|
showButton && (
|
||||||
|
|||||||
Reference in New Issue
Block a user