(() => { const CatalogManager = require('./assets/js/catalogmanager') const ConfigManager = require('./assets/js/configmanager') const ProfileAssetManager = require('./assets/js/profileassetmanager') const installCatalogList = document.getElementById('installCatalogList') const installDetailTitle = document.getElementById('installDetailTitle') const installDetailSummary = document.getElementById('installDetailSummary') const installDetailMeta = document.getElementById('installDetailMeta') const installDetailInfo = document.getElementById('installDetailInfo') const installDetailBody = document.getElementById('installDetailBody') const installDetailAddButton = document.getElementById('installDetailAddButton') let selectedProfileId = null let latestCatalog = null function describeProfileKind(kind){ switch(kind){ case 'map': return '오리지널 맵' case 'server-pack': return '플러그인 맵 + 서버팩' case 'modpack': default: return '모드팩' } } function createInstallBadge(text){ const badge = document.createElement('span') badge.className = 'launcherBadge' badge.textContent = text return badge } function createInfoLine(label, value){ const line = document.createElement('div') line.className = 'launcherInfoLine' const labelElement = document.createElement('span') labelElement.className = 'launcherInfoLabel' labelElement.textContent = label const valueElement = document.createElement('span') valueElement.className = 'launcherInfoValue' valueElement.textContent = value line.appendChild(labelElement) line.appendChild(valueElement) return line } function showInstallMessage(title, message){ if(typeof setOverlayContent === 'function'){ setOverlayContent(title, message, '확인') setOverlayHandler(() => toggleOverlay(false)) toggleOverlay(true) } } function buildDetailText(profile){ if(typeof profile.details === 'string' && profile.details.trim().length > 0){ return profile.details.trim() } switch(profile.kind){ case 'map': return '이 프로필은 싱글플레이 월드를 바로 실행하기 위한 항목입니다. 필요한 클라이언트 배포 파일과 월드 자료는 관리자가 미리 등록해둡니다.' case 'server-pack': return '이 프로필은 서버 실행/접속 흐름을 함께 다루는 항목입니다. 클라이언트 파일과 서버 번들은 관리자가 미리 등록하며, 사용자는 라이브러리에서 실행과 접속만 진행합니다.' case 'modpack': default: return '이 프로필은 일반 모드팩 클라이언트입니다. 필요한 배포 파일은 관리자가 미리 등록하며, 사용자는 라이브러리에 추가한 뒤 실행만 하면 됩니다.' } } function renderDetailPanel(profile){ const installedIds = new Set( ConfigManager.getInstalledLibraryProfiles().map((installedProfile) => installedProfile.id) ) const installed = installedIds.has(profile.id) installDetailTitle.textContent = profile.name installDetailSummary.textContent = profile.description || '설명이 없습니다.' installDetailMeta.innerHTML = '' installDetailMeta.appendChild(createInstallBadge(describeProfileKind(profile.kind))) if(installed){ installDetailMeta.appendChild(createInstallBadge('라이브러리 보유')) } if(!profile.launchReady){ installDetailMeta.appendChild(createInstallBadge('실행 준비 필요')) } if(profile.kind === 'server-pack' && profile.hostReady){ installDetailMeta.appendChild(createInstallBadge('로컬 호스팅 가능')) } installDetailInfo.innerHTML = '' installDetailInfo.appendChild(createInfoLine('프로필 ID', profile.id)) installDetailInfo.appendChild(createInfoLine('종류', describeProfileKind(profile.kind))) installDetailInfo.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요')) if(profile.defaultServerAddress){ installDetailInfo.appendChild(createInfoLine('기본 주소', profile.defaultServerAddress)) } if(profile.kind === 'map' && profile.worldDirectoryName){ installDetailInfo.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName)) } if(profile.kind === 'server-pack'){ installDetailInfo.appendChild(createInfoLine('로컬 호스팅', profile.hostReady ? '가능' : '관리자 설정 필요')) } if(profile.launchIssues.length > 0){ installDetailInfo.appendChild(createInfoLine('확인 필요', profile.launchIssues.join(' / '))) } else if(profile.hostIssues.length > 0){ installDetailInfo.appendChild(createInfoLine('호스팅 참고', profile.hostIssues.join(' / '))) } installDetailBody.textContent = buildDetailText(profile) installDetailAddButton.disabled = installed || !profile.launchReady installDetailAddButton.textContent = installed ? '이미 라이브러리에 있음' : '라이브러리에 추가' installDetailAddButton.onclick = async () => { try { const installedProfile = await CatalogManager.installProfile(profile.id) await ProfileAssetManager.prefetchProfileAssets(installedProfile) if(installedProfile.kind === 'server-pack' && installedProfile.hostReady){ await ProfileAssetManager.ensureServerBundleInstalled(installedProfile) } if(typeof refreshSelectedProfileButton === 'function'){ refreshSelectedProfileButton() } renderDetailPanel(profile) await renderInstallView() if(typeof refreshLibraryView === 'function'){ await refreshLibraryView() } showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`) } catch (error) { console.error(error) const message = error instanceof Error ? error.message : '프로필 설치 중 오류가 발생했습니다.' showInstallMessage('설치 실패', message) } } } function renderEmptyDetailPanel(){ installDetailTitle.textContent = '프로필을 선택하세요' installDetailSummary.textContent = '아래 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.' installDetailMeta.innerHTML = '' installDetailInfo.innerHTML = '' installDetailBody.textContent = '관리자가 등록한 프로필 상세 설명이 여기에 표시됩니다.' installDetailAddButton.disabled = true installDetailAddButton.textContent = '라이브러리에 추가' installDetailAddButton.onclick = null } function selectProfile(profileId){ selectedProfileId = profileId if(latestCatalog == null){ renderEmptyDetailPanel() return } const profile = latestCatalog.profiles.find((entry) => entry.id === profileId) if(profile == null){ renderEmptyDetailPanel() return } renderDetailPanel(profile) } async function renderInstallView(){ installCatalogList.innerHTML = '' try { const catalog = await CatalogManager.loadCatalog() latestCatalog = catalog const installedIds = new Set( ConfigManager.getInstalledLibraryProfiles().map((profile) => profile.id) ) if(catalog.sourceError != null){ const warningCard = document.createElement('article') warningCard.className = 'launcherCard' warningCard.innerHTML = '
관리자가 등록한 카탈로그를 읽지 못했습니다. 현재 보이는 목록이 없다면 배포 주소 또는 로컬 카탈로그 파일을 관리자 측에서 확인해야 합니다.
' installCatalogList.appendChild(warningCard) } for(const profile of catalog.profiles){ const row = document.createElement('article') row.className = 'launcherListItem' if(profile.id === selectedProfileId){ row.setAttribute('selected', 'true') } const main = document.createElement('div') main.className = 'launcherListItemMain' const titleRow = document.createElement('div') titleRow.className = 'launcherListTitleRow' const textGroup = document.createElement('div') textGroup.className = 'launcherListTextGroup' const title = document.createElement('h3') title.className = 'launcherListTitle' title.textContent = profile.name const meta = document.createElement('div') meta.className = 'launcherListMeta' meta.appendChild(createInstallBadge(describeProfileKind(profile.kind))) if(installedIds.has(profile.id)){ meta.appendChild(createInstallBadge('설치됨')) } if(profile.kind === 'server-pack' && profile.hostReady){ meta.appendChild(createInstallBadge('호스팅 가능')) } textGroup.appendChild(title) const description = document.createElement('p') description.className = 'launcherListDescription' description.textContent = profile.description || '설명이 없습니다.' const actions = document.createElement('div') actions.className = 'launcherListActions' const detailButton = document.createElement('button') detailButton.className = 'launcherSecondaryButton' detailButton.textContent = '자세히 보기' detailButton.addEventListener('click', async (event) => { event.stopPropagation() selectProfile(profile.id) await renderInstallView() }) const installButton = document.createElement('button') installButton.className = 'launcherPrimaryButton' installButton.textContent = installedIds.has(profile.id) ? '설치됨' : '라이브러리에 추가' installButton.disabled = installedIds.has(profile.id) || !profile.launchReady installButton.addEventListener('click', async (event) => { event.stopPropagation() try { const installedProfile = await CatalogManager.installProfile(profile.id) await ProfileAssetManager.prefetchProfileAssets(installedProfile) if(installedProfile.kind === 'server-pack' && installedProfile.hostReady){ await ProfileAssetManager.ensureServerBundleInstalled(installedProfile) } if(typeof refreshSelectedProfileButton === 'function'){ refreshSelectedProfileButton() } if(typeof refreshServerStatus === 'function'){ refreshServerStatus(true) } selectProfile(profile.id) await renderInstallView() if(typeof refreshLibraryView === 'function'){ await refreshLibraryView() } showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`) } catch (error) { console.error(error) const message = error instanceof Error ? error.message : '프로필 설치 중 오류가 발생했습니다.' showInstallMessage('설치 실패', message) } }) actions.appendChild(detailButton) actions.appendChild(installButton) titleRow.appendChild(textGroup) titleRow.appendChild(meta) main.appendChild(titleRow) main.appendChild(description) row.appendChild(main) row.appendChild(actions) row.addEventListener('click', async () => { selectProfile(profile.id) await renderInstallView() }) installCatalogList.appendChild(row) } if(catalog.profiles.length === 0){ renderEmptyDetailPanel() return } const selectedProfileStillExists = catalog.profiles.some((profile) => profile.id === selectedProfileId) if(!selectedProfileStillExists){ selectedProfileId = catalog.profiles[0].id } selectProfile(selectedProfileId) } catch (error) { console.error(error) latestCatalog = null renderEmptyDetailPanel() const errorCard = document.createElement('article') errorCard.className = 'launcherCard' errorCard.innerHTML = '관리자가 등록한 카탈로그를 읽지 못했습니다.
' installCatalogList.appendChild(errorCard) } } document.getElementById('installBackButton').addEventListener('click', () => { switchView(getCurrentView(), VIEWS.landing) }) document.getElementById('installDetailOpenLibraryButton').addEventListener('click', async () => { if(typeof refreshLibraryView === 'function'){ await refreshLibraryView() } switchView(getCurrentView(), VIEWS.library) }) window.refreshInstallView = renderInstallView renderEmptyDetailPanel() renderInstallView() })()