model-explorer / js /ui /sidebarToggle.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* SidebarToggle - Toggle αΊ¨n/Hiện Thanh BΓͺn TrΓ‘i
* Cung cαΊ₯p nΓΊt toggle để αΊ©n/hiện sidebar, mở rα»™ng vΓΉng nα»™i dung chΓ­nh.
* LΖ°u trαΊ‘ng thΓ‘i vΓ o localStorage vΓ  khΓ΄i phα»₯c khi tαΊ£i lαΊ‘i trang.
* Requirements: 27.1, 27.2, 27.3, 27.4, 27.5, 27.6
*/
const SidebarToggle = (function () {
const STORAGE_KEY_DEFAULT = 'onnx_explorer_sidebar_state';
const STATE_VISIBLE = 'visible';
const STATE_HIDDEN = 'hidden';
class SidebarToggle {
/**
* @param {string} sidebarSelector - CSS selector cho sidebar
* @param {string} mainContentSelector - CSS selector cho vΓΉng nα»™i dung chΓ­nh
* @param {string} [storageKey] - Key localStorage
*/
constructor(sidebarSelector, mainContentSelector, storageKey) {
/** @type {string} */
this._sidebarSelector = sidebarSelector;
/** @type {string} */
this._mainContentSelector = mainContentSelector;
/** @type {string} */
this._storageKey = storageKey || STORAGE_KEY_DEFAULT;
/** @type {HTMLElement|null} */
this._sidebarEl = null;
/** @type {HTMLElement|null} */
this._mainContentEl = null;
/** @type {HTMLButtonElement|null} */
this._btn = null;
/** @type {boolean} */
this._hidden = false;
/** @type {Function|null} */
this._onTransitionEndBound = null;
/** @type {boolean} */
this._initialized = false;
}
// ─── Public API ─────────────────────────────────────────────────────
/**
* Khởi tαΊ‘o: tΓ¬m elements, tαΊ‘o nΓΊt toggle, khΓ΄i phα»₯c trαΊ‘ng thΓ‘i, gαΊ―n events.
*/
init() {
if (this._initialized) return;
this._sidebarEl = document.querySelector(this._sidebarSelector);
this._mainContentEl = document.querySelector(this._mainContentSelector);
if (!this._sidebarEl) {
console.warn('[SidebarToggle] Sidebar element not found:', this._sidebarSelector);
return;
}
if (!this._mainContentEl) {
console.warn('[SidebarToggle] Main content element not found:', this._mainContentSelector);
return;
}
this._createButton();
this._bindEvents();
this._restoreState();
this._initialized = true;
console.log('[SidebarToggle] Initialized');
}
/**
* αΊ¨n thanh bΓͺn trΓ‘i.
*/
hideSidebar() {
if (this._hidden) return;
this._sidebarEl.classList.add('sidebar-hidden');
this._mainContentEl.classList.add('sidebar-collapsed');
this._hidden = true;
this._updateButtonIcon();
this._saveState();
}
/**
* Hiện thanh bΓͺn trΓ‘i.
*/
showSidebar() {
if (!this._hidden) return;
this._sidebarEl.classList.remove('sidebar-hidden');
this._mainContentEl.classList.remove('sidebar-collapsed');
this._hidden = false;
this._updateButtonIcon();
this._saveState();
}
/**
* Toggle trαΊ‘ng thΓ‘i αΊ©n/hiện.
*/
toggle() {
if (this._hidden) {
this.showSidebar();
} else {
this.hideSidebar();
}
}
/**
* Kiểm tra sidebar Δ‘ang αΊ©n hay hiện.
* @returns {boolean}
*/
isHidden() {
return this._hidden;
}
/**
* Hủy và dọn dẹp event listeners, DOM.
*/
destroy() {
if (this._onTransitionEndBound && this._sidebarEl) {
this._sidebarEl.removeEventListener('transitionend', this._onTransitionEndBound);
this._onTransitionEndBound = null;
}
if (this._btn && this._btn.parentNode) {
this._btn.parentNode.removeChild(this._btn);
this._btn = null;
}
if (this._sidebarEl) {
this._sidebarEl.classList.remove('sidebar-hidden');
}
if (this._mainContentEl) {
this._mainContentEl.classList.remove('sidebar-collapsed');
}
this._sidebarEl = null;
this._mainContentEl = null;
this._hidden = false;
this._initialized = false;
}
// ─── Private ────────────────────────────────────────────────────────
/**
* Create the toggle button and insert it before the logo in the header.
*/
_createButton() {
const navbarBrand = document.querySelector('.navbar-brand');
if (!navbarBrand || !navbarBrand.parentNode) {
console.warn('[SidebarToggle] .navbar-brand not found');
return;
}
const btn = document.createElement('button');
btn.className = 'btn btn-outline-light btn-sm me-2 sidebar-toggle-btn';
btn.id = 'sidebarToggleBtn';
btn.title = 'Toggle Sidebar';
btn.innerHTML = '<i class="fas fa-bars"></i>';
navbarBrand.parentNode.insertBefore(btn, navbarBrand);
this._btn = btn;
}
/**
* Bind click and transitionend events.
*/
_bindEvents() {
if (this._btn) {
this._btn.addEventListener('click', () => {
this.toggle();
});
}
this._onTransitionEndBound = this._onTransitionEnd.bind(this);
if (this._sidebarEl) {
this._sidebarEl.addEventListener('transitionend', this._onTransitionEndBound);
}
}
/**
* Handle transitionend: resize the Cytoscape graph to fit new dimensions.
* @param {TransitionEvent} e
*/
_onTransitionEnd(e) {
// Only react to transitions on the sidebar element itself
if (e.target !== this._sidebarEl) return;
this._resizeGraph();
}
/**
* Update button icon based on current state.
* fa-bars when sidebar is visible, fa-chevron-right when hidden.
*/
_updateButtonIcon() {
if (!this._btn) return;
const icon = this._btn.querySelector('i');
if (!icon) return;
if (this._hidden) {
icon.className = 'fas fa-chevron-right';
this._btn.title = 'Show Sidebar';
} else {
icon.className = 'fas fa-bars';
this._btn.title = 'Toggle Sidebar';
}
}
/**
* Save current state to localStorage.
*/
_saveState() {
try {
localStorage.setItem(this._storageKey, this._hidden ? STATE_HIDDEN : STATE_VISIBLE);
} catch (err) {
console.warn('[SidebarToggle] Could not save state:', err);
}
}
/**
* Restore state from localStorage on init.
*/
_restoreState() {
try {
const saved = localStorage.getItem(this._storageKey);
if (saved === STATE_HIDDEN) {
this.hideSidebar();
}
} catch (err) {
console.warn('[SidebarToggle] Could not restore state:', err);
}
}
/**
* Call cy.resize() 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('[SidebarToggle] Could not resize graph:', err);
}
}
}
return SidebarToggle;
})();
// Export as global for browser usage
window.SidebarToggle = SidebarToggle;