[251115] fix: DetailPanel FullScree issue
🕐 커밋 시간: 2025. 11. 15. 20:10:38 📊 변경 통계: • 총 파일: 7개 • 추가: +134줄 • 삭제: -63줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/homeActions.js ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx ~ com.twin.app.shoptime/src/reducers/homeReducer.js ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/homeActions.js (javascript): ✅ Added: setDefaultFocus() 📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript): 🔄 Modified: normalizeModalStyle() 🔧 주요 변경 내용: • 타입 시스템 안정성 강화 • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -112,6 +112,7 @@ export const types = {
|
|||||||
CHECK_ENTER_THROUGH_GNB: 'CHECK_ENTER_THROUGH_GNB',
|
CHECK_ENTER_THROUGH_GNB: 'CHECK_ENTER_THROUGH_GNB',
|
||||||
SET_DEFAULT_FOCUS: 'SET_DEFAULT_FOCUS',
|
SET_DEFAULT_FOCUS: 'SET_DEFAULT_FOCUS',
|
||||||
SET_BANNER_INDEX: 'SET_BANNER_INDEX',
|
SET_BANNER_INDEX: 'SET_BANNER_INDEX',
|
||||||
|
SET_VIDEO_TRANSITION_LOCK: 'SET_VIDEO_TRANSITION_LOCK',
|
||||||
RESET_HOME_INFO: 'RESET_HOME_INFO',
|
RESET_HOME_INFO: 'RESET_HOME_INFO',
|
||||||
UPDATE_HOME_INFO: 'UPDATE_HOME_INFO',
|
UPDATE_HOME_INFO: 'UPDATE_HOME_INFO',
|
||||||
|
|
||||||
|
|||||||
@@ -483,6 +483,11 @@ export const setDefaultFocus = (focus) => ({
|
|||||||
payload: focus,
|
payload: focus,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setVideoTransitionLock = (isLocked) => ({
|
||||||
|
type: types.SET_VIDEO_TRANSITION_LOCK,
|
||||||
|
payload: Boolean(isLocked),
|
||||||
|
});
|
||||||
|
|
||||||
export const checkEnterThroughGNB = (boolean) => ({
|
export const checkEnterThroughGNB = (boolean) => ({
|
||||||
type: types.CHECK_ENTER_THROUGH_GNB,
|
type: types.CHECK_ENTER_THROUGH_GNB,
|
||||||
payload: boolean,
|
payload: boolean,
|
||||||
|
|||||||
@@ -1,170 +1,168 @@
|
|||||||
import React, { useCallback, useMemo, useRef, useEffect } from "react";
|
import React, { useCallback, useMemo, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
import ReactPlayer from "react-player";
|
import ReactPlayer from 'react-player';
|
||||||
|
|
||||||
import handle from "@enact/core/handle";
|
import handle from '@enact/core/handle';
|
||||||
|
|
||||||
var handledMediaEventsMap = [
|
var handledMediaEventsMap = [
|
||||||
"onReady",
|
'onReady',
|
||||||
"onStart",
|
'onStart',
|
||||||
"onPlay",
|
'onPlay',
|
||||||
"onProgress",
|
'onProgress',
|
||||||
"onDuration",
|
'onDuration',
|
||||||
"onPause",
|
'onPause',
|
||||||
"onBuffer",
|
'onBuffer',
|
||||||
"onBufferEnd",
|
'onBufferEnd',
|
||||||
"onSeek",
|
'onSeek',
|
||||||
"onEnded",
|
'onEnded',
|
||||||
"onError",
|
'onError',
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TReactPlayer({
|
export default function TReactPlayer({
|
||||||
mediaEventsMap = handledMediaEventsMap,
|
mediaEventsMap = handledMediaEventsMap,
|
||||||
videoRef,
|
videoRef,
|
||||||
url,
|
url,
|
||||||
dispatch,
|
dispatch,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const playerRef = useRef(null);
|
const playerRef = useRef(null);
|
||||||
|
|
||||||
const handleEvent = useCallback(
|
const handleEvent = useCallback(
|
||||||
(type) => (ev) => {
|
(type) => (ev) => {
|
||||||
if (type === "onReady") {
|
if (type === 'onReady') {
|
||||||
if (videoRef) {
|
if (videoRef) {
|
||||||
const videoNode = playerRef.current.getInternalPlayer();
|
const videoNode = playerRef.current.getInternalPlayer();
|
||||||
videoRef(videoNode);
|
videoRef(videoNode);
|
||||||
const iframeEl =
|
const iframeEl =
|
||||||
typeof playerRef.current?.getInternalPlayer === "function"
|
typeof playerRef.current?.getInternalPlayer === 'function'
|
||||||
? playerRef.current.getInternalPlayer("iframe")
|
? playerRef.current.getInternalPlayer('iframe')
|
||||||
: null;
|
: null;
|
||||||
if (iframeEl) {
|
if (iframeEl) {
|
||||||
iframeEl.setAttribute("tabIndex", "-1");
|
iframeEl.setAttribute('tabIndex', '-1');
|
||||||
iframeEl.setAttribute("aria-hidden", "true");
|
iframeEl.setAttribute('aria-hidden', 'true');
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
videoNode.tagName &&
|
videoNode.tagName &&
|
||||||
!Object.prototype.hasOwnProperty.call(videoNode, "proportionPlayed")
|
!Object.prototype.hasOwnProperty.call(videoNode, 'proportionPlayed')
|
||||||
) {
|
) {
|
||||||
Object.defineProperties(videoNode, {
|
Object.defineProperties(videoNode, {
|
||||||
error: {
|
error: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return videoNode.networkState === videoNode.NETWORK_NO_SOURCE;
|
return videoNode.networkState === videoNode.NETWORK_NO_SOURCE;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return videoNode.readyState < videoNode.HAVE_ENOUGH_DATA;
|
return videoNode.readyState < videoNode.HAVE_ENOUGH_DATA;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
proportionLoaded: {
|
proportionLoaded: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return (
|
return (
|
||||||
videoNode.buffered.length &&
|
videoNode.buffered.length &&
|
||||||
videoNode.buffered.end(videoNode.buffered.length - 1) /
|
videoNode.buffered.end(videoNode.buffered.length - 1) / videoNode.duration
|
||||||
videoNode.duration
|
);
|
||||||
);
|
},
|
||||||
},
|
},
|
||||||
},
|
proportionPlayed: {
|
||||||
proportionPlayed: {
|
get: function () {
|
||||||
get: function () {
|
return videoNode.currentTime / videoNode.duration;
|
||||||
return videoNode.currentTime / videoNode.duration;
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
} else if (!Object.prototype.hasOwnProperty.call(videoNode, 'proportionPlayed')) {
|
||||||
} else if (
|
videoNode.play = videoNode.playVideo;
|
||||||
!Object.prototype.hasOwnProperty.call(videoNode, "proportionPlayed")
|
videoNode.pause = videoNode.pauseVideo;
|
||||||
) {
|
videoNode.seek = videoNode.seekTo;
|
||||||
videoNode.play = videoNode.playVideo;
|
Object.defineProperties(videoNode, {
|
||||||
videoNode.pause = videoNode.pauseVideo;
|
currentTime: {
|
||||||
videoNode.seek = videoNode.seekTo;
|
get: function () {
|
||||||
Object.defineProperties(videoNode, {
|
return videoNode.getCurrentTime();
|
||||||
currentTime: {
|
},
|
||||||
get: function () {
|
set: function (time) {
|
||||||
return videoNode.getCurrentTime();
|
videoNode.seekTo(time);
|
||||||
},
|
},
|
||||||
set: function (time) {
|
},
|
||||||
videoNode.seekTo(time);
|
duration: {
|
||||||
},
|
get: function () {
|
||||||
},
|
return videoNode.getDuration();
|
||||||
duration: {
|
},
|
||||||
get: function () {
|
},
|
||||||
return videoNode.getDuration();
|
paused: {
|
||||||
},
|
get: function () {
|
||||||
},
|
return videoNode.getPlayerState() !== 1;
|
||||||
paused: {
|
},
|
||||||
get: function () {
|
},
|
||||||
return videoNode.getPlayerState() !== 1;
|
error: {
|
||||||
},
|
get: function () {
|
||||||
},
|
return !!videoNode?.playerInfo?.videoData?.errorCode;
|
||||||
error: {
|
},
|
||||||
get: function () {
|
},
|
||||||
return !!videoNode?.playerInfo?.videoData?.errorCode;
|
loading: {
|
||||||
},
|
get: function () {
|
||||||
},
|
return !videoNode?.playerInfo?.videoData?.isPlayable; //todo
|
||||||
loading: {
|
},
|
||||||
get: function () {
|
},
|
||||||
return !videoNode?.playerInfo?.videoData?.isPlayable; //todo
|
proportionLoaded: {
|
||||||
},
|
get: function () {
|
||||||
},
|
return videoNode?.getVideoBytesLoaded() ?? 0;
|
||||||
proportionLoaded: {
|
},
|
||||||
get: function () {
|
},
|
||||||
return videoNode?.getVideoBytesLoaded() ?? 0;
|
proportionPlayed: {
|
||||||
},
|
get: function () {
|
||||||
},
|
const duration = videoNode.getDuration();
|
||||||
proportionPlayed: {
|
return duration ? videoNode.getCurrentTime() / duration : 0;
|
||||||
get: function () {
|
},
|
||||||
const duration = videoNode.getDuration();
|
},
|
||||||
return duration ? videoNode.getCurrentTime() / duration : 0;
|
playbackRate: {
|
||||||
},
|
get: function () {
|
||||||
},
|
return videoNode?.playerInfo?.playbackRate;
|
||||||
playbackRate: {
|
},
|
||||||
get: function () {
|
set: function (playbackRate) {
|
||||||
return videoNode?.playerInfo?.playbackRate;
|
if (videoNode && videoNode.setPlaybackRate) {
|
||||||
},
|
videoNode.setPlaybackRate(playbackRate);
|
||||||
set: function (playbackRate) {
|
}
|
||||||
if (videoNode && videoNode.setPlaybackRate) {
|
},
|
||||||
videoNode.setPlaybackRate(playbackRate);
|
},
|
||||||
}
|
});
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
handle.forward('onLoadStart', { type, ev }, rest);
|
||||||
}
|
}
|
||||||
}
|
handle.forward('onUpdate', { type, ev }, rest);
|
||||||
handle.forward("onLoadStart", { type, ev }, rest);
|
},
|
||||||
}
|
[videoRef]
|
||||||
handle.forward("onUpdate", { type, ev }, rest);
|
);
|
||||||
},
|
|
||||||
[videoRef]
|
const handledMediaEvents = useMemo(() => {
|
||||||
);
|
const events = {};
|
||||||
|
for (let i = 0; i < mediaEventsMap.length; i++) {
|
||||||
const handledMediaEvents = useMemo(() => {
|
const eventName = mediaEventsMap[i];
|
||||||
const events = {};
|
events[eventName] = handleEvent(eventName);
|
||||||
for (let i = 0; i < mediaEventsMap.length; i++) {
|
}
|
||||||
const eventName = mediaEventsMap[i];
|
return events;
|
||||||
events[eventName] = handleEvent(eventName);
|
}, [handleEvent, mediaEventsMap]);
|
||||||
}
|
|
||||||
return events;
|
useEffect(() => {
|
||||||
}, [handleEvent, mediaEventsMap]);
|
return () => {
|
||||||
|
const videoNode = playerRef.current?.getInternalPlayer();
|
||||||
useEffect(() => {
|
if (videoNode && videoNode.pause) {
|
||||||
return () => {
|
videoNode.pause();
|
||||||
const videoNode = playerRef.current?.getInternalPlayer();
|
videoNode.src = '';
|
||||||
if (videoNode && videoNode.pause) {
|
videoNode.srcObject = null;
|
||||||
videoNode.pause();
|
}
|
||||||
videoNode.src = "";
|
};
|
||||||
videoNode.srcObject = null;
|
}, []);
|
||||||
}
|
|
||||||
};
|
return (
|
||||||
}, []);
|
<ReactPlayer
|
||||||
|
ref={playerRef}
|
||||||
return (
|
url={url}
|
||||||
<ReactPlayer
|
progressInterval={1000}
|
||||||
ref={playerRef}
|
config={rest.config}
|
||||||
url={url}
|
{...handledMediaEvents}
|
||||||
progressInterval={1000}
|
{...rest}
|
||||||
{...handledMediaEvents}
|
playsinline // Add playsinline attribute here
|
||||||
{...rest}
|
/>
|
||||||
playsinline // Add playsinline attribute here
|
);
|
||||||
/>
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const initialState = {
|
|||||||
termsIdMap: {}, // added new property to initialState
|
termsIdMap: {}, // added new property to initialState
|
||||||
optionalTermsAvailable: false, // 선택약관 존재 여부
|
optionalTermsAvailable: false, // 선택약관 존재 여부
|
||||||
persistentVideoInfo: null, // 영구재생 비디오 정보
|
persistentVideoInfo: null, // 영구재생 비디오 정보
|
||||||
|
videoTransitionLocked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const homeReducer = (state = initialState, action) => {
|
export const homeReducer = (state = initialState, action) => {
|
||||||
@@ -192,6 +193,12 @@ export const homeReducer = (state = initialState, action) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case types.SET_VIDEO_TRANSITION_LOCK:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
videoTransitionLocked: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
case types.CHECK_ENTER_THROUGH_GNB: {
|
case types.CHECK_ENTER_THROUGH_GNB: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.p
|
|||||||
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
|
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
|
||||||
import liveShow from '../../../../assets/images/tag-liveshow.png';
|
import liveShow from '../../../../assets/images/tag-liveshow.png';
|
||||||
import { changeAppStatus } from '../../../actions/commonActions';
|
import { changeAppStatus } from '../../../actions/commonActions';
|
||||||
import { updateHomeInfo } from '../../../actions/homeActions';
|
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
|
||||||
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
|
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
|
||||||
import { pushPanel } from '../../../actions/panelActions';
|
import { pushPanel } from '../../../actions/panelActions';
|
||||||
import {
|
import {
|
||||||
@@ -83,8 +83,13 @@ export default function RandomUnit({
|
|||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
const [videoError, setVideoError] = useState(false);
|
const [videoError, setVideoError] = useState(false);
|
||||||
const [liveIndicies, setLiveIndicies] = useState([]);
|
const [liveIndicies, setLiveIndicies] = useState([]);
|
||||||
|
const defaultFocus = useSelector((state) => state.home.defaultFocus);
|
||||||
|
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
|
||||||
|
|
||||||
const timerRef = useRef();
|
const timerRef = useRef();
|
||||||
|
const keepTimerOnBlurRef = useRef(false);
|
||||||
|
const hasAutoPlayStartedRef = useRef(false);
|
||||||
|
const isDefaultAutoPlayTarget = defaultFocus === spotlightId;
|
||||||
const bannerDataRef = useRef(bannerData);
|
const bannerDataRef = useRef(bannerData);
|
||||||
const randomDataRef = useRef(
|
const randomDataRef = useRef(
|
||||||
bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0
|
bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0
|
||||||
@@ -92,12 +97,36 @@ export default function RandomUnit({
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
// ✅ 현재 PlayerPanel의 modal 상태 추적
|
|
||||||
const playerPanelInfo = useSelector((state) => {
|
const playerPanelInfo = 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;
|
return playerPanel?.panelInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleStartVideo = useCallback(
|
||||||
|
(videoProps) => {
|
||||||
|
dispatch(setVideoTransitionLock(true));
|
||||||
|
dispatch(startVideoPlayerNew(videoProps));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const finishAndUnlock = useCallback(() => {
|
||||||
|
dispatch(finishVideoPreview());
|
||||||
|
dispatch(setVideoTransitionLock(false));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVideoTransitionLocked && isCurrentBannerVideoPlaying) {
|
||||||
|
dispatch(setVideoTransitionLock(false));
|
||||||
|
}
|
||||||
|
}, [dispatch, isCurrentBannerVideoPlaying, isVideoTransitionLocked]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVideoTransitionLocked && videoError) {
|
||||||
|
dispatch(setVideoTransitionLock(false));
|
||||||
|
}
|
||||||
|
}, [dispatch, isVideoTransitionLocked, videoError]);
|
||||||
|
|
||||||
const topContentsLogInfo = useMemo(() => {
|
const topContentsLogInfo = useMemo(() => {
|
||||||
if (randomDataRef.current) {
|
if (randomDataRef.current) {
|
||||||
const currentRandomData = randomDataRef.current;
|
const currentRandomData = randomDataRef.current;
|
||||||
@@ -403,8 +432,8 @@ export default function RandomUnit({
|
|||||||
let action = linkType === 'DSP00507' ? startVideoPlayer : pushPanel;
|
let action = linkType === 'DSP00507' ? startVideoPlayer : pushPanel;
|
||||||
|
|
||||||
const isNavigatingToDetail = linkInfo.name === panel_names.DETAIL_PANEL;
|
const isNavigatingToDetail = linkInfo.name === panel_names.DETAIL_PANEL;
|
||||||
if (isNavigatingToDetail && playerPanelInfo?.modal !== false && isCurrentBannerVideoPlaying) {
|
if (isNavigatingToDetail && playerPanelInfo?.modal !== false) {
|
||||||
dispatch(finishVideoPreview());
|
finishAndUnlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(action(linkInfo));
|
dispatch(action(linkInfo));
|
||||||
@@ -428,13 +457,13 @@ export default function RandomUnit({
|
|||||||
topContentsLogInfo,
|
topContentsLogInfo,
|
||||||
playerPanelInfo?.modal,
|
playerPanelInfo?.modal,
|
||||||
sendBannerLog,
|
sendBannerLog,
|
||||||
isCurrentBannerVideoPlaying,
|
finishAndUnlock,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 투데이즈딜 클릭
|
// 투데이즈딜 클릭
|
||||||
const todayDealClick = useCallback(() => {
|
const todayDealClick = useCallback(() => {
|
||||||
if (playerPanelInfo?.modal !== false && isCurrentBannerVideoPlaying) {
|
if (playerPanelInfo?.modal !== false) {
|
||||||
dispatch(finishVideoPreview());
|
finishAndUnlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -463,7 +492,7 @@ export default function RandomUnit({
|
|||||||
topContentsLogInfo,
|
topContentsLogInfo,
|
||||||
sendBannerLog,
|
sendBannerLog,
|
||||||
playerPanelInfo?.modal,
|
playerPanelInfo?.modal,
|
||||||
isCurrentBannerVideoPlaying,
|
finishAndUnlock,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 비디오 클릭
|
// 비디오 클릭
|
||||||
@@ -491,20 +520,18 @@ export default function RandomUnit({
|
|||||||
playerPanelInfo?.modal
|
playerPanelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(
|
handleStartVideo({
|
||||||
startVideoPlayerNew({
|
bannerId: spotlightId,
|
||||||
bannerId: spotlightId,
|
showUrl: randomData.showUrl,
|
||||||
showUrl: randomData.showUrl,
|
patnrId: randomData.patnrId,
|
||||||
patnrId: randomData.patnrId,
|
showId: randomData.showId,
|
||||||
showId: randomData.showId,
|
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
|
||||||
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
|
lgCatCd: randomData.lgCatCd,
|
||||||
lgCatCd: randomData.lgCatCd,
|
chanId: randomData.brdcChnlId,
|
||||||
chanId: randomData.brdcChnlId,
|
modal: false,
|
||||||
modal: false,
|
modalContainerId: spotlightId,
|
||||||
modalContainerId: spotlightId,
|
modalClassName: css.videoModal,
|
||||||
modalClassName: css.videoModal,
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
sendLogTopContents({
|
sendLogTopContents({
|
||||||
@@ -526,6 +553,7 @@ export default function RandomUnit({
|
|||||||
onBlur,
|
onBlur,
|
||||||
playerPanelInfo?.modal,
|
playerPanelInfo?.modal,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
handleStartVideo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 투데이즈 딜 가격 정보
|
// 투데이즈 딜 가격 정보
|
||||||
@@ -574,34 +602,55 @@ export default function RandomUnit({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFocused && !videoError) {
|
if (isFocused && !videoError) {
|
||||||
timerRef.current = setTimeout(
|
if (timerRef.current) {
|
||||||
() =>
|
clearTimeout(timerRef.current);
|
||||||
dispatch(
|
}
|
||||||
startVideoPlayerNew({
|
keepTimerOnBlurRef.current = isDefaultAutoPlayTarget && !hasAutoPlayStartedRef.current;
|
||||||
bannerId: spotlightId,
|
timerRef.current = setTimeout(() => {
|
||||||
showUrl: randomData.showUrl,
|
handleStartVideo({
|
||||||
patnrId: randomData.patnrId,
|
bannerId: spotlightId,
|
||||||
showId: randomData.showId,
|
showUrl: randomData.showUrl,
|
||||||
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
|
patnrId: randomData.patnrId,
|
||||||
lgCatCd: randomData.lgCatCd,
|
showId: randomData.showId,
|
||||||
chanId: randomData.brdcChnlId,
|
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
|
||||||
modal: true,
|
lgCatCd: randomData.lgCatCd,
|
||||||
modalContainerId: spotlightId,
|
chanId: randomData.brdcChnlId,
|
||||||
modalClassName: css.videoModal,
|
modal: true,
|
||||||
isVerticalModal: !isHorizontal,
|
modalContainerId: spotlightId,
|
||||||
})
|
modalClassName: css.videoModal,
|
||||||
),
|
isVerticalModal: !isHorizontal,
|
||||||
1000
|
});
|
||||||
);
|
if (isDefaultAutoPlayTarget) {
|
||||||
|
hasAutoPlayStartedRef.current = true;
|
||||||
|
}
|
||||||
|
keepTimerOnBlurRef.current = false;
|
||||||
|
timerRef.current = null;
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
setVideoError(false);
|
setVideoError(false);
|
||||||
// dispatch(finishVideoPreview());
|
if (timerRef.current && !keepTimerOnBlurRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
timerRef.current = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timerRef.current);
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
timerRef.current = null;
|
||||||
|
}
|
||||||
|
keepTimerOnBlurRef.current = false;
|
||||||
};
|
};
|
||||||
}, [isFocused]);
|
}, [
|
||||||
|
isFocused,
|
||||||
|
videoError,
|
||||||
|
isHorizontal,
|
||||||
|
randomData,
|
||||||
|
dispatch,
|
||||||
|
isDefaultAutoPlayTarget,
|
||||||
|
spotlightId,
|
||||||
|
handleStartVideo,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFocused && broadcast?.type === 'videoError') {
|
if (isFocused && broadcast?.type === 'videoError') {
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
import React, {
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} 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';
|
||||||
@@ -33,14 +24,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 { getHomeOnSaleInfo } from '../../actions/onSaleActions';
|
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
|
||||||
import { updatePanel } from '../../actions/panelActions';
|
import { updatePanel } from '../../actions/panelActions';
|
||||||
import {
|
import {
|
||||||
@@ -53,8 +38,7 @@ import TBody from '../../components/TBody/TBody';
|
|||||||
import TButton, { TYPES } from '../../components/TButton/TButton';
|
import TButton, { TYPES } from '../../components/TButton/TButton';
|
||||||
import TPanel from '../../components/TPanel/TPanel';
|
import TPanel from '../../components/TPanel/TPanel';
|
||||||
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';
|
||||||
@@ -193,6 +177,7 @@ const HomePanel = ({ isOnTop }) => {
|
|||||||
const focusedContainerIdRef = usePrevious(focusedContainerId);
|
const focusedContainerIdRef = usePrevious(focusedContainerId);
|
||||||
|
|
||||||
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
||||||
|
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
|
||||||
|
|
||||||
const onCancel = useCallback(() => {
|
const onCancel = useCallback(() => {
|
||||||
const currentSpot = Spotlight.getCurrent();
|
const currentSpot = Spotlight.getCurrent();
|
||||||
@@ -225,7 +210,7 @@ const HomePanel = ({ isOnTop }) => {
|
|||||||
visible: false,
|
visible: false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [dispatch]);
|
}, [dispatch, isVideoTransitionLocked]);
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
dispatch(setHidePopup());
|
dispatch(setHidePopup());
|
||||||
@@ -275,7 +260,7 @@ const HomePanel = ({ isOnTop }) => {
|
|||||||
break;
|
break;
|
||||||
case TEMPLATE_CODE_CONF.PICK_FOR_YOU:
|
case TEMPLATE_CODE_CONF.PICK_FOR_YOU:
|
||||||
nowMenu = LOG_MENU.HOME_PICKED_FOR_YOU;
|
nowMenu = LOG_MENU.HOME_PICKED_FOR_YOU;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
nowMenu = LOG_MENU.HOME_TOP;
|
nowMenu = LOG_MENU.HOME_TOP;
|
||||||
break;
|
break;
|
||||||
@@ -482,69 +467,74 @@ const HomePanel = ({ isOnTop }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ✅ useCallback: 의존성은 dispatch만
|
// ✅ useCallback: 의존성은 dispatch만
|
||||||
const _onScroll = useCallback((e) => {
|
const _onScroll = useCallback(
|
||||||
const currentScrollTop = e.scrollTop;
|
(e) => {
|
||||||
const prevScrollTop = prevScrollTopRef.current;
|
const currentScrollTop = e.scrollTop;
|
||||||
|
const prevScrollTop = prevScrollTopRef.current;
|
||||||
|
|
||||||
// ✅ 최상단 도달: 1px 비디오 복구 시도 (shouldShrinkRef.current로 읽음)
|
// ✅ 최상단 도달: 1px 비디오 복구 시도 (shouldShrinkRef.current로 읽음)
|
||||||
if (currentScrollTop <= 1) {
|
if (currentScrollTop <= 1) {
|
||||||
if (shouldShrinkRef.current && !expandIntervalRef.current) {
|
if (shouldShrinkRef.current && !expandIntervalRef.current) {
|
||||||
// console.log('[HomePanel] At top (scrollTop <= 1) - starting video expansion');
|
// console.log('[HomePanel] At top (scrollTop <= 1) - starting video expansion');
|
||||||
expandAttemptRef.current = 0;
|
expandAttemptRef.current = 0;
|
||||||
|
|
||||||
// Interval 시작: 200ms마다 복구 시도
|
// Interval 시작: 200ms마다 복구 시도
|
||||||
expandIntervalRef.current = setInterval(() => {
|
expandIntervalRef.current = setInterval(() => {
|
||||||
// 종료 조건: 최대 3회 시도
|
// 종료 조건: 최대 3회 시도
|
||||||
if (expandAttemptRef.current >= 3) {
|
if (expandAttemptRef.current >= 3) {
|
||||||
// console.log('[HomePanel] Max expansion attempts reached (3)');
|
// console.log('[HomePanel] Max expansion attempts reached (3)');
|
||||||
clearInterval(expandIntervalRef.current);
|
clearInterval(expandIntervalRef.current);
|
||||||
expandIntervalRef.current = null;
|
expandIntervalRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('[HomePanel] Expansion attempt', expandAttemptRef.current + 1);
|
// console.log('[HomePanel] Expansion attempt', expandAttemptRef.current + 1);
|
||||||
|
dispatch(expandVideoFrom1px());
|
||||||
|
expandAttemptRef.current++;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 최상단 벗어남: interval 정리
|
||||||
|
else if (currentScrollTop > 1 && expandIntervalRef.current) {
|
||||||
|
// console.log('[HomePanel] Left top - clearing expand interval');
|
||||||
|
clearInterval(expandIntervalRef.current);
|
||||||
|
expandIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 로직: 아래로 스크롤
|
||||||
|
if (currentScrollTop > prevScrollTop) {
|
||||||
|
// 아래로 스크롤: 비디오를 1px로 축소
|
||||||
|
// console.log('[HomePanel] Scrolling down - shrinking video');
|
||||||
|
if (!isVideoTransitionLocked) {
|
||||||
|
dispatch(shrinkVideoTo1px());
|
||||||
|
}
|
||||||
|
// 기존 타이머 취소
|
||||||
|
if (scrollExpandTimerRef.current) {
|
||||||
|
clearTimeout(scrollExpandTimerRef.current);
|
||||||
|
scrollExpandTimerRef.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 기존 로직: 위로 스크롤 (0이 아닌)
|
||||||
|
else if (currentScrollTop < prevScrollTop && currentScrollTop > 1) {
|
||||||
|
// 위로 스크롤 (최상단 아님): 1초 후 복구
|
||||||
|
// console.log('[HomePanel] Scrolling up - will expand after 1s');
|
||||||
|
// 기존 타이머 취소
|
||||||
|
if (scrollExpandTimerRef.current) {
|
||||||
|
clearTimeout(scrollExpandTimerRef.current);
|
||||||
|
}
|
||||||
|
// 1초 후 자동으로 크기 조정
|
||||||
|
scrollExpandTimerRef.current = setTimeout(() => {
|
||||||
|
// console.log('[HomePanel] 1s passed - auto expanding video');
|
||||||
dispatch(expandVideoFrom1px());
|
dispatch(expandVideoFrom1px());
|
||||||
expandAttemptRef.current++;
|
scrollExpandTimerRef.current = null;
|
||||||
}, 200);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 최상단 벗어남: interval 정리
|
|
||||||
else if (currentScrollTop > 1 && expandIntervalRef.current) {
|
|
||||||
// console.log('[HomePanel] Left top - clearing expand interval');
|
|
||||||
clearInterval(expandIntervalRef.current);
|
|
||||||
expandIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 기존 로직: 아래로 스크롤
|
// 이전 scrollTop 업데이트
|
||||||
if (currentScrollTop > prevScrollTop) {
|
prevScrollTopRef.current = currentScrollTop;
|
||||||
// 아래로 스크롤: 비디오를 1px로 축소
|
},
|
||||||
// console.log('[HomePanel] Scrolling down - shrinking video');
|
[dispatch]
|
||||||
dispatch(shrinkVideoTo1px());
|
);
|
||||||
// 기존 타이머 취소
|
|
||||||
if (scrollExpandTimerRef.current) {
|
|
||||||
clearTimeout(scrollExpandTimerRef.current);
|
|
||||||
scrollExpandTimerRef.current = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 기존 로직: 위로 스크롤 (0이 아닌)
|
|
||||||
else if (currentScrollTop < prevScrollTop && currentScrollTop > 1) {
|
|
||||||
// 위로 스크롤 (최상단 아님): 1초 후 복구
|
|
||||||
// console.log('[HomePanel] Scrolling up - will expand after 1s');
|
|
||||||
// 기존 타이머 취소
|
|
||||||
if (scrollExpandTimerRef.current) {
|
|
||||||
clearTimeout(scrollExpandTimerRef.current);
|
|
||||||
}
|
|
||||||
// 1초 후 자동으로 크기 조정
|
|
||||||
scrollExpandTimerRef.current = setTimeout(() => {
|
|
||||||
// console.log('[HomePanel] 1s passed - auto expanding video');
|
|
||||||
dispatch(expandVideoFrom1px());
|
|
||||||
scrollExpandTimerRef.current = null;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 이전 scrollTop 업데이트
|
|
||||||
prevScrollTopRef.current = currentScrollTop;
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
const _onFocusedContainerId = useCallback(
|
const _onFocusedContainerId = useCallback(
|
||||||
(containerId) => {
|
(containerId) => {
|
||||||
@@ -584,7 +574,16 @@ const HomePanel = ({ isOnTop }) => {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[panelInfo, firstSpot, enterThroughGNB, defaultFocus, cbChangePageRef, dispatch, isOnTop, panels]
|
[
|
||||||
|
panelInfo,
|
||||||
|
firstSpot,
|
||||||
|
enterThroughGNB,
|
||||||
|
defaultFocus,
|
||||||
|
cbChangePageRef,
|
||||||
|
dispatch,
|
||||||
|
isOnTop,
|
||||||
|
panels,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const bestSellerLoaded = useCallback(() => {
|
const bestSellerLoaded = useCallback(() => {
|
||||||
|
|||||||
@@ -941,7 +941,7 @@ const MediaPanel = React.forwardRef(
|
|||||||
// fullscreen 전환 완료 후 플래그 초기화
|
// fullscreen 전환 완료 후 플래그 초기화
|
||||||
isTransitioningToFullscreen.current = false;
|
isTransitioningToFullscreen.current = false;
|
||||||
};
|
};
|
||||||
}, [panelInfo?.modal, isOnTop]);
|
}, [isOnTop]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {
|
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {
|
||||||
|
|||||||
Reference in New Issue
Block a user