Reverted to edit edt-fb357fcb-7bf8-4196-adc5-e2dc2fed4b19: "Fix notch issue on iOS
Addresses the notch display issue on iOS devices."
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
|
||||
/**
|
||||
* 이벤트 버스 최적화 - 통합된 이벤트 관리 시스템
|
||||
*/
|
||||
|
||||
// 전역 이벤트 이름 정의
|
||||
export const APP_EVENTS = {
|
||||
// 데이터 변경 이벤트
|
||||
DATA_UPDATED: 'app:data-updated',
|
||||
TRANSACTION_UPDATED: 'app:transaction-updated',
|
||||
BUDGET_UPDATED: 'app:budget-updated',
|
||||
CATEGORY_UPDATED: 'app:category-updated',
|
||||
|
||||
// 인증 이벤트
|
||||
AUTH_STATE_CHANGED: 'app:auth-state-changed',
|
||||
USER_LOGGED_IN: 'app:user-logged-in',
|
||||
USER_LOGGED_OUT: 'app:user-logged-out',
|
||||
|
||||
// 동기화 이벤트
|
||||
SYNC_STARTED: 'app:sync-started',
|
||||
SYNC_COMPLETED: 'app:sync-completed',
|
||||
SYNC_FAILED: 'app:sync-failed',
|
||||
|
||||
// UI 이벤트
|
||||
PAGE_LOADED: 'app:page-loaded',
|
||||
TAB_CHANGED: 'app:tab-changed',
|
||||
}
|
||||
|
||||
// 최적화된 이벤트 발생 함수 (디바운스, 이벤트 통합 등)
|
||||
let queuedEvents: Record<string, any> = {};
|
||||
let eventQueueTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
/**
|
||||
* 이벤트 발생 - 디바운스 적용 (짧은 시간 내 동일 이벤트는 마지막 한 번만 발생)
|
||||
*/
|
||||
export function emitEvent(eventName: string, detail?: any): void {
|
||||
// 이벤트 대기열에 저장
|
||||
queuedEvents[eventName] = { detail };
|
||||
|
||||
// 이미 타이머가 설정되어 있으면 기존 타이머 유지
|
||||
if (eventQueueTimer) return;
|
||||
|
||||
// 타이머 설정 (10ms 후 대기열의 이벤트 모두 발생)
|
||||
eventQueueTimer = setTimeout(() => {
|
||||
// 대기열의 모든 이벤트 처리
|
||||
for (const [name, data] of Object.entries(queuedEvents)) {
|
||||
try {
|
||||
// 표준 이벤트 발생
|
||||
window.dispatchEvent(new CustomEvent(name, {
|
||||
detail: data.detail,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}));
|
||||
|
||||
// 하위 호환성 유지를 위한 기존 이벤트 매핑
|
||||
if (name === APP_EVENTS.TRANSACTION_UPDATED) {
|
||||
window.dispatchEvent(new Event('transactionUpdated'));
|
||||
} else if (name === APP_EVENTS.BUDGET_UPDATED) {
|
||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||
} else if (name === APP_EVENTS.CATEGORY_UPDATED) {
|
||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||
}
|
||||
|
||||
console.log(`[이벤트] 발생: ${name}`);
|
||||
} catch (error) {
|
||||
console.error(`[이벤트] 발생 오류 (${name}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 대기열 및 타이머 초기화
|
||||
queuedEvents = {};
|
||||
eventQueueTimer = null;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 여러 이벤트 한 번에 발생 (배치 이벤트)
|
||||
*/
|
||||
export function emitBatchEvents(events: { name: string; detail?: any }[]): void {
|
||||
// 모든 이벤트를 대기열에 추가
|
||||
events.forEach(event => {
|
||||
queuedEvents[event.name] = { detail: event.detail };
|
||||
});
|
||||
|
||||
// 기존 타이머 취소
|
||||
if (eventQueueTimer) {
|
||||
clearTimeout(eventQueueTimer);
|
||||
}
|
||||
|
||||
// 새 타이머 설정 (즉시 실행)
|
||||
eventQueueTimer = setTimeout(() => {
|
||||
// 대기열의 모든 이벤트 처리
|
||||
for (const [name, data] of Object.entries(queuedEvents)) {
|
||||
try {
|
||||
// 표준 이벤트 발생
|
||||
window.dispatchEvent(new CustomEvent(name, {
|
||||
detail: data.detail,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}));
|
||||
|
||||
// 하위 호환성 유지를 위한 기존 이벤트 매핑
|
||||
if (name === APP_EVENTS.TRANSACTION_UPDATED) {
|
||||
window.dispatchEvent(new Event('transactionUpdated'));
|
||||
} else if (name === APP_EVENTS.BUDGET_UPDATED) {
|
||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||
} else if (name === APP_EVENTS.CATEGORY_UPDATED) {
|
||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||
}
|
||||
|
||||
console.log(`[이벤트] 배치 발생: ${name}`);
|
||||
} catch (error) {
|
||||
console.error(`[이벤트] 배치 발생 오류 (${name}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 대기열 및 타이머 초기화
|
||||
queuedEvents = {};
|
||||
eventQueueTimer = null;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지가 포커스를 얻었을 때 전체 데이터 새로고침 이벤트 발생
|
||||
*/
|
||||
export function setupPageVisibilityEvents(): void {
|
||||
// 포커스 이벤트
|
||||
window.addEventListener('focus', () => {
|
||||
console.log('[이벤트] 창이 포커스를 얻음');
|
||||
emitEvent(APP_EVENTS.PAGE_LOADED);
|
||||
});
|
||||
|
||||
// 가시성 변경 이벤트
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
console.log('[이벤트] 페이지가 다시 보임');
|
||||
emitEvent(APP_EVENTS.PAGE_LOADED);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[이벤트] 페이지 가시성 이벤트 설정 완료');
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
|
||||
/**
|
||||
* 성능 최적화를 위한 유틸리티 함수
|
||||
*/
|
||||
|
||||
// 디바운스: 여러 호출 중 마지막 호출만 실행
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
return function (...args: Parameters<T>) {
|
||||
const later = () => {
|
||||
timeout = null;
|
||||
func(...args);
|
||||
};
|
||||
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 스로틀: 일정 시간 내에 한 번만 실행
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle = false;
|
||||
|
||||
return function (...args: Parameters<T>) {
|
||||
if (!inThrottle) {
|
||||
func(...args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => {
|
||||
inThrottle = false;
|
||||
}, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 실행 중인 작업 추적
|
||||
const pendingOperations: Record<string, boolean> = {};
|
||||
|
||||
// 중복 실행 방지 (한 번에 동일 작업 한 번만 실행)
|
||||
export function preventDuplicateOperation<T extends (...args: any[]) => Promise<any>>(
|
||||
operationKey: string,
|
||||
func: T
|
||||
): (...args: Parameters<T>) => Promise<ReturnType<T> | void> {
|
||||
return async function (...args: Parameters<T>): Promise<ReturnType<T> | void> {
|
||||
// 이미 실행 중인 작업이면 중단
|
||||
if (pendingOperations[operationKey]) {
|
||||
console.log(`[성능] ${operationKey} 작업이 이미 실행 중입니다. 중복 요청 무시.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 작업 시작 플래그 설정
|
||||
pendingOperations[operationKey] = true;
|
||||
console.log(`[성능] ${operationKey} 작업 시작`);
|
||||
|
||||
// 작업 실행
|
||||
const result = await func(...args);
|
||||
return result;
|
||||
} finally {
|
||||
// 작업 종료 후 플래그 해제
|
||||
pendingOperations[operationKey] = false;
|
||||
console.log(`[성능] ${operationKey} 작업 완료`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 네트워크 요청 캐시 (메모리 캐시, 세션 내 유효)
|
||||
const requestCache: Record<string, { data: any; timestamp: number }> = {};
|
||||
|
||||
// 캐시 설정 (TTL: 캐시 유효 시간(ms))
|
||||
export function withCache<T extends (...args: any[]) => Promise<any>>(
|
||||
cacheKey: string,
|
||||
func: T,
|
||||
ttl: number = 60000 // 기본값 1분
|
||||
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
|
||||
return async function (...args: Parameters<T>): Promise<ReturnType<T>> {
|
||||
const now = Date.now();
|
||||
|
||||
// 캐시 유효성 확인
|
||||
if (
|
||||
requestCache[cacheKey] &&
|
||||
now - requestCache[cacheKey].timestamp < ttl
|
||||
) {
|
||||
console.log(`[성능] 캐시된 데이터 사용: ${cacheKey}`);
|
||||
return requestCache[cacheKey].data;
|
||||
}
|
||||
|
||||
// 캐시 만료 또는 없음 - 새로 요청
|
||||
console.log(`[성능] 새 데이터 요청: ${cacheKey}`);
|
||||
const result = await func(...args);
|
||||
|
||||
// 결과 캐싱
|
||||
requestCache[cacheKey] = {
|
||||
data: result,
|
||||
timestamp: now
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// 배치 상태 업데이트 관리
|
||||
export function batchedUpdates<T>(updates: () => T): T {
|
||||
// React의 unstable_batchedUpdates 사용 (가능한 경우)
|
||||
if (typeof window !== 'undefined' && 'ReactDOM' in window) {
|
||||
// @ts-ignore: ReactDOM이 전역에 존재할 수 있음
|
||||
return window.ReactDOM.unstable_batchedUpdates(updates);
|
||||
}
|
||||
|
||||
// 폴백: 기본 실행
|
||||
return updates();
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
|
||||
/**
|
||||
* 동기화 최적화 유틸리티
|
||||
*/
|
||||
import { debounce, throttle, preventDuplicateOperation } from '../performance/debounceThrottle';
|
||||
import { trySyncAllData } from '@/utils/syncUtils';
|
||||
import { toast } from '@/hooks/useToast.wrapper';
|
||||
|
||||
// 동기화 상태 플래그
|
||||
let isSyncRunning = false;
|
||||
let lastSyncTime = 0;
|
||||
const MIN_SYNC_INTERVAL = 30000; // 최소 동기화 간격 (30초)
|
||||
|
||||
/**
|
||||
* 최적화된 동기화 함수 - 중복 방지, 속도 제한 적용
|
||||
*/
|
||||
export const optimizedSync = preventDuplicateOperation(
|
||||
'sync-all-data',
|
||||
async (userId: string) => {
|
||||
// 이미 동기화 중이면 중단
|
||||
if (isSyncRunning) {
|
||||
console.log('[최적화] 이미 동기화 작업이 진행 중입니다.');
|
||||
return { success: false, reason: 'already-running' };
|
||||
}
|
||||
|
||||
// 너무 빈번한 동기화 요청 방지
|
||||
const now = Date.now();
|
||||
if (now - lastSyncTime < MIN_SYNC_INTERVAL) {
|
||||
console.log(`[최적화] 동기화 요청이 너무 빈번합니다. ${MIN_SYNC_INTERVAL / 1000}초 후 다시 시도하세요.`);
|
||||
return { success: false, reason: 'too-frequent' };
|
||||
}
|
||||
|
||||
try {
|
||||
isSyncRunning = true;
|
||||
console.log('[최적화] 동기화 시작...');
|
||||
|
||||
// 네트워크 상태 확인
|
||||
if (!navigator.onLine) {
|
||||
console.log('[최적화] 오프라인 상태입니다. 동기화를 건너뜁니다.');
|
||||
return { success: false, reason: 'offline' };
|
||||
}
|
||||
|
||||
// 실제 동기화 수행
|
||||
const result = await trySyncAllData(userId);
|
||||
|
||||
// 마지막 동기화 시간 기록
|
||||
lastSyncTime = Date.now();
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('[최적화] 동기화 오류:', error);
|
||||
|
||||
// 중요 오류만 사용자에게 표시
|
||||
toast({
|
||||
title: "동기화 오류",
|
||||
description: "데이터 동기화 중 문제가 발생했습니다. 나중에 다시 시도해주세요.",
|
||||
variant: "destructive",
|
||||
});
|
||||
|
||||
return { success: false, reason: 'error', error };
|
||||
} finally {
|
||||
isSyncRunning = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 디바운스된 동기화 함수 (빠르게 여러 번 호출해도 마지막 한 번만 실행)
|
||||
*/
|
||||
export const debouncedSync = debounce(
|
||||
async (userId: string) => {
|
||||
console.log('[최적화] 디바운스된 동기화 실행');
|
||||
return optimizedSync(userId);
|
||||
},
|
||||
2000 // 2초 대기
|
||||
);
|
||||
|
||||
/**
|
||||
* 스로틀된 동기화 함수 (일정 시간 내 한 번만 실행)
|
||||
*/
|
||||
export const throttledSync = throttle(
|
||||
async (userId: string) => {
|
||||
console.log('[최적화] 스로틀된 동기화 실행');
|
||||
return optimizedSync(userId);
|
||||
},
|
||||
10000 // 10초마다 최대 한 번
|
||||
);
|
||||
|
||||
/**
|
||||
* 자동 동기화 시도 (에러 무시)
|
||||
*/
|
||||
export const attemptBackgroundSync = async (userId: string) => {
|
||||
try {
|
||||
return await optimizedSync(userId);
|
||||
} catch (error) {
|
||||
console.error('[최적화] 백그라운드 동기화 오류 (무시됨):', error);
|
||||
return { success: false, reason: 'background-error' };
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user