[251019] fix: PlayerPanel Optimization-1
🕐 커밋 시간: 2025. 10. 19. 21:45:39 📊 변경 통계: • 총 파일: 11개 • 추가: +119줄 • 삭제: -101줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/productActions.js ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js ~ com.twin.app.shoptime/src/hooks/useReviews/useReviews.js ~ com.twin.app.shoptime/src/reducers/productReducer.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabButton/PlayerTabButton.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/FeaturedShowContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/productActions.js (javascript): 🔄 Modified: resetShowAllReviews() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabButton/PlayerTabButton.jsx (javascript): ❌ Deleted: handleTabOnClick() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx (javascript): 🔄 Modified: Spottable() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -280,15 +280,15 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
// });
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log('[UserReviews]-API ✅ API 성공 응답:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
retCode: response.data && response.data.retCode,
|
||||
retMsg: response.data && response.data.retMsg,
|
||||
hasData: !!(response.data && response.data.data),
|
||||
fullResponse: response.data,
|
||||
});
|
||||
// console.log('[UserReviews]-API ✅ API 성공 응답:', {
|
||||
// status: response.status,
|
||||
// statusText: response.statusText,
|
||||
// headers: response.headers,
|
||||
// retCode: response.data && response.data.retCode,
|
||||
// retMsg: response.data && response.data.retMsg,
|
||||
// hasData: !!(response.data && response.data.data),
|
||||
// fullResponse: response.data,
|
||||
// });
|
||||
|
||||
// ==================== [임시 테스트] 빈 리뷰 응답 시뮬레이션 - 코멘트 처리 ====================
|
||||
// 30% 확률로 리뷰 없는 상품 응답 반환 (테스트용)
|
||||
@@ -311,16 +311,16 @@ export const getUserReviews = (requestParams) => (dispatch, getState) => {
|
||||
} */
|
||||
// ==================== [임시 테스트] 끝 - 코멘트 처리 ====================
|
||||
|
||||
if (response.data && response.data.data) {
|
||||
console.log('[UserReviews] 📊 API 데이터 상세:', {
|
||||
reviewListLength: response.data.data.reviewList ? response.data.data.reviewList.length : 0,
|
||||
reviewDetail: response.data.data.reviewDetail,
|
||||
reviewList_sample:
|
||||
(response.data.data.reviewList && response.data.data.reviewList[0]) || 'empty',
|
||||
totRvwCnt: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwCnt,
|
||||
totRvwAvg: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwAvg,
|
||||
});
|
||||
}
|
||||
// if (response.data && response.data.data) {
|
||||
// console.log('[UserReviews] 📊 API 데이터 상세:', {
|
||||
// reviewListLength: response.data.data.reviewList ? response.data.data.reviewList.length : 0,
|
||||
// reviewDetail: response.data.data.reviewDetail,
|
||||
// reviewList_sample:
|
||||
// (response.data.data.reviewList && response.data.data.reviewList[0]) || 'empty',
|
||||
// totRvwCnt: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwCnt,
|
||||
// totRvwAvg: response.data.data.reviewDetail && response.data.data.reviewDetail.totRvwAvg,
|
||||
// });
|
||||
// }
|
||||
|
||||
// 실제 API 응답에서 data 부분 추출
|
||||
const apiData = extractReviewApiData(response.data);
|
||||
|
||||
@@ -1064,9 +1064,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
showControls = () => {
|
||||
console.log('[VideoPlayer] showControls 호출');
|
||||
if (this.props.disabled) {
|
||||
console.log('[VideoPlayer] disabled true - return');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1138,10 +1136,6 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
toggleControls = () => {
|
||||
console.log(
|
||||
'[VideoPlayer] toggleControls 호출, mediaControlsVisible:',
|
||||
this.state.mediaControlsVisible
|
||||
);
|
||||
if (this.state.mediaControlsVisible) {
|
||||
this.hideControls();
|
||||
} else {
|
||||
|
||||
@@ -867,19 +867,11 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.props.belowContentsVisible !== undefined &&
|
||||
prevProps.belowContentsVisible !== this.props.belowContentsVisible
|
||||
) {
|
||||
console.log('[VideoPlayer] belowContentsVisible 변경 감지:', {
|
||||
prev: prevProps.belowContentsVisible,
|
||||
current: this.props.belowContentsVisible,
|
||||
mediaControlsVisible: this.state.mediaControlsVisible,
|
||||
});
|
||||
|
||||
if (this.props.belowContentsVisible && !this.state.mediaControlsVisible) {
|
||||
// TabContainerV2가 표시될 때 controls도 표시
|
||||
console.log('[VideoPlayer] TabContainerV2 표시 - controls 강제 표시');
|
||||
this.showControls();
|
||||
} else if (!this.props.belowContentsVisible && this.state.mediaControlsVisible) {
|
||||
// TabContainerV2가 숨겨질 때 controls도 숨김
|
||||
console.log('[VideoPlayer] TabContainerV2 숨김 - controls 숨김');
|
||||
this.hideControls();
|
||||
}
|
||||
}
|
||||
@@ -1049,7 +1041,6 @@ const VideoPlayerBase = class extends React.Component {
|
||||
|
||||
// TabContainerV2가 표시 중이면 자동으로 닫지 않음
|
||||
if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) {
|
||||
console.log('[VideoPlayer] TabContainerV2 표시 중 - autoClose 비활성화');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1116,9 +1107,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
showControls = () => {
|
||||
console.log('[VideoPlayer] showControls 호출');
|
||||
if (this.props.disabled) {
|
||||
console.log('[VideoPlayer] disabled true - return');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1190,10 +1179,6 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
toggleControls = () => {
|
||||
console.log(
|
||||
'[VideoPlayer] toggleControls 호출, mediaControlsVisible:',
|
||||
this.state.mediaControlsVisible
|
||||
);
|
||||
if (this.state.mediaControlsVisible) {
|
||||
this.hideControls();
|
||||
} else {
|
||||
@@ -1988,12 +1973,9 @@ const VideoPlayerBase = class extends React.Component {
|
||||
// Player Interaction events
|
||||
//
|
||||
onVideoClick = () => {
|
||||
console.log('[VideoPlayer] onVideoClick 호출');
|
||||
|
||||
// tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글
|
||||
if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) {
|
||||
const willShowControls = !this.state.mediaControlsVisible;
|
||||
console.log('[VideoPlayer] 클릭 - 상태 동기화 토글:', willShowControls);
|
||||
|
||||
// belowContentsVisible을 먼저 변경 (componentDidUpdate에서 mediaControls 동기화됨)
|
||||
this.props.setBelowContentsVisible(willShowControls);
|
||||
@@ -2002,7 +1984,6 @@ const VideoPlayerBase = class extends React.Component {
|
||||
|
||||
// 외부 onClick 핸들러가 있으면 호출
|
||||
if (this.props.onClick) {
|
||||
console.log('[VideoPlayer] onClick 호출');
|
||||
this.props.onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -239,20 +239,20 @@ const useReviews = (prdtId, patnrId) => {
|
||||
});
|
||||
|
||||
// 별점이 없는 리뷰들 로그 출력
|
||||
if (noStarReviews.length > 0) {
|
||||
console.log('[UserReviews]-NoStar 별점 없는 리뷰들:', {
|
||||
totalReviews: allReviews.length,
|
||||
noStarCount: noStarReviews.length,
|
||||
noStarReviews: noStarReviews.map((review) => ({
|
||||
rvwId: review.rvwId,
|
||||
rvwScr: review.rvwScr,
|
||||
rvwRtng: review.rvwRtng,
|
||||
rating: review.rating,
|
||||
rvwCtnt: review.rvwCtnt?.substring(0, 50) + '...',
|
||||
wrtrNknm: review.wrtrNknm,
|
||||
})),
|
||||
});
|
||||
}
|
||||
// if (noStarReviews.length > 0) {
|
||||
// console.log('[UserReviews]-NoStar 별점 없는 리뷰들:', {
|
||||
// totalReviews: allReviews.length,
|
||||
// noStarCount: noStarReviews.length,
|
||||
// noStarReviews: noStarReviews.map((review) => ({
|
||||
// rvwId: review.rvwId,
|
||||
// rvwScr: review.rvwScr,
|
||||
// rvwRtng: review.rvwRtng,
|
||||
// rating: review.rating,
|
||||
// rvwCtnt: review.rvwCtnt?.substring(0, 50) + '...',
|
||||
// wrtrNknm: review.wrtrNknm,
|
||||
// })),
|
||||
// });
|
||||
// }
|
||||
|
||||
// 별점이 있는 리뷰만 카운트 (1~5점)
|
||||
let ratingSum = 0;
|
||||
@@ -538,14 +538,14 @@ const useReviews = (prdtId, patnrId) => {
|
||||
const apiTotalCount = reviewDetail && reviewDetail.totRvwCnt ? reviewDetail.totRvwCnt : 0;
|
||||
const actualReviewCount = allReviews.length;
|
||||
|
||||
if (apiTotalCount > actualReviewCount && actualReviewCount > 0) {
|
||||
console.log('[UserReviews]-API Mismatch API 개수와 실제 개수 불일치:', {
|
||||
apiTotalCount,
|
||||
actualReviewCount,
|
||||
difference: apiTotalCount - actualReviewCount,
|
||||
reason: 'API가 최대 100개만 반환하는 페이징 제한',
|
||||
});
|
||||
}
|
||||
// if (apiTotalCount > actualReviewCount && actualReviewCount > 0) {
|
||||
// console.log('[UserReviews]-API Mismatch API 개수와 실제 개수 불일치:', {
|
||||
// apiTotalCount,
|
||||
// actualReviewCount,
|
||||
// difference: apiTotalCount - actualReviewCount,
|
||||
// reason: 'API가 최대 100개만 반환하는 페이징 제한',
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
totalReviews: actualReviewCount, // 실제로 받은 리뷰 개수 사용 (API 제한 반영)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { types } from "../actions/actionTypes";
|
||||
import { curry, get, set } from "../utils/fp";
|
||||
import { types } from '../actions/actionTypes';
|
||||
import { curry, get, set } from '../utils/fp';
|
||||
|
||||
const initialState = {
|
||||
bestSellerData: {},
|
||||
@@ -12,62 +12,57 @@ const initialState = {
|
||||
|
||||
// FP: handlers map (curried), pure and immutable updates only
|
||||
const handleBestSeller = curry((state, action) =>
|
||||
set("bestSellerData", get("payload", action), state)
|
||||
set('bestSellerData', get('payload', action), state)
|
||||
);
|
||||
|
||||
const handleProductOption = curry((state, action) =>
|
||||
set("prdtOptInfo", get(["payload", "prdtOptInfo"], action), state)
|
||||
set('prdtOptInfo', get(['payload', 'prdtOptInfo'], action), state)
|
||||
);
|
||||
|
||||
const handleProductGroup = curry((state, action) =>
|
||||
set("groupInfo", get(["payload", "groupInfo"], action), state)
|
||||
set('groupInfo', get(['payload', 'groupInfo'], action), state)
|
||||
);
|
||||
|
||||
const handleProductImageLength = curry((state, action) =>
|
||||
set(
|
||||
"productImageLength",
|
||||
(get("payload", action) ? get("payload", action) : 0) + 1,
|
||||
state
|
||||
)
|
||||
set('productImageLength', (get('payload', action) ? get('payload', action) : 0) + 1, state)
|
||||
);
|
||||
|
||||
const handleVideoIndicatorFocus = curry((state, action) =>
|
||||
set("videoIndicatorFocus", get("payload", action), state)
|
||||
set('videoIndicatorFocus', get('payload', action), state)
|
||||
);
|
||||
|
||||
const handleClearProductDetail = curry((state) => set("prdtOptInfo", null, state));
|
||||
const handleClearProductDetail = curry((state) => set('prdtOptInfo', null, state));
|
||||
|
||||
const handleProductOptionId = curry((state, action) =>
|
||||
set("prodOptCdCval", get("payload", action), state)
|
||||
set('prodOptCdCval', get('payload', action), state)
|
||||
);
|
||||
|
||||
// 유저 리뷰 데이터 핸들러 추가
|
||||
const handleUserReview = curry((state, action) => {
|
||||
const reviewData = get("payload", action);
|
||||
const prdtId = get(["payload", "prdtId"], action);
|
||||
const reviewData = get('payload', action);
|
||||
const prdtId = get(['payload', 'prdtId'], action);
|
||||
|
||||
console.log("[UserReviews] Reducer - Storing review data:", {
|
||||
prdtId: prdtId,
|
||||
hasData: !!reviewData,
|
||||
reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0,
|
||||
totalCount: reviewData && reviewData.reviewDetail ? reviewData.reviewDetail.totRvwCnt : 0
|
||||
});
|
||||
// console.log("[UserReviews] Reducer - Storing review data:", {
|
||||
// prdtId: prdtId,
|
||||
// hasData: !!reviewData,
|
||||
// reviewListLength: reviewData && reviewData.reviewList ? reviewData.reviewList.length : 0,
|
||||
// totalCount: reviewData && reviewData.reviewDetail ? reviewData.reviewDetail.totRvwCnt : 0
|
||||
// });
|
||||
|
||||
return set("reviewData", reviewData,
|
||||
set("loadedPrdtId", prdtId, state));
|
||||
return set('reviewData', reviewData, set('loadedPrdtId', prdtId, state));
|
||||
});
|
||||
|
||||
// showAllReviews 토글 핸들러
|
||||
const handleToggleShowAllReviews = curry((state, action) => {
|
||||
const currentValue = get("showAllReviews", state);
|
||||
const currentValue = get('showAllReviews', state);
|
||||
// console.log("[UserReviews] Toggle showAllReviews:", !currentValue);
|
||||
return set("showAllReviews", !currentValue, state);
|
||||
return set('showAllReviews', !currentValue, state);
|
||||
});
|
||||
|
||||
// showAllReviews 초기화 핸들러
|
||||
const handleResetShowAllReviews = curry((state, action) => {
|
||||
// console.log("[UserReviews] Reset showAllReviews to false");
|
||||
return set("showAllReviews", false, state);
|
||||
return set('showAllReviews', false, state);
|
||||
});
|
||||
|
||||
const handlers = {
|
||||
@@ -84,7 +79,7 @@ const handlers = {
|
||||
};
|
||||
|
||||
export const productReducer = (state = initialState, action = {}) => {
|
||||
const type = get("type", action);
|
||||
const type = get('type', action);
|
||||
const handler = handlers[type];
|
||||
return handler ? handler(state, action) : state;
|
||||
};
|
||||
|
||||
@@ -169,12 +169,12 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
|
||||
}, []);
|
||||
|
||||
// 이미지 클릭으로 All-Images 모드 팝업 열기
|
||||
const handleOpenAllImagesPopup = useCallback((imageIndex = 0) => {
|
||||
console.log('[UserReviews] Opening popup in All-Images mode, image index:', imageIndex);
|
||||
setSelectedImageIndex(imageIndex);
|
||||
setPopupMode('all-images');
|
||||
setIsPopupOpen(true);
|
||||
}, []);
|
||||
// const handleOpenAllImagesPopup = useCallback((imageIndex = 0) => {
|
||||
// console.log('[UserReviews] Opening popup in All-Images mode, image index:', imageIndex);
|
||||
// setSelectedImageIndex(imageIndex);
|
||||
// setPopupMode('all-images');
|
||||
// setIsPopupOpen(true);
|
||||
// }, []);
|
||||
|
||||
const handleClosePopup = useCallback(() => {
|
||||
// console.log("[UserReviews] Closing popup and resetting mode");
|
||||
|
||||
@@ -269,7 +269,9 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
const vodLogParamsRef = useRef(null);
|
||||
const mediaLogParamsRef = useRef(null);
|
||||
const prevNowMenuRef = useRef(null);
|
||||
const watchInterval = useRef(null);
|
||||
const watchIntervalLive = useRef(null);
|
||||
const watchIntervalVod = useRef(null);
|
||||
const watchIntervalMedia = useRef(null);
|
||||
// useEffect(() => {
|
||||
// console.log("###videoLoaded", videoLoaded);
|
||||
// if (nowMenu) {
|
||||
@@ -443,7 +445,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalLive.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...liveLogParamsRef.current,
|
||||
@@ -458,7 +460,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isModalLiveLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalLive.current);
|
||||
dispatch(
|
||||
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -476,7 +478,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalLive.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...liveLogParamsRef.current,
|
||||
@@ -491,7 +493,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isFullLiveLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalLive.current);
|
||||
dispatch(
|
||||
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -509,7 +511,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalLive.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...liveLogParamsRef.current,
|
||||
@@ -524,7 +526,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isDetailLiveLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalLive.current);
|
||||
dispatch(
|
||||
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -596,7 +598,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalVod.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...vodLogParamsRef.current,
|
||||
@@ -611,7 +613,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isModalVodLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalVod.current);
|
||||
dispatch(
|
||||
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -629,7 +631,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalVod.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...vodLogParamsRef.current,
|
||||
@@ -644,7 +646,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isFullVodLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalVod.current);
|
||||
dispatch(
|
||||
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -662,7 +664,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalVod.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...vodLogParamsRef.current,
|
||||
@@ -677,7 +679,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isDetailVodLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalVod.current);
|
||||
dispatch(
|
||||
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -752,7 +754,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
if (logStatus.isModalMediaLogReady && panelInfo?.modal) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalMedia.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...mediaLogParamsRef.current,
|
||||
@@ -767,7 +769,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isModalMediaLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalMedia.current);
|
||||
dispatch(
|
||||
sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
@@ -781,7 +783,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
if (logStatus.isFullMediaLogReady && !panelInfo?.modal) {
|
||||
let watchStrtDt = formatGMTString(new Date());
|
||||
|
||||
watchInterval.current = setInterval(() => {
|
||||
watchIntervalMedia.current = setInterval(() => {
|
||||
let watchEndDt = formatGMTString(new Date());
|
||||
let watchRecord = {
|
||||
...mediaLogParamsRef.current,
|
||||
@@ -796,7 +798,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
...status,
|
||||
isFullMediaLogReady: false,
|
||||
}));
|
||||
clearInterval(watchInterval.current);
|
||||
clearInterval(watchIntervalMedia.current);
|
||||
dispatch(
|
||||
sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () =>
|
||||
dispatch(changeLocalSettings({ watchRecord: {} }))
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
|
||||
import classNames from "classnames";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import classNames from 'classnames';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { updatePanel } from "../../../../actions/panelActions";
|
||||
import { panel_names } from "../../../../utils/Config";
|
||||
import { SpotlightIds } from "../../../../utils/SpotlightIds";
|
||||
import css from "./PlayerTabButton.module.less";
|
||||
import { updatePanel } from '../../../../actions/panelActions';
|
||||
import { panel_names } from '../../../../utils/Config';
|
||||
import { SpotlightIds } from '../../../../utils/SpotlightIds';
|
||||
import css from './PlayerTabButton.module.less';
|
||||
|
||||
const SpottableComponent = Spottable("button");
|
||||
const SpottableComponent = Spottable('button');
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "default-element", preserveld: true },
|
||||
"div"
|
||||
{ enterTo: 'default-element', preserveld: true },
|
||||
'div'
|
||||
);
|
||||
export default function PlayerTabButton({
|
||||
sideContentsVisible,
|
||||
@@ -26,7 +26,8 @@ export default function PlayerTabButton({
|
||||
const dispatch = useDispatch();
|
||||
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
|
||||
|
||||
const handleTabOnClick = (e) => {
|
||||
const handleTabOnClick = useCallback(
|
||||
(e) => {
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
@@ -36,12 +37,16 @@ export default function PlayerTabButton({
|
||||
})
|
||||
);
|
||||
setSideContentsVisible((prev) => !prev);
|
||||
setTimeout(() => {
|
||||
|
||||
// microtask 큐를 사용하여 상태 업데이트 이후 포커스 이동
|
||||
Promise.resolve().then(() => {
|
||||
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
e.stopPropagation();
|
||||
};
|
||||
},
|
||||
[dispatch, setSideContentsVisible]
|
||||
);
|
||||
|
||||
const _onSpotlightLeft = useCallback(
|
||||
(e) => {
|
||||
@@ -52,7 +57,7 @@ export default function PlayerTabButton({
|
||||
}
|
||||
if (!sideContentsVisible) {
|
||||
e.stopPropagation();
|
||||
Spotlight.focus("videoIndicator-up-button");
|
||||
Spotlight.focus('videoIndicator-up-button');
|
||||
}
|
||||
},
|
||||
[sideContentsVisible]
|
||||
@@ -60,10 +65,10 @@ export default function PlayerTabButton({
|
||||
|
||||
const onSpotlightDown = useCallback(
|
||||
(e) => {
|
||||
if (countryCode !== "US") {
|
||||
if (videoType === "VOD") {
|
||||
Spotlight.focus("videoIndicator-up-button");
|
||||
} else if (videoType === "LIVE") {
|
||||
if (countryCode !== 'US') {
|
||||
if (videoType === 'VOD') {
|
||||
Spotlight.focus('videoIndicator-up-button');
|
||||
} else if (videoType === 'LIVE') {
|
||||
Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
|
||||
}
|
||||
return;
|
||||
@@ -80,7 +85,7 @@ export default function PlayerTabButton({
|
||||
(e) => {
|
||||
if (!sideContentsVisible) {
|
||||
e.stopPropagation();
|
||||
Spotlight.focus("videoIndicator-up-button");
|
||||
Spotlight.focus('videoIndicator-up-button');
|
||||
}
|
||||
},
|
||||
[sideContentsVisible]
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import Spotlight from '@enact/spotlight';
|
||||
|
||||
import defaultImage from "../../../../../assets/images/img-thumb-empty-144@3x.png";
|
||||
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, removeSpecificTags } from "../../../../utils/helperMethods";
|
||||
import PlayerItemCard, { TYPES } from "../../PlayerItemCard/PlayerItemCard";
|
||||
import ListEmptyContents from "../TabContents/ListEmptyContents/ListEmptyContents";
|
||||
import css from "./LiveChannelContents.module.less";
|
||||
import { getMainCategoryShowDetail } from "../../../../actions/mainActions";
|
||||
import { sendLogTotalRecommend } from "../../../../actions/logActions";
|
||||
import defaultImage from '../../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
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, removeSpecificTags } from '../../../../utils/helperMethods';
|
||||
import PlayerItemCard, { TYPES } from '../../PlayerItemCard/PlayerItemCard';
|
||||
import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
|
||||
import css from './LiveChannelContents.module.less';
|
||||
import { getMainCategoryShowDetail } from '../../../../actions/mainActions';
|
||||
import { sendLogTotalRecommend } from '../../../../actions/logActions';
|
||||
|
||||
export default function FeaturedShowContents({
|
||||
featuredShowsInfos,
|
||||
@@ -25,10 +25,12 @@ export default function FeaturedShowContents({
|
||||
tabIndex,
|
||||
handleItemFocus,
|
||||
tabTitle,
|
||||
panelInfo
|
||||
panelInfo,
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const isClickBlocked = useRef(false);
|
||||
const blockTimeoutRef = useRef(null);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
() => () => {
|
||||
if (handleItemFocus) {
|
||||
@@ -61,8 +63,8 @@ export default function FeaturedShowContents({
|
||||
category: catNm,
|
||||
partner: patncNm,
|
||||
contextName: LOG_CONTEXT_NAME.SHOW,
|
||||
messageId: LOG_MESSAGE_ID.CONTENTCLICK
|
||||
}
|
||||
messageId: LOG_MESSAGE_ID.CONTENTCLICK,
|
||||
};
|
||||
dispatch(sendLogTotalRecommend(params));
|
||||
//중복클릭방지
|
||||
if (isClickBlocked.current) {
|
||||
@@ -70,8 +72,15 @@ export default function FeaturedShowContents({
|
||||
}
|
||||
|
||||
isClickBlocked.current = true;
|
||||
setTimeout(() => {
|
||||
|
||||
// 이전 타이머가 있으면 정리
|
||||
if (blockTimeoutRef.current) {
|
||||
clearTimeout(blockTimeoutRef.current);
|
||||
}
|
||||
|
||||
blockTimeoutRef.current = setTimeout(() => {
|
||||
isClickBlocked.current = false;
|
||||
blockTimeoutRef.current = null;
|
||||
}, 600);
|
||||
|
||||
if (currentVideoShowId && currentVideoShowId === showId) {
|
||||
@@ -107,29 +116,30 @@ export default function FeaturedShowContents({
|
||||
spotlightId={`tabChannel-video-${index}`}
|
||||
videoVerticalVisible={videoVerticalVisible}
|
||||
selectedIndex={index}
|
||||
currentVideoVisible={
|
||||
currentVideoShowId === featuredShowsInfos[index].showId
|
||||
}
|
||||
currentVideoVisible={currentVideoShowId === featuredShowsInfos[index].showId}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[
|
||||
featuredShowsInfos,
|
||||
currentVideoInfo,
|
||||
currentVideoShowId,
|
||||
videoVerticalVisible,
|
||||
isClickBlocked,
|
||||
]
|
||||
[featuredShowsInfos, currentVideoInfo, currentVideoShowId, videoVerticalVisible, isClickBlocked]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
Spotlight.focus("tabChannel-video-" + selectedIndex);
|
||||
Spotlight.focus('tabChannel-video-' + selectedIndex);
|
||||
});
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [featuredShowsInfos]);
|
||||
|
||||
// cleanup useEffect
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (blockTimeoutRef.current) {
|
||||
clearTimeout(blockTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={css.container}>
|
||||
|
||||
@@ -29,6 +29,8 @@ export default function LiveChannelContents({
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const isClickBlocked = useRef(false);
|
||||
const blockTimeoutRef = useRef(null);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
() => () => {
|
||||
if (handleItemFocus) {
|
||||
@@ -75,8 +77,15 @@ export default function LiveChannelContents({
|
||||
}
|
||||
|
||||
isClickBlocked.current = true;
|
||||
setTimeout(() => {
|
||||
|
||||
// 이전 타이머가 있으면 정리
|
||||
if (blockTimeoutRef.current) {
|
||||
clearTimeout(blockTimeoutRef.current);
|
||||
}
|
||||
|
||||
blockTimeoutRef.current = setTimeout(() => {
|
||||
isClickBlocked.current = false;
|
||||
blockTimeoutRef.current = null;
|
||||
}, 600);
|
||||
|
||||
if (!showId) return;
|
||||
@@ -144,6 +153,15 @@ export default function LiveChannelContents({
|
||||
|
||||
const containerClass = version === 2 ? cssV2.container : css.container;
|
||||
|
||||
// cleanup useEffect
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (blockTimeoutRef.current) {
|
||||
clearTimeout(blockTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={containerClass}>
|
||||
|
||||
@@ -120,9 +120,11 @@ export default function TabContainerV2({
|
||||
// tabIndex가 2(ShopNowButton)로 변경되면 자동으로 포커스 이동
|
||||
useEffect(() => {
|
||||
if (tabIndex === 2) {
|
||||
setTimeout(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
Spotlight.focus('below-tab-shop-now-button');
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
}, [tabIndex]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user