/** * 프랍 라이브러리 JavaScript * Unity에서 업로드된 JSON 데이터를 기반으로 프랍 목록을 표시 */ (function() { 'use strict'; // 데이터 파일 경로 const DATA_PATH = 'data/props.json'; // 상태 let propsData = []; let filteredData = []; let currentView = 'grid'; let searchQuery = ''; // DOM 요소 const elements = { grid: document.getElementById('propsGrid'), searchInput: document.getElementById('searchInput'), totalCount: document.getElementById('totalCount'), filteredCount: document.getElementById('filteredCount'), lastUpdated: document.getElementById('lastUpdated'), noData: document.getElementById('noData'), noResults: document.getElementById('noResults'), imageModal: document.getElementById('imageModal'), modalImage: document.getElementById('modalImage'), modalTitle: document.getElementById('modalTitle'), modalDetails: document.getElementById('modalDetails') }; /** * 초기화 */ async function init() { await loadData(); setupEventListeners(); } /** * 데이터 로드 */ async function loadData() { try { const response = await fetch(DATA_PATH + '?t=' + Date.now()); if (!response.ok) { throw new Error('데이터를 찾을 수 없습니다'); } const data = await response.json(); propsData = data.props || []; // 마지막 업데이트 시간 표시 if (data.lastUpdated) { const date = new Date(data.lastUpdated); elements.lastUpdated.textContent = `마지막 업데이트: ${formatDate(date)}`; } // 통계 업데이트 elements.totalCount.textContent = propsData.length; // 데이터 렌더링 filterAndRender(); } catch (error) { console.error('데이터 로드 실패:', error); showNoData(); } } /** * 이벤트 리스너 설정 */ function setupEventListeners() { // 검색 elements.searchInput.addEventListener('input', debounce((e) => { searchQuery = e.target.value.toLowerCase(); filterAndRender(); }, 300)); // 뷰 전환 document.querySelectorAll('.view-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); currentView = btn.dataset.view; updateViewMode(); }); }); // 모달 닫기 elements.imageModal.querySelector('.modal-overlay').addEventListener('click', closeModal); elements.imageModal.querySelector('.modal-close').addEventListener('click', closeModal); // ESC 키로 모달 닫기 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); }); } /** * 필터링 및 렌더링 */ function filterAndRender() { // 필터링 filteredData = propsData.filter(prop => { // 검색 필터 - 프리펩 이름과 폴더 이름 모두 검색 if (searchQuery) { const searchTarget = `${prop.name} ${prop.folderName || ''}`.toLowerCase(); if (!searchTarget.includes(searchQuery)) { return false; } } return true; }); // 통계 업데이트 elements.filteredCount.textContent = filteredData.length; // 렌더링 render(); } /** * 카드 렌더링 */ function render() { // 로딩/에러 상태 숨기기 const loadingPlaceholder = elements.grid.querySelector('.loading-placeholder'); if (loadingPlaceholder) { loadingPlaceholder.remove(); } elements.noData.style.display = 'none'; elements.noResults.style.display = 'none'; // 데이터 없음 if (propsData.length === 0) { elements.grid.innerHTML = ''; showNoData(); return; } // 검색 결과 없음 if (filteredData.length === 0) { elements.grid.innerHTML = ''; elements.noResults.style.display = 'block'; return; } // 카드 생성 elements.grid.innerHTML = filteredData.map(prop => createCard(prop)).join(''); // 카드 클릭 이벤트 elements.grid.querySelectorAll('.prop-card').forEach((card, index) => { card.addEventListener('click', () => openModal(filteredData[index])); }); // 뷰 모드 적용 updateViewMode(); } /** * 카드 HTML 생성 */ function createCard(prop) { const thumbnailHtml = prop.thumbnailUrl ? `${escapeHtml(prop.name)}` : `
🎁
`; // 폴더 이름이 프리펩 이름과 다르면 폴더 배지 표시 const folderBadge = (prop.folderName && prop.folderName !== prop.name) ? `${escapeHtml(prop.folderName)}` : ''; return `
${thumbnailHtml} ${folderBadge}

${escapeHtml(prop.name)}

${prop.modelCount > 0 ? `🎨${prop.modelCount}` : ''} ${prop.textureCount > 0 ? `🖼️${prop.textureCount}` : ''}
`; } /** * 뷰 모드 업데이트 */ function updateViewMode() { elements.grid.classList.toggle('list-view', currentView === 'list'); } /** * 모달 열기 */ function openModal(prop) { if (prop.thumbnailUrl) { elements.modalImage.src = prop.thumbnailUrl; elements.modalImage.alt = prop.name; } else { elements.modalImage.src = ''; elements.modalImage.alt = ''; } elements.modalTitle.textContent = prop.name; // 상세 정보 표시 elements.modalDetails.innerHTML = ` ${(prop.folderName && prop.folderName !== prop.name) ? ` ` : ''} ${prop.modelCount > 0 ? ` ` : ''} ${prop.textureCount > 0 ? ` ` : ''} ${prop.materialCount > 0 ? ` ` : ''} `; elements.imageModal.classList.add('active'); document.body.style.overflow = 'hidden'; } /** * 모달 닫기 */ function closeModal() { elements.imageModal.classList.remove('active'); document.body.style.overflow = ''; } /** * 데이터 없음 표시 */ function showNoData() { elements.grid.innerHTML = ''; elements.noData.style.display = 'block'; } /** * 날짜 포맷 */ function formatDate(date) { return date.toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } /** * HTML 이스케이프 */ function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } /** * 디바운스 */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // 페이지 로드 시 초기화 document.addEventListener('DOMContentLoaded', init); })();