Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
// ============================================
|
||||
// 비디오 모달 관리 모듈 (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 };
|
||||
}
|
||||
Reference in New Issue
Block a user