/** * FullscreenManager - Quản Lý Chế Độ Toàn Màn Hình * Cung cấp nút "Fullscreen" để mở rộng card đồ thị chiếm toàn bộ viewport. * Sử dụng Fullscreen API gốc của trình duyệt. * Requirements: 26.1, 26.2, 26.3, 26.4, 26.5, 26.6 */ const FullscreenManager = (function () { class FullscreenManager { /** * @param {string} graphCardSelector - CSS selector cho card chứa đồ thị */ constructor(graphCardSelector) { /** @type {string} */ this._cardSelector = graphCardSelector; /** @type {HTMLElement|null} */ this._cardElement = null; /** @type {HTMLButtonElement|null} */ this._btn = null; /** @type {Function|null} */ this._onFullscreenChangeBound = null; /** @type {boolean} */ this._initialized = false; } // ─── Public API ───────────────────────────────────────────────────── /** * Khởi tạo: tìm card element, tạo nút fullscreen, gắn event listeners. */ init() { if (this._initialized) return; this._cardElement = this._resolveCardElement(); if (!this._cardElement) { console.warn('[FullscreenManager] Card element not found for selector:', this._cardSelector); return; } this._createButton(); this._bindEvents(); this._initialized = true; console.log('[FullscreenManager] Initialized'); } /** * Vào chế độ toàn màn hình cho card đồ thị. */ enterFullscreen() { if (!this._cardElement) return; if (this.isFullscreen()) return; const el = this._cardElement; if (el.requestFullscreen) { el.requestFullscreen(); } else if (el.webkitRequestFullscreen) { el.webkitRequestFullscreen(); } else if (el.msRequestFullscreen) { el.msRequestFullscreen(); } } /** * Thoát chế độ toàn màn hình. */ exitFullscreen() { if (!this.isFullscreen()) return; if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } } /** * Toggle giữa fullscreen và normal. */ toggle() { if (this.isFullscreen()) { this.exitFullscreen(); } else { this.enterFullscreen(); } } /** * Kiểm tra trạng thái fullscreen hiện tại. * @returns {boolean} */ isFullscreen() { return !!( document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement ); } /** * Hủy và dọn dẹp event listeners, DOM. */ destroy() { if (this._onFullscreenChangeBound) { document.removeEventListener('fullscreenchange', this._onFullscreenChangeBound); document.removeEventListener('webkitfullscreenchange', this._onFullscreenChangeBound); this._onFullscreenChangeBound = null; } if (this._btn && this._btn.parentNode) { this._btn.parentNode.removeChild(this._btn); this._btn = null; } if (this._cardElement) { this._cardElement.classList.remove('graph-card-fullscreen'); this._cardElement = null; } this._initialized = false; } // ─── Private ──────────────────────────────────────────────────────── /** * Resolve the card element from the selector. * The selector targets the card that is the parent of #graphContainer. * @returns {HTMLElement|null} */ _resolveCardElement() { if (this._cardSelector) { // Try direct selector first const el = document.querySelector(this._cardSelector); if (el) return el; } // Fallback: find the .card ancestor of #graphContainer const graphContainer = document.getElementById('graphContainer'); if (graphContainer) { return graphContainer.closest('.card'); } return null; } /** * Create the fullscreen toggle button and insert it into #graphExportContainer. */ _createButton() { const exportContainer = document.getElementById('graphExportContainer'); if (!exportContainer) { console.warn('[FullscreenManager] #graphExportContainer not found'); return; } const btn = document.createElement('button'); btn.className = 'btn btn-outline-secondary btn-sm ms-2 fullscreen-btn'; btn.id = 'fullscreenBtn'; btn.title = 'Toggle Fullscreen'; btn.innerHTML = 'Fullscreen'; exportContainer.appendChild(btn); this._btn = btn; } /** * Bind click and fullscreenchange events. */ _bindEvents() { // Button click if (this._btn) { this._btn.addEventListener('click', () => { this.toggle(); }); } // Fullscreen change event this._onFullscreenChangeBound = this._onFullscreenChange.bind(this); document.addEventListener('fullscreenchange', this._onFullscreenChangeBound); document.addEventListener('webkitfullscreenchange', this._onFullscreenChangeBound); } /** * Handle fullscreenchange event: update button icon/text, apply styles, resize graph. */ _onFullscreenChange() { const isFs = this.isFullscreen(); // Update button icon and text this._updateButton(isFs); // Toggle fullscreen CSS class on the card if (this._cardElement) { if (isFs) { this._cardElement.classList.add('graph-card-fullscreen'); this._cardElement.style.background = '#fff'; } else { this._cardElement.classList.remove('graph-card-fullscreen'); this._cardElement.style.background = ''; } } // Adjust #graphContainer height this._adjustGraphContainerHeight(isFs); // Resize the Cytoscape graph after a short delay to let the DOM settle setTimeout(() => { this._resizeGraph(); }, 100); } /** * Update button icon and text based on fullscreen state. * @param {boolean} isFs */ _updateButton(isFs) { if (!this._btn) return; if (isFs) { this._btn.innerHTML = 'Exit Fullscreen'; this._btn.title = 'Exit Fullscreen'; } else { this._btn.innerHTML = 'Fullscreen'; this._btn.title = 'Toggle Fullscreen'; } } /** * Adjust the #graphContainer height for fullscreen mode. * @param {boolean} isFs */ _adjustGraphContainerHeight(isFs) { const graphContainer = document.getElementById('graphContainer'); if (!graphContainer) return; if (isFs) { // In fullscreen, expand to fill viewport minus card header (~60px) graphContainer.style.height = 'calc(100vh - 60px)'; } else { // Restore original height graphContainer.style.height = ''; } } /** * Call cy.resize() and cy.fit() on the Cytoscape instance to adjust to new dimensions. */ _resizeGraph() { try { if (window._onnxApp && typeof window._onnxApp.getGraphVisualizer === 'function') { const visualizer = window._onnxApp.getGraphVisualizer(); if (visualizer && visualizer._cy) { visualizer._cy.resize(); visualizer._cy.fit(); } } } catch (err) { console.warn('[FullscreenManager] Could not resize graph:', err); } } } return FullscreenManager; })(); // Export as global for browser usage window.FullscreenManager = FullscreenManager;