// ============================================ // 비디오 카드 렌더링 모듈 // 공통 모듈 활용 (ErrorHandler, DOMUtils, EventManager, Utils, AnimationUtils) // ============================================ class VideoCardRenderer { constructor(config, dependencies = {}) { // 의존성 주입 (폴백 포함) this.domUtils = dependencies.domUtils || (typeof DOMUtils !== 'undefined' ? DOMUtils : null); this.errorHandler = dependencies.errorHandler || (typeof ErrorHandler !== 'undefined' ? ErrorHandler : null); this.eventManager = dependencies.eventManager || (typeof eventManager !== 'undefined' ? eventManager : null); this.utils = dependencies.utils || (typeof Utils !== 'undefined' ? Utils : null); this.animationUtils = dependencies.animationUtils || (typeof AnimationUtils !== 'undefined' ? AnimationUtils : null); // 이벤트 리스너 ID 저장 (정리용) this.listenerIds = []; try { // 입력값 유효성 검증 if (!config || typeof config !== 'object') { this._handleError(new Error('config가 유효하지 않습니다.'), 'constructor'); config = {}; } this.config = config; this.animationDelay = config.animationDelay || 50; } catch (error) { this._handleError(error, 'constructor'); } } /** * 에러 처리 헬퍼 * @private */ _handleError(error, context, additionalInfo = {}) { if (this.errorHandler) { this.errorHandler.handle(error, { context: `VideoCardRenderer.${context}`, component: 'VideoCardRenderer', ...additionalInfo }, false); } else { console.error(`[VideoCardRenderer] ${context}:`, error, additionalInfo); } } // 비디오 카드들 렌더링 (AnimationUtils 활용) async renderCards(videos, containerId = "videoCardsContainer") { try { // 입력값 유효성 검증 if (!videos || !Array.isArray(videos)) { this._handleError(new Error('videos가 배열이 아닙니다.'), 'renderCards', { containerId }); return; } if (!containerId || typeof containerId !== 'string') { this._handleError(new Error('containerId가 유효하지 않습니다.'), 'renderCards', { videos }); return; } const container = this.domUtils?.$(`#${containerId}`) || document.querySelector(`#${containerId}`); if (!container) { this._handleError(new Error(`컨테이너를 찾을 수 없습니다: #${containerId}`), 'renderCards', { containerId }); return; } if (this.domUtils) { this.domUtils.empty(container); } else { container.innerHTML = ''; } const cards = []; videos.forEach((video, index) => { try { const card = this.createVideoCard(video); if (card) { cards.push(card); container.appendChild(card); } } catch (error) { this._handleError(error, 'renderCards.createCard', { index, video }); } }); // 순차적 애니메이션 (AnimationUtils 활용) if (this.animationUtils && cards.length > 0) { await this.animationUtils.sequentialAnimate(cards, "show", this.animationDelay); } else if (typeof AnimationUtils !== 'undefined' && cards.length > 0) { await AnimationUtils.sequentialAnimate(cards, "show", this.animationDelay); } } catch (error) { this._handleError(error, 'renderCards', { videos, containerId }); } } // 비디오 카드 생성 (DOMUtils, VideoBase 활용) createVideoCard(video) { try { // 입력값 유효성 검증 if (!video || typeof video !== 'object') { this._handleError(new Error('video가 유효하지 않습니다.'), 'createVideoCard'); return null; } // VideoModel 사용 (있는 경우) let videoModel; if (typeof VideoModel !== 'undefined' && video instanceof VideoModel) { videoModel = video; } else if (typeof VideoModel !== 'undefined') { try { videoModel = new VideoModel(video); } catch (error) { this._handleError(error, 'createVideoCard.VideoModel', { video }); // VideoModel 생성 실패 시 원본 video 사용 videoModel = video; } } else { videoModel = video; } const keywords = videoModel.keywords || []; const keywordTags = Array.isArray(keywords) ? keywords.map((kw) => { const escapedKw = this._escapeHtml(String(kw || '')); return `${escapedKw}`; }).join(" ") : ""; const categoryClass = this.getCategoryClass(videoModel.category); const pickerName = videoModel.picker && String(videoModel.picker).trim(); const pickBadge = pickerName ? `