[251011] fix: MediaPlayer작업-2
🕐 커밋 시간: 2025. 10. 11. 23:11:17 📊 변경 통계: • 총 파일: 6개 • 추가: +118줄 • 삭제: -84줄 📁 추가된 파일: + com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/mediaActions.js ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/CustomerImages/CustomerImages.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/mediaActions.js (javascript): ✅ Added: resumeModalMedia() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx (javascript): 🔄 Modified: Spottable() 📄 com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx (javascript): ✅ Added: getControlsHandleAboveHoldConfig(), shouldJump(), calcNumberValueOfPlaybackRate(), getDurFmt(), onSpotlightFocus(), getVideoPhoneNumberClassNames() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -23,31 +23,39 @@ export const startMediaPlayer =
|
||||
const topPanel = panels[panels.length - 1];
|
||||
let panelWorkingAction = pushPanel;
|
||||
|
||||
console.log('[startMediaPlayer] ========== Called ==========');
|
||||
console.log('[startMediaPlayer] Current panels:', JSON.stringify(panels, null, 2));
|
||||
console.log('[startMediaPlayer] topPanel:', JSON.stringify(topPanel, null, 2));
|
||||
|
||||
if (topPanel && topPanel.name === panel_names.MEDIA_PANEL) {
|
||||
panelWorkingAction = updatePanel;
|
||||
console.log('[startMediaPlayer] Using updatePanel (existing MediaPanel)');
|
||||
} else {
|
||||
console.log('[startMediaPanel] Using pushPanel (new MediaPanel)');
|
||||
}
|
||||
|
||||
console.log('[startMediaPlayer] Starting MediaPanel:', {
|
||||
const allParams = {
|
||||
modal,
|
||||
modalContainerId,
|
||||
showUrl: rest.showUrl,
|
||||
});
|
||||
modalClassName,
|
||||
spotlightDisable,
|
||||
...rest,
|
||||
};
|
||||
|
||||
console.log('[startMediaPlayer] All parameters:', JSON.stringify(allParams, null, 2));
|
||||
|
||||
dispatch(
|
||||
panelWorkingAction(
|
||||
{
|
||||
name: panel_names.MEDIA_PANEL,
|
||||
panelInfo: {
|
||||
modal,
|
||||
modalContainerId,
|
||||
modalClassName,
|
||||
...rest,
|
||||
},
|
||||
panelInfo: allParams,
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
console.log('[startMediaPlayer] Panel action dispatched');
|
||||
|
||||
if (modal && modalContainerId && !spotlightDisable) {
|
||||
Spotlight.setPointerMode(false);
|
||||
startMediaFocusTimer = setTimeout(() => {
|
||||
@@ -63,6 +71,10 @@ export const finishMediaPreview = () => (dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
console.log('[finishMediaPreview] ========== Called ==========');
|
||||
console.log('[finishMediaPreview] Current panels:', JSON.stringify(panels, null, 2));
|
||||
console.log('[finishMediaPreview] topPanel:', JSON.stringify(topPanel, null, 2));
|
||||
|
||||
if (topPanel && topPanel.name === panel_names.MEDIA_PANEL && topPanel.panelInfo.modal) {
|
||||
console.log('[finishMediaPreview] Closing modal MediaPanel');
|
||||
|
||||
@@ -71,6 +83,9 @@ export const finishMediaPreview = () => (dispatch, getState) => {
|
||||
startMediaFocusTimer = null;
|
||||
}
|
||||
dispatch(popPanel());
|
||||
console.log('[finishMediaPreview] popPanel dispatched');
|
||||
} else {
|
||||
console.log('[finishMediaPreview] Not closing - no modal MediaPanel on top');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -149,21 +164,46 @@ export const resumeModalMedia = () => (dispatch, getState) => {
|
||||
export const switchMediaToFullscreen = () => (dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
|
||||
console.log('[switchMediaToFullscreen] ========== Called ==========');
|
||||
console.log('[switchMediaToFullscreen] Current panels:', JSON.stringify(panels, null, 2));
|
||||
|
||||
const modalMediaPanel = panels.find(
|
||||
(panel) => panel.name === panel_names.MEDIA_PANEL && panel.panelInfo?.modal
|
||||
);
|
||||
|
||||
console.log(
|
||||
'[switchMediaToFullscreen] modalMediaPanel found:',
|
||||
JSON.stringify(modalMediaPanel, null, 2)
|
||||
);
|
||||
|
||||
if (modalMediaPanel) {
|
||||
console.log('[switchMediaToFullscreen] Switching to fullscreen');
|
||||
console.log('[switchMediaToFullscreen] Switching to fullscreen - updating modal to false');
|
||||
console.log(
|
||||
'[switchMediaToFullscreen] Existing panelInfo:',
|
||||
JSON.stringify(modalMediaPanel.panelInfo, null, 2)
|
||||
);
|
||||
|
||||
const newPanelInfo = {
|
||||
...modalMediaPanel.panelInfo,
|
||||
modal: false,
|
||||
};
|
||||
|
||||
console.log(
|
||||
'[switchMediaToFullscreen] New panelInfo to dispatch:',
|
||||
JSON.stringify(newPanelInfo, null, 2)
|
||||
);
|
||||
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.MEDIA_PANEL,
|
||||
panelInfo: {
|
||||
...modalMediaPanel.panelInfo,
|
||||
modal: false,
|
||||
},
|
||||
panelInfo: newPanelInfo,
|
||||
})
|
||||
);
|
||||
console.log('[switchMediaToFullscreen] updatePanel dispatched');
|
||||
} else {
|
||||
console.log(
|
||||
'[switchMediaToFullscreen] No modal MediaPanel found - cannot switch to fullscreen'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
2598
com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx
Normal file
2598
com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1940,6 +1940,13 @@ const VideoPlayerBase = class extends React.Component {
|
||||
onVideoClick = () => {
|
||||
console.log('[VideoPlayer] onVideoClick 호출');
|
||||
|
||||
// modal 상태일 때 외부 onClick 핸들러가 있으면 호출
|
||||
if (this.props.panelInfo?.modal && this.props.onClick) {
|
||||
console.log('[VideoPlayer] modal에서 onClick 호출');
|
||||
this.props.onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
// TabContainerV2 토글
|
||||
if (this.props.setBelowContentsVisible && this.props.belowContentsVisible !== undefined) {
|
||||
console.log('[VideoPlayer] belowContentsVisible 토글:', !this.props.belowContentsVisible);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import { startMediaPlayer, finishMediaPreview } from '../../../../actions/mediaActions';
|
||||
import {
|
||||
startMediaPlayer,
|
||||
finishMediaPreview,
|
||||
switchMediaToFullscreen,
|
||||
} from '../../../../actions/mediaActions';
|
||||
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||
import { panel_names } from '../../../../utils/Config';
|
||||
import playImg from '../../../../../assets/images/btn/btn-play-thumb-nor.png';
|
||||
@@ -57,34 +61,22 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
}, [canPlayVideo]);
|
||||
|
||||
const videoContainerOnBlur = useCallback(() => {
|
||||
console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo);
|
||||
if (canPlayVideo) {
|
||||
setFocused(false);
|
||||
|
||||
// MediaPanel이 modal로 열려있지 않을 때만 종료
|
||||
// (열려있을 때는 MediaPanel 자체에서 생명주기 관리)
|
||||
const currentTopPanel = panels[panels.length - 1];
|
||||
const isMediaPanelModalOpen =
|
||||
currentTopPanel &&
|
||||
currentTopPanel.name === panel_names.MEDIA_PANEL &&
|
||||
currentTopPanel.panelInfo.modal === true;
|
||||
|
||||
console.log('[ProductVideo] onBlur:', {
|
||||
isMediaPanelModalOpen,
|
||||
currentTopPanelName: currentTopPanel?.name,
|
||||
willClosePanel: !isMediaPanelModalOpen,
|
||||
});
|
||||
|
||||
if (!isMediaPanelModalOpen) {
|
||||
console.log('[ProductVideo] Closing MediaPanel from onBlur');
|
||||
dispatch(finishMediaPreview());
|
||||
} else {
|
||||
console.log('[ProductVideo] MediaPanel is open, skipping finishMediaPreview');
|
||||
}
|
||||
console.log('[ProductVideo] Calling finishMediaPreview');
|
||||
// ProductVideo에서 포커스가 벗어나면 비디오 재생 종료
|
||||
dispatch(finishMediaPreview());
|
||||
}
|
||||
}, [canPlayVideo, dispatch, panels]);
|
||||
}, [canPlayVideo, dispatch]);
|
||||
|
||||
// MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능
|
||||
const handleVideoClick = useCallback(() => {
|
||||
console.log('[ProductVideo] ========== handleVideoClick 호출 ==========');
|
||||
console.log('[ProductVideo] canPlayVideo:', canPlayVideo);
|
||||
console.log('[ProductVideo] panels.length:', panels.length);
|
||||
console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2));
|
||||
|
||||
if (canPlayVideo) {
|
||||
const currentTopPanel = panels[panels.length - 1];
|
||||
|
||||
@@ -94,37 +86,44 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
|
||||
currentTopPanel.name === panel_names.MEDIA_PANEL &&
|
||||
currentTopPanel.panelInfo.modal === true;
|
||||
|
||||
// modal=true로 재생 중이면 modal=false(전체화면)로 변경
|
||||
const newModalState = isCurrentlyPlayingModal ? false : modalState;
|
||||
console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2));
|
||||
console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal);
|
||||
|
||||
dispatch(
|
||||
startMediaPlayer({
|
||||
qrCurrentItem: productInfo,
|
||||
showUrl: productInfo?.prdtMediaUrl,
|
||||
showNm: productInfo?.prdtNm,
|
||||
patnrNm: productInfo?.patncNm,
|
||||
patncLogoPath: productInfo?.patncLogoPath,
|
||||
orderPhnNo: productInfo?.orderPhnNo,
|
||||
disclaimer: productInfo?.disclaimer,
|
||||
subtitle: productInfo?.prdtMediaSubtitlUrl,
|
||||
lgCatCd: productInfo?.catCd,
|
||||
patnrId: productInfo?.patnrId,
|
||||
lgCatNm: productInfo?.catNm,
|
||||
prdtId: productInfo?.prdtId,
|
||||
patncNm: productInfo?.patncNm,
|
||||
prdtNm: productInfo?.prdtNm,
|
||||
thumbnailUrl: productInfo?.thumbnailUrl960,
|
||||
shptmBanrTpNm: 'MEDIA',
|
||||
modal: newModalState,
|
||||
modalContainerId: 'product-video-player',
|
||||
modalClassName: modalClassNameChange(),
|
||||
spotlightDisable: true,
|
||||
})
|
||||
);
|
||||
|
||||
// 모달 상태가 변경된 경우 상태 업데이트
|
||||
// modal로 재생 중이면 전체화면으로 전환
|
||||
if (isCurrentlyPlayingModal) {
|
||||
console.log(
|
||||
'[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***'
|
||||
);
|
||||
dispatch(switchMediaToFullscreen());
|
||||
setModalState(false);
|
||||
} else {
|
||||
console.log('[ProductVideo] *** Starting modal MediaPanel ***');
|
||||
console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2));
|
||||
// 처음 재생 시작 - modal=true로 시작
|
||||
dispatch(
|
||||
startMediaPlayer({
|
||||
qrCurrentItem: productInfo,
|
||||
showUrl: productInfo?.prdtMediaUrl,
|
||||
showNm: productInfo?.prdtNm,
|
||||
patnrNm: productInfo?.patncNm,
|
||||
patncLogoPath: productInfo?.patncLogoPath,
|
||||
orderPhnNo: productInfo?.orderPhnNo,
|
||||
disclaimer: productInfo?.disclaimer,
|
||||
subtitle: productInfo?.prdtMediaSubtitlUrl,
|
||||
lgCatCd: productInfo?.catCd,
|
||||
patnrId: productInfo?.patnrId,
|
||||
lgCatNm: productInfo?.catNm,
|
||||
prdtId: productInfo?.prdtId,
|
||||
patncNm: productInfo?.patncNm,
|
||||
prdtNm: productInfo?.prdtNm,
|
||||
thumbnailUrl: productInfo?.thumbnailUrl960,
|
||||
shptmBanrTpNm: 'MEDIA',
|
||||
modal: true,
|
||||
modalContainerId: 'product-video-player',
|
||||
modalClassName: modalClassNameChange(),
|
||||
spotlightDisable: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import THeader from '../../../../../components/THeader/THeader';
|
||||
@@ -17,22 +12,18 @@ import css from './CustomerImages.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{
|
||||
enterTo: "default-element",
|
||||
enterTo: 'default-element',
|
||||
preserveld: true,
|
||||
leaveFor: {
|
||||
left: "spotlight-product-info-section-container",
|
||||
left: 'spotlight-product-info-section-container',
|
||||
},
|
||||
},
|
||||
"div"
|
||||
'div'
|
||||
);
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
export default function CustomerImages({
|
||||
onImageClick,
|
||||
onViewMoreClick,
|
||||
imageData,
|
||||
}) {
|
||||
export default function CustomerImages({ onImageClick, onViewMoreClick, imageData }) {
|
||||
// Props로 전달받은 imageData 사용 (useReviews Hook에서 추출된 이미지 데이터)
|
||||
const [selectedIndex, setSelectedIndex] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@@ -77,15 +68,15 @@ export default function CustomerImages({
|
||||
// 왼쪽 화살표 키
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
console.log(
|
||||
"[CustomerImages] Left arrow pressed, focusing product-details-button"
|
||||
);
|
||||
Spotlight.focus("product-details-button");
|
||||
// console.log(
|
||||
// "[CustomerImages] Left arrow pressed, focusing product-details-button"
|
||||
// );
|
||||
Spotlight.focus('product-details-button');
|
||||
} else if (ev.keyCode === 13) {
|
||||
// Enter 키
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
console.log("[CustomerImages] Enter pressed on image:", index);
|
||||
// console.log("[CustomerImages] Enter pressed on image:", index);
|
||||
handleReviewImageClick(index);
|
||||
}
|
||||
},
|
||||
@@ -98,7 +89,7 @@ export default function CustomerImages({
|
||||
<Container className={css.container}>
|
||||
<THeader
|
||||
className={classNames(css.tHeader, css.customTHeader)}
|
||||
title={$L("Customer Images")}
|
||||
title={$L('Customer Images')}
|
||||
/>
|
||||
{imageData && imageData.length > 0 ? (
|
||||
<div className={css.wrapper}>
|
||||
@@ -108,15 +99,15 @@ export default function CustomerImages({
|
||||
const displayImages = imageData.slice(startIndex, endIndex);
|
||||
const hasMoreImages = imageData.length > endIndex;
|
||||
|
||||
console.log("[CustomerImages] Pagination debug:", {
|
||||
currentPage,
|
||||
IMAGES_PER_PAGE,
|
||||
totalImages: imageData.length,
|
||||
startIndex,
|
||||
endIndex,
|
||||
displayImagesCount: displayImages.length,
|
||||
hasMoreImages,
|
||||
});
|
||||
// console.log("[CustomerImages] Pagination debug:", {
|
||||
// currentPage,
|
||||
// IMAGES_PER_PAGE,
|
||||
// totalImages: imageData.length,
|
||||
// startIndex,
|
||||
// endIndex,
|
||||
// displayImagesCount: displayImages.length,
|
||||
// hasMoreImages,
|
||||
// });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -128,18 +119,14 @@ export default function CustomerImages({
|
||||
<SpottableComponent
|
||||
className={classNames(
|
||||
css.reviewCard,
|
||||
selectedIndex === actualIndex
|
||||
? css.selectedReviewImage
|
||||
: null,
|
||||
focusIdx === displayIndex ? css.focused : ""
|
||||
selectedIndex === actualIndex ? css.selectedReviewImage : null,
|
||||
focusIdx === displayIndex ? css.focused : ''
|
||||
)}
|
||||
key={`review-image-${imgId}-${reviewId}`}
|
||||
onClick={() => handleReviewImageClick(actualIndex)}
|
||||
onKeyDown={(ev) => handleKeyDown(ev, actualIndex)}
|
||||
spotlightId={`customer-image-${actualIndex}`}
|
||||
onFocus={() =>
|
||||
displayIndex < 5 ? setFocusIdx(displayIndex) : ""
|
||||
}
|
||||
onFocus={() => (displayIndex < 5 ? setFocusIdx(displayIndex) : '')}
|
||||
>
|
||||
<img
|
||||
className={css.reviewImg}
|
||||
@@ -153,16 +140,13 @@ export default function CustomerImages({
|
||||
}); */
|
||||
}}
|
||||
onError={(e) => {
|
||||
console.error(
|
||||
`[CustomerImages] Image load failed:`,
|
||||
{
|
||||
index: actualIndex,
|
||||
imgUrl,
|
||||
imgId,
|
||||
error: e.target.error,
|
||||
}
|
||||
);
|
||||
e.target.style.display = "none";
|
||||
console.error(`[CustomerImages] Image load failed:`, {
|
||||
index: actualIndex,
|
||||
imgUrl,
|
||||
imgId,
|
||||
error: e.target.error,
|
||||
});
|
||||
e.target.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</SpottableComponent>
|
||||
@@ -195,15 +179,13 @@ export default function CustomerImages({
|
||||
<div className={css.wrapper}>
|
||||
<div
|
||||
style={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
fontSize: "16px",
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
{imageData
|
||||
? "No customer images available"
|
||||
: "Loading customer images..."}
|
||||
{imageData ? 'No customer images available' : 'Loading customer images...'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as PanelActions from '../../actions/panelActions';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import Media from '../../components/VideoPlayer/Media';
|
||||
import TReactPlayer from '../../components/VideoPlayer/TReactPlayer';
|
||||
import { VideoPlayer } from '../../components/VideoPlayer/VideoPlayer';
|
||||
import { VideoPlayer } from '../../components/VideoPlayer/MediaPlayer';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import { panel_names } from '../../utils/Config';
|
||||
import css from './MediaPanel.module.less';
|
||||
@@ -60,10 +60,9 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
// modal/full screen에 따른 일시정지/재생 처리
|
||||
useEffect(() => {
|
||||
console.log('[MediaPanel] isOnTop:', {
|
||||
isOnTop,
|
||||
panelInfo,
|
||||
});
|
||||
console.log('[MediaPanel] ========== isOnTop useEffect ==========');
|
||||
console.log('[MediaPanel] isOnTop:', isOnTop);
|
||||
console.log('[MediaPanel] panelInfo:', JSON.stringify(panelInfo, null, 2));
|
||||
|
||||
if (panelInfo && panelInfo.modal) {
|
||||
if (!isOnTop) {
|
||||
@@ -276,6 +275,12 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
setVideoLoaded(false);
|
||||
}, [currentPlayingUrl]);
|
||||
|
||||
console.log('[MediaPanel] ========== Rendering ==========');
|
||||
console.log('[MediaPanel] isOnTop:', isOnTop);
|
||||
console.log('[MediaPanel] panelInfo:', JSON.stringify(panelInfo, null, 2));
|
||||
console.log('[MediaPanel] currentPlayingUrl:', currentPlayingUrl);
|
||||
console.log('[MediaPanel] hasVideoPlayer:', !!videoPlayer.current);
|
||||
|
||||
return (
|
||||
<TPanel
|
||||
isTabActivated={false}
|
||||
|
||||
Reference in New Issue
Block a user