[251019] fix: DetailPanel -> ProductAlLSection 백그라운드 비디오 제어

🕐 커밋 시간: 2025. 10. 19. 16:37:58

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +129줄
  • 삭제: -13줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/playActions.js (javascript):
     Added: resumeModalVideo()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
This commit is contained in:
2025-10-19 16:38:03 +09:00
parent cd1788ad4f
commit 5a7c296326
4 changed files with 215 additions and 160 deletions

View File

@@ -139,13 +139,20 @@ export const resumeModalVideo = () => (dispatch, getState) => {
export const pauseFullscreenVideo = () => (dispatch, getState) => { export const pauseFullscreenVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels; const panels = getState().panels.panels;
// console.log('[BgVideo] pauseFullscreenVideo called - panels:', {
// panelsCount: panels?.length,
// panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal, isPaused: p.panelInfo?.isPaused }))
// });
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널) // 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
const fullscreenPlayerPanel = panels.find( const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal (panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
); );
// console.log('[BgVideo] pauseFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel);
if (fullscreenPlayerPanel) { if (fullscreenPlayerPanel) {
console.log('[pauseFullscreenVideo] Pausing fullscreen video'); // console.log('[BgVideo] pauseFullscreenVideo - dispatching updatePanel with isPaused: true');
dispatch( dispatch(
updatePanel({ updatePanel({
name: panel_names.PLAYER_PANEL, name: panel_names.PLAYER_PANEL,
@@ -155,6 +162,8 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
}, },
}) })
); );
} else {
console.log('[BgVideo] pauseFullscreenVideo - No fullscreen PlayerPanel found, skipping');
} }
}; };
@@ -162,13 +171,21 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
export const resumeFullscreenVideo = () => (dispatch, getState) => { export const resumeFullscreenVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels; const panels = getState().panels.panels;
// console.log('[BgVideo] resumeFullscreenVideo called - panels:', {
// panelsCount: panels?.length,
// panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal, isPaused: p.panelInfo?.isPaused }))
// });
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널) // 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
const fullscreenPlayerPanel = panels.find( const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal (panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
); );
// console.log('[BgVideo] resumeFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel);
// console.log('[BgVideo] resumeFullscreenVideo - isPaused:', fullscreenPlayerPanel?.panelInfo?.isPaused);
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) { if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
console.log('[resumeFullscreenVideo] Resuming fullscreen video'); // console.log('[BgVideo] resumeFullscreenVideo - dispatching updatePanel with isPaused: false');
dispatch( dispatch(
updatePanel({ updatePanel({
name: panel_names.PLAYER_PANEL, name: panel_names.PLAYER_PANEL,
@@ -178,6 +195,8 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => {
}, },
}) })
); );
} else {
console.log('[BgVideo] resumeFullscreenVideo - Not resuming (not found or not paused)');
} }
}; };

View File

@@ -1,116 +1,89 @@
// src/views/DetailPanel/DetailPanel.new.jsx // src/views/DetailPanel/DetailPanel.new.jsx
import React, { import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from 'react-redux';
import Spinner from "@enact/sandstone/Spinner"; import Spinner from '@enact/sandstone/Spinner';
import Spotlight from "@enact/spotlight"; import Spotlight from '@enact/spotlight';
import { setContainerLastFocusedElement } from "@enact/spotlight/src/container"; import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import indicatorDefaultImage from "../../../assets/images/img-thumb-empty-144@3x.png"; import indicatorDefaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
import { getDeviceAdditionInfo } from "../../actions/deviceActions"; import { getDeviceAdditionInfo } from '../../actions/deviceActions';
import { getThemeCurationDetailInfo } from "../../actions/homeActions"; import { getThemeCurationDetailInfo } from '../../actions/homeActions';
import { getMainCategoryDetail, getMainYouMayLike } from '../../actions/mainActions';
import { popPanel, updatePanel } from '../../actions/panelActions';
import { import {
getMainCategoryDetail, finishVideoPreview,
getMainYouMayLike, pauseFullscreenVideo,
} from "../../actions/mainActions"; resumeFullscreenVideo,
import { popPanel, updatePanel } from "../../actions/panelActions"; } from '../../actions/playActions';
import { finishVideoPreview } from "../../actions/playActions"; import { clearProductDetail, getProductOptionId } from '../../actions/productActions';
import { import TBody from '../../components/TBody/TBody';
clearProductDetail, import TPanel from '../../components/TPanel/TPanel';
getProductOptionId, import { panel_names } from '../../utils/Config';
} from "../../actions/productActions"; import fp from '../../utils/fp';
import TBody from "../../components/TBody/TBody"; import { $L, getQRCodeUrl } from '../../utils/helperMethods';
import TPanel from "../../components/TPanel/TPanel"; import { SpotlightIds } from '../../utils/SpotlightIds';
import { panel_names } from "../../utils/Config"; import DetailPanelBackground from './components/DetailPanelBackground';
import fp from "../../utils/fp"; import THeaderCustom from './components/THeaderCustom';
import { $L, getQRCodeUrl } from "../../utils/helperMethods"; import css from './DetailPanel.module.less';
import { SpotlightIds } from "../../utils/SpotlightIds"; import DetailPanelSkeleton from './DetailPanelSkeleton/DetailPanelSkeleton';
import DetailPanelBackground from "./components/DetailPanelBackground"; import ProductAllSection from './ProductAllSection/ProductAllSection';
import THeaderCustom from "./components/THeaderCustom"; import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay';
import css from "./DetailPanel.module.less";
import DetailPanelSkeleton from "./DetailPanelSkeleton/DetailPanelSkeleton";
import ProductAllSection from "./ProductAllSection/ProductAllSection";
import ThemeItemListOverlay from "./ThemeItemListOverlay/ThemeItemListOverlay";
export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const productData = useSelector((state) => state.main.productData); const productData = useSelector((state) => state.main.productData);
const youmaylikeData = useSelector((state) => state.main.youmaylikeData); const youmaylikeData = useSelector((state) => state.main.youmaylikeData);
const themeProductInfos = useSelector( const themeProductInfos = useSelector((state) => state.home.themeCurationDetailInfoData);
(state) => state.home.themeCurationDetailInfoData
);
const isLoading = useSelector((state) => const isLoading = useSelector((state) =>
fp.pipe(() => state, fp.get("common.appStatus.showLoadingPanel.show"))() fp.pipe(() => state, fp.get('common.appStatus.showLoadingPanel.show'))()
); );
const themeData = useSelector((state) => const themeData = useSelector((state) =>
fp.pipe( fp.pipe(
() => state, () => state,
fp.get("home.productData.themeInfo"), fp.get('home.productData.themeInfo'),
(list) => list && list[0] (list) => list && list[0]
)() )()
); );
const webOSVersion = useSelector( const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
(state) => state.common.appStatus.webOSVersion
);
const panels = useSelector((state) => state.panels.panels); const panels = useSelector((state) => state.panels.panels);
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const localRecentItems = useSelector((state) => const localRecentItems = useSelector((state) =>
fp.pipe(() => state, fp.get("localSettings.recentItems"))() fp.pipe(() => state, fp.get('localSettings.recentItems'))()
); );
const { httpHeader } = useSelector((state) => state.common); const { httpHeader } = useSelector((state) => state.common);
const { popupVisible, activePopup } = useSelector( const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
(state) => state.common.popup const [lgCatCd, setLgCatCd] = useState('');
);
const [lgCatCd, setLgCatCd] = useState("");
const [themeProductInfo, setThemeProductInfo] = useState(null); const [themeProductInfo, setThemeProductInfo] = useState(null);
const containerRef = useRef(null); const containerRef = useRef(null);
const panelType = useMemo( const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]);
() => fp.pipe(() => panelInfo, fp.get("type"))(),
[panelInfo]
);
const panelCurationId = useMemo( const panelCurationId = useMemo(
() => fp.pipe(() => panelInfo, fp.get("curationId"))(), () => fp.pipe(() => panelInfo, fp.get('curationId'))(),
[panelInfo]
);
const panelPatnrId = useMemo(
() => fp.pipe(() => panelInfo, fp.get("patnrId"))(),
[panelInfo]
);
const panelPrdtId = useMemo(
() => fp.pipe(() => panelInfo, fp.get("prdtId"))(),
[panelInfo] [panelInfo]
); );
const panelPatnrId = useMemo(() => fp.pipe(() => panelInfo, fp.get('patnrId'))(), [panelInfo]);
const panelPrdtId = useMemo(() => fp.pipe(() => panelInfo, fp.get('prdtId'))(), [panelInfo]);
const panelLiveReqFlag = useMemo( const panelLiveReqFlag = useMemo(
() => fp.pipe(() => panelInfo, fp.get("liveReqFlag"))(), () => fp.pipe(() => panelInfo, fp.get('liveReqFlag'))(),
[panelInfo]
);
const panelBgImgNo = useMemo(
() => fp.pipe(() => panelInfo, fp.get("bgImgNo"))(),
[panelInfo] [panelInfo]
); );
const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]);
// PlayerPanel에서 진입했는지 여부를 panelInfo에서 추출 // PlayerPanel에서 진입했는지 여부를 panelInfo에서 추출
const panelLaunchedFromPlayer = useMemo( const panelLaunchedFromPlayer = useMemo(
() => fp.pipe(() => panelInfo, fp.get("launchedFromPlayer"))(), () => fp.pipe(() => panelInfo, fp.get('launchedFromPlayer'))(),
[panelInfo] [panelInfo]
); );
const productPmtSuptYn = useMemo( const productPmtSuptYn = useMemo(
() => fp.pipe(() => productData, fp.get("pmtSuptYn"))(), () => fp.pipe(() => productData, fp.get('pmtSuptYn'))(),
[productData] [productData]
); );
const productGrPrdtProcYn = useMemo( const productGrPrdtProcYn = useMemo(
() => fp.pipe(() => productData, fp.get("grPrdtProcYn"))(), () => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(),
[productData] [productData]
); );
@@ -118,7 +91,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
() => () =>
fp.pipe( fp.pipe(
() => panelType, () => panelType,
(type) => (type === "theme" ? themeData : productData) (type) => (type === 'theme' ? themeData : productData)
)(), )(),
[panelType, themeData, productData] [panelType, themeData, productData]
); );
@@ -144,7 +117,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const onSpotlightUpTButton = useCallback((e) => { const onSpotlightUpTButton = useCallback((e) => {
e.stopPropagation(); e.stopPropagation();
Spotlight.focus("spotlightId_backBtn"); Spotlight.focus('spotlightId_backBtn');
}, []); }, []);
const onClick = useCallback( const onClick = useCallback(
@@ -159,12 +132,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const shouldUpdatePanel = const shouldUpdatePanel =
fp.pipe( fp.pipe(
() => panels, () => panels,
fp.get("length"), fp.get('length'),
(length) => length === 4 (length) => length === 4
)() && )() &&
fp.pipe( fp.pipe(
() => panels, () => panels,
fp.get("1.name"), fp.get('1.name'),
(name) => name === panel_names.PLAYER_PANEL (name) => name === panel_names.PLAYER_PANEL
)(); )();
@@ -173,7 +146,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
updatePanel({ updatePanel({
name: panel_names.PLAYER_PANEL, name: panel_names.PLAYER_PANEL,
panelInfo: { panelInfo: {
thumbnail: fp.pipe(() => panelInfo, fp.get("thumbnailUrl"))(), thumbnail: fp.pipe(() => panelInfo, fp.get('thumbnailUrl'))(),
}, },
}) })
); );
@@ -191,31 +164,29 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const handleScrollToSection = useCallback( const handleScrollToSection = useCallback(
(sectionId) => { (sectionId) => {
console.log("DetailPanel: handleScrollToSection called with:", sectionId); console.log('DetailPanel: handleScrollToSection called with:', sectionId);
console.log("DetailPanel: scrollToSection function:", scrollToSection); console.log('DetailPanel: scrollToSection function:', scrollToSection);
const scrollAction = fp.pipe( const scrollAction = fp.pipe(
() => ({ scrollToSection, sectionId }), () => ({ scrollToSection, sectionId }),
({ scrollToSection, sectionId }) => { ({ scrollToSection, sectionId }) => {
if (fp.isNotNil(scrollToSection)) { if (fp.isNotNil(scrollToSection)) {
return { return {
action: "execute", action: 'execute',
scrollFunction: scrollToSection, scrollFunction: scrollToSection,
sectionId, sectionId,
}; };
} else { } else {
return { action: "store", sectionId }; return { action: 'store', sectionId };
} }
} }
)(); )();
// 액션에 따른 처리 // 액션에 따른 처리
if (scrollAction.action === "execute") { if (scrollAction.action === 'execute') {
scrollAction.scrollFunction(scrollAction.sectionId); scrollAction.scrollFunction(scrollAction.sectionId);
} else { } else {
console.log( console.log('DetailPanel: scrollToSection function is null, storing pending scroll');
"DetailPanel: scrollToSection function is null, storing pending scroll"
);
setPendingScrollSection(scrollAction.sectionId); setPendingScrollSection(scrollAction.sectionId);
} }
}, },
@@ -233,10 +204,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
)(); )();
if (shouldExecutePendingScroll) { if (shouldExecutePendingScroll) {
console.log( console.log('DetailPanel: executing pending scroll to:', pendingScrollSection);
"DetailPanel: executing pending scroll to:",
pendingScrollSection
);
// 메모리 누수 방지를 위한 cleanup 함수 // 메모리 누수 방지를 위한 cleanup 함수
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
@@ -262,7 +230,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}, },
() => { () => {
// 테마 데이터 로딩 // 테마 데이터 로딩
const isThemeType = panelType === "theme"; const isThemeType = panelType === 'theme';
if (isThemeType) { if (isThemeType) {
dispatch( dispatch(
@@ -284,7 +252,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
getMainCategoryDetail({ getMainCategoryDetail({
patnrId: panelPatnrId, patnrId: panelPatnrId,
prdtId: panelPrdtId, prdtId: panelPrdtId,
liveReqFlag: panelLiveReqFlag || "N", liveReqFlag: panelLiveReqFlag || 'N',
}) })
); );
} }
@@ -315,11 +283,11 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
exclPatnrId: panelInfo?.patnrId, exclPatnrId: panelInfo?.patnrId,
exclPrdtId: panelInfo?.prdtId, exclPrdtId: panelInfo?.prdtId,
catDpTh3: catDpTh3:
panelInfo?.type === "theme" panelInfo?.type === 'theme'
? themeProductInfos[selectedIndex]?.catDpTh3 ? themeProductInfos[selectedIndex]?.catDpTh3
: productData?.catDpTh3, : productData?.catDpTh3,
catDpTh4: catDpTh4:
panelInfo?.type === "theme" panelInfo?.type === 'theme'
? themeProductInfos[selectedIndex]?.catDpTh4 ? themeProductInfos[selectedIndex]?.catDpTh4
: productData?.catDpTh4, : productData?.catDpTh4,
}; };
@@ -336,7 +304,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
setLgCatCd(productData.catCd); setLgCatCd(productData.catCd);
} else if ( } else if (
themeProductInfos && themeProductInfos &&
themeProductInfos[selectedIndex]?.pmtSuptYn === "N" && themeProductInfos[selectedIndex]?.pmtSuptYn === 'N' &&
panelInfo?.curationId panelInfo?.curationId
) { ) {
const themeCatCd = themeProductInfos[selectedIndex]?.catCd; const themeCatCd = themeProductInfos[selectedIndex]?.catCd;
@@ -350,7 +318,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// selectedIndex, // selectedIndex,
// themeProductPmtSuptYn: themeProductInfos?.[selectedIndex]?.pmtSuptYn // themeProductPmtSuptYn: themeProductInfos?.[selectedIndex]?.pmtSuptYn
// }); // });
setLgCatCd(""); setLgCatCd('');
} }
}, [productData, themeProductInfos, selectedIndex, panelInfo?.curationId]); }, [productData, themeProductInfos, selectedIndex, panelInfo?.curationId]);
@@ -387,7 +355,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
dispatch(clearProductDetail()); dispatch(clearProductDetail());
}, },
() => { () => {
setContainerLastFocusedElement(null, ["indicator-GridListContainer"]); setContainerLastFocusedElement(null, ['indicator-GridListContainer']);
} }
)(); )();
}; };
@@ -410,9 +378,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const conditionCheckers = useMemo( const conditionCheckers = useMemo(
() => ({ () => ({
hasDataAndCondition: fp.curry( hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(data)),
(conditionFn, data) => fp.isNotNil(data) && conditionFn(data)
),
equalTo: fp.curry((expected, actual) => actual === expected), equalTo: fp.curry((expected, actual) => actual === expected),
checkAllConditions: fp.curry((conditions, data) => checkAllConditions: fp.curry((conditions, data) =>
fp.reduce( fp.reduce(
@@ -443,7 +409,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// 테마 타입 체크 // 테마 타입 체크
() => () =>
createTypeChecker( createTypeChecker(
"theme", 'theme',
() => () =>
fp.pipe( fp.pipe(
() => ({ panelCurationId, themeData }), () => ({ panelCurationId, themeData }),
@@ -453,10 +419,10 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
() => { () => {
const themeProduct = fp.pipe( const themeProduct = fp.pipe(
() => themeData, () => themeData,
fp.get("productInfos"), fp.get('productInfos'),
fp.get(selectedIndex.toString()) fp.get(selectedIndex.toString())
)(); )();
setProductType("theme"); setProductType('theme');
setThemeProductInfo(themeProduct); setThemeProductInfo(themeProduct);
} }
), ),
@@ -464,7 +430,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// Buy Now 타입 체크 (curry 활용) // Buy Now 타입 체크 (curry 활용)
() => () =>
createTypeChecker( createTypeChecker(
"buyNow", 'buyNow',
() => () =>
fp.pipe( fp.pipe(
() => ({ () => ({
@@ -483,21 +449,21 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}) => { }) => {
const conditions = [ const conditions = [
() => fp.isNotNil(productData), () => fp.isNotNil(productData),
() => conditionCheckers.equalTo("Y")(productPmtSuptYn), () => conditionCheckers.equalTo('Y')(productPmtSuptYn),
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn), () => conditionCheckers.equalTo('N')(productGrPrdtProcYn),
() => fp.isNotNil(panelPrdtId), () => fp.isNotNil(panelPrdtId),
() => versionComparators.isVersionGTE("6.0")(webOSVersion), () => versionComparators.isVersionGTE('6.0')(webOSVersion),
]; ];
return conditionCheckers.checkAllConditions(conditions)({}); return conditionCheckers.checkAllConditions(conditions)({});
} }
)(), )(),
() => setProductType("buyNow") () => setProductType('buyNow')
), ),
// Shop By Mobile 타입 체크 (curry 활용) // Shop By Mobile 타입 체크 (curry 활용)
() => () =>
createTypeChecker( createTypeChecker(
"shopByMobile", 'shopByMobile',
() => () =>
fp.pipe( fp.pipe(
() => ({ () => ({
@@ -516,23 +482,21 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}) => { }) => {
if (!productData) return false; if (!productData) return false;
const isDirectMobile = const isDirectMobile = conditionCheckers.equalTo('N')(productPmtSuptYn);
conditionCheckers.equalTo("N")(productPmtSuptYn);
const conditionalMobileConditions = [ const conditionalMobileConditions = [
() => conditionCheckers.equalTo("Y")(productPmtSuptYn), () => conditionCheckers.equalTo('Y')(productPmtSuptYn),
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn), () => conditionCheckers.equalTo('N')(productGrPrdtProcYn),
() => versionComparators.isVersionLT("6.0")(webOSVersion), () => versionComparators.isVersionLT('6.0')(webOSVersion),
() => fp.isNotNil(panelPrdtId), () => fp.isNotNil(panelPrdtId),
]; ];
const isConditionalMobile = const isConditionalMobile = conditionCheckers.checkAllConditions(
conditionCheckers.checkAllConditions( conditionalMobileConditions
conditionalMobileConditions )({});
)({});
return isDirectMobile || isConditionalMobile; return isDirectMobile || isConditionalMobile;
} }
)(), )(),
() => setProductType("shopByMobile") () => setProductType('shopByMobile')
), ),
]; ];
@@ -552,13 +516,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
productGrPrdtProcYn, productGrPrdtProcYn,
webOSVersion, webOSVersion,
}), }),
({ ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => ({
productData,
panelPrdtId,
productPmtSuptYn,
productGrPrdtProcYn,
webOSVersion,
}) => ({
pmtSuptYn: productPmtSuptYn, pmtSuptYn: productPmtSuptYn,
grPrdtProcYn: productGrPrdtProcYn, grPrdtProcYn: productGrPrdtProcYn,
prdtId: panelPrdtId, prdtId: panelPrdtId,
@@ -566,8 +524,8 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}) })
)(); )();
console.warn("Unknown product type:", productData); console.warn('Unknown product type:', productData);
console.warn("Product data properties:", debugInfo); console.warn('Product data properties:', debugInfo);
} }
}, [ }, [
panelCurationId, panelCurationId,
@@ -583,7 +541,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
useEffect(() => { useEffect(() => {
// productData가 로드된 후에만 getProductType 실행 // productData가 로드된 후에만 getProductType 실행
if (productData || (panelType === "theme" && themeData)) { if (productData || (panelType === 'theme' && themeData)) {
getProductType(); getProductType();
} }
}, [getProductType, productData, themeData, panelType]); }, [getProductType, productData, themeData, panelType]);
@@ -597,7 +555,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}, [themeData, selectedIndex]); }, [themeData, selectedIndex]);
const imageUrl = useMemo( const imageUrl = useMemo(
() => fp.pipe(() => productData, fp.get("thumbnailUrl960"))(), () => fp.pipe(() => productData, fp.get('thumbnailUrl960'))(),
[productData] [productData]
); );
@@ -611,21 +569,20 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
() => ({ panelPrdtId, productData }), () => ({ panelPrdtId, productData }),
({ panelPrdtId, productData }) => ({ panelPrdtId, productData }) =>
fp.isNotNil(panelPrdtId) && fp.isNotNil(panelPrdtId) &&
fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)()
? fp.pipe(() => productData, fp.get("prdtNm"))() ? fp.pipe(() => productData, fp.get('prdtNm'))()
: null : null
)(); )();
const themeTitle = fp.pipe( const themeTitle = fp.pipe(
() => ({ panelType, themeData }), () => ({ panelType, themeData }),
({ panelType, themeData }) => ({ panelType, themeData }) =>
panelType === "theme" && panelType === 'theme' && fp.pipe(() => themeData, fp.get('curationNm'), fp.isNotNil)()
fp.pipe(() => themeData, fp.get("curationNm"), fp.isNotNil)() ? fp.pipe(() => themeData, fp.get('curationNm'))()
? fp.pipe(() => themeData, fp.get("curationNm"))()
: null : null
)(); )();
return productTitle || themeTitle || ""; return productTitle || themeTitle || '';
} }
)(), )(),
[panelPrdtId, productData, panelType, themeData] [panelPrdtId, productData, panelType, themeData]
@@ -636,10 +593,9 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
fp.pipe( fp.pipe(
() => ({ panelPrdtId, productData }), () => ({ panelPrdtId, productData }),
({ panelPrdtId, productData }) => ({ panelPrdtId, productData }) =>
fp.isNotNil(panelPrdtId) && fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)()
fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() ? fp.pipe(() => productData, fp.get('prdtNm'))()
? fp.pipe(() => productData, fp.get("prdtNm"))() : ''
: ""
)(), )(),
[panelPrdtId, productData] [panelPrdtId, productData]
); );
@@ -653,6 +609,49 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
}; };
}, []); }, []);
// 백그라운드 전체화면 비디오 제어: DetailPanel 진입/퇴장 시
useEffect(() => {
// console.log('[BgVideo] DetailPanel mounted - checking panels:', {
// panelsCount: panels?.length,
// panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal }))
// });
// 전체화면 PlayerPanel(modal=false)이 존재하는지 확인
const hasFullscreenPlayerPanel = fp.pipe(
() => panels,
(panelList) =>
panelList.some(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
)
)();
// ProductAllSection에 비디오가 있는지 확인
const hasProductVideo = fp.pipe(() => productData, fp.get('prdtMediaUrl'), fp.isNotNil)();
// console.log('[BgVideo] hasFullscreenPlayerPanel:', hasFullscreenPlayerPanel);
// console.log('[BgVideo] hasProductVideo:', hasProductVideo);
// 전체화면 PlayerPanel이 있고, 제품에 비디오가 있을 때만 백그라운드 비디오 멈춤
if (hasFullscreenPlayerPanel && hasProductVideo) {
// console.log('[BgVideo] DetailPanel - Product has video, dispatching pauseFullscreenVideo()');
dispatch(pauseFullscreenVideo());
} else {
console.log('[BgVideo] DetailPanel - Skipping pause:', {
reason: !hasFullscreenPlayerPanel ? 'no fullscreen PlayerPanel' : 'no product video',
});
}
return () => {
// DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개
// console.log('[BgVideo] DetailPanel unmounting');
if (hasFullscreenPlayerPanel && hasProductVideo) {
// console.log('[BgVideo] DetailPanel - Product had video, dispatching resumeFullscreenVideo()');
dispatch(resumeFullscreenVideo());
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // 마운트/언마운트 시에만 실행
return ( return (
<div ref={containerRef}> <div ref={containerRef}>
<DetailPanelBackground launchedFromPlayer={panelLaunchedFromPlayer} /> <DetailPanelBackground launchedFromPlayer={panelLaunchedFromPlayer} />
@@ -687,12 +686,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
({ isLoading, panelInfo, productDataSource, productType }) => { ({ isLoading, panelInfo, productDataSource, productType }) => {
const hasRequiredData = fp.pipe( const hasRequiredData = fp.pipe(
() => [panelInfo, productDataSource, productType], () => [panelInfo, productDataSource, productType],
(data) => (data) => fp.reduce((acc, item) => acc && fp.isNotNil(item), true, data)
fp.reduce(
(acc, item) => acc && fp.isNotNil(item),
true,
data
)
)(); )();
return { return {

View File

@@ -17,6 +17,7 @@ import {
stopAutoClose, stopAutoClose,
resetAutoClose, resetAutoClose,
} from '../../../../actions/videoOverlayActions'; } from '../../../../actions/videoOverlayActions';
import { pauseFullscreenVideo, resumeFullscreenVideo } from '../../../../actions/playActions';
import css from './ProductVideo.module.less'; import css from './ProductVideo.module.less';
const SpottableComponent = Spottable('div'); const SpottableComponent = Spottable('div');
@@ -143,7 +144,13 @@ export default function ProductVideoV2({
// 썸네일 클릭 핸들러 - 비디오 재생 시작 + Redux dispatch + MediaPlayer 메서드 호출 // 썸네일 클릭 핸들러 - 비디오 재생 시작 + Redux dispatch + MediaPlayer 메서드 호출
const handleThumbnailClick = useCallback(() => { const handleThumbnailClick = useCallback(() => {
if (canPlayVideo && !isPlaying) { if (canPlayVideo && !isPlaying) {
// console.log('[BgVideo] ProductVideoV2 - Starting video playback');
setIsPlaying(true); setIsPlaying(true);
// 백그라운드 전체화면 비디오 일시정지
// console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video');
dispatch(pauseFullscreenVideo());
// Redux: 오버레이 상태 초기화 // Redux: 오버레이 상태 초기화
dispatch(showControls()); dispatch(showControls());
dispatch(startAutoClose(3000)); dispatch(startAutoClose(3000));
@@ -157,8 +164,14 @@ export default function ProductVideoV2({
// 비디오 종료 핸들러 - 썸네일로 복귀 + Redux cleanup + MediaPlayer 메서드 호출 // 비디오 종료 핸들러 - 썸네일로 복귀 + Redux cleanup + MediaPlayer 메서드 호출
const handleVideoEnded = useCallback(() => { const handleVideoEnded = useCallback(() => {
// console.log('[BgVideo] ProductVideoV2 - Video ended');
setIsPlaying(false); setIsPlaying(false);
setIsFullscreen(false); // 전체화면도 해제 setIsFullscreen(false); // 전체화면도 해제
// 백그라운드 전체화면 비디오 재생 재개
// console.log('[BgVideo] ProductVideoV2 - Resuming background fullscreen video');
dispatch(resumeFullscreenVideo());
// Redux: 오버레이 상태 정리 // Redux: 오버레이 상태 정리
dispatch(stopAutoClose()); dispatch(stopAutoClose());
dispatch(hideControls()); dispatch(hideControls());
@@ -185,12 +198,19 @@ export default function ProductVideoV2({
const handleBackButton = useCallback(() => { const handleBackButton = useCallback(() => {
if (isFullscreen) { if (isFullscreen) {
// 전체화면이면 일반 모드로 // 전체화면이면 일반 모드로
// console.log('[BgVideo] ProductVideoV2 - Exiting fullscreen to normal mode');
setIsFullscreen(false); setIsFullscreen(false);
dispatch(switchToModal()); dispatch(switchToModal());
videoPlayerRef.current?.stopAutoCloseTimeout?.(); videoPlayerRef.current?.stopAutoCloseTimeout?.();
} else if (isPlaying) { } else if (isPlaying) {
// 일반 모드에서 재생 중이면 썸네일로 // 일반 모드에서 재생 중이면 썸네일로
// console.log('[BgVideo] ProductVideoV2 - Stopping video (back button)');
setIsPlaying(false); setIsPlaying(false);
// 백그라운드 전체화면 비디오 재생 재개
// console.log('[BgVideo] ProductVideoV2 - Resuming background fullscreen video');
dispatch(resumeFullscreenVideo());
dispatch(stopAutoClose()); dispatch(stopAutoClose());
dispatch(hideControls()); dispatch(hideControls());
// MediaPlayer 직접 제어: autoClose 타이머 중지 및 controls 숨김 // MediaPlayer 직접 제어: autoClose 타이머 중지 및 controls 숨김
@@ -202,7 +222,7 @@ export default function ProductVideoV2({
// 사용자 활동 감지 시 autoClose 타이머 리셋 // 사용자 활동 감지 시 autoClose 타이머 리셋
const handleUserActivity = useCallback(() => { const handleUserActivity = useCallback(() => {
if (isPlaying && !isFullscreen) { if (isPlaying && !isFullscreen) {
console.log('[ProductVideoV2] User activity detected - resetting autoClose timer'); // console.log('[ProductVideoV2] User activity detected - resetting autoClose timer');
dispatch(resetAutoClose()); dispatch(resetAutoClose());
videoPlayerRef.current?.startAutoCloseTimeout?.(); videoPlayerRef.current?.startAutoCloseTimeout?.();
} }
@@ -268,7 +288,12 @@ export default function ProductVideoV2({
useEffect(() => { useEffect(() => {
if (autoPlay && canPlayVideo && !isPlaying) { if (autoPlay && canPlayVideo && !isPlaying) {
autoPlayTimerRef.current = setTimeout(() => { autoPlayTimerRef.current = setTimeout(() => {
// console.log('[BgVideo] ProductVideoV2 - AutoPlay starting');
setIsPlaying(true); setIsPlaying(true);
// 백그라운드 전체화면 비디오 일시정지
// console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video (autoPlay)');
dispatch(pauseFullscreenVideo());
}, 500); }, 500);
} }
@@ -279,7 +304,7 @@ export default function ProductVideoV2({
autoPlayTimerRef.current = null; autoPlayTimerRef.current = null;
} }
}; };
}, [autoPlay, canPlayVideo, isPlaying]); }, [autoPlay, canPlayVideo, isPlaying, dispatch]);
// VideoPlayer wrapper를 적절한 container로 이동 // VideoPlayer wrapper를 적절한 container로 이동
useEffect(() => { useEffect(() => {
@@ -317,14 +342,21 @@ export default function ProductVideoV2({
} }
}, [isFullscreen, isPlaying]); }, [isFullscreen, isPlaying]);
// 컴포넌트 언마운트 시 Redux 상태 정리 // 컴포넌트 언마운트 시 Redux 상태 정리 및 백그라운드 비디오 재생 재개
useEffect(() => { useEffect(() => {
return () => { return () => {
console.log('[ProductVideoV2] Component unmounting - cleaning up Redux state'); // console.log('[BgVideo] ProductVideoV2 - Component unmounting');
// 비디오가 재생 중이었다면 백그라운드 비디오 재생 재개
if (isPlaying) {
// console.log('[BgVideo] ProductVideoV2 - Was playing, resuming background video');
dispatch(resumeFullscreenVideo());
}
dispatch(stopAutoClose()); dispatch(stopAutoClose());
dispatch(hideControls()); dispatch(hideControls());
}; };
}, [dispatch]); }, [dispatch, isPlaying]);
if (!canPlayVideo) return null; if (!canPlayVideo) return null;
@@ -396,11 +428,9 @@ export default function ProductVideoV2({
{typeof window === 'object' && window.PalmSystem && ( {typeof window === 'object' && window.PalmSystem && (
<source src={productInfo?.prdtMediaUrl} type={videoType} /> <source src={productInfo?.prdtMediaUrl} type={videoType} />
)} )}
{productInfo?.prdtMediaSubtitlUrl && {productInfo?.prdtMediaSubtitlUrl && typeof window === 'object' && window.PalmSystem && (
typeof window === 'object' && <track kind="subtitles" src={productInfo?.prdtMediaSubtitlUrl} default />
window.PalmSystem && ( )}
<track kind="subtitles" src={productInfo?.prdtMediaSubtitlUrl} default />
)}
</VideoPlayer> </VideoPlayer>
</div> </div>
) : null; ) : null;

View File

@@ -356,16 +356,28 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
// 새로운 useEffect 추가 (라인 328 이후) // 새로운 useEffect 추가 (라인 328 이후)
useEffect(() => { useEffect(() => {
if (panelInfo?.modal && videoPlayer.current) { // console.log('[BgVideo] PlayerPanel useEffect - panelInfo:', {
if (panelInfo.isPaused) { // modal: panelInfo?.modal,
console.log('[PlayerPanel] Executing pause via videoPlayer.current'); // isPaused: panelInfo?.isPaused,
// hasVideoPlayer: !!videoPlayer.current
// });
// modal 여부와 관계없이 videoPlayer가 있고 isPaused 값이 명시적일 때 제어
if (videoPlayer.current && panelInfo?.isPaused !== undefined) {
if (panelInfo.isPaused === true) {
// console.log('[BgVideo] PlayerPanel - Executing pause via videoPlayer.current');
videoPlayer.current.pause(); videoPlayer.current.pause();
} else if (panelInfo.isPaused === false) { } else if (panelInfo.isPaused === false) {
console.log('[PlayerPanel] Executing play via videoPlayer.current'); // console.log('[BgVideo] PlayerPanel - Executing play via videoPlayer.current');
videoPlayer.current.play(); videoPlayer.current.play();
} }
} else {
console.log('[BgVideo] PlayerPanel - Skipping video control:', {
hasVideoPlayer: !!videoPlayer.current,
isPausedValue: panelInfo?.isPaused,
});
} }
}, [panelInfo?.isPaused, panelInfo?.modal]); }, [panelInfo?.isPaused]);
// creating live log params // creating live log params
useEffect(() => { useEffect(() => {