/** * 视频编辑功能工具函数 */ import { EditPoint, EditPointPosition } from './types'; /** * 防抖函数 */ export function debounce any>( func: T, wait: number ): (...args: Parameters) => void { let timeout: NodeJS.Timeout; return (...args: Parameters) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } /** * 节流函数 */ export function throttle any>( func: T, limit: number ): (...args: Parameters) => void { let inThrottle: boolean; return (...args: Parameters) => { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /** * 计算两点之间的距离 */ export function calculateDistance( point1: { x: number; y: number }, point2: { x: number; y: number } ): number { const dx = point2.x - point1.x; const dy = point2.y - point1.y; return Math.sqrt(dx * dx + dy * dy); } /** * 检查点是否在矩形区域内 */ export function isPointInRect( point: { x: number; y: number }, rect: { x: number; y: number; width: number; height: number } ): boolean { return ( point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height ); } /** * 将屏幕坐标转换为百分比坐标 */ export function screenToPercentage( screenPoint: { x: number; y: number }, containerSize: { width: number; height: number } ): EditPointPosition { return { x: Math.max(0, Math.min(100, (screenPoint.x / containerSize.width) * 100)), y: Math.max(0, Math.min(100, (screenPoint.y / containerSize.height) * 100)) }; } /** * 将百分比坐标转换为屏幕坐标 */ export function percentageToScreen( percentagePoint: EditPointPosition, containerSize: { width: number; height: number } ): { x: number; y: number } { return { x: (percentagePoint.x / 100) * containerSize.width, y: (percentagePoint.y / 100) * containerSize.height }; } /** * 生成唯一ID */ export function generateId(): string { return `edit-point-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * 格式化时间戳为可读格式 */ export function formatTimestamp(seconds: number): string { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } /** * 验证编辑点数据 */ export function validateEditPoint(editPoint: Partial): string[] { const errors: string[] = []; if (!editPoint.videoId) { errors.push('Video ID is required'); } if (!editPoint.projectId) { errors.push('Project ID is required'); } if (!editPoint.position) { errors.push('Position is required'); } else { if (editPoint.position.x < 0 || editPoint.position.x > 100) { errors.push('Position X must be between 0 and 100'); } if (editPoint.position.y < 0 || editPoint.position.y > 100) { errors.push('Position Y must be between 0 and 100'); } } if (editPoint.timestamp !== undefined && editPoint.timestamp < 0) { errors.push('Timestamp must be non-negative'); } if (editPoint.description && editPoint.description.length > 500) { errors.push('Description must be less than 500 characters'); } return errors; } /** * 计算最佳输入框位置(避免重叠) */ export function calculateOptimalInputPosition( editPoint: EditPointPosition, containerSize: { width: number; height: number }, existingPositions: { x: number; y: number }[], inputSize: { width: number; height: number } = { width: 280, height: 120 } ): { x: number; y: number; connectionEnd: { x: number; y: number } } { const pointScreen = percentageToScreen(editPoint, containerSize); // 候选位置(相对于编辑点) const candidates = [ { x: 60, y: -60, direction: 'top-right' }, { x: -60 - inputSize.width, y: -60, direction: 'top-left' }, { x: 60, y: 60, direction: 'bottom-right' }, { x: -60 - inputSize.width, y: 60, direction: 'bottom-left' }, { x: 80, y: -30, direction: 'right' }, { x: -80 - inputSize.width, y: -30, direction: 'left' } ]; // 为每个候选位置计算得分 const scoredCandidates = candidates.map(candidate => { const absolutePos = { x: pointScreen.x + candidate.x, y: pointScreen.y + candidate.y }; let score = 0; // 检查是否在容器内 if ( absolutePos.x >= 10 && absolutePos.x + inputSize.width <= containerSize.width - 10 && absolutePos.y >= 10 && absolutePos.y + inputSize.height <= containerSize.height - 10 ) { score += 100; } // 检查与现有位置的距离 const minDistance = Math.min( ...existingPositions.map(pos => calculateDistance(absolutePos, pos)), Infinity ); score += Math.min(minDistance / 10, 50); // 偏好右上角位置 if (candidate.direction === 'top-right') { score += 20; } return { ...candidate, position: absolutePos, score }; }); // 选择得分最高的位置 const bestCandidate = scoredCandidates.reduce((best, current) => current.score > best.score ? current : best ); // 计算连接线终点 const connectionEnd = { x: bestCandidate.position.x + (bestCandidate.x > 0 ? 0 : inputSize.width), y: bestCandidate.position.y + inputSize.height / 2 }; return { x: Math.max(10, Math.min(containerSize.width - inputSize.width - 10, bestCandidate.position.x)), y: Math.max(10, Math.min(containerSize.height - inputSize.height - 10, bestCandidate.position.y)), connectionEnd }; } /** * 深度比较两个对象是否相等 */ export function deepEqual(obj1: any, obj2: any): boolean { if (obj1 === obj2) return true; if (obj1 == null || obj2 == null) return false; if (typeof obj1 !== typeof obj2) return false; if (typeof obj1 !== 'object') return obj1 === obj2; const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (const key of keys1) { if (!keys2.includes(key)) return false; if (!deepEqual(obj1[key], obj2[key])) return false; } return true; } /** * 创建错误处理装饰器 */ export function withErrorHandling any>( fn: T, errorHandler?: (error: Error) => void ): T { return ((...args: Parameters) => { try { const result = fn(...args); if (result instanceof Promise) { return result.catch((error: Error) => { console.error('Async function error:', error); errorHandler?.(error); throw error; }); } return result; } catch (error) { console.error('Function error:', error); errorHandler?.(error as Error); throw error; } }) as T; } /** * 性能监控装饰器 */ export function withPerformanceMonitoring any>( fn: T, name: string ): T { return ((...args: Parameters) => { const start = performance.now(); const result = fn(...args); if (result instanceof Promise) { return result.finally(() => { const end = performance.now(); console.log(`${name} took ${end - start} milliseconds`); }); } else { const end = performance.now(); console.log(`${name} took ${end - start} milliseconds`); return result; } }) as T; } /** * 本地存储工具 */ export const storage = { get(key: string, defaultValue: T): T { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue; } catch { return defaultValue; } }, set(key: string, value: T): void { try { localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.error('Failed to save to localStorage:', error); } }, remove(key: string): void { try { localStorage.removeItem(key); } catch (error) { console.error('Failed to remove from localStorage:', error); } } };