Spaces:
Running
Running
| /** | |
| * 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 = '<i class="fas fa-expand me-1"></i>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 = '<i class="fas fa-compress me-1"></i>Exit Fullscreen'; | |
| this._btn.title = 'Exit Fullscreen'; | |
| } else { | |
| this._btn.innerHTML = '<i class="fas fa-expand me-1"></i>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; | |