[251114] feat: ProductAllSection ProductVideo.v3
🕐 커밋 시간: 2025. 11. 14. 14:24:07 📊 변경 통계: • 총 파일: 13개 • 추가: +135줄 • 삭제: -54줄 📁 추가된 파일: + 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/hooks/useMediaPanelController.js + com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx + com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx + com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/mediaActions.js ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/mediaActions.js (javascript): 🔄 Modified: switchMediaToModal() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator(), extractProductMeta() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx (javascript): ✅ Added: Spottable() 📄 com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.js (javascript): ✅ Added: getControlsHandleAboveHoldConfig(), shouldJump(), calcNumberValueOfPlaybackRate(), getDurFmt(), getVideoPhoneNumberClassNames() 📄 com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.module.less (unknown): ✅ Added: gradient(), focused(), position(), rotate(), applySkins(), scale() 📄 com.twin.app.shoptime/src/hooks/useMediaPanelController.js (javascript): ✅ Added: MediaPanelControllerProvider(), useMediaPanelController() 📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript): ✅ Added: findSelector(), getLogTpNo(), normalizeModalStyle(), parseValue(), handleVisibilityChange(), onKeyDown(), handleEvent(), propsAreEqual() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -238,10 +238,10 @@ export const switchMediaToModal = (modalContainerId, modalClassName) => (dispatc
|
||||
export const minimizeModalMedia = () => (dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
|
||||
console.log('[minimizeModalMedia] ========== Called ==========');
|
||||
console.log('[minimizeModalMedia] Total panels:', panels.length);
|
||||
console.log('[Minimize] ========== Called ==========');
|
||||
console.log('[Minimize] Total panels:', panels.length);
|
||||
console.log(
|
||||
'[minimizeModalMedia] All panels:',
|
||||
'[Minimize] All panels:',
|
||||
JSON.stringify(
|
||||
panels.map((p) => ({ name: p.name, modal: p.panelInfo?.modal })),
|
||||
null,
|
||||
@@ -253,15 +253,13 @@ export const minimizeModalMedia = () => (dispatch, getState) => {
|
||||
(panel) => panel.name === panel_names.MEDIA_PANEL && panel.panelInfo?.modal
|
||||
);
|
||||
|
||||
console.log('[minimizeModalMedia] Found modalMediaPanel:', !!modalMediaPanel);
|
||||
console.log('[Minimize] Found modalMediaPanel:', !!modalMediaPanel);
|
||||
if (modalMediaPanel) {
|
||||
console.log(
|
||||
'[minimizeModalMedia] modalMediaPanel.panelInfo:',
|
||||
'[Minimize] modalMediaPanel.panelInfo:',
|
||||
JSON.stringify(modalMediaPanel.panelInfo, null, 2)
|
||||
);
|
||||
console.log(
|
||||
'[minimizeModalMedia] ✅ Minimizing modal MediaPanel (modal=false, isMinimized=true)'
|
||||
);
|
||||
console.log('[Minimize] ✅ Minimizing modal MediaPanel (modal=false, isMinimized=true)');
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.MEDIA_PANEL,
|
||||
@@ -269,13 +267,14 @@ export const minimizeModalMedia = () => (dispatch, getState) => {
|
||||
...modalMediaPanel.panelInfo,
|
||||
modal: false, // fullscreen 모드로 전환
|
||||
isMinimized: true, // modal-minimized 클래스 적용 (1px 크기)
|
||||
shouldShrinkTo1px: true, // shrink 플래그 추가
|
||||
// modalContainerId, modalClassName 등은 복원을 위해 유지
|
||||
// isPaused는 변경하지 않음 - 재생은 계속됨
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.log('[minimizeModalMedia] ❌ No modal MediaPanel found - cannot minimize');
|
||||
console.log('[Minimize] ❌ No modal MediaPanel found - cannot minimize');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,10 +285,10 @@ export const minimizeModalMedia = () => (dispatch, getState) => {
|
||||
export const restoreModalMedia = () => (dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
|
||||
console.log('[restoreModalMedia] ========== Called ==========');
|
||||
console.log('[restoreModalMedia] Total panels:', panels.length);
|
||||
console.log('[Restore]] ========== Called ==========');
|
||||
console.log('[Restore] Total panels:', panels.length);
|
||||
console.log(
|
||||
'[restoreModalMedia] All panels:',
|
||||
'[Restore] All panels:',
|
||||
JSON.stringify(
|
||||
panels.map((p) => ({
|
||||
name: p.name,
|
||||
@@ -325,6 +324,7 @@ export const restoreModalMedia = () => (dispatch, getState) => {
|
||||
...minimizedMediaPanel.panelInfo,
|
||||
modal: true, // modal 모드로 복원 (원래 위치로 복귀)
|
||||
isMinimized: false, // 최소화 해제
|
||||
shouldShrinkTo1px: false, // shrink 플래그 초기화
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
2674
com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.js
Normal file
2674
com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.v3.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,843 @@
|
||||
// VideoPlayer.module.less
|
||||
//
|
||||
@import "~@enact/sandstone/styles/variables.less";
|
||||
@import "~@enact/sandstone/styles/mixins.less";
|
||||
@import "~@enact/sandstone/styles/skin.less";
|
||||
@import "../../style/utils.module.less";
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
.videoPlayer {
|
||||
// Set by counting the IconButtons inside the side components.
|
||||
--liftDistance: 0px;
|
||||
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
|
||||
:focus {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.video {
|
||||
height: calc(100% - 4px);
|
||||
width: calc(100% - 4px);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.media {
|
||||
height: calc(100% - 4px);
|
||||
width: calc(100% - 4px);
|
||||
background: #000;
|
||||
|
||||
&.mediaBackground {
|
||||
&:after {
|
||||
width: 560px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(255, 255, 255, 1),
|
||||
transparent
|
||||
);
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preloadVideo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
> img {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 12px;
|
||||
}
|
||||
&.noRadiusThumbnail {
|
||||
> img {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
&.verticalThumbnail {
|
||||
> img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
&.smallThumbnail {
|
||||
&::after {
|
||||
.focused(@boxShadow:0, @borderRadius: 12px);
|
||||
border: 6px solid @PRIMARY_COLOR_RED;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.disclaimer {
|
||||
.size(@w: 100% , @h: 54px);
|
||||
display: flex;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
> span {
|
||||
.size(@w: 18px , @h: 18px);
|
||||
background-image: url("../../../assets/images/icons/ic-alert-20@3x.png");
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
margin: 0 12px 0 20px;
|
||||
}
|
||||
> h3 {
|
||||
.size(@w: 100% , @h: 54px);
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
line-height: 54px;
|
||||
.elip(@clamp:1);
|
||||
.marquee {
|
||||
width: 100%;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
.videoOverlayWithPhoneNumberFull {
|
||||
bottom: 59px;
|
||||
left: 141px;
|
||||
}
|
||||
.videoOverlayWithPhoneNumber {
|
||||
display: none;
|
||||
&.ru {
|
||||
display: flex;
|
||||
|
||||
width: 22%;
|
||||
height: 12.5%;
|
||||
bottom: 6.5% !important;
|
||||
left: 48% !important;
|
||||
padding: 4px !important;
|
||||
margin-bottom: 0 !important;
|
||||
> div:first-child {
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
}
|
||||
> div:last-child {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
> span {
|
||||
font-size: 18px;
|
||||
line-height: 18px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.us {
|
||||
&.vertical {
|
||||
width: 105px;
|
||||
height: 66px;
|
||||
bottom: 225px !important;
|
||||
left: -96px !important;
|
||||
}
|
||||
&.horizontal {
|
||||
> div:first-child {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
> div:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
> span {
|
||||
font-size: 16px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
&.qvc {
|
||||
display: flex;
|
||||
width: 18%;
|
||||
height: 22%;
|
||||
padding: 1%;
|
||||
bottom: 4% !important;
|
||||
left: 4.5% !important;
|
||||
> div:first-child {
|
||||
font-size: 48%;
|
||||
}
|
||||
> div:last-child {
|
||||
> img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
> span {
|
||||
font-size: 48%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hsn {
|
||||
display: flex;
|
||||
width: 18.5%;
|
||||
height: 22%;
|
||||
padding: 1%;
|
||||
bottom: 4% !important;
|
||||
left: 7% !important;
|
||||
> div:first-child {
|
||||
font-size: 48%;
|
||||
}
|
||||
> div:last-child {
|
||||
> img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
> span {
|
||||
font-size: 48%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.verticalModal {
|
||||
> div:last-child {
|
||||
> img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
> span {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> img {
|
||||
width: 102px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
div > img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.videoOverlayMedia {
|
||||
bottom: 24% !important;
|
||||
left: 7% !important;
|
||||
&.callToOrderHide {
|
||||
display: none !important;
|
||||
}
|
||||
&.qvc {
|
||||
display: flex;
|
||||
width: 23%;
|
||||
height: 15%;
|
||||
padding: 30px 5px;
|
||||
> div:first-child {
|
||||
flex: none;
|
||||
font-size: 13px !important;
|
||||
line-height: 13px !important;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
> div:last-child {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
height: 4px;
|
||||
> img {
|
||||
flex: none;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
> span {
|
||||
flex: none;
|
||||
font-size: 15px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hsn {
|
||||
display: flex;
|
||||
width: 23%;
|
||||
height: 15%;
|
||||
padding: 30px 5px;
|
||||
> div:first-child {
|
||||
flex: none;
|
||||
font-size: 13px !important;
|
||||
line-height: 13px !important;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
> div:last-child {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
height: 4px;
|
||||
> img {
|
||||
flex: none;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
> span {
|
||||
flex: none;
|
||||
font-size: 15px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
height: 100%;
|
||||
> div {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
left: calc(50% - 110px);
|
||||
top: calc(50% - 110px);
|
||||
background-color: transparent;
|
||||
> div {
|
||||
> div {
|
||||
-webkit-animation: mulShdSpinWhite 1.2s infinite ease !important;
|
||||
animation: mulShdSpinWhite 1.2s infinite ease !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes mulShdSpinWhite {
|
||||
0%,
|
||||
100% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em #fff,
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0em -2.6em 0em 0em #fff,
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7),
|
||||
1.8em -1.8em 0 0em #fff, 2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7),
|
||||
1.8em -1.8em 0 0em #fff, 2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
25% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), 2.5em 0em 0 0em #fff,
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), 2.5em 0em 0 0em #fff,
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.7), 1.75em 1.75em 0 0em #fff,
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.7), 1.75em 1.75em 0 0em #fff,
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
50% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), 0em 2.5em 0 0em #fff,
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), 0em 2.5em 0 0em #fff,
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.7), -1.8em 1.8em 0 0em #fff,
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.7), -1.8em 1.8em 0 0em #fff,
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
75% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), -2.6em 0em 0 0em #fff,
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), -2.6em 0em 0 0em #fff,
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #fff;
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mulShdSpinWhite {
|
||||
0%,
|
||||
100% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em #fff,
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
||||
box-shadow: 0em -2.6em 0em 0em #fff,
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7),
|
||||
1.8em -1.8em 0 0em #fff, 2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.7),
|
||||
1.8em -1.8em 0 0em #fff, 2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
25% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), 2.5em 0em 0 0em #fff,
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7), 2.5em 0em 0 0em #fff,
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.7), 1.75em 1.75em 0 0em #fff,
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.7), 1.75em 1.75em 0 0em #fff,
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
50% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), 0em 2.5em 0 0em #fff,
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7), 0em 2.5em 0 0em #fff,
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.7), -1.8em 1.8em 0 0em #fff,
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.7), -1.8em 1.8em 0 0em #fff,
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
75% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), -2.6em 0em 0 0em #fff,
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7), -2.6em 0em 0 0em #fff,
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
-webkit-box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #fff;
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: 0, @left: 0);
|
||||
}
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0.25turn);
|
||||
}
|
||||
33% {
|
||||
transform: rotate(0.5turn);
|
||||
}
|
||||
80% {
|
||||
transform: rotate(0.95turn);
|
||||
}
|
||||
85% {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(1.25turn);
|
||||
}
|
||||
}
|
||||
.spinner {
|
||||
background-image: url("../../../assets/images/player/icon_loading.png");
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
border-radius: 20.8125rem;
|
||||
overflow: hidden;
|
||||
// margin: 490px auto;
|
||||
left: calc(50% - 50px);
|
||||
top: calc(50% - 50px);
|
||||
animation: none 1.25s linear infinite;
|
||||
animation-name: spin;
|
||||
// animation-play-state: paused;
|
||||
}
|
||||
.controlFeedbackBtnLayer {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 94px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
&.lift {
|
||||
transform: translateY(~"calc(var(--liftDistance) * -1)");
|
||||
transition: transform 0.3s linear;
|
||||
}
|
||||
}
|
||||
.fullscreen {
|
||||
.miniFeedback {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
top: 506px;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 94px;
|
||||
-webkit-margin-end: 0px;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.liveFullScreen {
|
||||
.bottom {
|
||||
bottom: 78px;
|
||||
}
|
||||
}
|
||||
.bottom {
|
||||
position: absolute;
|
||||
z-index: 3; // Value assigned as part of the VideoPlayer API so layers may be inserted in-between
|
||||
bottom: 70px;
|
||||
// bottom: 78px;
|
||||
// bottom: -18px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 70px;
|
||||
// left: @sand-video-player-padding-side;
|
||||
// right: @sand-video-player-padding-side;
|
||||
|
||||
&.videoVerticalBottom {
|
||||
height: 54px;
|
||||
}
|
||||
&.lift {
|
||||
transform: translateY(~"calc(var(--liftDistance) * -1)");
|
||||
transition: transform 0.3s linear;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
pointer-events: none;
|
||||
|
||||
.sliderContainer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.infoFrame {
|
||||
display: flex;
|
||||
margin-left: 64px;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
// display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
margin-left: 60px;
|
||||
margin-right: 59px;
|
||||
height: 70px;
|
||||
bottom: -20px;
|
||||
> *:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.enact-locale-rtl({
|
||||
direction: ltr;
|
||||
});
|
||||
|
||||
&.videoVertical {
|
||||
margin-left: 680px;
|
||||
margin-right: 673px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controlsHandleAbove {
|
||||
pointer-events: none;
|
||||
.position(@position: absolute, @top: 0, @right: 0, @bottom: auto, @left: 0);
|
||||
}
|
||||
|
||||
// Skin colors
|
||||
.applySkins({
|
||||
.fullscreen {
|
||||
.bottom {
|
||||
background-color: @sand-video-player-bottom-bg-color;
|
||||
|
||||
.infoFrame {
|
||||
text-shadow: @sand-video-player-title-text-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
z-index: 2;
|
||||
|
||||
&.scrim::before,
|
||||
&.scrim::after {
|
||||
width: 1920px;
|
||||
height: 50%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
content: "";
|
||||
z-index: 4;
|
||||
}
|
||||
&.scrim::before {
|
||||
bottom: 0;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, transparent 50%);
|
||||
}
|
||||
&.scrim::after {
|
||||
top: 0;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
// &.scrim::after {
|
||||
// background: @sand-video-player-scrim-gradient-color
|
||||
// }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ========== MediaPlayer.v2 Controls ==========
|
||||
.controlsContainer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20px 40px 30px;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.7) 60%, transparent 100%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.times {
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controlsButtons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.playPauseBtn {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.backBtn {
|
||||
padding: 12px 24px;
|
||||
font-size: 18px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
15
com.twin.app.shoptime/src/hooks/useMediaPanelController.js
Normal file
15
com.twin.app.shoptime/src/hooks/useMediaPanelController.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
const MediaPanelControllerContext = createContext(null);
|
||||
|
||||
export function MediaPanelControllerProvider({ controller, children }) {
|
||||
return (
|
||||
<MediaPanelControllerContext.Provider value={controller}>
|
||||
{children}
|
||||
</MediaPanelControllerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useMediaPanelController() {
|
||||
return useContext(MediaPanelControllerContext);
|
||||
}
|
||||
@@ -1,41 +1,22 @@
|
||||
// src/views/DetailPanel/DetailPanel.new.jsx
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
||||
|
||||
import { getDeviceAdditionInfo } from '../../actions/deviceActions';
|
||||
import { getThemeCurationDetailInfo } from '../../actions/homeActions';
|
||||
import {
|
||||
getMainCategoryDetail,
|
||||
getMainYouMayLike,
|
||||
} from '../../actions/mainActions';
|
||||
import { getMainCategoryDetail, getMainYouMayLike } from '../../actions/mainActions';
|
||||
import { finishModalMediaForce } from '../../actions/mediaActions';
|
||||
import {
|
||||
popPanel,
|
||||
updatePanel,
|
||||
} from '../../actions/panelActions';
|
||||
import { popPanel, updatePanel } from '../../actions/panelActions';
|
||||
import {
|
||||
finishVideoPreview,
|
||||
pauseFullscreenVideo,
|
||||
resumeFullscreenVideo,
|
||||
} from '../../actions/playActions';
|
||||
import {
|
||||
clearProductDetail,
|
||||
getProductOptionId,
|
||||
} from '../../actions/productActions';
|
||||
import { clearProductDetail, getProductOptionId } from '../../actions/productActions';
|
||||
import { clearAllToasts } from '../../actions/toastActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
@@ -148,7 +129,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const onClick = useCallback(
|
||||
const onBackClick = useCallback(
|
||||
(isCancelClick) => (ev) => {
|
||||
fp.pipe(
|
||||
() => {
|
||||
@@ -195,7 +176,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
|
||||
const onBackButtonFocus = useCallback(() => {
|
||||
dispatch(clearAllToasts());
|
||||
},[dispatch])
|
||||
}, [dispatch]);
|
||||
|
||||
const handleScrollToSection = useCallback(
|
||||
(sectionId) => {
|
||||
@@ -429,13 +410,22 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: { shouldReload: false }
|
||||
panelInfo: { shouldReload: false },
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[DetailPanel] Reload complete');
|
||||
}
|
||||
}, [panelShouldReload, dispatch, panelType, panelPatnrId, panelCurationId, panelBgImgNo, panelPrdtId, panelLiveReqFlag]);
|
||||
}, [
|
||||
panelShouldReload,
|
||||
dispatch,
|
||||
panelType,
|
||||
panelPatnrId,
|
||||
panelCurationId,
|
||||
panelBgImgNo,
|
||||
panelPrdtId,
|
||||
panelLiveReqFlag,
|
||||
]);
|
||||
|
||||
// 최근 본 상품 트리거 예시:
|
||||
// useEffect(() => {
|
||||
@@ -730,7 +720,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
<TPanel
|
||||
isTabActivated={false}
|
||||
className={css.detailPanelWrap}
|
||||
handleCancel={onClick(true)}
|
||||
handleCancel={onBackClick(true)}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<THeaderCustom
|
||||
@@ -738,7 +728,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
prdtId={productData?.prdtId}
|
||||
title={headerTitle}
|
||||
onBackButton
|
||||
onClick={onClick(false)}
|
||||
onClick={onBackClick(false)}
|
||||
onBackButtonFocus={onBackButtonFocus}
|
||||
spotlightDisabled={isLoading}
|
||||
onSpotlightUp={onSpotlightUpTButton}
|
||||
@@ -747,7 +737,6 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||
ariaLabel={ariaLabel}
|
||||
logoImg={productData?.patncLogoPath}
|
||||
patnrId={panelPatnrId}
|
||||
|
||||
/>
|
||||
<TBody
|
||||
className={css.tbody}
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
// import { throttle } from 'lodash';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
|
||||
// import Spottable from '@enact/spotlight/Spottable';
|
||||
//image
|
||||
import arrowDown
|
||||
from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||
import indicatorDefaultImage
|
||||
from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
import arrowDown from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||
import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
// import { pushPanel } from '../../../actions/panelActions';
|
||||
import { minimizeModalMedia, restoreModalMedia } from '../../../actions/mediaActions';
|
||||
import { pauseFullscreenVideo } from '../../../actions/playActions';
|
||||
import { resetShowAllReviews } from '../../../actions/productActions';
|
||||
import {
|
||||
clearAllToasts,
|
||||
removeToast,
|
||||
showToast,
|
||||
} from '../../../actions/toastActions';
|
||||
import { clearAllToasts, removeToast, showToast } from '../../../actions/toastActions';
|
||||
// ProductInfoSection imports
|
||||
import TButton from '../../../components/TButton/TButton';
|
||||
import useReviews from '../../../hooks/useReviews/useReviews';
|
||||
@@ -67,16 +51,13 @@ import StarRating from '../components/StarRating';
|
||||
// ProductContentSection imports
|
||||
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
|
||||
import DetailPanelSkeleton from '../DetailPanelSkeleton/DetailPanelSkeleton';
|
||||
import ProductDescription
|
||||
from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||
import ProductDetail
|
||||
from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||
import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||
import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||
import { ProductVideoV2 } from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx';
|
||||
import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo';
|
||||
import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo.v3';
|
||||
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
|
||||
// import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
|
||||
import YouMayAlsoLike
|
||||
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||
import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||
import QRCode from '../ProductInfoSection/QRCode/QRCode';
|
||||
import ProductOverview from '../ProductOverview/ProductOverview';
|
||||
// CSS imports
|
||||
@@ -120,10 +101,7 @@ const HorizontalContainer = SpotlightContainerDecorator(
|
||||
const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
||||
pipe(
|
||||
when(
|
||||
() =>
|
||||
isVal(productType) &&
|
||||
productType === 'theme' &&
|
||||
isVal(themeProductInfo),
|
||||
() => isVal(productType) && productType === 'theme' && isVal(themeProductInfo),
|
||||
() => themeProductInfo
|
||||
),
|
||||
defaultTo(productInfo),
|
||||
@@ -165,9 +143,7 @@ export default function ProductAllSection({
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux 상태
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus.webOSVersion
|
||||
);
|
||||
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
|
||||
const groupInfos = useSelector((state) => state.product.groupInfo);
|
||||
|
||||
// YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독
|
||||
@@ -275,9 +251,7 @@ export default function ProductAllSection({
|
||||
if (webOSVersion < '6.0') {
|
||||
return (
|
||||
productData?.pmtSuptYn === 'N' ||
|
||||
(productData?.pmtSuptYn === 'Y' &&
|
||||
productData?.grPrdtProcYn === 'N' &&
|
||||
panelInfo?.prdtId)
|
||||
(productData?.pmtSuptYn === 'Y' && productData?.grPrdtProcYn === 'N' && panelInfo?.prdtId)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -298,10 +272,7 @@ export default function ProductAllSection({
|
||||
|
||||
// 여행/테마 상품 - DetailPanel.backup.jsx와 동일한 로직
|
||||
const isTravelProductVisible = useMemo(() => {
|
||||
return (
|
||||
panelInfo?.curationId &&
|
||||
(panelInfo?.type === 'theme' || panelInfo?.type === 'hotel')
|
||||
);
|
||||
return panelInfo?.curationId && (panelInfo?.type === 'theme' || panelInfo?.type === 'hotel');
|
||||
}, [panelInfo]);
|
||||
|
||||
// useReviews Hook 사용 - 모든 리뷰 관련 로직을 담당
|
||||
@@ -374,47 +345,50 @@ export default function ProductAllSection({
|
||||
// reviewListData가 반복적으로 초기화되어 Chrome에서 진입 불가 발생
|
||||
|
||||
// BUY NOW 버튼 클릭 핸들러 - Toast로 BuyOption 표시
|
||||
const handleBuyNowClick = useCallback((e) => {
|
||||
// console.log('[BuyNow] Buy Now button clicked');
|
||||
e.stopPropagation();
|
||||
const handleBuyNowClick = useCallback(
|
||||
(e) => {
|
||||
// console.log('[BuyNow] Buy Now button clicked');
|
||||
e.stopPropagation();
|
||||
|
||||
console.log('[ProductAllSection] 🛒 BUY NOW clicked - productData:', {
|
||||
prdtId: productData?.prdtId,
|
||||
patnrId: productData?.patnrId,
|
||||
prdtNm: productData?.prdtNm,
|
||||
hasProductData: !!productData,
|
||||
});
|
||||
if(openToast === false){
|
||||
dispatch(
|
||||
showToast({
|
||||
id: productData.prdtId,
|
||||
message: '',
|
||||
type: 'buyOption',
|
||||
duration: 0,
|
||||
position: 'bottom-center',
|
||||
// 🚀 BuyOption에 전달할 props 데이터
|
||||
productInfo: productData,
|
||||
selectedPatnrId: productData?.patnrId,
|
||||
selectedPrdtId: productData?.prdtId,
|
||||
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
|
||||
onToastClose: () => {
|
||||
setTimeout(() => {
|
||||
setOpenToast(false);
|
||||
Spotlight.focus('detail-buy-now-button');
|
||||
}, 100);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
setOpenToast(true);
|
||||
}
|
||||
}, [dispatch, productData, openToast]);
|
||||
console.log('[ProductAllSection] 🛒 BUY NOW clicked - productData:', {
|
||||
prdtId: productData?.prdtId,
|
||||
patnrId: productData?.patnrId,
|
||||
prdtNm: productData?.prdtNm,
|
||||
hasProductData: !!productData,
|
||||
});
|
||||
if (openToast === false) {
|
||||
dispatch(
|
||||
showToast({
|
||||
id: productData.prdtId,
|
||||
message: '',
|
||||
type: 'buyOption',
|
||||
duration: 0,
|
||||
position: 'bottom-center',
|
||||
// 🚀 BuyOption에 전달할 props 데이터
|
||||
productInfo: productData,
|
||||
selectedPatnrId: productData?.patnrId,
|
||||
selectedPrdtId: productData?.prdtId,
|
||||
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
|
||||
onToastClose: () => {
|
||||
setTimeout(() => {
|
||||
setOpenToast(false);
|
||||
Spotlight.focus('detail-buy-now-button');
|
||||
}, 100);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
setOpenToast(true);
|
||||
}
|
||||
},
|
||||
[dispatch, productData, openToast]
|
||||
);
|
||||
|
||||
//닫히도록
|
||||
const handleCloseToast = useCallback(() => {
|
||||
dispatch(clearAllToasts());
|
||||
setOpenToast(false);
|
||||
},[dispatch])
|
||||
}, [dispatch]);
|
||||
|
||||
// 스크롤 컨테이너의 클릭 이벤트 추적용 로깅
|
||||
const handleScrollContainerClick = useCallback((e) => {
|
||||
@@ -425,11 +399,14 @@ export default function ProductAllSection({
|
||||
bubbles: e.bubbles,
|
||||
defaultPrevented: e.defaultPrevented,
|
||||
timestamp: new Date().getTime(),
|
||||
eventPath: e.composedPath?.().slice(0, 5).map(el => ({
|
||||
tag: el.tagName,
|
||||
className: el.className,
|
||||
id: el.id
|
||||
}))
|
||||
eventPath: e
|
||||
.composedPath?.()
|
||||
.slice(0, 5)
|
||||
.map((el) => ({
|
||||
tag: el.tagName,
|
||||
className: el.className,
|
||||
id: el.id,
|
||||
})),
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -441,11 +418,14 @@ export default function ProductAllSection({
|
||||
currentTarget: e.currentTarget?.className,
|
||||
bubbles: e.bubbles,
|
||||
defaultPrevented: e.defaultPrevented,
|
||||
eventPath: e.composedPath?.().slice(0, 8).map(el => ({
|
||||
tag: el.tagName,
|
||||
className: el.className,
|
||||
id: el.id
|
||||
}))
|
||||
eventPath: e
|
||||
.composedPath?.()
|
||||
.slice(0, 8)
|
||||
.map((el) => ({
|
||||
tag: el.tagName,
|
||||
className: el.className,
|
||||
id: el.id,
|
||||
})),
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -455,10 +435,7 @@ export default function ProductAllSection({
|
||||
// TODO: 장바구니 추가 로직 구현
|
||||
}, []);
|
||||
|
||||
const { revwGrd, orderPhnNo } = useMemo(
|
||||
() => extractProductMeta(productInfo),
|
||||
[productInfo]
|
||||
);
|
||||
const { revwGrd, orderPhnNo } = useMemo(() => extractProductMeta(productInfo), [productInfo]);
|
||||
|
||||
const [favoriteOverride, setFavoriteOverride] = useState(null);
|
||||
const favoriteFlag = useMemo(
|
||||
@@ -467,8 +444,7 @@ export default function ProductAllSection({
|
||||
);
|
||||
|
||||
const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false);
|
||||
const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] =
|
||||
useState(false);
|
||||
const [isShowUserReviewsFocused, setIsShowUserReviewsFocused] = useState(false);
|
||||
|
||||
const reviewTotalCount = stats.totalReviews;
|
||||
|
||||
@@ -491,12 +467,9 @@ export default function ProductAllSection({
|
||||
);
|
||||
|
||||
// User Reviews 스크롤 핸들러 추가
|
||||
const handleUserReviewsClick = useCallback(
|
||||
() => {
|
||||
scrollToSection('scroll-marker-user-reviews');
|
||||
},
|
||||
[scrollToSection]
|
||||
);
|
||||
const handleUserReviewsClick = useCallback(() => {
|
||||
scrollToSection('scroll-marker-user-reviews');
|
||||
}, [scrollToSection]);
|
||||
|
||||
// ProductVideo V1 전용 - MediaPanel minimize 포함
|
||||
const handleScrollToImagesV1 = useCallback(() => {
|
||||
@@ -571,11 +544,7 @@ export default function ProductAllSection({
|
||||
}
|
||||
|
||||
// 이미지들 추가
|
||||
if (
|
||||
productData &&
|
||||
productData.imgUrls600 &&
|
||||
productData.imgUrls600.length > 0
|
||||
) {
|
||||
if (productData && productData.imgUrls600 && productData.imgUrls600.length > 0) {
|
||||
productData.imgUrls600.forEach((image, imgIndex) => {
|
||||
items.push({
|
||||
type: 'image',
|
||||
@@ -595,9 +564,7 @@ export default function ProductAllSection({
|
||||
// renderItems에 Video가 존재하는지 확인하는 boolean 상태
|
||||
const hasVideo = useMemo(() => {
|
||||
return (
|
||||
renderItems &&
|
||||
renderItems.length > 0 &&
|
||||
renderItems.some((item) => item.type === 'video')
|
||||
renderItems && renderItems.length > 0 && renderItems.some((item) => item.type === 'video')
|
||||
);
|
||||
}, [renderItems]);
|
||||
|
||||
@@ -640,10 +607,7 @@ export default function ProductAllSection({
|
||||
pipe(
|
||||
() => setOpenThemeItemOverlay(true),
|
||||
tap(() => {
|
||||
const timerId = setTimeout(
|
||||
() => Spotlight.focus('theme-close-button'),
|
||||
0
|
||||
);
|
||||
const timerId = setTimeout(() => Spotlight.focus('theme-close-button'), 0);
|
||||
timersRef.current.push(timerId);
|
||||
})
|
||||
),
|
||||
@@ -661,6 +625,9 @@ export default function ProductAllSection({
|
||||
);
|
||||
const scrollPositionRef = useRef(0);
|
||||
const prevScrollPositionRef = useRef(0); // 이전 스크롤 위치 추적
|
||||
const prevScrollTopRef = useRef(0); // HomePanel 스타일 스크롤 위치 추적
|
||||
const scrollExpandTimerRef = useRef(null); // 스크롤 확장 타이머
|
||||
const mediaMinimizedRef = useRef(false);
|
||||
|
||||
const handleArrowClickAlternative = useCallback(() => {
|
||||
const currentHeight = scrollPositionRef.current;
|
||||
@@ -672,8 +639,7 @@ export default function ProductAllSection({
|
||||
});
|
||||
|
||||
// documentHeight를 활용하여 반복 계산 제거
|
||||
const totalHeight =
|
||||
documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0);
|
||||
const totalHeight = documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0);
|
||||
const isAtBottom = scrollPositionRef.current + 1100 >= totalHeight;
|
||||
|
||||
if (isAtBottom) {
|
||||
@@ -681,20 +647,71 @@ export default function ProductAllSection({
|
||||
}
|
||||
}, [documentHeight, scrollTop]);
|
||||
|
||||
// const handleScroll = useCallback(
|
||||
// (e) => {
|
||||
// scrollPositionRef.current = e.scrollTop;
|
||||
// if (documentHeight) {
|
||||
// const isAtBottom =
|
||||
// scrollPositionRef.current + 944 >=
|
||||
// documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0);
|
||||
// if (isAtBottom !== isBottom) {
|
||||
// setIsBottom(isAtBottom);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// [documentHeight, isBottom]
|
||||
// );
|
||||
const handleScroll = useCallback(
|
||||
(e) => {
|
||||
const currentScrollTop = e.scrollTop;
|
||||
const prevScrollTop = prevScrollTopRef.current;
|
||||
|
||||
scrollPositionRef.current = currentScrollTop;
|
||||
|
||||
// 기존 bottom 체크 로직 유지
|
||||
if (documentHeight) {
|
||||
const isAtBottom =
|
||||
scrollPositionRef.current + 944 >=
|
||||
documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0);
|
||||
if (isAtBottom !== isBottom) {
|
||||
setIsBottom(isAtBottom);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔽 ProductVideo (v3.jsx)에만 HomePanel 스타일 즉각 스크롤 로직 적용
|
||||
// ProductVideo.v3.jsx는 ProductVideo로 import되어 productVideoVersion === 1일 때 사용됨
|
||||
if (productVideoVersion === 1) {
|
||||
const isScrollingDown = currentScrollTop > prevScrollTop;
|
||||
|
||||
prevScrollTopRef.current = currentScrollTop;
|
||||
|
||||
// 아래로 스크롤: 즉시 1px 축소 (HomePanel과 동일)
|
||||
if (isScrollingDown && currentScrollTop > 0) {
|
||||
console.log('🚀 [ProductVideo.v3] onScroll Down - immediate minimize');
|
||||
dispatch(minimizeModalMedia());
|
||||
setShouldMinimizeMedia(true);
|
||||
|
||||
// 기존 타이머 취소
|
||||
if (scrollExpandTimerRef.current) {
|
||||
clearTimeout(scrollExpandTimerRef.current);
|
||||
scrollExpandTimerRef.current = null;
|
||||
}
|
||||
}
|
||||
// 위로 스크롤 (최상단 아님): 1초 후 복구
|
||||
else if (!isScrollingDown && currentScrollTop > 1) {
|
||||
console.log('🚀 [ProductVideo.v3] onScroll Up - expand after 1s');
|
||||
|
||||
if (scrollExpandTimerRef.current) {
|
||||
clearTimeout(scrollExpandTimerRef.current);
|
||||
}
|
||||
scrollExpandTimerRef.current = setTimeout(() => {
|
||||
dispatch(restoreModalMedia());
|
||||
setShouldMinimizeMedia(false);
|
||||
scrollExpandTimerRef.current = null;
|
||||
}, 1000);
|
||||
}
|
||||
// 최상단 도달: 즉시 복구 (HomePanel과 동일)
|
||||
else if (currentScrollTop <= 1) {
|
||||
console.log('🚀 [ProductVideo.v3] onScroll AtTop - immediate expand');
|
||||
dispatch(restoreModalMedia());
|
||||
setShouldMinimizeMedia(false);
|
||||
|
||||
if (scrollExpandTimerRef.current) {
|
||||
clearTimeout(scrollExpandTimerRef.current);
|
||||
scrollExpandTimerRef.current = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// v2: onScrollStop에서 처리 (기존 로직 유지)
|
||||
},
|
||||
[documentHeight, isBottom, productVideoVersion, dispatch]
|
||||
);
|
||||
|
||||
// 스크롤 멈추었을 때만 호출 (성능 최적화)
|
||||
const handleScrollStop = useCallback(
|
||||
@@ -712,13 +729,29 @@ export default function ProductAllSection({
|
||||
}
|
||||
|
||||
console.log('📍 [ProductAllSection] 스크롤 멈춤 - 위치:', currentScrollTop);
|
||||
|
||||
const shouldMinimize = currentScrollTop > 0;
|
||||
console.log('📍 [ProductAllSection] setShouldMinimizeMedia 호출:', shouldMinimize);
|
||||
setShouldMinimizeMedia(shouldMinimize); // state 업데이트만
|
||||
setShouldMinimizeMedia((prev) => {
|
||||
if (prev === shouldMinimize) return prev;
|
||||
console.log('📍 [ProductAllSection] setShouldMinimizeMedia 호출:', shouldMinimize);
|
||||
return shouldMinimize;
|
||||
});
|
||||
},
|
||||
[documentHeight, isBottom]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldMinimizeMedia && !mediaMinimizedRef.current) {
|
||||
console.log('📍 [ProductAllSection] MediaPanel 최소화 (effect)');
|
||||
dispatch(minimizeModalMedia());
|
||||
mediaMinimizedRef.current = true;
|
||||
} else if (!shouldMinimizeMedia && mediaMinimizedRef.current) {
|
||||
console.log('📍 [ProductAllSection] MediaPanel 복원 (effect)');
|
||||
dispatch(restoreModalMedia());
|
||||
mediaMinimizedRef.current = false;
|
||||
}
|
||||
}, [shouldMinimizeMedia, dispatch]);
|
||||
|
||||
const handleButtonFocus = useCallback((buttonType) => {
|
||||
if (activeButton !== buttonType) {
|
||||
setActiveButton(buttonType);
|
||||
@@ -829,6 +862,12 @@ export default function ProductAllSection({
|
||||
scrollToImagesTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// 🔽 스크롤 확장 타이머 cleanup 추가
|
||||
if (scrollExpandTimerRef.current) {
|
||||
clearTimeout(scrollExpandTimerRef.current);
|
||||
scrollExpandTimerRef.current = null;
|
||||
}
|
||||
|
||||
console.log('[ProductAllSection] cleanup 완료 on unmount');
|
||||
};
|
||||
}, []);
|
||||
@@ -875,11 +914,7 @@ export default function ProductAllSection({
|
||||
>
|
||||
<div className={css.qrWrapper}>
|
||||
{isShowQRCode ? (
|
||||
<QRCode
|
||||
productInfo={productData}
|
||||
productType={productType}
|
||||
kind={'detail'}
|
||||
/>
|
||||
<QRCode productInfo={productData} productType={productType} kind={'detail'} />
|
||||
) : (
|
||||
<div className={css.qrRollingWrap}>
|
||||
<div className={css.innerText}>
|
||||
@@ -929,9 +964,7 @@ export default function ProductAllSection({
|
||||
onClick={handleShopByMobileOpen}
|
||||
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||
>
|
||||
<div className={css.shopByMobileText}>
|
||||
{$L('SHOP BY MOBILE')}
|
||||
</div>
|
||||
<div className={css.shopByMobileText}>{$L('SHOP BY MOBILE')}</div>
|
||||
</TButton>
|
||||
{panelInfo && (
|
||||
<div className={css.favoriteBtnWrapper}>
|
||||
@@ -950,9 +983,7 @@ export default function ProductAllSection({
|
||||
<div className={css.callToOrderSection}>
|
||||
{orderPhnNo && (
|
||||
<>
|
||||
<div className={css.callToOrderText}>
|
||||
{$L('Call to Order')}
|
||||
</div>
|
||||
<div className={css.callToOrderText}>{$L('Call to Order')}</div>
|
||||
<div className={css.phoneSection}>
|
||||
<div className={css.phoneIconContainer}>
|
||||
<div className={css.phoneIcon} />
|
||||
@@ -979,7 +1010,7 @@ export default function ProductAllSection({
|
||||
</TButton>
|
||||
{isReviewDataComplete && (
|
||||
<>
|
||||
{/*
|
||||
{/*
|
||||
{console.log('[ProductAllSection_useReviewList] 🎯 버튼 렌더링:', {
|
||||
hasReviews,
|
||||
reviewTotalCount,
|
||||
@@ -1020,17 +1051,15 @@ export default function ProductAllSection({
|
||||
})()} */}
|
||||
</Container>
|
||||
|
||||
{panelInfo &&
|
||||
panelInfo.type === 'theme' &&
|
||||
!openThemeItemOverlay && (
|
||||
<TButton
|
||||
className={css.themeButton}
|
||||
onClick={handleThemeItemButtonClick}
|
||||
spotlightId="theme-open-button"
|
||||
>
|
||||
{$L('THEME ITEM')}
|
||||
</TButton>
|
||||
)}
|
||||
{panelInfo && panelInfo.type === 'theme' && !openThemeItemOverlay && (
|
||||
<TButton
|
||||
className={css.themeButton}
|
||||
onClick={handleThemeItemButtonClick}
|
||||
spotlightId="theme-open-button"
|
||||
>
|
||||
{$L('THEME ITEM')}
|
||||
</TButton>
|
||||
)}
|
||||
|
||||
<DetailMobileSendPopUp
|
||||
ismobileSendPopupOpen={mobileSendPopupOpen}
|
||||
@@ -1060,7 +1089,7 @@ export default function ProductAllSection({
|
||||
spotlightId="main-content-scroller"
|
||||
spotlightDisabled={false}
|
||||
spotlightRestrict="none"
|
||||
// onScroll={handleScroll}
|
||||
onScroll={handleScroll}
|
||||
onScrollStop={handleScrollStop}
|
||||
onClick={handleScrollContainerClick}
|
||||
>
|
||||
@@ -1073,36 +1102,32 @@ export default function ProductAllSection({
|
||||
onBlur={handleButtonBlur}
|
||||
>
|
||||
{/* 비디오가 있으면 먼저 렌더링 (productVideoVersion이 3이 아닐 때만) */}
|
||||
{hasVideo &&
|
||||
renderItems[0].type === 'video' &&
|
||||
productVideoVersion !== 3 && (
|
||||
<>
|
||||
{productVideoVersion === 1 ? (
|
||||
<ProductVideo
|
||||
key="product-video-0"
|
||||
productInfo={productData}
|
||||
videoUrl={renderItems[0].url}
|
||||
thumbnailUrl={renderItems[0].thumbnail}
|
||||
autoPlay={true}
|
||||
continuousPlay={true}
|
||||
onScrollToImages={handleScrollToImagesV1}
|
||||
/>
|
||||
) : (
|
||||
<ProductVideoV2
|
||||
key="product-video-v2-0"
|
||||
productInfo={productData}
|
||||
videoUrl={renderItems[0].url}
|
||||
thumbnailUrl={renderItems[0].thumbnail}
|
||||
autoPlay={true}
|
||||
onScrollToImages={handleScrollToImagesV2}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
id="scroll-marker-after-video"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
</>
|
||||
)}
|
||||
{hasVideo && renderItems[0].type === 'video' && productVideoVersion !== 3 && (
|
||||
<>
|
||||
{productVideoVersion === 1 ? (
|
||||
<ProductVideo
|
||||
key="product-video-0"
|
||||
productInfo={productData}
|
||||
videoUrl={renderItems[0].url}
|
||||
thumbnailUrl={renderItems[0].thumbnail}
|
||||
autoPlay={true}
|
||||
continuousPlay={true}
|
||||
onScrollToImages={handleScrollToImagesV1}
|
||||
onFocus={() => console.log('[ProductVideo V1] Focused')}
|
||||
/>
|
||||
) : (
|
||||
<ProductVideoV2
|
||||
key="product-video-v2-0"
|
||||
productInfo={productData}
|
||||
videoUrl={renderItems[0].url}
|
||||
thumbnailUrl={renderItems[0].thumbnail}
|
||||
autoPlay={true}
|
||||
onScrollToImages={handleScrollToImagesV2}
|
||||
/>
|
||||
)}
|
||||
<div id="scroll-marker-after-video" className={css.scrollMarker}></div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 이미지들만 렌더링 (비디오 제외) */}
|
||||
{renderItems.length > 0
|
||||
@@ -1121,10 +1146,7 @@ export default function ProductAllSection({
|
||||
))
|
||||
: !hasVideo && <ProductDetail productInfo={productData} />}
|
||||
</div>
|
||||
<div
|
||||
id="scroll-marker-product-details"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
<div id="scroll-marker-product-details" className={css.scrollMarker}></div>
|
||||
<div
|
||||
id="product-description-section"
|
||||
ref={descriptionRef}
|
||||
@@ -1134,10 +1156,7 @@ export default function ProductAllSection({
|
||||
<ProductDescription productInfo={productData} />
|
||||
</div>
|
||||
{/* 리뷰 데이터가 완전할 때만 UserReviews 섹션 표시 */}
|
||||
<div
|
||||
id="scroll-marker-user-reviews"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
<div id="scroll-marker-user-reviews" className={css.scrollMarker}></div>
|
||||
{isReviewDataComplete && (
|
||||
<div
|
||||
id="user-reviews-section"
|
||||
@@ -1165,10 +1184,7 @@ export default function ProductAllSection({
|
||||
)}
|
||||
</div>
|
||||
<div ref={youMayAlsoLikelRef}>
|
||||
<div
|
||||
id="scroll-marker-you-may-also-like"
|
||||
className={css.scrollMarker}
|
||||
></div>
|
||||
<div id="scroll-marker-you-may-also-like" className={css.scrollMarker}></div>
|
||||
{hasYouMayAlsoLike && (
|
||||
<div id="you-may-also-like-section">
|
||||
{/* {(() => {
|
||||
|
||||
@@ -120,8 +120,6 @@
|
||||
padding: 0 15px 0 30px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export default function ProductVideo({
|
||||
onScrollToImages,
|
||||
autoPlay = false, // 자동 재생 여부
|
||||
continuousPlay = false, // 반복 재생 여부
|
||||
onFocus = null, // 외부에서 전달된 포커스 핸들러
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -115,8 +116,13 @@ export default function ProductVideo({
|
||||
console.log('[ProductVideo] Calling restoreModalMedia');
|
||||
// ProductVideo에 포커스가 돌아오면 비디오 복원
|
||||
// dispatch(restoreModalMedia());
|
||||
|
||||
// 외부에서 전달된 onFocus 핸들러도 호출
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
}
|
||||
}, [canPlayVideo, dispatch]);
|
||||
}, [canPlayVideo, dispatch, onFocus]);
|
||||
|
||||
const videoContainerOnBlur = useCallback(() => {
|
||||
console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo);
|
||||
|
||||
@@ -614,12 +614,7 @@ export function ProductVideoV2({
|
||||
debugLog('[Fullscreen Container] Enter key - overlay visible, allow default behavior');
|
||||
}
|
||||
},
|
||||
[
|
||||
isPlaying,
|
||||
isFullscreen,
|
||||
toggleOverlayVisibility,
|
||||
mediaOverlayState.controls?.visible,
|
||||
]
|
||||
[isPlaying, isFullscreen, toggleOverlayVisibility, mediaOverlayState.controls?.visible]
|
||||
);
|
||||
|
||||
// 마우스 다운 (클릭) 이벤트 - capture phase에서 처리
|
||||
@@ -1178,6 +1173,3 @@ export function ProductVideoV2({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import {
|
||||
startMediaPlayer,
|
||||
finishMediaPreview,
|
||||
switchMediaToFullscreen,
|
||||
minimizeModalMedia,
|
||||
restoreModalMedia,
|
||||
} 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';
|
||||
import css from './ProductVideo.module.less';
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
export default function ProductVideo({
|
||||
productInfo,
|
||||
videoUrl,
|
||||
thumbnailUrl,
|
||||
onScrollToImages,
|
||||
autoPlay = false, // 자동 재생 여부
|
||||
continuousPlay = false, // 반복 재생 여부
|
||||
onFocus = null, // 외부에서 전달된 포커스 핸들러
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// MediaPanel 상태 체크를 위한 selectors 추가
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const [isLaunchedFromPlayer, setIsLaunchedFromPlayer] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [modalState, setModalState] = useState(true); // 모달 상태 관리 추가
|
||||
const [hasAutoPlayed, setHasAutoPlayed] = useState(false); // 자동 재생 완료 여부
|
||||
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// MediaPanel 상태 체크 로직 + 모달 상태 복원
|
||||
useEffect(() => {
|
||||
if (
|
||||
topPanel &&
|
||||
topPanel.name === panel_names.MEDIA_PANEL &&
|
||||
topPanel.panelInfo.modal === false
|
||||
) {
|
||||
return; // MediaPanel이 전체화면 모드일 때는 처리하지 않음
|
||||
}
|
||||
|
||||
// MediaPanel이 닫혔을 때 modalState를 true로 복원
|
||||
if (!topPanel || topPanel.name !== panel_names.MEDIA_PANEL) {
|
||||
setModalState(true);
|
||||
}
|
||||
}, [topPanel]);
|
||||
|
||||
// autoPlay 기능: 컴포넌트 마운트 시 자동으로 비디오 재생
|
||||
useEffect(() => {
|
||||
if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo) {
|
||||
console.log('[ProductVideo] Auto-playing video');
|
||||
setHasAutoPlayed(true);
|
||||
|
||||
// 짧은 딜레이 후 재생 시작 (컴포넌트 마운트 완료 후)
|
||||
setTimeout(() => {
|
||||
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,
|
||||
continuousPlay, // 반복 재생 옵션 전달
|
||||
})
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
}, [
|
||||
autoPlay,
|
||||
canPlayVideo,
|
||||
hasAutoPlayed,
|
||||
productInfo,
|
||||
dispatch,
|
||||
modalClassNameChange,
|
||||
continuousPlay,
|
||||
]);
|
||||
|
||||
// 비디오 재생 가능 여부 체크
|
||||
const canPlayVideo = useMemo(() => {
|
||||
return Boolean(productInfo?.prdtMediaUrl);
|
||||
}, [productInfo]);
|
||||
|
||||
// 모달 CSS 클래스 변경 로직
|
||||
const modalClassNameChange = useCallback(() => {
|
||||
if (focused) {
|
||||
return css.videoModal;
|
||||
}
|
||||
return '';
|
||||
}, [focused]);
|
||||
|
||||
// 포커스 이벤트 핸들러
|
||||
const videoContainerOnFocus = useCallback(() => {
|
||||
if (canPlayVideo) {
|
||||
setFocused(true);
|
||||
console.log('[ProductVideo] Calling restoreModalMedia');
|
||||
// ProductVideo에 포커스가 돌아오면 비디오 복원
|
||||
// dispatch(restoreModalMedia());
|
||||
|
||||
// 외부에서 전달된 onFocus 핸들러도 호출
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
}
|
||||
}, [canPlayVideo, dispatch, onFocus]);
|
||||
|
||||
const videoContainerOnBlur = useCallback(() => {
|
||||
console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo);
|
||||
// if (canPlayVideo) {
|
||||
// setFocused(false);
|
||||
// // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지
|
||||
// dispatch(minimizeModalMedia());
|
||||
// }
|
||||
}, [canPlayVideo, dispatch]);
|
||||
|
||||
// Spotlight Down 키 핸들러 - 비디오 다음 이미지로 스크롤
|
||||
const handleSpotlightDown = useCallback(
|
||||
(e) => {
|
||||
if (canPlayVideo && onScrollToImages) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dispatch(minimizeModalMedia());
|
||||
onScrollToImages();
|
||||
return true; // 이벤트 처리 완료
|
||||
}
|
||||
return false; // Spotlight가 기본 동작 수행
|
||||
},
|
||||
[canPlayVideo, onScrollToImages, 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];
|
||||
|
||||
// 현재 MediaPanel이 modal=true로 재생 중인지 확인
|
||||
const isCurrentlyPlayingModal =
|
||||
currentTopPanel &&
|
||||
currentTopPanel.name === panel_names.MEDIA_PANEL &&
|
||||
currentTopPanel.panelInfo.modal === true;
|
||||
|
||||
console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2));
|
||||
console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal);
|
||||
|
||||
// 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,
|
||||
continuousPlay, // 반복 재생 옵션 전달
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLaunchedFromPlayer) {
|
||||
setIsLaunchedFromPlayer(false);
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
productInfo,
|
||||
canPlayVideo,
|
||||
isLaunchedFromPlayer,
|
||||
modalClassNameChange,
|
||||
panels,
|
||||
modalState,
|
||||
]);
|
||||
|
||||
if (!canPlayVideo) return null;
|
||||
|
||||
return (
|
||||
<SpottableComponent
|
||||
className={css.videoContainer}
|
||||
onClick={handleVideoClick}
|
||||
onFocus={videoContainerOnFocus}
|
||||
onBlur={videoContainerOnBlur}
|
||||
onSpotlightDown={handleSpotlightDown}
|
||||
spotlightId="product-video-player"
|
||||
aria-label={`${productInfo?.prdtNm} 동영상 재생`}
|
||||
>
|
||||
<div className={css.videoThumbnailWrapper}>
|
||||
<CustomImage
|
||||
src={thumbnailUrl}
|
||||
alt={`${productInfo?.prdtNm} 동영상 썸네일`}
|
||||
className={css.videoThumbnail}
|
||||
/>
|
||||
<div className={css.playButtonOverlay}>
|
||||
<img src={playImg} alt="재생" />
|
||||
</div>
|
||||
</div>
|
||||
</SpottableComponent>
|
||||
);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ import IntroPanel from '../IntroPanel/IntroPanel.new';
|
||||
import JustForYouPanel from '../JustForYouPanel/JustForYouPanel';
|
||||
import JustForYouTestPanel from '../JustForYouTestPanel/JustForYouTestPanel';
|
||||
import LoadingPanel from '../LoadingPanel/LoadingPanel';
|
||||
import MediaPanel from '../MediaPanel/MediaPanel';
|
||||
import MediaPanel from '../MediaPanel/MediaPanel.v3';
|
||||
import MyPagePanel from '../MyPagePanel/MyPagePanel';
|
||||
import OnSalePanel from '../OnSalePanel/OnSalePanel';
|
||||
import PlayerPanel from '../PlayerPanel/PlayerPanel';
|
||||
@@ -163,7 +163,7 @@ export default function MainView({ className, initService }) {
|
||||
const [imagePreloaded, setImagePreloaded] = useState(false);
|
||||
const [ariaHidden, setAriaHidden] = useState(false);
|
||||
const [showEndOfServicePopup, setShowEndOfServicePopup] = useState(false);
|
||||
|
||||
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// BUYNOW_CONFIG 초기화 (마운트 시 한 번만 실행)
|
||||
@@ -208,14 +208,19 @@ export default function MainView({ className, initService }) {
|
||||
console.log(`[MainView] 🔍 Top panel name: ${topPanel?.name}`);
|
||||
console.log(`[MainView] 🔍 isStandalonePanel check:`, isStandalonePanel(topPanel?.name));
|
||||
console.log(`[MainView] 🔍 STANDALONE_PANELS:`, STANDALONE_PANELS);
|
||||
console.log(`[MainView] 🔍 All panels:`, panels.map(p => ({ name: p.name, hasModal: !!p.panelInfo?.modal })));
|
||||
console.log(
|
||||
`[MainView] 🔍 All panels:`,
|
||||
panels.map((p) => ({ name: p.name, hasModal: !!p.panelInfo?.modal }))
|
||||
);
|
||||
}
|
||||
|
||||
if (isStandalonePanel(topPanel?.name)) {
|
||||
if (DEBUG_MODE) {
|
||||
console.log(`[MainView] ✅ Standalone panel detected: ${topPanel?.name} - rendering independently`);
|
||||
console.log(
|
||||
`[MainView] ✅ Standalone panel detected: ${topPanel?.name} - rendering independently`
|
||||
);
|
||||
}
|
||||
renderingPanels = [topPanel]; // 단독 패널만 단독으로 렌더링
|
||||
renderingPanels = [topPanel]; // 단독 패널만 단독으로 렌더링
|
||||
}
|
||||
// 기존 3-layer 구조 체크: PlayerPanel + DetailPanel + MediaPanel(modal)
|
||||
else {
|
||||
@@ -716,7 +721,12 @@ export default function MainView({ className, initService }) {
|
||||
}, [webOSVersion]);
|
||||
const handleErrorPopupClose = useCallback(() => {
|
||||
if (DEBUG_MODE) {
|
||||
console.log('handleErrorPopupClose 호출됨! activePopup:', activePopup, 'popupData:', popupData);
|
||||
console.log(
|
||||
'handleErrorPopupClose 호출됨! activePopup:',
|
||||
activePopup,
|
||||
'popupData:',
|
||||
popupData
|
||||
);
|
||||
}
|
||||
if (popupData?.shouldPopPanel) {
|
||||
dispatch(popPanel());
|
||||
|
||||
2629
com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx
Normal file
2629
com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,142 @@
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
@videoBackgroundColor: black;
|
||||
.videoContainer {
|
||||
position: absolute;
|
||||
background-color: @videoBackgroundColor;
|
||||
z-index: 21;
|
||||
outline: none;
|
||||
|
||||
&:focus-visible,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.videoFrame {
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.videoFrame:focus-within {
|
||||
outline: 2px solid #c70850;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.videoFrame video,
|
||||
.videoFrame iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playButton {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
background-image: url("../../../assets/images/btn/btn-video-play-nor@3x.png");
|
||||
background-size: cover;
|
||||
|
||||
&:focus {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
border-radius: 50%;
|
||||
background-color: #c70850;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.pauseButton {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
background-image: url("../../../assets/images/btn/btn-voc-pause-nor@3x.png");
|
||||
background-size: cover;
|
||||
|
||||
&:focus {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
border-radius: 50%;
|
||||
background-color: #c70850;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.toOpenBtn {
|
||||
.size(@w: 147px, @h: 243px);
|
||||
min-width: 60px !important;
|
||||
margin: 192px 0 136px 512px;
|
||||
text-align: center;
|
||||
background-image: url(../../../assets/images/btn/btn-toopen-foc.svg);
|
||||
}
|
||||
|
||||
.videoReduce {
|
||||
.size(@w:78px, @h:78px);
|
||||
background: url("../../../assets/images/btn/btn-video-min-nor@3x.png")
|
||||
no-repeat center center/60px 60px;
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
bottom: 150px;
|
||||
z-index: 3;
|
||||
|
||||
&:focus {
|
||||
.size(@w:78px, @h:78px);
|
||||
background-color: rgba(199, 8, 80, 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
&.shrinkTo1px {
|
||||
/* 모달 비디오를 1px로 축소 */
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
background-color: @videoBackgroundColor;
|
||||
overflow: visible;
|
||||
.tabContainer,
|
||||
.arrow,
|
||||
.toOpenBtn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.modal,
|
||||
&.modal-minimized,
|
||||
&.background {
|
||||
/* 실제 css */
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
/* //실제 css */
|
||||
/* 테스트 용도 */
|
||||
// width: 1920px;
|
||||
// height: 1080px;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
/* //테스트 용도 */
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
background-color: @videoBackgroundColor;
|
||||
overflow: visible;
|
||||
.tabContainer,
|
||||
.arrow,
|
||||
.toOpenBtn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.background-visible {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
background-color: @videoBackgroundColor;
|
||||
overflow: visible;
|
||||
.tabContainer,
|
||||
.arrow,
|
||||
.toOpenBtn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.hideSubtitle {
|
||||
video::cue {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user