[251113] style: views - TReactPlayer.jsx, ProductVideo.module.less, Pro...
🕐 커밋 시간: 2025. 11. 13. 10:59:09 📊 변경 통계: • 총 파일: 5개 • 추가: +173줄 • 삭제: -76줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.module.less 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 중간 규모 기능 개선
This commit is contained in:
@@ -33,6 +33,14 @@ export default function TReactPlayer({
|
||||
if (videoRef) {
|
||||
const videoNode = playerRef.current.getInternalPlayer();
|
||||
videoRef(videoNode);
|
||||
const iframeEl =
|
||||
typeof playerRef.current?.getInternalPlayer === "function"
|
||||
? playerRef.current.getInternalPlayer("iframe")
|
||||
: null;
|
||||
if (iframeEl) {
|
||||
iframeEl.setAttribute("tabIndex", "-1");
|
||||
iframeEl.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
if (
|
||||
videoNode.tagName &&
|
||||
!Object.prototype.hasOwnProperty.call(videoNode, "proportionPlayed")
|
||||
|
||||
@@ -98,16 +98,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.youtubeSafe {
|
||||
:global(.react-player),
|
||||
:global(.react-player iframe) {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 전체화면 모드 (ProductVideoV2 엔터키 토글)
|
||||
.fullscreen {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 1920px !important;
|
||||
height: 1080px !important;
|
||||
max-width: 1920px !important;
|
||||
z-index: 99999 !important; // 최상위 레이어
|
||||
margin: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
z-index: 99999 !important; // 전체 화면 기준
|
||||
border-radius: 0 !important;
|
||||
background-color: @COLOR_BLACK;
|
||||
|
||||
@@ -115,12 +120,39 @@
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
// 전체화면 모드에서는 포커스가 밖으로 나가지 않도록
|
||||
// Spotlight container가 이 영역만 관리
|
||||
// 전체화면 모드에서 포커스가 벗어나지 않도록
|
||||
// Spotlight container는 별도 관리
|
||||
}
|
||||
|
||||
.fullscreenPlayer {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
border-radius: 0 !important;
|
||||
z-index: 100000 !important;
|
||||
background-color: @COLOR_BLACK;
|
||||
|
||||
:global(.videoPlayer) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
background-color: @COLOR_BLACK;
|
||||
}
|
||||
|
||||
:global(video) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover;
|
||||
background-color: @COLOR_BLACK;
|
||||
}
|
||||
|
||||
:global(.react-player),
|
||||
:global(.react-player iframe) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
background-color: @COLOR_BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
.videoThumbnailContainer {
|
||||
@@ -158,8 +190,8 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 99999;
|
||||
background-color: @COLOR_BLACK;
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ export function ProductVideoV2({
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// toggleOverlayVisibility();
|
||||
toggleOverlayVisibility();
|
||||
},
|
||||
[isPlaying, isFullscreen, toggleOverlayVisibility]
|
||||
);
|
||||
@@ -558,23 +558,29 @@ export function ProductVideoV2({
|
||||
(e) => {
|
||||
if (!isPlaying || !isFullscreen) return;
|
||||
|
||||
// ESC 키만 오버레이 토글로 처리
|
||||
const isOverlayVisible = mediaOverlayState.controls?.visible;
|
||||
|
||||
if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
debugLog('🖥️ [Fullscreen Container] ESC 키 - 오버레이 토글 실행');
|
||||
debugLog('[Fullscreen Container] ESC key - toggling overlay');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleOverlayVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter 키는 기본 동작 허용 (포커스된 요소의 동작 수행)
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
debugLog('🖥️ [Fullscreen Container] Enter 키 - 포커스된 요소 동작 허용');
|
||||
// Enter 키는 preventDefault하지 않고 기본 동작 허용
|
||||
return;
|
||||
if (!isOverlayVisible) {
|
||||
debugLog('[Fullscreen Container] Enter key - overlay hidden, showing controls');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleOverlayVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('[Fullscreen Container] Enter key - overlay visible, allow default behavior');
|
||||
}
|
||||
},
|
||||
[isPlaying, isFullscreen, toggleOverlayVisibility]
|
||||
[isPlaying, isFullscreen, toggleOverlayVisibility, mediaOverlayState.controls?.visible]
|
||||
);
|
||||
|
||||
// 마우스 다운 (클릭) 이벤트 - capture phase에서 처리
|
||||
@@ -764,6 +770,17 @@ export function ProductVideoV2({
|
||||
}
|
||||
}, [isFullscreen, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlaying) return;
|
||||
|
||||
if (isFullscreen && isYoutube) {
|
||||
videoPlayerRef.current?.showControls?.();
|
||||
dispatch(setMediaControlShow());
|
||||
dispatch(resetMediaAutoClose());
|
||||
dispatch(startMediaAutoClose(5000));
|
||||
}
|
||||
}, [dispatch, isFullscreen, isPlaying, isYoutube]);
|
||||
|
||||
useEffect(() => {
|
||||
const wasFullscreen = prevFullscreenRef.current;
|
||||
if (wasFullscreen && !isFullscreen && isPlaying) {
|
||||
@@ -924,11 +941,19 @@ export function ProductVideoV2({
|
||||
|
||||
const host = ensurePortalHost();
|
||||
if (!host) return null;
|
||||
const shouldDisableIframeInteraction = isYoutube && isFullscreen;
|
||||
const wrapperClasses = [
|
||||
css.videoPlayerWrapper,
|
||||
isFullscreen ? css.fullscreenPlayer : '',
|
||||
shouldDisableIframeInteraction ? css.youtubeSafe : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div
|
||||
ref={videoPlayerWrapperRef}
|
||||
className={`${css.videoPlayerWrapper} ${isFullscreen ? css.fullscreenPlayer : ''}`}
|
||||
className={wrapperClasses}
|
||||
onMouseMove={handleUserActivity}
|
||||
onTouchMove={handleUserActivity}
|
||||
onWheel={handleUserActivity}
|
||||
@@ -1113,3 +1138,7 @@ export function ProductVideoV2({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -100,6 +100,14 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
}
|
||||
}, [panelInfo?.isPaused, panelInfo?.modal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoPlayer.current) return;
|
||||
if (!isYoutube) return;
|
||||
if (panelInfo?.modal) return;
|
||||
|
||||
videoPlayer.current.showControls?.();
|
||||
}, [isYoutube, panelInfo?.modal]);
|
||||
|
||||
const getPlayer = useCallback((ref) => {
|
||||
videoPlayer.current = ref;
|
||||
}, []);
|
||||
@@ -463,6 +471,7 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
// minimized 상태일 때는 spotlightRestrict 해제 (포커스 이동 허용)
|
||||
const containerSpotlightRestrict = panelInfo.isMinimized ? 'none' : 'self-only';
|
||||
const shouldDisableIframeInteraction = isYoutube && !panelInfo.modal;
|
||||
|
||||
return (
|
||||
<TPanel
|
||||
@@ -477,57 +486,64 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
spotlightId="spotlightId-media-video-container"
|
||||
>
|
||||
{currentPlayingUrl && (
|
||||
<VideoPlayer
|
||||
setApiProvider={getPlayer}
|
||||
disabled={panelInfo.modal}
|
||||
onEnded={onEnded}
|
||||
noAutoPlay={false}
|
||||
noAutoShowMediaControls={panelInfo.modal} // modal 상태에서는 자동으로 controls를 보여주지 않음
|
||||
autoCloseTimeout={3000}
|
||||
onBackButton={onClickBack}
|
||||
onClick={onVideoClick}
|
||||
spotlightDisabled={panelInfo.modal}
|
||||
isYoutube={isYoutube}
|
||||
src={currentPlayingUrl}
|
||||
loop={panelInfo.continuousPlay || false}
|
||||
style={panelInfo.modal ? modalStyle : {}}
|
||||
modalScale={panelInfo.modal ? modalScale : 1}
|
||||
modalClassName={panelInfo.modal && panelInfo.modalClassName}
|
||||
onError={mediainfoHandler}
|
||||
onTimeUpdate={mediainfoHandler}
|
||||
onLoadedData={mediainfoHandler}
|
||||
onLoadedMetadata={mediainfoHandler}
|
||||
onDurationChange={mediainfoHandler}
|
||||
reactPlayerConfig={reactPlayerSubtitleConfig}
|
||||
thumbnailUrl={videoLoaded ? '' : videoThumbnailUrl}
|
||||
videoComponent={
|
||||
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
|
||||
}
|
||||
// VideoOverlay props - 단순화
|
||||
type="MEDIA"
|
||||
panelInfo={panelInfo}
|
||||
captionEnable={false}
|
||||
setIsSubtitleActive={setIsSubtitleActive}
|
||||
setCurrentTime={setCurrentTime}
|
||||
setIsVODPaused={setIsVODPaused}
|
||||
// PlayerOverlayContents props (빈 배열로 전달하여 null 에러 방지)
|
||||
playListInfo={[]}
|
||||
selectedIndex={0}
|
||||
videoVerticalVisible={false}
|
||||
sideContentsVisible={false}
|
||||
setSideContentsVisible={setSideContentsVisible}
|
||||
handleIndicatorDownClick={handleIndicatorDownClick}
|
||||
handleIndicatorUpClick={handleIndicatorUpClick}
|
||||
>
|
||||
{typeof window === 'object' && window.PalmSystem && (
|
||||
<source src={currentPlayingUrl} type={videoType} />
|
||||
<div
|
||||
className={classNames(
|
||||
css.videoPlayerWrapper,
|
||||
shouldDisableIframeInteraction && css.youtubeSafe
|
||||
)}
|
||||
{isSubtitleActive &&
|
||||
!panelInfo.modal &&
|
||||
typeof window === 'object' &&
|
||||
window.PalmSystem &&
|
||||
currentSubtitleUrl && <track kind="subtitles" src={currentSubtitleUrl} default />}
|
||||
</VideoPlayer>
|
||||
>
|
||||
<VideoPlayer
|
||||
setApiProvider={getPlayer}
|
||||
disabled={panelInfo.modal}
|
||||
onEnded={onEnded}
|
||||
noAutoPlay={false}
|
||||
noAutoShowMediaControls={panelInfo.modal} // modal 모드에서는 자동으로 controls가 올라오지 않도록 설정
|
||||
autoCloseTimeout={3000}
|
||||
onBackButton={onClickBack}
|
||||
onClick={onVideoClick}
|
||||
spotlightDisabled={panelInfo.modal}
|
||||
isYoutube={isYoutube}
|
||||
src={currentPlayingUrl}
|
||||
loop={panelInfo.continuousPlay || false}
|
||||
style={panelInfo.modal ? modalStyle : {}}
|
||||
modalScale={panelInfo.modal ? modalScale : 1}
|
||||
modalClassName={panelInfo.modal && panelInfo.modalClassName}
|
||||
onError={mediainfoHandler}
|
||||
onTimeUpdate={mediainfoHandler}
|
||||
onLoadedData={mediainfoHandler}
|
||||
onLoadedMetadata={mediainfoHandler}
|
||||
onDurationChange={mediainfoHandler}
|
||||
reactPlayerConfig={reactPlayerSubtitleConfig}
|
||||
thumbnailUrl={videoLoaded ? '' : videoThumbnailUrl}
|
||||
videoComponent={
|
||||
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
|
||||
}
|
||||
// VideoOverlay props - 간소화
|
||||
type="MEDIA"
|
||||
panelInfo={panelInfo}
|
||||
captionEnable={false}
|
||||
setIsSubtitleActive={setIsSubtitleActive}
|
||||
setCurrentTime={setCurrentTime}
|
||||
setIsVODPaused={setIsVODPaused}
|
||||
// PlayerOverlayContents props (빈 배열로 전달하여 null 처리)
|
||||
playListInfo={[]}
|
||||
selectedIndex={0}
|
||||
videoVerticalVisible={false}
|
||||
sideContentsVisible={false}
|
||||
setSideContentsVisible={setSideContentsVisible}
|
||||
handleIndicatorDownClick={handleIndicatorDownClick}
|
||||
handleIndicatorUpClick={handleIndicatorUpClick}
|
||||
>
|
||||
{typeof window === 'object' && window.PalmSystem && (
|
||||
<source src={currentPlayingUrl} type={videoType} />
|
||||
)}
|
||||
{isSubtitleActive &&
|
||||
!panelInfo.modal &&
|
||||
typeof window === 'object' &&
|
||||
window.PalmSystem &&
|
||||
currentSubtitleUrl && <track kind="subtitles" src={currentSubtitleUrl} default />}
|
||||
</VideoPlayer>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</TPanel>
|
||||
|
||||
@@ -60,3 +60,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.videoPlayerWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.youtubeSafe {
|
||||
:global(.react-player),
|
||||
:global(.react-player iframe) {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user