Files
edu/js/main/Videomodalmanager.js
T

362 lines
10 KiB
JavaScript

// ============================================
// 비디오 모달 관리 모듈 (Videomodalmanager.js)
// ============================================
//
// [역할] 비디오 카드 클릭 시 모달을 열고, YouTube 영상을 재생합니다.
//
// [파일명 참고] "Videomodalmanager"는 legacy 표기입니다.
// 클래스명은 VideoModalManager (PascalCase)를 사용합니다.
//
// [의존성] VideoModalBase(부모), DOMUtils, EventManager, ErrorHandler, Utils
//
// [사용 예] index.html 등에서:
// const modalManager = new VideoModalManager({ videos: [...] });
// modalManager.init();
//
// ============================================
class VideoModalManager extends VideoModalBase {
constructor(config, dependencies = {}) {
// 입력값 유효성 검증 (super 호출 전에 가능한 작업만)
if (!config || typeof config !== 'object') {
config = {};
}
// VideoModalBase에 전달할 config 준비
const baseConfig = {
videos: config.videos || [],
modalPath: config.modalPath || "./_modal/video.php",
modalPathTemplate: config.modalPathTemplate || "./_modal/video-{type}.php",
enableHeightAdjustment: config.enableHeightAdjustment !== false,
enableCommentResizer: config.enableCommentResizer !== false,
enableCommentBox: config.enableCommentBox !== false,
...config,
};
// 부모 클래스 생성자 호출 (반드시 먼저 호출)
super(baseConfig);
// 의존성 주입 (폴백 포함) - super() 호출 후
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 = [];
this._isInitialized = false;
this._openVideoPromise = null;
try {
// 추가 초기화 작업
} catch (error) {
this._handleError(error, 'constructor');
}
}
/**
* 에러 처리 헬퍼
* @private
*/
_handleError(error, context, additionalInfo = {}) {
if (this.errorHandler) {
this.errorHandler.handle(error, {
context: `VideoModalManager.${context}`,
component: 'VideoModalManager',
...additionalInfo
}, false);
} else {
console.error(`[VideoModalManager] ${context}:`, error, additionalInfo);
}
}
// 초기화
init() {
try {
if (this._isInitialized) {
return;
}
this.setupCardClickEvents();
this._isInitialized = true;
} catch (error) {
this._handleError(error, 'init');
}
}
async openVideo(videoIdOrData) {
if (this._openVideoPromise) {
return this._openVideoPromise;
}
this._openVideoPromise = super.openVideo(videoIdOrData)
.finally(() => {
this._openVideoPromise = null;
});
return this._openVideoPromise;
}
// 카드 클릭 이벤트 설정 (DOMUtils, EventManager 활용)
setupCardClickEvents() {
try {
const containers = Array.from(document.querySelectorAll('[data-video-cards-container="true"], .js-video-cards-container, #videoCardsContainer'));
if (containers.length === 0) {
console.warn("[VideoModalManager] videoCardsContainer를 찾을 수 없습니다.");
return;
}
// VideoModalManager 인스턴스를 참조하기 위해 변수에 저장
const self = this;
const clickHandler = function (e) {
try {
// 북마크 클릭은 카드 오픈 로직에서 제외해야 체크 토글/change 저장이 정상 동작한다.
if (e.target && e.target.closest && e.target.closest('.bookmark')) {
return;
}
const trigger = e.target && e.target.closest
? e.target.closest('.book-info-btn[data-video-id], .books-item[data-video-id], .card[data-video-id], [data-video-id]')
: null;
if (!trigger) {
return;
}
e.preventDefault();
e.stopPropagation();
const videoIdAttr = trigger.getAttribute("data-video-id");
if (!videoIdAttr) {
console.warn("[VideoModalManager] data-video-id 속성이 없습니다.");
return;
}
const videoId = videoIdAttr;
if (!videoId) {
self._handleError(new Error(`유효하지 않은 videoId: ${videoIdAttr}`), 'setupCardClickEvents.clickHandler');
return;
}
self.openVideo(videoId);
} catch (error) {
self._handleError(error, 'setupCardClickEvents.clickHandler');
}
};
containers.forEach((container) => {
const nativeHandler = function (e) {
clickHandler(e);
};
container.addEventListener("click", nativeHandler);
self.listenerIds.push({ element: container, handler: nativeHandler, type: 'native' });
});
} catch (error) {
this._handleError(error, 'setupCardClickEvents');
}
}
// 기존 메서드 호환성을 위한 래퍼
async loadVideoModal(videoId) {
try {
// 입력값 유효성 검증
if (!videoId || (typeof videoId !== 'number' && typeof videoId !== 'string')) {
this._handleError(new Error(`유효하지 않은 videoId: ${videoId}`), 'loadVideoModal');
return null;
}
const id = videoId;
if (!id) {
this._handleError(new Error(`유효하지 않은 videoId: ${videoId}`), 'loadVideoModal');
return null;
}
return await this.openVideo(id);
} catch (error) {
this._handleError(error, 'loadVideoModal', { videoId });
return null;
}
}
// 기존 코드 호환성을 위한 래퍼 메서드들
get currentModal() {
try {
return this.currentModalElement;
} catch (error) {
this._handleError(error, 'currentModal.get');
return null;
}
}
set currentModal(value) {
try {
this.currentModalElement = value;
} catch (error) {
this._handleError(error, 'currentModal.set', { value });
}
}
// 기존 메서드 호환성 유지 (VideoModalBase의 메서드 사용)
adjustVideoListHeight() {
try {
if (this.currentModalElement) {
super.adjustVideoListHeight(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'adjustVideoListHeight');
}
}
setupCommentResizer() {
try {
if (this.currentModalElement) {
super.setupCommentResizer(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'setupCommentResizer');
}
}
setupCommentBox() {
try {
if (this.currentModalElement) {
super.setupCommentBox(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'setupCommentBox');
}
}
showCommentSection() {
try {
if (this.currentModalElement) {
super.showCommentSection(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'showCommentSection');
}
}
adjustCommentOnlyLayout() {
try {
if (this.currentModalElement) {
super.adjustCommentOnlyLayout(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'adjustCommentOnlyLayout');
}
}
setupEssentialLayout() {
try {
if (this.currentModalElement) {
super.setupEssentialLayout(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'setupEssentialLayout');
}
}
setupLearningLayout() {
try {
if (this.currentModalElement) {
super.setupLearningLayout(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'setupLearningLayout');
}
}
initializeHeightAdjustment() {
try {
if (this.currentModalElement) {
super.initializeHeightAdjustment(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'initializeHeightAdjustment');
}
}
setupResizeObserver() {
try {
if (this.currentModalElement) {
super.setupResizeObserver(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'setupResizeObserver');
}
}
setupMutationObserver() {
try {
if (this.currentModalElement) {
super.setupMutationObserver(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'setupMutationObserver');
}
}
async waitForImagesAndAdjust() {
try {
if (this.currentModalElement) {
await super.waitForImagesAndAdjust(this.currentModalElement);
}
} catch (error) {
this._handleError(error, 'waitForImagesAndAdjust');
}
}
destroyModal() {
try {
// 이벤트 리스너 제거
if (this.eventManager && this.listenerIds.length > 0) {
this.listenerIds.forEach(({ element, id, type }) => {
if (type === 'delegate') {
this.eventManager.undelegate(element, id);
} else if (type === 'native') {
element.removeEventListener('click', id || arguments[0]);
} else {
this.eventManager.off(element, id);
}
});
this.listenerIds = [];
} else if (this.listenerIds.length > 0) {
this.listenerIds.forEach(({ element, handler, type }) => {
if (type === 'native' && element && handler) {
element.removeEventListener('click', handler);
}
});
this.listenerIds = [];
}
// 부모 클래스의 destroy 호출
if (super.destroy && typeof super.destroy === 'function') {
super.destroy();
} else if (this.destroy && typeof this.destroy === 'function') {
this.destroy();
}
this._isInitialized = false;
} catch (error) {
this._handleError(error, 'destroyModal');
}
}
}
if (typeof window !== 'undefined') {
window.VideoModalManager = VideoModalManager;
}
if (typeof globalThis !== 'undefined') {
globalThis.VideoModalManager = VideoModalManager;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { VideoModalManager };
}