[251115] fix: DetailPanel FullScreen Focus Move
🕐 커밋 시간: 2025. 11. 15. 22:03:44 📊 변경 통계: • 총 파일: 17개 • 추가: +573줄 • 삭제: -87줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/MediaOverlayContents.jsx 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/panelActions.js ~ com.twin.app.shoptime/src/components/MediaPlayer/MediaControls.js ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.module.less ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.js ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less ~ com.twin.app.shoptime/src/utils/SpotlightIds.js ~ com.twin.app.shoptime/src/views/HomePanel/BestSeller/BestSeller.jsx ~ com.twin.app.shoptime/src/views/HomePanel/EventPopUpBanner/EventPopUpBanner.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx ~ com.twin.app.shoptime/src/views/HomePanel/PickedForYou/PickedForYou.jsx ~ com.twin.app.shoptime/src/views/HomePanel/SubCategory/SubCategory.jsx ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx ~ com.twin.app.shoptime/src/views/SearchPanel/SearchResultsNew/ItemCard.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/panelActions.js (javascript): ✅ Added: updatePanel() 📄 com.twin.app.shoptime/src/components/MediaPlayer/MediaControls.js (javascript): ✅ Added: onSpotlightRight(), onSpotlightUp(), MediaControlsDecoratorHOC(), handleCancel() ❌ Deleted: onSpotlightRight(), onSpotlightUp(), MediaControlsDecoratorHOC(), handleCancel() 📄 com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less (unknown): ✅ Added: position() ❌ Deleted: position() 📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/src/views/HomePanel/SubCategory/SubCategory.jsx (javascript): 🔄 Modified: getExpsOrdByLgCatCd() 📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript): 🔄 Modified: normalizeModalStyle() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator(), PlayerOverlayContents() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/MediaOverlayContents.jsx (javascript): ✅ Added: MediaOverlayContents() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선 • 공통 유틸리티 함수 최적화
This commit is contained in:
@@ -11,6 +11,8 @@ export const SOURCE_MENUS = {
|
||||
HOME_ROLLING_UNIT: 'home_rolling_unit',
|
||||
HOME_EVENT_POPUP: 'home_event_popup',
|
||||
HOME_TODAYS_DEAL: 'home_todays_deal',
|
||||
SEARCH_RESULT: 'search_result',
|
||||
HOME_GENERAL: 'home_general',
|
||||
THEMED_PRODUCT: 'themed_product',
|
||||
GENERAL_PRODUCT: 'general_product',
|
||||
};
|
||||
@@ -43,6 +45,417 @@ export const resetPanels = (panels) => ({
|
||||
payload: panels,
|
||||
});
|
||||
|
||||
/**
|
||||
* DetailPanel로 이동하는 공통 액션 함수
|
||||
* @param {Object} params - 이동 파라미터
|
||||
* @param {string} params.patnrId - 파트너 ID
|
||||
* @param {string} params.prdtId - 상품 ID
|
||||
* @param {string} [params.curationId] - 큐레이션 ID (테마 상품인 경우)
|
||||
* @param {string} [params.nowShelf] - 현재 셸프 ID
|
||||
* @param {string} [params.type] - 상품 타입 ('theme' 등)
|
||||
* @param {string} [params.sourceMenu] - 시작 메뉴 (SOURCE_MENUS 상수 사용)
|
||||
* @param {Object} [params.additionalInfo] - 추가 정보
|
||||
* @returns {Function} Redux thunk 함수
|
||||
*/
|
||||
export const navigateToDetail = ({
|
||||
patnrId,
|
||||
prdtId,
|
||||
curationId,
|
||||
nowShelf,
|
||||
type,
|
||||
sourceMenu,
|
||||
additionalInfo = {},
|
||||
}) => {
|
||||
return (dispatch, getState) => {
|
||||
const panelInfo = {
|
||||
patnrId,
|
||||
prdtId,
|
||||
...additionalInfo,
|
||||
};
|
||||
|
||||
// 선택적 파라미터들 추가
|
||||
if (curationId) panelInfo.curationId = curationId;
|
||||
if (nowShelf) panelInfo.nowShelf = nowShelf;
|
||||
if (type) panelInfo.type = type;
|
||||
if (sourceMenu) panelInfo.sourceMenu = sourceMenu;
|
||||
|
||||
// 로깅
|
||||
console.log(`[navigateToDetail] ${sourceMenu || 'unknown'} → DetailPanel`, {
|
||||
patnrId,
|
||||
prdtId,
|
||||
curationId,
|
||||
nowShelf,
|
||||
type,
|
||||
sourceMenu,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// sourceMenu에 따른 사전 처리
|
||||
switch (sourceMenu) {
|
||||
case SOURCE_MENUS.HOME_BEST_SELLER:
|
||||
case SOURCE_MENUS.HOME_PICKED_FOR_YOU:
|
||||
case SOURCE_MENUS.HOME_SUB_CATEGORY:
|
||||
case SOURCE_MENUS.HOME_EVENT_POPUP:
|
||||
case SOURCE_MENUS.HOME_TODAYS_DEAL:
|
||||
case SOURCE_MENUS.HOME_RANDOM_UNIT:
|
||||
case SOURCE_MENUS.HOME_ROLLING_UNIT:
|
||||
case SOURCE_MENUS.HOME_GENERAL: {
|
||||
// 🔽 모든 HomePanel에서 DetailPanel로 이동 시 HomeBanner modal 비디오 정지
|
||||
const state = getState();
|
||||
const playerPanelInfo = state.panels.panels.find(
|
||||
(p) => p.name === panel_names.PLAYER_PANEL
|
||||
);
|
||||
|
||||
// playerPanel이 없는 경우 비디오 정지 로직 건너뛰기
|
||||
if (!playerPanelInfo) {
|
||||
// 비디오가 없어도 HomePanel 상태 저장
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
lastSelectedProduct: { patnrId, prdtId },
|
||||
lastActionSource: sourceMenu,
|
||||
...additionalInfo,
|
||||
},
|
||||
})
|
||||
);
|
||||
panelInfo.fromHome = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const isCurrentBannerVideoPlaying = playerPanelInfo.panelInfo?.modal !== false;
|
||||
|
||||
// HomeBanner의 modal=true 비디오가 재생 중이면 정지
|
||||
if (isCurrentBannerVideoPlaying) {
|
||||
// 🔽 비디오 상태 저장 후 정지
|
||||
const { finishVideoPreview } = require('./playActions');
|
||||
|
||||
// 비디오 복원을 위한 상태 저장
|
||||
const videoStateToRestore = {
|
||||
...playerPanelInfo.panelInfo,
|
||||
wasPlaying: true,
|
||||
restoreOnBack: true,
|
||||
sourceMenu,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
// HomePanel에 비디오 복원 상태 저장
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
videoStateToRestore,
|
||||
lastSelectedProduct: { patnrId, prdtId },
|
||||
lastActionSource: sourceMenu,
|
||||
...additionalInfo,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// 비디오 상태 저장 후 정지 (로그는 개발 시 필요 시 주석 해제)
|
||||
|
||||
dispatch(finishVideoPreview());
|
||||
} else {
|
||||
// 비디오가 재생 중이 아니어도 HomePanel 상태 저장
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
lastSelectedProduct: { patnrId, prdtId },
|
||||
lastActionSource: sourceMenu,
|
||||
...additionalInfo,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// HomePanel 내부 컴포넌트들: 기본 HomePanel 상태 저장
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
lastSelectedProduct: { patnrId, prdtId },
|
||||
lastActionSource: sourceMenu,
|
||||
...additionalInfo,
|
||||
},
|
||||
})
|
||||
);
|
||||
panelInfo.fromHome = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SOURCE_MENUS.SEARCH_RESULT:
|
||||
// Search: 현재 패널 상태 저장 (updatePanel)
|
||||
if (additionalInfo.searchVal && additionalInfo.currentSpot) {
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.SEARCH_PANEL,
|
||||
panelInfo: {
|
||||
searchVal: additionalInfo.searchVal,
|
||||
currentSpot: additionalInfo.currentSpot,
|
||||
tab: additionalInfo.tab || 0,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
panelInfo.fromSearch = true;
|
||||
panelInfo.searchQuery = additionalInfo.searchVal;
|
||||
break;
|
||||
|
||||
case SOURCE_MENUS.THEMED_PRODUCT:
|
||||
// 테마 상품: 별도 처리 필요할 경우
|
||||
break;
|
||||
|
||||
case SOURCE_MENUS.GENERAL_PRODUCT:
|
||||
default:
|
||||
// 일반 상품: 기본 처리
|
||||
break;
|
||||
}
|
||||
|
||||
// DetailPanel push
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo,
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 테마 상품을 위한 DetailPanel 이동 헬퍼 함수
|
||||
* @param {Object} params - 이동 파라미터
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateToThemeDetail = ({
|
||||
patnrId,
|
||||
prdtId,
|
||||
curationId,
|
||||
sourceMenu = SOURCE_MENUS.THEMED_PRODUCT,
|
||||
...additionalInfo
|
||||
}) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
curationId,
|
||||
type: 'theme',
|
||||
sourceMenu,
|
||||
...additionalInfo,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 홈패널 BestSeller에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromBestSeller = ({ patnrId, prdtId, spotlightId }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
nowShelf: spotlightId,
|
||||
sourceMenu: SOURCE_MENUS.HOME_BEST_SELLER,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 홈패널 PickedForYou에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromPickedForYou = ({ patnrId, prdtId, spotlightId }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
nowShelf: spotlightId,
|
||||
sourceMenu: SOURCE_MENUS.HOME_PICKED_FOR_YOU,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 홈패널 SubCategory에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromSubCategory = ({ patnrId, prdtId, spotlightId }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
nowShelf: spotlightId,
|
||||
sourceMenu: SOURCE_MENUS.HOME_SUB_CATEGORY,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 홈패널 RandomUnit 배너에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromRandomUnit = ({ patnrId, prdtId, curationId, type = 'product' }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
curationId,
|
||||
type: type === 'theme' ? 'theme' : undefined,
|
||||
sourceMenu: SOURCE_MENUS.HOME_RANDOM_UNIT,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 홈패널 RollingUnit 배너에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromRollingUnit = ({ patnrId, prdtId, curationId, additionalInfo = {} }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
curationId,
|
||||
sourceMenu: SOURCE_MENUS.HOME_ROLLING_UNIT,
|
||||
...additionalInfo,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 홈패널 EventPopUpBanner에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromEventPopup = ({ patnrId, prdtId }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
sourceMenu: SOURCE_MENUS.HOME_EVENT_POPUP,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* SearchPanel에서 DetailPanel로 이동
|
||||
* @param {Object} params - 검색 및 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromSearch = ({
|
||||
patnrId,
|
||||
prdtId,
|
||||
searchQuery,
|
||||
currentSpot,
|
||||
additionalInfo = {},
|
||||
}) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
sourceMenu: SOURCE_MENUS.SEARCH_RESULT,
|
||||
additionalInfo: {
|
||||
searchVal: searchQuery,
|
||||
currentSpot,
|
||||
tab: 0,
|
||||
...additionalInfo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* HomePanel 일반 클릭에서 DetailPanel로 이동
|
||||
* @param {Object} params - 상품 정보
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const navigateFromHomeGeneral = ({ patnrId, prdtId, additionalInfo = {} }) => {
|
||||
return navigateToDetail({
|
||||
patnrId,
|
||||
prdtId,
|
||||
sourceMenu: SOURCE_MENUS.HOME_GENERAL,
|
||||
additionalInfo,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* DetailPanel에서 돌아올 때 비디오 복원 함수
|
||||
* HomePanel에 저장된 비디오 상태를 확인하고 복원
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const restoreVideoOnBack = () => {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const panels = state.panels.panels;
|
||||
|
||||
// HomePanel 찾기
|
||||
const homePanel = panels.find((p) => p.name === panel_names.HOME_PANEL);
|
||||
const videoStateToRestore = homePanel?.panelInfo?.videoStateToRestore;
|
||||
|
||||
if (!videoStateToRestore || !videoStateToRestore.restoreOnBack) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 비디오 복원 시작 (로그는 개발 시 필요 시 주석 해제)
|
||||
|
||||
// 비디오 상태 복원
|
||||
const { startVideoPlayerNew } = require('./playActions');
|
||||
|
||||
// 복원할 비디오 정보 추출
|
||||
const restoreInfo = {
|
||||
bannerId: videoStateToRestore.bannerId || videoStateToRestore.playerState?.currentBannerId,
|
||||
patnrId: videoStateToRestore.patnrId,
|
||||
showId: videoStateToRestore.showId,
|
||||
shptmBanrTpNm: videoStateToRestore.shptmBanrTpNm,
|
||||
lgCatCd: videoStateToRestore.lgCatCd,
|
||||
modal: true, // HomeBanner는 항상 modal
|
||||
modalContainerId: videoStateToRestore.modalContainerId,
|
||||
modalClassName: videoStateToRestore.modalClassName,
|
||||
chanId: videoStateToRestore.chanId,
|
||||
};
|
||||
|
||||
// 비디오 재생 시작
|
||||
dispatch(
|
||||
startVideoPlayerNew({
|
||||
...restoreInfo,
|
||||
spotlightDisable: false,
|
||||
})
|
||||
);
|
||||
|
||||
// 복원 상태 정리
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.HOME_PANEL,
|
||||
panelInfo: {
|
||||
...homePanel.panelInfo,
|
||||
videoStateToRestore: {
|
||||
...videoStateToRestore,
|
||||
restoreOnBack: false, // 복원 완료 후 플래그 초기화
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* DetailPanel 닫기 시 비디오 복원 확인 함수
|
||||
* DetailPanel 패널이 제거될 때 자동으로 비디오 복원 시도
|
||||
* @returns {Function} Redux thunk
|
||||
*/
|
||||
export const handleDetailPanelCloseWithVideoRestore = () => {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const panels = state.panels.panels;
|
||||
|
||||
// 현재 최상단 패널이 DetailPanel인지 확인
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
if (topPanel?.name === panel_names.DETAIL_PANEL) {
|
||||
// 기존 DetailPanel 닫기 로직 수행
|
||||
dispatch({
|
||||
type: 'POP_PANEL_WITH_VIDEO_RESTORE',
|
||||
payload: panel_names.DETAIL_PANEL,
|
||||
});
|
||||
|
||||
// 비디오 복원 시도 (약간의 지연 후)
|
||||
setTimeout(() => {
|
||||
dispatch(restoreVideoOnBack());
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* [251114] 명시적 포커스 이동
|
||||
* Panel의 비동기 작업(useEffect, 타이머 등)이 포커스를 탈취하는 것을 방지
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -588,6 +588,8 @@
|
||||
|
||||
.overlay {
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0);
|
||||
pointer-events: auto;
|
||||
z-index: 10;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% {
|
||||
@@ -714,6 +716,7 @@
|
||||
|
||||
.controlsHandleAbove {
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,15 @@
|
||||
* - 메모리 효율성 우선
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import DurationFmt from 'ilib/lib/DurationFmt';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -33,7 +41,7 @@ import {
|
||||
setMediaControlToggle,
|
||||
startMediaAutoClose,
|
||||
stopMediaAutoClose,
|
||||
resetMediaAutoClose
|
||||
resetMediaAutoClose,
|
||||
} from '../../actions/mediaOverlayActions';
|
||||
|
||||
import css from './MediaPlayer.module.less';
|
||||
@@ -49,11 +57,12 @@ const RootContainer = SpotlightContainerDecorator(
|
||||
|
||||
// DurationFmt memoization
|
||||
const memoGetDurFmt = memoize(
|
||||
() => new DurationFmt({
|
||||
length: 'medium',
|
||||
style: 'clock',
|
||||
useNative: false,
|
||||
})
|
||||
() =>
|
||||
new DurationFmt({
|
||||
length: 'medium',
|
||||
style: 'clock',
|
||||
useNative: false,
|
||||
})
|
||||
);
|
||||
|
||||
const getDurFmt = () => {
|
||||
@@ -68,7 +77,7 @@ const getDurFmt = () => {
|
||||
format: (time) => {
|
||||
if (!time || !time.millisecond) return '00:00';
|
||||
return secondsToTime(Math.floor(time.millisecond / 1000));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -159,7 +168,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
try {
|
||||
// URL 파싱 시도
|
||||
const url = new URL(src);
|
||||
return ['youtube.com', 'youtu.be', 'm.youtube.com'].some(domain =>
|
||||
return ['youtube.com', 'youtu.be', 'm.youtube.com'].some((domain) =>
|
||||
url.hostname.includes(domain)
|
||||
);
|
||||
} catch {
|
||||
@@ -231,65 +240,75 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
}
|
||||
}, [ActualVideoComponent]);
|
||||
|
||||
const handleUpdate = useCallback((ev) => {
|
||||
const el = videoRef.current;
|
||||
if (!el) return;
|
||||
const handleUpdate = useCallback(
|
||||
(ev) => {
|
||||
const el = videoRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const newCurrentTime = el.currentTime || 0;
|
||||
const newDuration = el.duration || 0;
|
||||
const newCurrentTime = el.currentTime || 0;
|
||||
const newDuration = el.duration || 0;
|
||||
|
||||
// 상태 업데이트
|
||||
setCurrentTime(newCurrentTime);
|
||||
setDuration(newDuration);
|
||||
setPaused(el.paused);
|
||||
setLoading(el.loading || false);
|
||||
setError(el.error || null);
|
||||
// 상태 업데이트
|
||||
setCurrentTime(newCurrentTime);
|
||||
setDuration(newDuration);
|
||||
setPaused(el.paused);
|
||||
setLoading(el.loading || false);
|
||||
setError(el.error || null);
|
||||
|
||||
// 함수형 업데이트로 stale closure 방지
|
||||
setSourceUnavailable((prevUnavailable) =>
|
||||
(el.loading && prevUnavailable) || el.error
|
||||
);
|
||||
// 함수형 업데이트로 stale closure 방지
|
||||
setSourceUnavailable((prevUnavailable) => (el.loading && prevUnavailable) || el.error);
|
||||
|
||||
// Proportion 계산
|
||||
updateProportionLoaded(); // 플랫폼별 계산 함수 호출
|
||||
setProportionPlayed(newDuration > 0 ? newCurrentTime / newDuration : 0);
|
||||
// Proportion 계산
|
||||
updateProportionLoaded(); // 플랫폼별 계산 함수 호출
|
||||
setProportionPlayed(newDuration > 0 ? newCurrentTime / newDuration : 0);
|
||||
|
||||
// 콜백 호출
|
||||
if (ev.type === 'timeupdate' && onTimeUpdate) {
|
||||
onTimeUpdate(ev);
|
||||
}
|
||||
if (ev.type === 'loadeddata' && onLoadedData) {
|
||||
onLoadedData(ev);
|
||||
}
|
||||
if (ev.type === 'loadedmetadata' && onLoadedMetadata) {
|
||||
onLoadedMetadata(ev);
|
||||
}
|
||||
if (ev.type === 'durationchange' && onDurationChange) {
|
||||
onDurationChange(ev);
|
||||
}
|
||||
}, [onTimeUpdate, onLoadedData, onLoadedMetadata, onDurationChange, updateProportionLoaded]);
|
||||
// 콜백 호출
|
||||
if (ev.type === 'timeupdate' && onTimeUpdate) {
|
||||
onTimeUpdate(ev);
|
||||
}
|
||||
if (ev.type === 'loadeddata' && onLoadedData) {
|
||||
onLoadedData(ev);
|
||||
}
|
||||
if (ev.type === 'loadedmetadata' && onLoadedMetadata) {
|
||||
onLoadedMetadata(ev);
|
||||
}
|
||||
if (ev.type === 'durationchange' && onDurationChange) {
|
||||
onDurationChange(ev);
|
||||
}
|
||||
},
|
||||
[onTimeUpdate, onLoadedData, onLoadedMetadata, onDurationChange, updateProportionLoaded]
|
||||
);
|
||||
|
||||
const handleEnded = useCallback((e) => {
|
||||
if (onEnded) {
|
||||
onEnded(e);
|
||||
}
|
||||
}, [onEnded]);
|
||||
const handleEnded = useCallback(
|
||||
(e) => {
|
||||
if (onEnded) {
|
||||
onEnded(e);
|
||||
}
|
||||
},
|
||||
[onEnded]
|
||||
);
|
||||
|
||||
const handleErrorEvent = useCallback((e) => {
|
||||
setError(e);
|
||||
if (onError) {
|
||||
onError(e);
|
||||
}
|
||||
}, [onError]);
|
||||
const handleErrorEvent = useCallback(
|
||||
(e) => {
|
||||
setError(e);
|
||||
if (onError) {
|
||||
onError(e);
|
||||
}
|
||||
},
|
||||
[onError]
|
||||
);
|
||||
|
||||
// ========== Controls Management ==========
|
||||
const showControls = useCallback((timeout = 3000) => {
|
||||
if (disabled || isModal) return;
|
||||
const showControls = useCallback(
|
||||
(timeout = 3000) => {
|
||||
if (disabled || isModal) return;
|
||||
|
||||
console.log('🎬 [MediaPlayer.v2] showControls called, dispatching setMediaControlShow');
|
||||
dispatch(setMediaControlShow());
|
||||
dispatch(startMediaAutoClose(timeout));
|
||||
}, [disabled, isModal, dispatch]);
|
||||
console.log('🎬 [MediaPlayer.v2] showControls called, dispatching setMediaControlShow');
|
||||
dispatch(setMediaControlShow());
|
||||
dispatch(startMediaAutoClose(timeout));
|
||||
},
|
||||
[disabled, isModal, dispatch]
|
||||
);
|
||||
|
||||
const hideControls = useCallback(() => {
|
||||
console.log('🎬 [MediaPlayer.v2] hideControls called, dispatching setMediaControlHide');
|
||||
@@ -351,10 +370,13 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
}, [currentTime, duration, paused, loading, error, proportionLoaded]);
|
||||
|
||||
// ========== Slider Event Handlers ==========
|
||||
const handleSliderChange = useCallback(({ value }) => {
|
||||
const time = value * duration;
|
||||
seek(time);
|
||||
}, [duration, seek]);
|
||||
const handleSliderChange = useCallback(
|
||||
({ value }) => {
|
||||
const time = value * duration;
|
||||
seek(time);
|
||||
},
|
||||
[duration, seek]
|
||||
);
|
||||
|
||||
const handleKnobMove = useCallback((ev) => {
|
||||
if (!videoRef.current) return;
|
||||
@@ -459,17 +481,21 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
}, [dispatch]);
|
||||
|
||||
// ========== Imperative Handle (API) ==========
|
||||
useImperativeHandle(ref, () => ({
|
||||
play,
|
||||
pause,
|
||||
seek,
|
||||
getMediaState,
|
||||
showControls,
|
||||
hideControls,
|
||||
toggleControls,
|
||||
areControlsVisible: () => controlsVisible,
|
||||
getVideoNode: () => videoRef.current,
|
||||
}), [play, pause, seek, getMediaState, showControls, hideControls, toggleControls, controlsVisible]);
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
play,
|
||||
pause,
|
||||
seek,
|
||||
getMediaState,
|
||||
showControls,
|
||||
hideControls,
|
||||
toggleControls,
|
||||
areControlsVisible: () => controlsVisible,
|
||||
getVideoNode: () => videoRef.current,
|
||||
}),
|
||||
[play, pause, seek, getMediaState, showControls, hideControls, toggleControls, controlsVisible]
|
||||
);
|
||||
|
||||
// ========== setApiProvider 호출 ==========
|
||||
useEffect(() => {
|
||||
@@ -486,7 +512,16 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
getVideoNode: () => videoRef.current,
|
||||
});
|
||||
}
|
||||
}, [setApiProvider, play, pause, seek, getMediaState, showControls, hideControls, toggleControls]);
|
||||
}, [
|
||||
setApiProvider,
|
||||
play,
|
||||
pause,
|
||||
seek,
|
||||
getMediaState,
|
||||
showControls,
|
||||
hideControls,
|
||||
toggleControls,
|
||||
]);
|
||||
|
||||
// ========== Video Props ==========
|
||||
const videoProps = useMemo(() => {
|
||||
@@ -535,7 +570,19 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
...baseProps,
|
||||
ref: assignVideoNode,
|
||||
};
|
||||
}, [ActualVideoComponent, assignVideoNode, src, paused, loop, muted, handleLoadStart, handleUpdate, handleEnded, handleErrorEvent, reactPlayerConfig]);
|
||||
}, [
|
||||
ActualVideoComponent,
|
||||
assignVideoNode,
|
||||
src,
|
||||
paused,
|
||||
loop,
|
||||
muted,
|
||||
handleLoadStart,
|
||||
handleUpdate,
|
||||
handleEnded,
|
||||
handleErrorEvent,
|
||||
reactPlayerConfig,
|
||||
]);
|
||||
|
||||
// ========== Spotlight Handler ==========
|
||||
const handleSpotlightFocus = useCallback(() => {
|
||||
@@ -564,18 +611,13 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
>
|
||||
{/* Video Element */}
|
||||
{ActualVideoComponent === Media ? (
|
||||
<ActualVideoComponent {...videoProps}>
|
||||
{children}
|
||||
</ActualVideoComponent>
|
||||
<ActualVideoComponent {...videoProps}>{children}</ActualVideoComponent>
|
||||
) : (
|
||||
<ActualVideoComponent {...videoProps} />
|
||||
)}
|
||||
|
||||
{/* Overlay */}
|
||||
<Overlay
|
||||
bottomControlsVisible={controlsVisible}
|
||||
onClick={handleVideoClick}
|
||||
>
|
||||
<Overlay bottomControlsVisible={controlsVisible} onClick={handleVideoClick}>
|
||||
{/* Loading + Thumbnail */}
|
||||
{loading && thumbnailUrl && (
|
||||
<>
|
||||
@@ -594,12 +636,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
{/* Slider Section */}
|
||||
<div className={css.sliderContainer}>
|
||||
{/* Times - Total */}
|
||||
<Times
|
||||
className={css.times}
|
||||
noCurrentTime
|
||||
total={duration}
|
||||
formatter={getDurFmt()}
|
||||
/>
|
||||
<Times className={css.times} noCurrentTime total={duration} formatter={getDurFmt()} />
|
||||
|
||||
{/* Times - Current */}
|
||||
<Times
|
||||
@@ -663,7 +700,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
onSpotlightRight={handleSpotlightFocus}
|
||||
onSpotlightLeft={handleSpotlightFocus}
|
||||
onClick={handleSpotlightFocus}
|
||||
spotlightDisabled={controlsVisible || shouldDisableSpotlight}
|
||||
spotlightDisabled={controlsVisible || shouldDisableSpotlight || !isModal}
|
||||
/>
|
||||
</RootContainer>
|
||||
);
|
||||
|
||||
@@ -724,6 +724,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
belowContentsVisible: PropTypes.bool,
|
||||
tabContainerVersion: PropTypes.number,
|
||||
tabIndexV2: PropTypes.number,
|
||||
overlayContentsComponent: PropTypes.elementType,
|
||||
dispatch: PropTypes.func,
|
||||
};
|
||||
|
||||
@@ -1121,6 +1122,8 @@ const VideoPlayerBase = class extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
Spotlight.setPointerMode(false);
|
||||
|
||||
// this.startDelayedFeedbackHide();
|
||||
// this.startDelayedTitleHide();
|
||||
|
||||
@@ -1426,6 +1429,15 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.showControlsFromPointer
|
||||
);
|
||||
|
||||
onVideoClickCapture = (ev) => {
|
||||
if (!this.state.mediaControlsVisible && !this.props.panelInfo?.modal) {
|
||||
this.activityDetected();
|
||||
this.onVideoClick();
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
handleControlsHandleAboveHoldPulse = () => {
|
||||
if (shouldJump(this.props, this.state)) {
|
||||
this.handleJump({
|
||||
@@ -1997,6 +2009,11 @@ const VideoPlayerBase = class extends React.Component {
|
||||
// Player Interaction events
|
||||
//
|
||||
onVideoClick = () => {
|
||||
console.log('[VideoPlayer] onVideoClick', {
|
||||
controlsVisible: this.state.mediaControlsVisible,
|
||||
panelInfoModal: this.props.panelInfo?.modal,
|
||||
pointerMode: Spotlight.getPointerMode(),
|
||||
});
|
||||
// tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글
|
||||
if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) {
|
||||
const willShowControls = !this.state.mediaControlsVisible;
|
||||
@@ -2012,7 +2029,18 @@ const VideoPlayerBase = class extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const willShowControls = !this.state.mediaControlsVisible;
|
||||
this.toggleControls();
|
||||
if (willShowControls && !this.props.panelInfo?.modal) {
|
||||
this.restoreOverlayFocus();
|
||||
}
|
||||
};
|
||||
|
||||
restoreOverlayFocus = () => {
|
||||
setTimeout(() => {
|
||||
Spotlight.setPointerMode(false);
|
||||
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||
});
|
||||
};
|
||||
|
||||
onSliderChange = ({ value }) => {
|
||||
@@ -2216,6 +2244,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
setSideContentsVisible,
|
||||
belowContentsVisible,
|
||||
tabContainerVersion,
|
||||
overlayContentsComponent,
|
||||
disclaimer,
|
||||
liveTotalTime,
|
||||
currentLiveTimeSeconds,
|
||||
@@ -2264,6 +2293,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
delete mediaProps.belowContentsVisible;
|
||||
delete mediaProps.tabContainerVersion;
|
||||
delete mediaProps.tabIndexV2;
|
||||
delete mediaProps.overlayContentsComponent;
|
||||
|
||||
mediaProps.autoPlay = !noAutoPlay;
|
||||
mediaProps.className = type !== 'MEDIA' ? css.video : css.media;
|
||||
@@ -2272,6 +2302,9 @@ const VideoPlayerBase = class extends React.Component {
|
||||
mediaProps.onLoadStart = this.handleLoadStart;
|
||||
mediaProps.onUpdate = this.handleEvent;
|
||||
mediaProps.ref = this.setVideoRef;
|
||||
if (!panelInfo.modal) {
|
||||
mediaProps.tabIndex = -1;
|
||||
}
|
||||
|
||||
//yhcho ReactPlayer
|
||||
if ((typeof window === 'object' && !window.PalmSystem) || isYoutube) {
|
||||
@@ -2287,6 +2320,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
}
|
||||
|
||||
const controlsAriaProps = this.getControlsAriaProps();
|
||||
const OverlayContents = overlayContentsComponent || PlayerOverlayContents;
|
||||
|
||||
let proportionSelection = selection;
|
||||
if (proportionSelection != null && this.state.duration) {
|
||||
@@ -2356,6 +2390,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
modalClassName
|
||||
)}
|
||||
onClick={this.activityDetected}
|
||||
onClickCapture={this.onVideoClickCapture}
|
||||
ref={this.setPlayerRef}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
spotlightId={spotlightId}
|
||||
@@ -2439,7 +2474,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
css.controlFeedbackBtnLayer + (this.state.infoVisible ? ' ' + css.lift : '')
|
||||
}
|
||||
>
|
||||
<PlayerOverlayContents
|
||||
<OverlayContents
|
||||
playListInfo={playListInfo && playListInfo}
|
||||
selectedIndex={selectedIndex}
|
||||
onClick={onBackButton}
|
||||
@@ -2571,7 +2606,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
onClick={this.onSpotlightFocus}
|
||||
selectionKeys={controlsHandleAboveSelectionKeys}
|
||||
spotlightDisabled={
|
||||
this.state.mediaControlsVisible || spotlightDisabled || panelInfo.modal
|
||||
this.state.mediaControlsVisible || spotlightDisabled || !panelInfo.modal
|
||||
}
|
||||
/>
|
||||
<Announce ref={this.setAnnounceRef} />
|
||||
|
||||
@@ -594,9 +594,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0);
|
||||
}
|
||||
.overlay {
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0);
|
||||
pointer-events: auto;
|
||||
z-index: 10;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0.25turn);
|
||||
@@ -720,10 +722,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.controlsHandleAbove {
|
||||
pointer-events: none;
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0);
|
||||
}
|
||||
.controlsHandleAbove {
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0);
|
||||
}
|
||||
|
||||
// Skin colors
|
||||
.applySkins({
|
||||
|
||||
@@ -42,6 +42,7 @@ export const SpotlightIds = {
|
||||
PLAYER_TAB_BUTTON: 'playerTabArrow',
|
||||
PLAYER_BACK_BUTTON: 'player-back-button',
|
||||
PLAYER_SUBTITLE_BUTTON: 'player-subtitlebutton',
|
||||
PLAYER_PLAY_BUTTON: 'player-play-button',
|
||||
|
||||
// searchPanel
|
||||
SEARCH_THEME: 'search_theme',
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import {
|
||||
SpotlightContainerDecorator,
|
||||
} from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import {
|
||||
pushPanel,
|
||||
updatePanel,
|
||||
} from '../../../actions/panelActions';
|
||||
import {
|
||||
navigateToDetailFromHome,
|
||||
} from '../../../actions/panelNavigationActions';
|
||||
import { pushPanel, updatePanel, navigateFromBestSeller } from '../../../actions/panelActions';
|
||||
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
|
||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||
import Tag from '../../../components/TItemCard/Tag';
|
||||
import TItemCard from '../../../components/TItemCard/TItemCard';
|
||||
@@ -30,23 +15,13 @@ import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||
import TScroller from '../../../components/TScroller/TScroller';
|
||||
import useScrollReset from '../../../hooks/useScrollReset';
|
||||
import useScrollTo from '../../../hooks/useScrollTo';
|
||||
import {
|
||||
LOG_CONTEXT_NAME,
|
||||
LOG_MESSAGE_ID,
|
||||
panel_names,
|
||||
} from '../../../utils/Config';
|
||||
import {
|
||||
$L,
|
||||
scaleW,
|
||||
} from '../../../utils/helperMethods';
|
||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
|
||||
import { $L, scaleW } from '../../../utils/helperMethods';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import css from './BestSeller.module.less';
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const SpottableComponent = Spottable('div');
|
||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
const BestSeller = ({
|
||||
order,
|
||||
@@ -58,25 +33,15 @@ const BestSeller = ({
|
||||
shelfTitle,
|
||||
}) => {
|
||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||
scrollLeft,
|
||||
true
|
||||
);
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
||||
|
||||
const bestSellerDatas = useSelector(
|
||||
(state) => state.product.bestSellerData?.bestSeller
|
||||
);
|
||||
|
||||
const bestSellerNewDatas = useSelector(
|
||||
(state) =>
|
||||
state.foryou?.recommendInfo?.recommendProduct
|
||||
);
|
||||
|
||||
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
|
||||
|
||||
const bestSellerNewDatas = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
|
||||
|
||||
const [drawChk, setDrawChk] = useState(false);
|
||||
const [firstChk, setFirstChk] = useState(0);
|
||||
@@ -84,59 +49,57 @@ const BestSeller = ({
|
||||
const [bestInfos, setBestInfos] = useState(null);
|
||||
const [bestItemNewData, setBestItemNewData] = useState([]);
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
setBestInfos(
|
||||
bestSellerNewDatas?.filter(
|
||||
(item) => item.recommendTpCd === "BESTSELLER"
|
||||
) || [] // 기본값으로 빈 배열 설정
|
||||
)
|
||||
},[bestSellerNewDatas])
|
||||
bestSellerNewDatas?.filter((item) => item.recommendTpCd === 'BESTSELLER') || [] // 기본값으로 빈 배열 설정
|
||||
);
|
||||
}, [bestSellerNewDatas]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!bestInfos || bestInfos.length === 0) {
|
||||
const baseData = bestSellerDatas?.map((item) => ({
|
||||
...item,
|
||||
foryou: false,
|
||||
})) || [];
|
||||
const baseData =
|
||||
bestSellerDatas?.map((item) => ({
|
||||
...item,
|
||||
foryou: false,
|
||||
})) || [];
|
||||
setBestItemNewData(baseData);
|
||||
return;
|
||||
}
|
||||
|
||||
const recommendedData = bestInfos[0].productInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData.map(item => item.prdtId));
|
||||
|
||||
const baseData = bestSellerDatas?.map((item) => ({
|
||||
...item,
|
||||
foryou: recommendedPrdtIds.has(item.prdtId),
|
||||
})) || [];
|
||||
|
||||
|
||||
const recommendedData =
|
||||
bestInfos[0].productInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
||||
|
||||
const baseData =
|
||||
bestSellerDatas?.map((item) => ({
|
||||
...item,
|
||||
foryou: recommendedPrdtIds.has(item.prdtId),
|
||||
})) || [];
|
||||
|
||||
setBestItemNewData(baseData);
|
||||
}, [bestSellerDatas, bestInfos]);
|
||||
|
||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
setDrawChk(true);
|
||||
}, [bestSellerNewDatas]);
|
||||
|
||||
const handleCardClick = useCallback(
|
||||
(patnrId, prdtId) => () => {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
patnrId: patnrId,
|
||||
prdtId: prdtId,
|
||||
nowShelf: spotlightId,
|
||||
},
|
||||
navigateFromBestSeller({
|
||||
patnrId,
|
||||
prdtId,
|
||||
spotlightId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, spotlightId]
|
||||
);
|
||||
|
||||
const handleMoreCardClick = useCallback(() => {
|
||||
@@ -144,7 +107,7 @@ const BestSeller = ({
|
||||
pushPanel({
|
||||
name: panel_names.TRENDING_NOW_PANEL,
|
||||
panelInfo: {
|
||||
pageName: "BS",
|
||||
pageName: 'BS',
|
||||
focusedContainerId: SpotlightIds.TRENDING_NOW_BEST_SELLER,
|
||||
},
|
||||
})
|
||||
@@ -171,21 +134,18 @@ const BestSeller = ({
|
||||
if (firstChk === 0 && itemIndex === 0) {
|
||||
const c = Spotlight.getCurrent();
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
cAriaLabel = "Best Seller, Heading 1," + cAriaLabel;
|
||||
c.setAttribute("aria-label", cAriaLabel);
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
cAriaLabel = 'Best Seller, Heading 1,' + cAriaLabel;
|
||||
c.setAttribute('aria-label', cAriaLabel);
|
||||
}
|
||||
setFirstChk(1);
|
||||
} else if (firstChk === 1 && itemIndex === 0) {
|
||||
const c = Spotlight.getCurrent();
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
if (cAriaLabel) {
|
||||
const newcAriaLabel = cAriaLabel.replace(
|
||||
"Best Seller, Heading 1,",
|
||||
""
|
||||
);
|
||||
c.setAttribute("aria-label", newcAriaLabel);
|
||||
const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
|
||||
c.setAttribute('aria-label', newcAriaLabel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -219,7 +179,6 @@ const BestSeller = ({
|
||||
handleShelfFocus();
|
||||
}
|
||||
}, [handleShelfFocus]);
|
||||
|
||||
|
||||
return (
|
||||
<Container
|
||||
@@ -230,7 +189,7 @@ const BestSeller = ({
|
||||
onFocus={_handleShelfFocus}
|
||||
>
|
||||
<SectionTitle
|
||||
title={$L("BEST SELLER")}
|
||||
title={$L('BEST SELLER')}
|
||||
data-title-index="homeBestSellerTitle"
|
||||
label="BEST SELLER"
|
||||
/>
|
||||
@@ -262,15 +221,15 @@ const BestSeller = ({
|
||||
) => {
|
||||
const rankText =
|
||||
rankOrd === 1
|
||||
? rankOrd + "st"
|
||||
? rankOrd + 'st'
|
||||
: rankOrd === 2
|
||||
? rankOrd + "nd"
|
||||
? rankOrd + 'nd'
|
||||
: rankOrd === 3
|
||||
? rankOrd + "rd"
|
||||
: rankOrd + "th";
|
||||
? rankOrd + 'rd'
|
||||
: rankOrd + 'th';
|
||||
return (
|
||||
<TItemCardNew
|
||||
key={"subItem" + itemIndex}
|
||||
key={'subItem' + itemIndex}
|
||||
contextName={LOG_CONTEXT_NAME.HOME}
|
||||
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
|
||||
order={itemIndex + 1}
|
||||
@@ -291,13 +250,13 @@ const BestSeller = ({
|
||||
onBlur={handleBlur(itemIndex)}
|
||||
onClick={handleCardClick(patnrId, prdtId)}
|
||||
offerInfo={offerInfo}
|
||||
spotlightId={"bestsellerItem" + itemIndex}
|
||||
spotlightId={'bestsellerItem' + itemIndex}
|
||||
firstLabel={rankText}
|
||||
label={itemIndex * 1 + 1 + " of " + bestItemNewData.length}
|
||||
label={itemIndex * 1 + 1 + ' of ' + bestItemNewData.length}
|
||||
lastLabel=" go to detail, button"
|
||||
euEnrgLblInfos={euEnrgLblInfos}
|
||||
>
|
||||
{foryou === true && <Tag text={"For You"} />}
|
||||
{foryou === true && <Tag text={'For You'} />}
|
||||
</TItemCardNew>
|
||||
);
|
||||
}
|
||||
@@ -308,7 +267,7 @@ const BestSeller = ({
|
||||
<SpottableComponent
|
||||
className={css.displayBox}
|
||||
onClick={handleMoreCardClick}
|
||||
spotlightId={"bestseller-item-more-btn"}
|
||||
spotlightId={'bestseller-item-more-btn'}
|
||||
aria-label="See more button"
|
||||
></SpottableComponent>
|
||||
</div>
|
||||
@@ -318,4 +277,4 @@ const BestSeller = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default BestSeller;
|
||||
export default BestSeller;
|
||||
|
||||
@@ -1,57 +1,38 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
|
||||
import { clearSMS } from "../../../actions/appDataActions";
|
||||
import {
|
||||
alertToast,
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
} from "../../../actions/commonActions";
|
||||
import { clearSMS } from '../../../actions/appDataActions';
|
||||
import { alertToast, setHidePopup, setShowPopup } from '../../../actions/commonActions';
|
||||
import {
|
||||
clearGetProductCouponDownload,
|
||||
getProductCouponDownload,
|
||||
} from "../../../actions/couponActions";
|
||||
import { setEventPopClickInfo } from "../../../actions/eventActions";
|
||||
import { sendLogGNB, sendLogShopByMobile } from "../../../actions/logActions";
|
||||
import { pushPanel } from "../../../actions/panelActions";
|
||||
import { startVideoPlayer } from "../../../actions/playActions";
|
||||
import MobileSendPopUp from "../../../components/MobileSend/MobileSendPopUp";
|
||||
import TPopUp from "../../../components/TPopUp/TPopUp";
|
||||
import { launchMembershipApp } from "../../../lunaSend";
|
||||
import {
|
||||
ACTIVE_POPUP,
|
||||
LOG_MENU,
|
||||
LOG_TP_NO,
|
||||
MYINFO_TABS,
|
||||
panel_names,
|
||||
} from "../../../utils/Config";
|
||||
import { $L, formatLocalDateTime } from "../../../utils/helperMethods";
|
||||
import css from "../EventPopUpBanner/EventPopUpBanner.module.less";
|
||||
} from '../../../actions/couponActions';
|
||||
import { setEventPopClickInfo } from '../../../actions/eventActions';
|
||||
import { sendLogGNB, sendLogShopByMobile } from '../../../actions/logActions';
|
||||
import { pushPanel, navigateFromEventPopup } from '../../../actions/panelActions';
|
||||
import { startVideoPlayer } from '../../../actions/playActions';
|
||||
import MobileSendPopUp from '../../../components/MobileSend/MobileSendPopUp';
|
||||
import TPopUp from '../../../components/TPopUp/TPopUp';
|
||||
import { launchMembershipApp } from '../../../lunaSend';
|
||||
import { ACTIVE_POPUP, LOG_MENU, LOG_TP_NO, MYINFO_TABS, panel_names } from '../../../utils/Config';
|
||||
import { $L, formatLocalDateTime } from '../../../utils/helperMethods';
|
||||
import css from '../EventPopUpBanner/EventPopUpBanner.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
export default function EventPopUpBanner() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const eventPopInfosData = useSelector(
|
||||
(state) => state.event.eventData.eventPopInfo
|
||||
);
|
||||
const eventPopInfosData = useSelector((state) => state.event.eventData.eventPopInfo);
|
||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||
const activePopup = useSelector((state) => state.common.popup.activePopup);
|
||||
const userNumber = useSelector(
|
||||
(state) => state.common?.appStatus.loginUserData.userNumber
|
||||
);
|
||||
const userNumber = useSelector((state) => state.common?.appStatus.loginUserData.userNumber);
|
||||
const smsSuccess = useSelector((state) => state.appData.sendSms.retCode);
|
||||
const couponDownloadSuccess = useSelector(
|
||||
(state) => state.coupon?.couponDownloadSuccess
|
||||
);
|
||||
const couponDownloadSuccess = useSelector((state) => state.coupon?.couponDownloadSuccess);
|
||||
|
||||
const timerRef = useRef();
|
||||
const shopByMobileLogRef = useRef(null);
|
||||
@@ -61,7 +42,7 @@ export default function EventPopUpBanner() {
|
||||
const evntTpCd = eventPopInfosData?.evntTpCd;
|
||||
const evntNm = eventPopInfosData?.evntNm;
|
||||
|
||||
const [smsTpCd, setSmsTpCd] = useState("");
|
||||
const [smsTpCd, setSmsTpCd] = useState('');
|
||||
const [eventPopData, setEventPopData] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -77,12 +58,12 @@ export default function EventPopUpBanner() {
|
||||
|
||||
const handleApply = useCallback(() => {
|
||||
if (eventPopData) {
|
||||
dispatch(setEventPopClickInfo({ evntApplcnFlag: "Y", evntId }));
|
||||
dispatch(setEventPopClickInfo({ evntApplcnFlag: 'Y', evntId }));
|
||||
|
||||
switch (evntTpCd) {
|
||||
case "EVT00102": // curation
|
||||
case "EVT00104": // curation + prize
|
||||
case "EVT00107": {
|
||||
case 'EVT00102': // curation
|
||||
case 'EVT00104': // curation + prize
|
||||
case 'EVT00107': {
|
||||
// curation + billing coupon
|
||||
dispatch(setHidePopup());
|
||||
dispatch(
|
||||
@@ -96,43 +77,43 @@ export default function EventPopUpBanner() {
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "EVT00105": {
|
||||
case 'EVT00105': {
|
||||
// coupon Only
|
||||
setSmsTpCd("APP00212");
|
||||
setSmsTpCd('APP00212');
|
||||
dispatch(setShowPopup(ACTIVE_POPUP.smsPopup));
|
||||
|
||||
const params = {
|
||||
befPrice: "",
|
||||
curationId: eventPopData?.curationId ?? "",
|
||||
curationNm: eventPopData?.curationNm ?? "",
|
||||
evntId: eventPopData?.evntId ?? "",
|
||||
evntNm: eventPopData?.evntNm ?? "",
|
||||
lastPrice: "",
|
||||
lgCatCd: "",
|
||||
lgCatNm: "",
|
||||
liveFlag: "N",
|
||||
befPrice: '',
|
||||
curationId: eventPopData?.curationId ?? '',
|
||||
curationNm: eventPopData?.curationNm ?? '',
|
||||
evntId: eventPopData?.evntId ?? '',
|
||||
evntNm: eventPopData?.evntNm ?? '',
|
||||
lastPrice: '',
|
||||
lgCatCd: '',
|
||||
lgCatNm: '',
|
||||
liveFlag: 'N',
|
||||
locDt: formatLocalDateTime(new Date()),
|
||||
logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.SHOP_BY_MOBILE,
|
||||
mbphNoFlag: "N",
|
||||
mbphNoFlag: 'N',
|
||||
patncNm: eventPopData?.patncNm,
|
||||
patnrId: eventPopData?.patnrId,
|
||||
prdtId: "",
|
||||
prdtNm: "",
|
||||
revwGrd: "",
|
||||
rewdAplyFlag: "N",
|
||||
shopByMobileFlag: "Y",
|
||||
shopTpNm: "popevent",
|
||||
showId: "",
|
||||
showNm: "",
|
||||
trmsAgrFlag: "N",
|
||||
tsvFlag: "N",
|
||||
prdtId: '',
|
||||
prdtNm: '',
|
||||
revwGrd: '',
|
||||
rewdAplyFlag: 'N',
|
||||
shopByMobileFlag: 'Y',
|
||||
shopTpNm: 'popevent',
|
||||
showId: '',
|
||||
showNm: '',
|
||||
trmsAgrFlag: 'N',
|
||||
tsvFlag: 'N',
|
||||
};
|
||||
|
||||
dispatch(sendLogShopByMobile(params));
|
||||
shopByMobileLogRef.current = params;
|
||||
break;
|
||||
}
|
||||
case "EVT00108": {
|
||||
case 'EVT00108': {
|
||||
// Direct+Billng+Coupon
|
||||
if (!userNumber) {
|
||||
dispatch(setShowPopup(ACTIVE_POPUP.loginPopup));
|
||||
@@ -152,7 +133,7 @@ export default function EventPopUpBanner() {
|
||||
|
||||
useEffect(() => {
|
||||
if (couponDownloadSuccess === 0) {
|
||||
dispatch(alertToast($L("Downloading coupon complete.")));
|
||||
dispatch(alertToast($L('Downloading coupon complete.')));
|
||||
if (eventPopData && eventPopData?.shptmLnkInfo?.shptmLnkTpCd) {
|
||||
dispatch(setHidePopup());
|
||||
navigateToLinkByTypeCode();
|
||||
@@ -174,7 +155,7 @@ export default function EventPopUpBanner() {
|
||||
|
||||
const handleSkip = useCallback(() => {
|
||||
if (eventPopInfosData) {
|
||||
dispatch(setEventPopClickInfo({ evntApplcnFlag: "N", evntId }));
|
||||
dispatch(setEventPopClickInfo({ evntApplcnFlag: 'N', evntId }));
|
||||
dispatch(setHidePopup());
|
||||
}
|
||||
|
||||
@@ -185,7 +166,7 @@ export default function EventPopUpBanner() {
|
||||
|
||||
useEffect(() => {
|
||||
timerRef.current = setTimeout(() => {
|
||||
Spotlight.focus("tPopupBtn1");
|
||||
Spotlight.focus('tPopupBtn1');
|
||||
}, 100);
|
||||
return () => clearTimeout(timerRef.current);
|
||||
}, []);
|
||||
@@ -200,23 +181,23 @@ export default function EventPopUpBanner() {
|
||||
|
||||
if (eventPopData && eventPopData.shptmLnkInfo) {
|
||||
switch (eventPopData.shptmLnkInfo.shptmLnkTpCd) {
|
||||
case "EVT00201":
|
||||
case 'EVT00201':
|
||||
break;
|
||||
case "EVT00202":
|
||||
case 'EVT00202':
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.FEATURED_BRANDS_PANEL,
|
||||
panelInfo: {
|
||||
from: "gnb",
|
||||
from: 'gnb',
|
||||
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
|
||||
},
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "EVT00203":
|
||||
case 'EVT00203':
|
||||
dispatch(pushPanel({ name: panel_names.TRENDING_NOW_PANEL }));
|
||||
break;
|
||||
case "EVT00204":
|
||||
case 'EVT00204':
|
||||
pushPanel({
|
||||
name: panel_names.HOT_PICKS_PANEL,
|
||||
panelInfo: {
|
||||
@@ -225,7 +206,7 @@ export default function EventPopUpBanner() {
|
||||
},
|
||||
});
|
||||
break;
|
||||
case "EVT00205":
|
||||
case 'EVT00205':
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.ON_SALE_PANEL,
|
||||
@@ -233,7 +214,7 @@ export default function EventPopUpBanner() {
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "EVT00206":
|
||||
case 'EVT00206':
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.CATEGORY_PANEL,
|
||||
@@ -243,28 +224,25 @@ export default function EventPopUpBanner() {
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "EVT00207":
|
||||
case 'EVT00207':
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
|
||||
prdtId: eventPopData.shptmLnkInfo?.lnkPrdtId,
|
||||
},
|
||||
navigateFromEventPopup({
|
||||
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
|
||||
prdtId: eventPopData.shptmLnkInfo?.lnkPrdtId,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "EVT00208":
|
||||
case 'EVT00208':
|
||||
dispatch(
|
||||
startVideoPlayer({
|
||||
modal: false,
|
||||
patnrId: eventPopData.shptmLnkInfo?.lnkPatnrId,
|
||||
showId: eventPopData.shptmLnkInfo?.lnkBrdcId,
|
||||
shptmBanrTpNm: "VOD",
|
||||
shptmBanrTpNm: 'VOD',
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "EVT00209":
|
||||
case 'EVT00209':
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.THEME_CURATION_PANEL,
|
||||
@@ -274,12 +252,12 @@ export default function EventPopUpBanner() {
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "EVT00210":
|
||||
case 'EVT00210':
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.MY_PAGE_PANEL,
|
||||
panelInfo: {
|
||||
menuNm: "My Info",
|
||||
menuNm: 'My Info',
|
||||
menuOrd: 3,
|
||||
tabForced: MYINFO_TABS.COUPON,
|
||||
},
|
||||
@@ -307,8 +285,8 @@ export default function EventPopUpBanner() {
|
||||
onClose={handleSkip}
|
||||
onClick={handleApply}
|
||||
hasButton
|
||||
button1Text={$L("Apply Now")}
|
||||
button2Text={$L("Skip")}
|
||||
button1Text={$L('Apply Now')}
|
||||
button2Text={$L('Skip')}
|
||||
className={css.eventPopup}
|
||||
>
|
||||
{eventPopInfosData && (
|
||||
@@ -322,10 +300,8 @@ export default function EventPopUpBanner() {
|
||||
<MobileSendPopUp
|
||||
open={popupVisible}
|
||||
onClose={onClose}
|
||||
title={$L("Send To Mobile")}
|
||||
subTitle={$L(
|
||||
"Enter your mobile number to receive detailed information via SMS"
|
||||
)}
|
||||
title={$L('Send To Mobile')}
|
||||
subTitle={$L('Enter your mobile number to receive detailed information via SMS')}
|
||||
smsTpCd={smsTpCd}
|
||||
evntId={eventPopData.evntId}
|
||||
patnrId={eventPopData.patnrId}
|
||||
@@ -337,7 +313,7 @@ export default function EventPopUpBanner() {
|
||||
kind="textPopup"
|
||||
open={popupVisible}
|
||||
hasText
|
||||
text={$L("Downloading coupon complete.")}
|
||||
text={$L('Downloading coupon complete.')}
|
||||
/>
|
||||
)}
|
||||
{activePopup === ACTIVE_POPUP.loginPopup && (
|
||||
@@ -345,10 +321,10 @@ export default function EventPopUpBanner() {
|
||||
kind="textPopup"
|
||||
hasText
|
||||
open={popupVisible}
|
||||
text={$L("Would you like to sign in?")}
|
||||
text={$L('Would you like to sign in?')}
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
button2Text={$L("CANCEL")}
|
||||
button1Text={$L('OK')}
|
||||
button2Text={$L('CANCEL')}
|
||||
onClick={handleLogin}
|
||||
onClose={onClose}
|
||||
></TPopUp>
|
||||
|
||||
@@ -17,7 +17,7 @@ import liveShow from '../../../../assets/images/tag-liveshow.png';
|
||||
import { changeAppStatus } from '../../../actions/commonActions';
|
||||
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
|
||||
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
|
||||
import { pushPanel } from '../../../actions/panelActions';
|
||||
import { pushPanel, navigateFromRandomUnit } from '../../../actions/panelActions';
|
||||
import {
|
||||
finishVideoPreview,
|
||||
startVideoPlayer,
|
||||
@@ -462,17 +462,11 @@ export default function RandomUnit({
|
||||
|
||||
// 투데이즈딜 클릭
|
||||
const todayDealClick = useCallback(() => {
|
||||
if (playerPanelInfo?.modal !== false) {
|
||||
finishAndUnlock();
|
||||
}
|
||||
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
patnrId: randomData.patnrId,
|
||||
prdtId: randomData.prdtId,
|
||||
},
|
||||
navigateFromRandomUnit({
|
||||
patnrId: randomData.patnrId,
|
||||
prdtId: randomData.prdtId,
|
||||
type: 'product',
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,41 +1,24 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import {
|
||||
SpotlightContainerDecorator,
|
||||
} from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import { pushPanel } from '../../../actions/panelActions';
|
||||
import { pushPanel, navigateFromPickedForYou } from '../../../actions/panelActions';
|
||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||
import TScroller from '../../../components/TScroller/TScroller';
|
||||
import useScrollReset from '../../../hooks/useScrollReset';
|
||||
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 { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import css from './PickedForYou.module.less';
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const SpottableComponent = Spottable('div');
|
||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
const PickedForYou = ({
|
||||
order,
|
||||
@@ -46,36 +29,25 @@ const PickedForYou = ({
|
||||
shelfLocation,
|
||||
shelfTitle,
|
||||
}) => {
|
||||
|
||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||
scrollLeft,
|
||||
true
|
||||
);
|
||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
||||
|
||||
const justForYouDatasNew = useSelector(
|
||||
(state) => state.foryou?.recommendInfo?.recommendProduct
|
||||
)
|
||||
|
||||
const justForYouDatasNew = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
|
||||
|
||||
const [drawChk, setDrawChk] = useState(false);
|
||||
const [firstChk, setFirstChk] = useState(0);
|
||||
|
||||
const [pickedForYou, setPickedForYou] = useState(null);
|
||||
|
||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||
useEffect(()=>{
|
||||
setPickedForYou(
|
||||
justForYouDatasNew?.filter(
|
||||
(item) => item.recommendTpCd === "PICKEDFORYOU"
|
||||
)
|
||||
)
|
||||
},[justForYouDatasNew])
|
||||
|
||||
|
||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||
useEffect(() => {
|
||||
setPickedForYou(justForYouDatasNew?.filter((item) => item.recommendTpCd === 'PICKEDFORYOU'));
|
||||
}, [justForYouDatasNew]);
|
||||
|
||||
useEffect(() => {
|
||||
setDrawChk(true);
|
||||
}, [justForYouDatasNew]);
|
||||
@@ -83,17 +55,14 @@ const PickedForYou = ({
|
||||
const handleCardClick = useCallback(
|
||||
(patnrId, prdtId) => () => {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
patnrId: patnrId,
|
||||
prdtId: prdtId,
|
||||
nowShelf: spotlightId,
|
||||
},
|
||||
navigateFromPickedForYou({
|
||||
patnrId,
|
||||
prdtId,
|
||||
spotlightId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, spotlightId]
|
||||
);
|
||||
|
||||
const handleMoreCardClick = useCallback(() => {
|
||||
@@ -101,7 +70,7 @@ const PickedForYou = ({
|
||||
pushPanel({
|
||||
name: panel_names.TRENDING_NOW_PANEL,
|
||||
panelInfo: {
|
||||
pageName: "BS",
|
||||
pageName: 'BS',
|
||||
focusedContainerId: SpotlightIds.TRENDING_NOW_BEST_SELLER,
|
||||
},
|
||||
})
|
||||
@@ -128,21 +97,18 @@ const PickedForYou = ({
|
||||
if (firstChk === 0 && itemIndex === 0) {
|
||||
const c = Spotlight.getCurrent();
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
cAriaLabel = "Best Seller, Heading 1," + cAriaLabel;
|
||||
c.setAttribute("aria-label", cAriaLabel);
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
cAriaLabel = 'Best Seller, Heading 1,' + cAriaLabel;
|
||||
c.setAttribute('aria-label', cAriaLabel);
|
||||
}
|
||||
setFirstChk(1);
|
||||
} else if (firstChk === 1 && itemIndex === 0) {
|
||||
const c = Spotlight.getCurrent();
|
||||
if (c) {
|
||||
let cAriaLabel = c.getAttribute("aria-label");
|
||||
let cAriaLabel = c.getAttribute('aria-label');
|
||||
if (cAriaLabel) {
|
||||
const newcAriaLabel = cAriaLabel.replace(
|
||||
"Best Seller, Heading 1,",
|
||||
""
|
||||
);
|
||||
c.setAttribute("aria-label", newcAriaLabel);
|
||||
const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
|
||||
c.setAttribute('aria-label', newcAriaLabel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -186,7 +152,7 @@ const PickedForYou = ({
|
||||
onFocus={_handleShelfFocus}
|
||||
>
|
||||
<SectionTitle
|
||||
title={$L("PICKED FOR YOU")}
|
||||
title={$L('PICKED FOR YOU')}
|
||||
data-title-index="homeBestSellerTitle"
|
||||
label="PICKED FOR YOU"
|
||||
/>
|
||||
@@ -198,26 +164,26 @@ const PickedForYou = ({
|
||||
noScrollByWheel
|
||||
>
|
||||
{pickedForYou &&
|
||||
pickedForYou?.[0] &&
|
||||
pickedForYou?.[0] &&
|
||||
pickedForYou?.[0].productInfos.map(
|
||||
(
|
||||
{
|
||||
prdtId,
|
||||
imgUrl,
|
||||
priceInfo,
|
||||
prdtNm,
|
||||
prdtNm,
|
||||
patnrId,
|
||||
offerInfo,
|
||||
brndNm,
|
||||
patncNm,
|
||||
catNm,
|
||||
euEnrgLblInfos
|
||||
euEnrgLblInfos,
|
||||
},
|
||||
itemIndex
|
||||
) => {
|
||||
return (
|
||||
<TItemCardNew
|
||||
key={"subItem" + itemIndex}
|
||||
key={'subItem' + itemIndex}
|
||||
contextName={LOG_CONTEXT_NAME.HOME}
|
||||
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
|
||||
order={itemIndex + 1}
|
||||
@@ -230,14 +196,14 @@ const PickedForYou = ({
|
||||
imageAlt={prdtId}
|
||||
imageSource={imgUrl}
|
||||
priceInfo={priceInfo}
|
||||
productName={prdtNm}
|
||||
productName={prdtNm}
|
||||
productId={prdtId}
|
||||
onFocus={handleFocus(itemIndex)}
|
||||
onBlur={handleBlur(itemIndex)}
|
||||
onClick={handleCardClick(patnrId, prdtId)}
|
||||
offerInfo={offerInfo}
|
||||
spotlightId={"bestsellerItem" + itemIndex}
|
||||
label={itemIndex * 1 + 1 + " of " + pickedForYou?.[0].productInfos.length}
|
||||
spotlightId={'bestsellerItem' + itemIndex}
|
||||
label={itemIndex * 1 + 1 + ' of ' + pickedForYou?.[0].productInfos.length}
|
||||
lastLabel=" go to detail, button"
|
||||
euEnrgLblInfos={euEnrgLblInfos}
|
||||
/>
|
||||
@@ -250,7 +216,7 @@ const PickedForYou = ({
|
||||
<SpottableComponent
|
||||
className={css.displayBox}
|
||||
onClick={handleMoreCardClick}
|
||||
spotlightId={"bestseller-item-more-btn"}
|
||||
spotlightId={'bestseller-item-more-btn'}
|
||||
aria-label="See more button"
|
||||
></SpottableComponent>
|
||||
</div>
|
||||
|
||||
@@ -1,46 +1,28 @@
|
||||
import React, {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { memo, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
SpotlightContainerDecorator,
|
||||
} from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
||||
|
||||
import { sendLogCuration } from '../../../actions/logActions';
|
||||
import { getSubCategory } from '../../../actions/mainActions';
|
||||
import { pushPanel } from '../../../actions/panelActions';
|
||||
import { pushPanel, navigateFromSubCategory } from '../../../actions/panelActions';
|
||||
import Tag from '../../../components/TItemCard/Tag';
|
||||
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||
import TScroller from '../../../components/TScroller/TScroller';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useScrollReset from '../../../hooks/useScrollReset';
|
||||
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 CategoryNav from '../../HomePanel/SubCategory/CategoryNav/CategoryNav';
|
||||
import css from '../../HomePanel/SubCategory/SubCategory.module.less';
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const Container = SpotlightContainerDecorator({ enterTo: null }, "div");
|
||||
const ContainerBasic = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const SpottableComponent = Spottable('div');
|
||||
const Container = SpotlightContainerDecorator({ enterTo: null }, 'div');
|
||||
const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
const getExpsOrdByLgCatCd = (array, value) => {
|
||||
const expsOrd = array.findIndex(({ lgCatCd }) => value === lgCatCd) + 1;
|
||||
@@ -58,21 +40,12 @@ export default memo(function SubCategory({
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
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 categoryItemInfos = useSelector(
|
||||
(state) => state.main.subCategoryData?.categoryItemInfos
|
||||
);
|
||||
const categoryInfos = useSelector((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 nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||
|
||||
@@ -110,9 +83,9 @@ export default memo(function SubCategory({
|
||||
getSubCategory(
|
||||
{
|
||||
lgCatCd: currentLgCatCd,
|
||||
pageSize: "10",
|
||||
tabType: "CAT00102",
|
||||
filterType: "CAT00202",
|
||||
pageSize: '10',
|
||||
tabType: 'CAT00102',
|
||||
filterType: 'CAT00202',
|
||||
recommendIncFlag: 'Y',
|
||||
},
|
||||
1
|
||||
@@ -124,7 +97,7 @@ export default memo(function SubCategory({
|
||||
}, [currentLgCatCd, dispatch, firstChk]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!nowMenuRef.current || !nowMenuRef.current.startsWith("Home")) {
|
||||
if (!nowMenuRef.current || !nowMenuRef.current.startsWith('Home')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,40 +152,47 @@ export default memo(function SubCategory({
|
||||
const handleCardClick = useCallback(
|
||||
(patnrId, prdtId) => () => {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: { patnrId, prdtId, nowShelf: spotlightId },
|
||||
navigateFromSubCategory({
|
||||
patnrId,
|
||||
prdtId,
|
||||
spotlightId,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, spotlightId]
|
||||
);
|
||||
|
||||
const handleScrollRight = useCallback((e) => {
|
||||
const container = e.currentTarget?.parentNode;
|
||||
const x = container.scrollWidth - container.clientWidth;
|
||||
const handleScrollRight = useCallback(
|
||||
(e) => {
|
||||
const container = e.currentTarget?.parentNode;
|
||||
const x = container.scrollWidth - container.clientWidth;
|
||||
|
||||
setTimeout(() => scrollLeft({ x, animate: true }));
|
||||
}, [scrollLeft]);
|
||||
setTimeout(() => scrollLeft({ x, animate: true }));
|
||||
},
|
||||
[scrollLeft]
|
||||
);
|
||||
|
||||
const handleMoreCardClick = useCallback((e) => {
|
||||
const lgCatCd = e.currentTarget?.getAttribute("data-catcd-num");
|
||||
const lgCatNm = e.currentTarget?.getAttribute("data-catcd-nm");
|
||||
const tab = 0;
|
||||
const handleMoreCardClick = useCallback(
|
||||
(e) => {
|
||||
const lgCatCd = e.currentTarget?.getAttribute('data-catcd-num');
|
||||
const lgCatNm = e.currentTarget?.getAttribute('data-catcd-nm');
|
||||
const tab = 0;
|
||||
|
||||
if (lgCatCd && lgCatNm) {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.CATEGORY_PANEL,
|
||||
panelInfo: {
|
||||
lgCatCd,
|
||||
lgCatNm,
|
||||
tab,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [dispatch]);
|
||||
if (lgCatCd && lgCatNm) {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.CATEGORY_PANEL,
|
||||
panelInfo: {
|
||||
lgCatCd,
|
||||
lgCatNm,
|
||||
tab,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const _handleItemFocus = useCallback(() => {
|
||||
if (handleItemFocus) {
|
||||
@@ -228,35 +208,34 @@ export default memo(function SubCategory({
|
||||
|
||||
useEffect(() => {
|
||||
if (!foruItemInfos || foruItemInfos.length === 0) {
|
||||
const baseData = categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||
...item,
|
||||
foryou: false,
|
||||
})) || [];
|
||||
const baseData =
|
||||
categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||
...item,
|
||||
foryou: false,
|
||||
})) || [];
|
||||
setCategoryItemNewData(baseData);
|
||||
return;
|
||||
}
|
||||
|
||||
const recommendedData = foruItemInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData.map(item => item.prdtId));
|
||||
|
||||
const baseData = categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||
...item,
|
||||
foryou: recommendedPrdtIds.has(item.prdtId),
|
||||
})) || [];
|
||||
|
||||
const recommendedData =
|
||||
foruItemInfos?.map((item) => ({
|
||||
...item,
|
||||
foryou: true,
|
||||
})) || [];
|
||||
|
||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
||||
|
||||
const baseData =
|
||||
categoryItemInfos?.subCatItemList?.map((item) => ({
|
||||
...item,
|
||||
foryou: recommendedPrdtIds.has(item.prdtId),
|
||||
})) || [];
|
||||
|
||||
setCategoryItemNewData([...baseData]);
|
||||
}, [categoryItemInfos?.subCatItemList, foruItemInfos]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
spotlightId={spotlightId}
|
||||
data-wheel-point
|
||||
onFocus={_handleShelfFocus}
|
||||
>
|
||||
<Container spotlightId={spotlightId} data-wheel-point onFocus={_handleShelfFocus}>
|
||||
<CategoryNav
|
||||
categoryInfos={categoryInfos}
|
||||
currentCategoryCode={currentLgCatCd}
|
||||
@@ -293,7 +272,7 @@ export default memo(function SubCategory({
|
||||
) => {
|
||||
return (
|
||||
<TItemCardNew
|
||||
key={"subItem" + itemIndex}
|
||||
key={'subItem' + itemIndex}
|
||||
contextName={LOG_CONTEXT_NAME.HOME}
|
||||
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
|
||||
catNm={categoryItemInfos?.catNm}
|
||||
@@ -314,16 +293,11 @@ export default memo(function SubCategory({
|
||||
offerInfo={offerInfo}
|
||||
data-catcd-num={currentLgCatCd}
|
||||
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"
|
||||
euEnrgLblInfos={euEnrgLblInfos}
|
||||
>
|
||||
{foryou === true && <Tag text={"For You"} />}
|
||||
{foryou === true && <Tag text={'For You'} />}
|
||||
</TItemCardNew>
|
||||
);
|
||||
}
|
||||
@@ -333,7 +307,7 @@ export default memo(function SubCategory({
|
||||
<SpottableComponent
|
||||
className={css.displayBox}
|
||||
onClick={handleMoreCardClick}
|
||||
spotlightId={"home-cate-more-btn-" + currentLgCatCd}
|
||||
spotlightId={'home-cate-more-btn-' + currentLgCatCd}
|
||||
onFocus={_handleItemFocus}
|
||||
data-catcd-num={currentLgCatCd}
|
||||
data-catcd-nm={currentLgCatNm}
|
||||
@@ -345,4 +319,4 @@ export default memo(function SubCategory({
|
||||
</ContainerBasic>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,6 +186,10 @@ export default function MainView({ className, initService }) {
|
||||
const isHomeOnTop = useMemo(() => {
|
||||
return !mainIndex && (panels.length <= 0 || (panels.length === 1 && panels[0].panelInfo.modal));
|
||||
}, [mainIndex, panels]);
|
||||
const hasDetailPanel = useMemo(
|
||||
() => panels.some((panel) => panel?.name === Config.panel_names.DETAIL_PANEL),
|
||||
[panels]
|
||||
);
|
||||
|
||||
const onPreImageLoadComplete = useCallback(() => {
|
||||
// console.log('MainView onPreImageLoadComplete');
|
||||
@@ -257,6 +261,7 @@ export default function MainView({ className, initService }) {
|
||||
return (
|
||||
<>
|
||||
{(isHomeOnTop ||
|
||||
hasDetailPanel ||
|
||||
(panels.length === 1 &&
|
||||
(panels[0]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||
panels[0]?.name === Config.panel_names.PLAYER_PANEL_NEW ||
|
||||
|
||||
@@ -61,6 +61,7 @@ import TPopUp from '../../components/TPopUp/TPopUp';
|
||||
import Media from '../../components/VideoPlayer/Media';
|
||||
import TReactPlayer from '../../components/VideoPlayer/TReactPlayer';
|
||||
import { VideoPlayer } from '../../components/VideoPlayer/VideoPlayer.v3';
|
||||
import MediaOverlayContents from '../PlayerPanel/PlayerOverlay/MediaOverlayContents';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useWhyDidYouUpdate from '../../hooks/useWhyDidYouUpdate';
|
||||
import * as Config from '../../utils/Config';
|
||||
@@ -2244,6 +2245,7 @@ const MediaPanel = React.forwardRef(
|
||||
spotlightId={
|
||||
panelInfo.modal ? 'modal-video-player' : panelInfo.modalContainerId || spotlightId
|
||||
}
|
||||
overlayContentsComponent={MediaOverlayContents}
|
||||
handleIndicatorDownClick={handleIndicatorDownClick}
|
||||
handleIndicatorUpClick={handleIndicatorUpClick}
|
||||
onError={mediainfoHandler}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import PlayerOverlayContents from './PlayerOverlayContents';
|
||||
|
||||
function MediaOverlayContents(props) {
|
||||
return <PlayerOverlayContents {...props} forceShowMediaOverlay />;
|
||||
}
|
||||
|
||||
export default MediaOverlayContents;
|
||||
@@ -37,11 +37,11 @@ function PlayerOverlayContents({
|
||||
handleIndicatorDownClick,
|
||||
tabContainerVersion,
|
||||
tabIndexV2,
|
||||
forceShowMediaOverlay = false,
|
||||
}) {
|
||||
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
|
||||
const dispatch = useDispatch();
|
||||
const onClickBack = (ev) => {
|
||||
|
||||
// TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지
|
||||
// (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음)
|
||||
if (tabContainerVersion === 2 && belowContentsVisible) {
|
||||
@@ -107,11 +107,15 @@ function PlayerOverlayContents({
|
||||
if (type === 'LIVE') {
|
||||
return Spotlight.focus('videoIndicator-down-button');
|
||||
}
|
||||
Spotlight.focus('videoPlayer_mediaControls');
|
||||
return Spotlight.focus(SpotlightIds.PLAYER_PLAY_BUTTON);
|
||||
},
|
||||
[type]
|
||||
);
|
||||
|
||||
const onSpotlightMoveSubtitleButton = useCallback(() => {
|
||||
return Spotlight.focus('player-subtitlebutton');
|
||||
}, []);
|
||||
|
||||
const onSpotlightMoveSlider = useCallback(
|
||||
(e) => {
|
||||
if (type === 'VOD') {
|
||||
@@ -154,9 +158,41 @@ function PlayerOverlayContents({
|
||||
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 onSpotlightMoveMediaButton(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 onSpotlightMoveMediaButton(ev);
|
||||
}
|
||||
if (ev.keyCode === 38 && currentId === SpotlightIds.PLAYER_PLAY_BUTTON) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return onSpotlightMoveBackButton();
|
||||
}
|
||||
},
|
||||
[onSpotlightMoveBackButton, onSpotlightMoveMediaButton, onSpotlightMoveSubtitleButton]
|
||||
);
|
||||
|
||||
const shouldShowExtendedControls = useMemo(
|
||||
() => forceShowMediaOverlay || type !== 'MEDIA',
|
||||
[forceShowMediaOverlay, type]
|
||||
);
|
||||
|
||||
const currentSideButtonStatus = useMemo(() => {
|
||||
if (
|
||||
type !== 'MEDIA' &&
|
||||
shouldShowExtendedControls &&
|
||||
!panelInfo?.modal &&
|
||||
!sideContentsVisible &&
|
||||
tabContainerVersion === 1
|
||||
@@ -164,7 +200,7 @@ function PlayerOverlayContents({
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [type, panelInfo, sideContentsVisible, tabContainerVersion]);
|
||||
}, [shouldShowExtendedControls, panelInfo, sideContentsVisible, tabContainerVersion]);
|
||||
|
||||
const noLiveContentsVisible = useMemo(() => {
|
||||
if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
|
||||
@@ -181,8 +217,8 @@ function PlayerOverlayContents({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className={css.overlayContainer}>
|
||||
{type !== 'MEDIA' && playListInfo.length > 1 && noLiveContentsVisible && (
|
||||
<Container className={css.overlayContainer} onKeyDownCapture={handleOverlayKeyDownCapture}>
|
||||
{shouldShowExtendedControls && playListInfo.length > 1 && noLiveContentsVisible && (
|
||||
<>
|
||||
<div className={css.indicatorUpButton}>
|
||||
<SpottableBtn
|
||||
@@ -232,7 +268,7 @@ function PlayerOverlayContents({
|
||||
setSideContentsVisible={setSideContentsVisible}
|
||||
sideContentsVisible={sideContentsVisible}
|
||||
onSpotlightLeft={
|
||||
type !== 'MEDIA' && playListInfo.length < 2 && onSpotlightMoveBackButton
|
||||
shouldShowExtendedControls && playListInfo.length < 2 && onSpotlightMoveBackButton
|
||||
}
|
||||
videoType={type}
|
||||
/>
|
||||
@@ -247,8 +283,10 @@ function PlayerOverlayContents({
|
||||
)}
|
||||
onClick={handleSubtitleOnClick}
|
||||
spotlightId="player-subtitlebutton"
|
||||
onSpotlightUp={onSpotlightMoveTabButton}
|
||||
onSpotlightLeft={onSpotlightMoveMediaButton}
|
||||
onSpotlightUp={onSpotlightMoveBackButton}
|
||||
onSpotlightLeft={onSpotlightMoveBackButton}
|
||||
onSpotlightRight={onSpotlightMoveMediaButton}
|
||||
onSpotlightDown={onSpotlightMoveMediaButton}
|
||||
aria-label="Caption"
|
||||
/>
|
||||
</div>
|
||||
@@ -263,6 +301,8 @@ function PlayerOverlayContents({
|
||||
? onSpotlightMoveBelowTab
|
||||
: onSpotlightMoveMediaButton
|
||||
}
|
||||
onSpotlightRight={onSpotlightMoveSubtitleButton}
|
||||
onSpotlightUp={onSpotlightMoveSubtitleButton}
|
||||
aria-label="Video Player Close"
|
||||
ref={backBtnRef}
|
||||
/>
|
||||
@@ -312,7 +352,8 @@ const propsAreEqual = (prev, next) => {
|
||||
prev.sideContentsVisible === next.sideContentsVisible &&
|
||||
prev.belowContentsVisible === next.belowContentsVisible &&
|
||||
prev.tabContainerVersion === next.tabContainerVersion &&
|
||||
prev.tabIndexV2 === next.tabIndexV2
|
||||
prev.tabIndexV2 === next.tabIndexV2 &&
|
||||
prev.forceShowMediaOverlay === next.forceShowMediaOverlay
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,11 +4,7 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
|
||||
import {
|
||||
pushPanel,
|
||||
updatePanel,
|
||||
} from '../../../actions/panelActions';
|
||||
import { navigateToDetailPanel } from '../../../actions/panelNavigationActions';
|
||||
import { pushPanel, updatePanel, navigateFromSearch } from '../../../actions/panelActions';
|
||||
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
||||
import TScroller from '../../../components/TScroller/TScroller';
|
||||
import { panel_names } from '../../../utils/Config';
|
||||
@@ -49,14 +45,16 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
|
||||
onClick(ev);
|
||||
}
|
||||
|
||||
// 순차 네비게이션 사용 (Chrome 68 호환)
|
||||
dispatch(navigateToDetailPanel(
|
||||
patnrId,
|
||||
prdtId,
|
||||
searchQuery,
|
||||
currentSpot,
|
||||
{ tab: 0 }
|
||||
));
|
||||
// 통합된 navigateToDetail 사용
|
||||
dispatch(
|
||||
navigateFromSearch({
|
||||
patnrId,
|
||||
prdtId,
|
||||
searchQuery,
|
||||
currentSpot,
|
||||
additionalInfo: { tab: 0 },
|
||||
})
|
||||
);
|
||||
},
|
||||
[onClick, dispatch, searchQuery]
|
||||
);
|
||||
@@ -65,7 +63,7 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
|
||||
<>
|
||||
<TScroller className={css.container} spotlightId={SpotlightIds.SEARCH_ITEM}>
|
||||
{itemInfo.map((item, index) => {
|
||||
const { thumbnail, title, dcPrice, price, soldout, contentId,euEnrgLblInfos } = item;
|
||||
const { thumbnail, title, dcPrice, price, soldout, contentId, euEnrgLblInfos } = item;
|
||||
const tokens = contentId && contentId.split('_');
|
||||
const patnrId = tokens?.[4] || '';
|
||||
const prdtId = tokens?.[5] || '';
|
||||
|
||||
Reference in New Issue
Block a user