/**
* 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;