mingle-website/js/props.js
68893236+KINDNICK@users.noreply.github.com 77662aefa6 ADD : 프랍페이지 추가
2026-01-08 21:05:01 +09:00

309 lines
9.0 KiB
JavaScript

/**
* 프랍 라이브러리 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.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
? `<img src="${escapeHtml(prop.thumbnailUrl)}" alt="${escapeHtml(prop.name)}" loading="lazy">`
: `<div class="no-thumbnail">🎁</div>`;
const prefabBadge = prop.prefabCount > 1
? `<span class="card-badge">${prop.prefabCount} variants</span>`
: '';
return `
<div class="prop-card" data-prop="${escapeHtml(prop.name)}">
<div class="card-thumbnail">
${thumbnailHtml}
${prefabBadge}
</div>
<div class="card-content">
<h3 class="card-title">${escapeHtml(prop.name)}</h3>
<div class="card-info">
${prop.prefabCount > 0 ? `<span class="card-info-item"><span class="icon">📦</span>${prop.prefabCount}</span>` : ''}
${prop.modelCount > 0 ? `<span class="card-info-item"><span class="icon">🎨</span>${prop.modelCount}</span>` : ''}
</div>
</div>
</div>
`;
}
/**
* 뷰 모드 업데이트
*/
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.prefabCount > 0 ? `
<div class="modal-detail-item">
<span class="icon">📦</span>
<span>프리펩</span>
<strong>${prop.prefabCount}</strong>개
</div>
` : ''}
${prop.modelCount > 0 ? `
<div class="modal-detail-item">
<span class="icon">🎨</span>
<span>모델</span>
<strong>${prop.modelCount}</strong>개
</div>
` : ''}
${prop.textureCount > 0 ? `
<div class="modal-detail-item">
<span class="icon">🖼️</span>
<span>텍스처</span>
<strong>${prop.textureCount}</strong>개
</div>
` : ''}
${prop.materialCount > 0 ? `
<div class="modal-detail-item">
<span class="icon">💎</span>
<span>머티리얼</span>
<strong>${prop.materialCount}</strong>개
</div>
` : ''}
`;
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);
})();