/** * UI 렌더링 및 이벤트를 관리하는 오케스트레이터 (Orchestrator) */ import { API } from './api.js'; import { createMemoCardHtml } from './components/MemoCard.js'; import { renderGroupList } from './components/SidebarUI.js'; import { ThemeManager } from './components/ThemeManager.js'; import { ModalManager } from './components/ModalManager.js'; import { I18nManager } from './utils/I18nManager.js'; const DOM = { memoGrid: document.getElementById('memoGrid'), groupList: document.getElementById('groupList'), modal: document.getElementById('memoModal'), loadingOverlay: document.getElementById('loadingOverlay'), searchInput: document.getElementById('searchInput'), sidebar: document.getElementById('sidebar'), systemNav: document.getElementById('systemNav'), scrollSentinel: document.getElementById('scrollSentinel') }; // 모듈 레벨의 설정 캐시 관리 (this 바인딩 문제 해결) let settingsCache = {}; export const UI = { /** * 사이드바 및 로그아웃 버튼 초기화 */ initSidebarToggle() { const toggle = document.getElementById('sidebarToggle'); const sidebar = DOM.sidebar; const overlay = document.getElementById('sidebarOverlay'); const logoutBtn = document.getElementById('logoutBtn'); if (toggle && sidebar) { const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true'; if (isCollapsed) { sidebar.classList.add('collapsed'); const calendar = document.getElementById('calendarContainer'); if (calendar) calendar.style.display = 'none'; } const toggleSidebar = () => { const isMobile = window.innerWidth <= 768; if (isMobile) { sidebar.classList.toggle('mobile-open'); overlay.style.display = sidebar.classList.contains('mobile-open') ? 'block' : 'none'; } else { sidebar.classList.toggle('collapsed'); const collapsed = sidebar.classList.contains('collapsed'); localStorage.setItem('sidebarCollapsed', collapsed); const calendar = document.getElementById('calendarContainer'); if (calendar) calendar.style.display = collapsed ? 'none' : 'block'; } }; toggle.onclick = toggleSidebar; const mobileBtn = document.getElementById('mobileMenuBtn'); if (mobileBtn) mobileBtn.onclick = toggleSidebar; if (overlay) { overlay.onclick = () => { sidebar.classList.remove('mobile-open'); overlay.style.display = 'none'; }; } } if (logoutBtn) { logoutBtn.onclick = () => { if (confirm(I18nManager.t('msg_logout_confirm'))) { window.location.href = '/logout'; } }; } }, /** * 환경 설정 및 테마 엔진 초기화 (ThemeManager 위임) */ async initSettings() { return await ThemeManager.initSettings(); }, /** * 무한 스크롤 초기화 */ initInfiniteScroll(onLoadMore) { if (!DOM.scrollSentinel) return; const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { onLoadMore(); } }, { threshold: 0.1 }); observer.observe(DOM.scrollSentinel); }, /** * 사이드바 시스템 고정 메뉴 상태 갱신 */ updateSidebar(memos, activeGroup, activeCategory, onGroupClick, onCategoryClick) { if (!DOM.systemNav) return; // 1. 시스템 그룹 동기화 DOM.systemNav.querySelectorAll('li').forEach(li => { const group = li.dataset.group; li.className = (group === activeGroup) ? 'active' : ''; li.onclick = () => onGroupClick(group); }); // 2. 카테고리 동기화 (Pinned Categories) import('./components/SidebarUI.js').then(({ renderCategoryList }) => { const categoryNav = document.getElementById('categoryNav'); // 💡 settingsCache가 비어있을 경우 ThemeManager에서 직접 복구 시도 const pinned = settingsCache.pinned_categories || (ThemeManager.settings ? ThemeManager.settings.pinned_categories : []); renderCategoryList(categoryNav, pinned, activeCategory, onCategoryClick); }); }, /** * 카테고리 기능 활성화 여부에 따라 UI 요소 노출 제어 */ applyCategoryVisibility(enabled) { const composerBar = document.getElementById('composerCategoryBar'); const sidebarSection = document.getElementById('categorySidebarSection'); if (composerBar) { // 작성기 칩 영역은 가로 정렬을 위해 flex 레이아웃이 필수입니다. composerBar.style.display = enabled ? 'flex' : 'none'; } if (sidebarSection) { // 사이드바 섹션은 기본 블록 레이아웃을 사용합니다. sidebarSection.style.display = enabled ? 'block' : 'none'; } console.log(`Category UI visibility updated: ${enabled ? 'VISIBLE' : 'HIDDEN'}`); }, /** * 설정 캐시 업데이트 (내부용) */ _updateSettingsCache(settings) { settingsCache = settings; }, /** * 메모 목록 메인 렌더링 (서버 사이드 필터링 결과 기반) */ renderMemos(memos, filters = {}, handlers, isAppend = false) { if (!isAppend) { DOM.memoGrid.innerHTML = ''; } if (!memos || memos.length === 0) { if (!isAppend) { DOM.memoGrid.innerHTML = `