[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

@@ -0,0 +1,772 @@
// src/hooks/useFocusHistory/useFocusHistory.js
import { useRef, useCallback, useState, useMemo } from 'react';
import fp from '../../utils/fp.js';
/**
* useFocusHistory Hook - 경량화된 포커스 히스토리 관리
* - 어떤 string 식별자든 받을 수 있는 범용적 구조
* - 현재 포커스와 이전 포커스 2개 상태만 추적 (Ring Buffer)
* - 전역 상태 관리 가능
* - 매우 간단한 인터페이스 제공
*/
// 🔽 [확장] Enhanced Ring Buffer - 10개 히스토리 + 패턴 인식 + 정책 엔진
const createFocusRingBuffer = () => {
const BUFFER_SIZE = 10;
const buffer = new Array(BUFFER_SIZE).fill(null); // 10개 히스토리
let head = -1; // Current position pointer
let size = 0; // Current elements count (0-10)
// 🔽 [개선] 포커스 항목 삽입 - 원본 ID 그대로 사용 (0부터 시작 통일)
const enqueue = (focusId) => {
// 중복 삽입 방지 - 원본 ID로 직접 비교
const currentFocusId = size > 0 ? buffer[head] : null;
if (size > 0 && currentFocusId === focusId) {
return { inserted: false, policy: null }; // 중복이므로 삽입하지 않음
}
// 다음 위치로 이동하고 원본 ID 저장
head = (head + 1) % BUFFER_SIZE;
buffer[head] = focusId;
if (size < BUFFER_SIZE) {
size++;
}
// 🔑 [신기능] 패턴 분석 후 정책 반환
const policy = calculateVideoPolicy();
return { inserted: true, policy }; // 삽입 성공 + 정책 반환
};
// 🔽 [수정] 현재 포커스 가져오기 (10개 버퍼)
const getCurrent = () => {
return size > 0 ? buffer[head] : null;
};
// 🔽 [수정] 이전 포커스 가져오기 (10개 버퍼)
const getPrevious = () => {
if (size < 2) return null;
const prevIndex = (head - 1 + BUFFER_SIZE) % BUFFER_SIZE;
return buffer[prevIndex];
};
// 🔽 [수정] 전체 히스토리 가져오기 (10개 - 최신 순서로 반환)
const getHistory = () => {
if (size === 0) return [];
const history = [];
for (let i = 0; i < Math.min(size, BUFFER_SIZE); i++) {
const index = (head - i + BUFFER_SIZE) % BUFFER_SIZE;
history.push(buffer[index]);
}
return history; // [current, previous, older, oldest, ...]
};
// 🔽 [편의 함수] 특정 거리의 히스토리 가져오기
const getHistoryAt = (distance) => {
if (distance >= size || distance < 0) return null;
const index = (head - distance + BUFFER_SIZE) % BUFFER_SIZE;
return buffer[index];
};
// 🔽 [수정] 전체 상태 가져오기 (확장)
const getState = () => ({
current: getCurrent(),
previous: getPrevious(),
history: getHistory(),
hasTransition: size >= 2 && getCurrent() !== getPrevious(),
size,
head,
});
// 🔽 [수정] 상태 초기화 (10개 버퍼)
const clear = () => {
buffer.fill(null);
head = -1;
size = 0;
};
// 🔽 [개선] 패턴 인식 엔진 - 더 깊은 히스토리 분석
const detectPattern = () => {
const history = getHistory(); // [current, previous, older, oldest, ...]
const current = history[0];
const previous = history[1];
if (!current) {
return { pattern: 'no-focus', videoTarget: null, confidence: 0 };
}
// 🔽 [로그] banner 간 이동 패턴 분석
// if (previous && (previous.startsWith('banner') && current.startsWith('banner'))) {
// console.log('[BannerFlow] 포커스 이동:', {
// from: previous,
// to: current,
// history: history.slice(0, 5),
// timestamp: new Date().toISOString()
// });
// }
// 직접 포커스 (banner1, banner2)
if (current === 'banner1') {
if (previous === 'icons') {
console.log('[DEBUG] 🔄 icons → banner1 복원 패턴');
return {
pattern: 'restore-banner1',
videoTarget: 'banner1',
confidence: 1.0,
shouldShowBorder: true,
};
}
console.log('[DEBUG] 🎯 banner1 직접 포커스 패턴');
return {
pattern: 'direct-banner1',
videoTarget: 'banner1',
confidence: 1.0,
shouldShowBorder: true,
};
}
if (current === 'banner2') {
if (previous === 'icons') {
console.log('[DEBUG] 🔄 icons → banner2 복원 패턴');
return {
pattern: 'restore-banner2',
videoTarget: 'banner2',
confidence: 1.0,
shouldShowBorder: true,
};
}
console.log('[DEBUG] 🎯 banner2 직접 포커스 패턴');
return {
pattern: 'direct-banner2',
videoTarget: 'banner2',
confidence: 1.0,
shouldShowBorder: true,
};
}
// icons 포커스 처리
if (current === 'icons') {
// console.log('[BannerFlow] 🛑 icons 포커스 - 동영상 중지');
return {
pattern: 'icons-stop',
videoTarget: null,
confidence: 1.0,
shouldShowBorder: false,
reason: 'icons 포커스로 동영상 중지',
};
}
// 🔽 [개선] 간접 포커스 (banner3, banner4) - 더 깊은 히스토리 확인
if (current === 'banner3' || current === 'banner4') {
console.log(`[DEBUG] 🔍 간접 포커스 (${current}) - 히스토리 분석 시작`);
console.log(`[DEBUG] 전체 히스토리:`, history);
// 히스토리에서 가장 최근의 banner1 또는 banner2 찾기
let lastVideoBanner = null;
let lastVideoBannerDistance = -1;
for (let i = 1; i < Math.min(history.length, 10); i++) {
if (history[i] === 'banner1' || history[i] === 'banner2') {
lastVideoBanner = history[i];
lastVideoBannerDistance = i;
console.log(`[DEBUG] 발견된 비디오 배너: ${lastVideoBanner} (거리: ${i})`);
break; // 가장 최근 것만 찾으면 됨
}
}
if (lastVideoBanner) {
console.log(`[DEBUG] 🔄 간접 포커스 유지 패턴:`, {
current: current,
maintainTarget: lastVideoBanner,
distance: lastVideoBannerDistance,
reason: `${current} 포커스, ${lastVideoBannerDistance}단계 이전 ${lastVideoBanner} 유지`,
});
return {
pattern: `maintain-${lastVideoBanner}`,
videoTarget: lastVideoBanner,
confidence: Math.max(0.7, 1.0 - lastVideoBannerDistance * 0.1), // 거리가 멀수록 신뢰도 감소
shouldShowBorder: false,
reason: `${current} 포커스, ${lastVideoBannerDistance}단계 이전 ${lastVideoBanner} 유지`,
};
} else {
console.log(`[DEBUG] ❓ 간접 포커스 - 히스토리 없음:`, {
current: current,
history: history.slice(0, 5),
reason: '비디오 배너 히스토리 없음, 기본값 banner1 사용',
});
// 비디오 배너 히스토리가 없으면 기본값
return {
pattern: 'default-banner1',
videoTarget: 'banner1',
confidence: 0.5,
shouldShowBorder: false,
reason: `${current} 포커스, 비디오 배너 히스토리 없음 - 기본값 banner1`,
};
}
}
// 기타 포커스 (icons, gnb 등)
return {
pattern: 'other-focus',
videoTarget: null,
confidence: 0,
shouldShowBorder: false,
reason: `비배너 포커스: ${current}`,
};
};
// 🔽 [신기능] 비디오 정책 계산
const calculateVideoPolicy = () => {
const patternResult = detectPattern();
return {
videoTarget: patternResult.videoTarget,
shouldShowBorder: patternResult.shouldShowBorder,
transition: patternResult.pattern,
confidence: patternResult.confidence,
reason: patternResult.reason,
timestamp: new Date().toISOString(),
};
};
// 🔽 [개선] 디버깅 정보
const getDebugInfo = () => {
const history = getHistory();
const pattern = detectPattern();
return {
buffer: [...buffer],
head,
size,
history: history,
historyLabeled: {
current: history[0] || null,
previous: history[1] || null,
older: history[2] || null,
oldest: history[3] || null,
full: history,
},
pattern,
policy: calculateVideoPolicy(),
};
};
return {
// 기존 기능
enqueue,
getCurrent,
getPrevious,
getState,
clear,
// 🔽 [신기능] 확장 기능
getHistory,
getHistoryAt, // 🔽 [추가] 특정 거리 히스토리
detectPattern,
calculateVideoPolicy,
getDebugInfo,
};
};
// 🔽 [개선] globalThis 기반 안전한 전역 상태 관리
// HMR(Hot Module Reload) 및 크로스 플랫폼 호환성 보장
let globalFocusBuffer = null;
// 전역 버퍼 네임스페이스 상수
const GLOBAL_BUFFER_KEY = '__FOCUS_BUFFER__';
/**
* 전역 버퍼 유효성 검증
* @param {*} buffer - 검증할 버퍼 객체
* @returns {boolean} 유효한 버퍼인지 여부
*/
const isValidBuffer = (buffer) => {
try {
return (
buffer &&
typeof buffer === 'object' &&
typeof buffer.enqueue === 'function' &&
typeof buffer.getCurrent === 'function' &&
typeof buffer.getState === 'function' &&
typeof buffer.clear === 'function'
);
} catch (error) {
console.warn('[FocusHistory] 버퍼 유효성 검증 실패:', error);
return false;
}
};
/**
* 안전한 전역 객체 접근 함수 (Chromium 68 호환)
* @returns {object} 전역 객체 (폴백 포함)
*/
const getGlobalObject = () => {
try {
// 🔽 [Chromium 68 호환] window 우선 (webOS TV 환경)
if (typeof window !== 'undefined') return window;
// Node.js 환경
if (typeof global !== 'undefined') return global;
// Web Worker 환경
// eslint-disable-next-line no-undef
if (typeof self !== 'undefined') return self;
// 🔽 [제거] globalThis는 Chromium 68에서 지원하지 않음
// if (typeof globalThis !== 'undefined') return globalThis;
// 최후의 수단 - 빈 객체
console.warn('[FocusHistory] 전역 객체 접근 불가, 빈 객체 사용');
return {};
} catch (error) {
console.error('[FocusHistory] 전역 객체 접근 오류:', error);
return {};
}
};
/**
* 전역 버퍼 복원 시도
* @returns {object|null} 복원된 버퍼 또는 null
*/
const attemptBufferRestore = () => {
try {
const globalObj = getGlobalObject();
const existingBuffer = globalObj[GLOBAL_BUFFER_KEY];
if (isValidBuffer(existingBuffer)) {
console.log('[FocusHistory] ✅ 기존 전역 버퍼 복원 성공');
return existingBuffer;
} else if (existingBuffer) {
console.warn('[FocusHistory] ⚠️ 손상된 전역 버퍼 발견, 제거 후 재생성');
delete globalObj[GLOBAL_BUFFER_KEY];
}
} catch (error) {
console.error('[FocusHistory] 버퍼 복원 시도 실패:', error);
}
return null;
};
/**
* 전역 버퍼 생성 및 등록
* @returns {object} 생성된 버퍼
*/
const createAndRegisterBuffer = () => {
try {
const newBuffer = createFocusRingBuffer();
const globalObj = getGlobalObject();
// 전역 객체에 안전하게 등록
if (globalObj && typeof globalObj === 'object') {
globalObj[GLOBAL_BUFFER_KEY] = newBuffer;
// 개발 환경에서 디버깅 편의성 제공
if (process.env.NODE_ENV === 'development') {
// 추가 접근 경로 제공 (하위 호환성)
globalObj.globalFocusBuffer = newBuffer;
}
}
console.log('[FocusHistory] 🆕 새 전역 버퍼 생성 및 등록 완료');
return newBuffer;
} catch (error) {
console.error('[FocusHistory] 버퍼 생성 및 등록 실패:', error);
// 최후의 수단 - 로컬 버퍼라도 반환
return createFocusRingBuffer();
}
};
/**
* 안전한 전역 버퍼 가져오기/생성 함수
* HMR, 모듈 재로드, 크로스 플랫폼 환경에서 안정적으로 동작
* @returns {object} 전역 포커스 버퍼 인스턴스
*/
const getOrCreateGlobalBuffer = () => {
try {
// 1단계: 이미 로드된 버퍼가 있고 유효한지 확인
if (isValidBuffer(globalFocusBuffer)) {
return globalFocusBuffer;
}
// 2단계: 전역 객체에서 기존 버퍼 복원 시도
const restoredBuffer = attemptBufferRestore();
if (restoredBuffer) {
globalFocusBuffer = restoredBuffer;
return globalFocusBuffer;
}
// 3단계: 새 버퍼 생성 및 등록
globalFocusBuffer = createAndRegisterBuffer();
return globalFocusBuffer;
} catch (error) {
console.error('[FocusHistory] 전역 버퍼 초기화 실패:', error);
// 최후의 수단: 최소한의 로컬 버퍼라도 제공
try {
if (!globalFocusBuffer) {
globalFocusBuffer = createFocusRingBuffer();
}
return globalFocusBuffer;
} catch (fallbackError) {
console.error('[FocusHistory] 폴백 버퍼 생성도 실패:', fallbackError);
// 더미 버퍼 반환 (앱 크래시 방지)
return {
enqueue: () => ({ inserted: false, policy: null }),
getCurrent: () => null,
getPrevious: () => null,
getState: () => ({
current: null,
previous: null,
history: [],
hasTransition: false,
size: 0,
head: -1,
}),
clear: () => {},
getHistory: () => [],
getHistoryAt: () => null,
detectPattern: () => ({ pattern: 'error', videoTarget: null, confidence: 0 }),
calculateVideoPolicy: () => ({
videoTarget: null,
shouldShowBorder: false,
transition: 'error',
confidence: 0,
}),
getDebugInfo: () => ({ error: 'Buffer creation failed' }),
};
}
}
};
export const useFocusHistory = (options = {}) => {
const {
enableLogging = process.env.NODE_ENV === 'development',
useGlobalState = true, // 전역 상태 사용 여부
logPrefix = '[focusHistory]',
} = options;
// 강제 리렌더링을 위한 상태
const [, forceUpdate] = useState({});
const triggerUpdate = useCallback(() => forceUpdate({}), []);
// 로컬 버퍼 참조
const localBufferRef = useRef(null);
// 🔽 [개선] 안전한 버퍼 초기화 로직
const buffer = useMemo(() => {
try {
if (useGlobalState) {
// 전역 버퍼 사용: 안전한 초기화 함수 호출
return getOrCreateGlobalBuffer();
} else {
// 로컬 버퍼 사용: 안전한 로컬 버퍼 생성
if (!localBufferRef.current) {
try {
localBufferRef.current = createFocusRingBuffer();
} catch (error) {
console.error('[FocusHistory] 로컬 버퍼 생성 실패:', error);
// 더미 버퍼로 폴백
localBufferRef.current = {
enqueue: () => ({ inserted: false, policy: null }),
getCurrent: () => null,
getPrevious: () => null,
getState: () => ({
current: null,
previous: null,
history: [],
hasTransition: false,
size: 0,
head: -1,
}),
clear: () => {},
getHistory: () => [],
getHistoryAt: () => null,
detectPattern: () => ({ pattern: 'error', videoTarget: null, confidence: 0 }),
calculateVideoPolicy: () => ({
videoTarget: null,
shouldShowBorder: false,
transition: 'error',
confidence: 0,
}),
getDebugInfo: () => ({ error: 'Local buffer creation failed' }),
};
}
}
return localBufferRef.current;
}
} catch (error) {
console.error('[FocusHistory] 버퍼 초기화 전체 실패:', error);
// 최후의 더미 버퍼
return {
enqueue: () => ({ inserted: false, policy: null }),
getCurrent: () => null,
getPrevious: () => null,
getState: () => ({
current: null,
previous: null,
history: [],
hasTransition: false,
size: 0,
head: -1,
}),
clear: () => {},
getHistory: () => [],
getHistoryAt: () => null,
detectPattern: () => ({ pattern: 'error', videoTarget: null, confidence: 0 }),
calculateVideoPolicy: () => ({
videoTarget: null,
shouldShowBorder: false,
transition: 'error',
confidence: 0,
}),
getDebugInfo: () => ({ error: 'Complete buffer initialization failed' }),
};
}
}, [useGlobalState]);
// 🔽 [개선] 안전한 포커스 히스토리 추가 + 정책 엔진 통합
const enqueue = useCallback(
(focusId) => {
try {
// 입력값 검증
if (!focusId || typeof focusId !== 'string') {
console.warn(`${logPrefix} Invalid focus ID:`, focusId);
return { inserted: false, policy: null };
}
// 버퍼 유효성 재검증
if (!isValidBuffer(buffer)) {
console.error(`${logPrefix} 버퍼가 손상됨, enqueue 실패`);
return { inserted: false, policy: null };
}
const previousFocus = buffer.getCurrent(); // 현재가 이전이 됨
const result = buffer.enqueue(focusId); // { inserted, policy }
if (result.inserted) {
// 상태 변경 시 리렌더링 트리거
triggerUpdate();
if (enableLogging) {
const current = buffer.getCurrent();
const previous = buffer.getPrevious();
const policy = result.policy;
// 🔽 [향상된 로깅] 패턴과 정책 정보 포함
if (previous && current && previous !== current) {
console.log(`${logPrefix} 🎯 ${previous}${current}`);
console.log(`${logPrefix} 📋 buffer:`, buffer.getHistory());
} else {
console.log(`${logPrefix} 🎯 초기 포커스: ${current}`);
console.log(`${logPrefix} 📋 buffer:`, buffer.getHistory());
}
// 디버그 모드에서는 전체 히스토리 표시
// if (process.env.NODE_ENV === 'development') {
// const debugInfo = buffer.getDebugInfo();
// console.log(`${logPrefix} 🔍 디버그:`, debugInfo);
// }
}
}
return result; // { inserted, policy } 반환
} catch (error) {
console.error(`${logPrefix} enqueue 실행 중 오류:`, error, { focusId });
// 오류 발생 시 안전한 기본값 반환
return { inserted: false, policy: null };
}
},
[buffer, enableLogging, logPrefix, triggerUpdate]
);
// 🔽 [개선] 안전한 상태 가져오기
const getQueueState = useCallback(() => {
try {
if (!isValidBuffer(buffer)) {
console.warn(`${logPrefix} getQueueState: 버퍼 무효`);
return {
current: null,
previous: null,
history: [],
haTransition: false,
size: 0,
head: -1,
};
}
return buffer.getState();
} catch (error) {
console.error(`${logPrefix} getQueueState 오류:`, error);
return {
current: null,
previous: null,
history: [],
hasTransition: false,
size: 0,
head: -1,
};
}
}, [buffer, logPrefix]);
// 🔽 [개선] 안전한 상태 초기화
const clearHistory = useCallback(() => {
try {
if (!isValidBuffer(buffer)) {
console.warn(`${logPrefix} clearHistory: 버퍼 무효`);
return;
}
buffer.clear();
triggerUpdate(); // 상태 변경 시 리렌더링 트리거
if (enableLogging) {
console.log(`${logPrefix} 히스토리 초기화됨`);
}
} catch (error) {
console.error(`${logPrefix} clearHistory 오류:`, error);
}
}, [buffer, enableLogging, logPrefix, triggerUpdate]);
// 🔽 [개선] 안전한 현재 상태 가져오기
const currentState = useMemo(() => {
try {
if (!isValidBuffer(buffer)) {
return {
current: null,
previous: null,
history: [],
hasTransition: false,
size: 0,
head: -1,
};
}
return buffer.getState();
} catch (error) {
console.error(`${logPrefix} currentState 계산 오류:`, error);
return {
current: null,
previous: null,
history: [],
hasTransition: false,
size: 0,
head: -1,
};
}
}, [buffer, logPrefix]);
// 🔽 [개선] 안전한 확장된 메서드들
const getHistory = useCallback(() => {
try {
return isValidBuffer(buffer) ? buffer.getHistory() : [];
} catch (error) {
console.error(`${logPrefix} getHistory 오류:`, error);
return [];
}
}, [buffer, logPrefix]);
const getHistoryAt = useCallback(
(distance) => {
try {
return isValidBuffer(buffer) ? buffer.getHistoryAt(distance) : null;
} catch (error) {
console.error(`${logPrefix} getHistoryAt 오류:`, error);
return null;
}
},
[buffer, logPrefix]
);
const detectPattern = useCallback(() => {
try {
return isValidBuffer(buffer)
? buffer.detectPattern()
: { pattern: 'error', videoTarget: null, confidence: 0 };
} catch (error) {
console.error(`${logPrefix} detectPattern 오류:`, error);
return { pattern: 'error', videoTarget: null, confidence: 0 };
}
}, [buffer, logPrefix]);
const calculateVideoPolicy = useCallback(() => {
try {
return isValidBuffer(buffer)
? buffer.calculateVideoPolicy()
: { videoTarget: null, shouldShowBorder: false, transition: 'error', confidence: 0 };
} catch (error) {
console.error(`${logPrefix} calculateVideoPolicy 오류:`, error);
return { videoTarget: null, shouldShowBorder: false, transition: 'error', confidence: 0 };
}
}, [buffer, logPrefix]);
const getDebugInfo = useCallback(() => {
try {
return isValidBuffer(buffer)
? buffer.getDebugInfo()
: { error: 'Buffer invalid or unavailable' };
} catch (error) {
console.error(`${logPrefix} getDebugInfo 오류:`, error);
return { error: 'getDebugInfo failed', details: error.message };
}
}, [buffer, logPrefix]);
// 🔽 [추가] getQueue - 최신 상태의 큐를 최신순(맨 앞)으로 반환
const getQueue = useCallback(() => {
try {
if (!isValidBuffer(buffer)) {
return []; // 빈 배열 반환
}
return buffer.getHistory(); // 이미 최신순으로 정렬됨 [current, previous, older, oldest, ...]
} catch (error) {
console.error(`${logPrefix} getQueue 오류:`, error);
return [];
}
}, [buffer, logPrefix]);
// 🔽 [추가] getQueueSafe - 최대 2개까지만 안전하게 가져오기 (undefined 방지)
const getQueueSafe = useCallback(() => {
try {
const queue = getQueue(); // 기본 큐 가져오기
// FP 방식으로 안전하게 최대 2개까지만 가져오기
return fp.pipe(
fp.defaultTo([]), // null/undefined 방지
(arr) => fp.slice(0, 2, arr), // 최대 2개까지만
fp.map(fp.defaultTo(null)) // 각 항목도 null로 안전하게 처리
)(queue);
} catch (error) {
console.error(`${logPrefix} getQueueSafe 오류:`, error);
return []; // 에러 시 빈 배열
}
}, [getQueue, logPrefix]);
return {
// 핵심 함수들
enqueue,
getQueueState,
clearHistory,
// 🔽 [신기능] 확장 함수들
getHistory,
getHistoryAt, // 🔽 [추가] 특정 거리 히스토리
getQueue, // 🔽 [추가] 최신 순으로 정렬된 큐
getQueueSafe, // 🔽 [추가] 최대 2개까지만 안전하게 가져오기
detectPattern,
calculateVideoPolicy,
getDebugInfo,
// 편의 속성들 (실시간 업데이트)
curFocused: currentState.current,
preFocused: currentState.previous,
hasTransition: currentState.hasTransition,
history: currentState.history, // 🔽 [추가] 전체 히스토리
// 배열 형태로 반환
focusTransition:
currentState.previous && currentState.current
? [currentState.previous, currentState.current]
: currentState.current
? [null, currentState.current]
: [null, null],
};
};
export default useFocusHistory;