[251006] feat: useFocusHistory,useVideoMove Migration

🕐 커밋 시간: 2025. 10. 06. 15:22:49

📊 변경 통계:
  • 총 파일: 27개
  • 추가: +151줄
  • 삭제: -21줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/actions/videoPlayActions.js
  + com.twin.app.shoptime/src/hooks/useFocusHistory/index.js
  + com.twin.app.shoptime/src/hooks/useFocusHistory/useFocusHistory.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/index.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.complete.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.final.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.fixed.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.old.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.opus-improved.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/useVideoPlay.simple.js
  + com.twin.app.shoptime/src/hooks/useVideoPlay/videoState.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/index.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.bak.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.brief.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.complete.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.fixed.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.original.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoMove.simple.js
  + com.twin.app.shoptime/src/hooks/useVideoTransition/useVideoTransition.js
  + com.twin.app.shoptime/src/reducers/videoPlayReducer.js

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx
  ~ com.twin.app.shoptime/src/store/store.js
  ~ com.twin.app.shoptime/src/utils/domUtils.js
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx

🔧 함수 변경 내용:
  📊 Function-level changes summary across 27 files:
    • Functions added: 55
    • Functions modified: 0
    • Functions deleted: 0
  📋 By language:
    • javascript: 27 files, 55 function changes

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 핵심 비즈니스 로직 개선
  • 공통 유틸리티 함수 최적화
This commit is contained in:
2025-10-06 15:22:53 +09:00
parent d0b8a2cba9
commit 1696bad8ff
27 changed files with 3729 additions and 183 deletions

View File

@@ -1,19 +1,13 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import classNames from "classnames";
import { useDispatch, useSelector } from "react-redux";
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import Spotlight from "@enact/spotlight";
import Spotlight from '@enact/spotlight';
import {
getContainerNavigableElements,
setContainerLastFocusedElement,
} from "@enact/spotlight/src/container";
} from '@enact/spotlight/src/container';
import {
changeAppStatus,
@@ -21,50 +15,53 @@ import {
setExitApp,
setHidePopup,
setShowPopup,
} from "../../actions/commonActions";
import { getWelcomeEventInfo } from "../../actions/eventActions";
} from '../../actions/commonActions';
import { getWelcomeEventInfo } from '../../actions/eventActions';
import {
checkEnterThroughGNB,
getHomeLayout,
getHomeMainContents,
updateHomeInfo,
} from "../../actions/homeActions";
import { sendLogGNB, sendLogTotalRecommend } from "../../actions/logActions";
import { getSubCategory, getTop20Show } from "../../actions/mainActions";
import { getHomeOnSaleInfo } from "../../actions/onSaleActions";
import { finishVideoPreview } from "../../actions/playActions";
import { getBestSeller } from "../../actions/productActions";
import TBody from "../../components/TBody/TBody";
import TButton, { TYPES } from "../../components/TButton/TButton";
import TPanel from "../../components/TPanel/TPanel";
import TPopUp from "../../components/TPopUp/TPopUp";
import TVerticalPagenator from "../../components/TVerticalPagenator/TVerticalPagenator";
import useDebugKey from "../../hooks/useDebugKey";
import usePrevious from "../../hooks/usePrevious";
} from '../../actions/homeActions';
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
import { getSubCategory, getTop20Show } from '../../actions/mainActions';
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
import { finishVideoPreview } from '../../actions/playActions';
import { getBestSeller } from '../../actions/productActions';
import TBody from '../../components/TBody/TBody';
import TButton, { TYPES } from '../../components/TButton/TButton';
import TPanel from '../../components/TPanel/TPanel';
import TPopUp from '../../components/TPopUp/TPopUp';
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
import useDebugKey from '../../hooks/useDebugKey';
import usePrevious from '../../hooks/usePrevious';
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
import { useVideoPlay } from '../../hooks/useVideoPlay/useVideoPlay';
import { useVideoMove } from '../../hooks/useVideoTransition/useVideoMove';
import {
ACTIVE_POPUP,
LOG_CONTEXT_NAME,
LOG_MENU,
LOG_MESSAGE_ID,
panel_names,
} from "../../utils/Config";
import { $L } from "../../utils/helperMethods";
import { SpotlightIds } from "../../utils/SpotlightIds";
import BestSeller from "../HomePanel/BestSeller/BestSeller";
import HomeBanner from "../HomePanel/HomeBanner/HomeBanner";
import HomeOnSale from "../HomePanel/HomeOnSale/HomeOnSale";
import css from "../HomePanel/HomePanel.module.less";
import PopularShow from "../HomePanel/PopularShow/PopularShow";
import SubCategory from "../HomePanel/SubCategory/SubCategory";
import EventPopUpBanner from "./EventPopUpBanner/EventPopUpBanner";
import { applyMiddleware } from "redux";
} from '../../utils/Config';
import { $L } from '../../utils/helperMethods';
import { SpotlightIds } from '../../utils/SpotlightIds';
import BestSeller from '../HomePanel/BestSeller/BestSeller';
import HomeBanner from '../HomePanel/HomeBanner/HomeBanner';
import HomeOnSale from '../HomePanel/HomeOnSale/HomeOnSale';
import css from '../HomePanel/HomePanel.module.less';
import PopularShow from '../HomePanel/PopularShow/PopularShow';
import SubCategory from '../HomePanel/SubCategory/SubCategory';
import EventPopUpBanner from './EventPopUpBanner/EventPopUpBanner';
import { applyMiddleware } from 'redux';
export const TEMPLATE_CODE_CONF = {
TOP: "DSP00101",
CATEGORY_ITEM: "DSP00102",
ON_SALE: "DSP00103",
POPULAR_SHOW: "DSP00104",
BEST_SELLER: "DSP00105",
TOP: 'DSP00101',
CATEGORY_ITEM: 'DSP00102',
ON_SALE: 'DSP00103',
POPULAR_SHOW: 'DSP00104',
BEST_SELLER: 'DSP00105',
};
const HomePanel = ({ isOnTop }) => {
@@ -72,69 +69,63 @@ const HomePanel = ({ isOnTop }) => {
useDebugKey({ isLandingPage: true });
// 🔽 HomeBanner 외부 7개 아이콘들의 focusHistory 추적
const focusHistory = useFocusHistory({
enableLogging: true,
useGlobalState: true,
logPrefix: '[HomePanel-Focus]',
});
// 🔽 useVideoPlay - 동영상 재생 제어
const videoPlay = useVideoPlay({
enableLogging: true,
logPrefix: '[HomePanel-VideoPlay]',
});
// 🔽 useVideoMove - 포커스 전환 기반 동영상 제어
const { playByTransition, cleanup } = useVideoMove({
enableLogging: true,
logPrefix: '[HomePanel-VideoMove]',
});
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
const homeLayoutInfo = useSelector((state) => state.home.layoutData);
const panelInfo = useSelector(
(state) => state.home.homeInfo?.panelInfo ?? {}
);
const panelInfo = useSelector((state) => state.home.homeInfo?.panelInfo ?? {});
const panels = useSelector((state) => state.panels.panels);
const webOSVersion = useSelector(
(state) => state.common.appStatus?.webOSVersion
);
const webOSVersion = useSelector((state) => state.common.appStatus?.webOSVersion);
const enterThroughGNB = useSelector((state) => state.home.enterThroughGNB);
const defaultFocus = useSelector((state) => state.home.defaultFocus);
const categoryInfos = useSelector(
(state) => state.onSale.homeOnSaleData?.data?.categoryInfos
);
const categoryInfos = useSelector((state) => state.onSale.homeOnSaleData?.data?.categoryInfos);
const categoryItemInfos = useSelector(
(state) => state.main.subCategoryData?.categoryItemInfos
);
const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
const { popupVisible, activePopup } = useSelector(
(state) => state.common.popup
);
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
const eventPopInfosData = useSelector(
(state) => state.event.eventData.eventPopInfo
);
const eventPopInfosData = useSelector((state) => state.event.eventData.eventPopInfo);
const eventData = useSelector((state) => state.event.eventData);
const eventClickSuccess = useSelector(
(state) => state.event.eventClickSuccess
);
const homeOnSaleInfos = useSelector(
(state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos
);
const bestSellerDatas = useSelector(
(state) => state.product.bestSellerData?.bestSeller
);
const eventClickSuccess = useSelector((state) => state.event.eventClickSuccess);
const homeOnSaleInfos = useSelector((state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos);
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
const isDeepLink = useSelector(
(state) => state.common.deepLinkInfo.isDeepLink
);
const isDeepLink = useSelector((state) => state.common.deepLinkInfo.isDeepLink);
const [btnDisabled, setBtnDisabled] = useState(true);
const [arrowBottom, setArrowBottom] = useState(true);
const [firstSpot, setFirstSpot] = useState(false);
const [eventPopOpen, setEventPopOpen] = useState(false);
const [nowShelf, setNowShelf] = useState(panelInfo.nowShelf);
const [firstLgCatCd, setFirstLgCatCd] = useState(
panelInfo.currentCatCd ?? null
);
const [firstLgCatCd, setFirstLgCatCd] = useState(panelInfo.currentCatCd ?? null);
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
const [focusedContainerId, setFocusedContainerId] = useState(
panelInfo.focusedContainerId
);
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo.focusedContainerId);
const isInitialRender = useRef(true);
const verticalPagenatorRef = useRef(null);
const currentSentMenuRef = useRef(null);
useEffect(() => {
if (nowMenu === "Home/Top") {
if (nowMenu === 'Home/Top') {
dispatch(
sendLogTotalRecommend({
messageId: LOG_MESSAGE_ID.HOME,
@@ -146,9 +137,7 @@ const HomePanel = ({ isOnTop }) => {
const sortedHomeLayoutInfo = useMemo(() => {
if (homeLayoutInfo && homeLayoutInfo.homeLayoutInfo) {
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort(
(x, y) => x.expsOrd - y.expsOrd
);
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort((x, y) => x.expsOrd - y.expsOrd);
return sorted;
}
return [];
@@ -216,9 +205,8 @@ const HomePanel = ({ isOnTop }) => {
if (sortedHomeLayoutInfo?.[0]) {
const containerId = sortedHomeLayoutInfo[0].shptmApphmDspyOptCd;
const navigableEls = getContainerNavigableElements(containerId);
const navigableIds = navigableEls.filter((el) => typeof el === "string");
const target =
containerId === TEMPLATE_CODE_CONF.TOP ? "banner0" : containerId;
const navigableIds = navigableEls.filter((el) => typeof el === 'string');
const target = containerId === TEMPLATE_CODE_CONF.TOP ? 'banner0' : containerId;
if (navigableIds.length > 0) {
setContainerLastFocusedElement(null, navigableIds);
@@ -291,9 +279,7 @@ const HomePanel = ({ isOnTop }) => {
<HomeBanner
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
firstSpot={
!panelInfo.focusedContainerId && !panelInfo.currentSpot
}
firstSpot={!panelInfo.focusedContainerId && !panelInfo.currentSpot}
className={css.homeBannerWrap}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
@@ -385,20 +371,18 @@ const HomePanel = ({ isOnTop }) => {
}
}
})}
{loadingComplete &&
sortedHomeLayoutInfo &&
sortedHomeLayoutInfo.length > 0 && (
<TButton
className={css.tButton}
onClick={handleTopButtonClick}
size={null}
type={TYPES.topButton}
spotlightId={"home-top-btn"}
spotlightDisabled={btnDisabled}
data-wheel-point={true}
aria-label="Move to Top, Button"
/>
)}
{loadingComplete && sortedHomeLayoutInfo && sortedHomeLayoutInfo.length > 0 && (
<TButton
className={css.tButton}
onClick={handleTopButtonClick}
size={null}
type={TYPES.topButton}
spotlightId={'home-top-btn'}
spotlightDisabled={btnDisabled}
data-wheel-point={true}
aria-label="Move to Top, Button"
/>
)}
</>
);
}, [
@@ -418,7 +402,7 @@ const HomePanel = ({ isOnTop }) => {
]);
const _onScrollStatusChanged = useCallback((status) => {
if (status === "end") {
if (status === 'end') {
setArrowBottom(false);
} else {
setArrowBottom(true);
@@ -452,7 +436,7 @@ const HomePanel = ({ isOnTop }) => {
dispatch(finishVideoPreview());
}
if (panelInfo.currentCatCd) {
Spotlight.focus("spotlightId-" + panelInfo.currentCatCd);
Spotlight.focus('spotlightId-' + panelInfo.currentCatCd);
}
if (panelInfo.currentSpot) {
Spotlight.focus(panelInfo.currentSpot);
@@ -465,15 +449,7 @@ const HomePanel = ({ isOnTop }) => {
}, 0);
}
},
[
panelInfo,
firstSpot,
enterThroughGNB,
defaultFocus,
cbChangePageRef,
dispatch,
isOnTop,
]
[panelInfo, firstSpot, enterThroughGNB, defaultFocus, cbChangePageRef, dispatch, isOnTop]
);
const bestSellerLoaded = useCallback(() => {
@@ -485,23 +461,21 @@ const HomePanel = ({ isOnTop }) => {
isInitialRender.current = false;
if (isDeepLink || (!panels.length && !panelInfo.focusedContainerId)) {
dispatch(
changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })
);
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
dispatch(getHomeMainContents());
dispatch(getHomeLayout());
dispatch(
getHomeOnSaleInfo({
homeSaleInfosIncFlag: "Y",
categoryIncFlag: "Y",
saleInfosIncFlag: "N",
homeSaleInfosIncFlag: 'Y',
categoryIncFlag: 'Y',
saleInfosIncFlag: 'N',
})
);
dispatch(getTop20Show());
dispatch(getBestSeller(bestSellerLoaded));
if (isDeepLink) {
dispatch(setDeepLink({ contentTarget: "", isDeepLink: false }));
dispatch(setDeepLink({ contentTarget: '', isDeepLink: false }));
}
}
}
@@ -525,8 +499,8 @@ const HomePanel = ({ isOnTop }) => {
{
lgCatCd: firstLgCatCd, //LG Electronics Base
pageSize: 10,
tabType: "CAT00102",
filterType: "CAT00202",
tabType: 'CAT00102',
filterType: 'CAT00202',
},
1
)
@@ -537,7 +511,7 @@ const HomePanel = ({ isOnTop }) => {
const checkBillngEvent = useCallback(
(eventTpCd) => {
if (webOSVersion && Number(webOSVersion) >= 4) {
if (eventTpCd === "EVT00108" || eventTpCd === "EVT00107") {
if (eventTpCd === 'EVT00108' || eventTpCd === 'EVT00107') {
if (webOSVersion && Number(webOSVersion) >= 6) {
setEventPopOpen(true);
} else setEventPopOpen(false);
@@ -577,14 +551,12 @@ const HomePanel = ({ isOnTop }) => {
let targetSpotlightCatcd = null;
let targetSpotlightCateNm = null;
if (c) {
targetSpotlightId = c.getAttribute("data-spotlight-id");
targetSpotlightCatcd = c.getAttribute("data-catcd-num");
targetSpotlightCateNm = c.getAttribute("data-catcd-nm");
targetSpotlightId = c.getAttribute('data-spotlight-id');
targetSpotlightCatcd = c.getAttribute('data-catcd-num');
targetSpotlightCateNm = c.getAttribute('data-catcd-nm');
}
const tBody = document.querySelector(
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`
);
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
const currentSpot = c && tBody.contains(c) ? targetSpotlightId : null;
dispatch(checkEnterThroughGNB(false));
@@ -624,7 +596,7 @@ const HomePanel = ({ isOnTop }) => {
<TVerticalPagenator
ref={verticalPagenatorRef}
className={css.tVerticalPagenator}
spotlightId={"home_verticalPagenator"}
spotlightId={'home_verticalPagenator'}
defaultContainerId={panelInfo.focusedContainerId}
disabled={!isOnTop}
onScrollStatusChanged={_onScrollStatusChanged}
@@ -639,10 +611,7 @@ const HomePanel = ({ isOnTop }) => {
)}
{arrowBottom && (
<p
className={classNames(css.arrow, css.arrowBottom)}
onClick={handleArrowClick}
/>
<p className={classNames(css.arrow, css.arrowBottom)} onClick={handleArrowClick} />
)}
{activePopup === ACTIVE_POPUP.exitPopup && (
@@ -652,15 +621,16 @@ const HomePanel = ({ isOnTop }) => {
onExit={onExit}
onClose={onClose}
hasButton
button1Text={$L("Exit")}
button2Text={$L("Cancel")}
button1Text={$L('Exit')}
button2Text={$L('Cancel')}
hasText
title={$L("Exit Shop Time")}
text={$L("Are you sure you want to exit Shop Time?")}
title={$L('Exit Shop Time')}
text={$L('Are you sure you want to exit Shop Time?')}
/>
)}
{(activePopup === ACTIVE_POPUP.eventPopup ||
activePopup === ACTIVE_POPUP.smsPopup) && <EventPopUpBanner />}
{(activePopup === ACTIVE_POPUP.eventPopup || activePopup === ACTIVE_POPUP.smsPopup) && (
<EventPopUpBanner />
)}
</TPanel>
</>
);