309 lines
12 KiB
JavaScript
309 lines
12 KiB
JavaScript
(() => {
|
|
const CatalogManager = require('./assets/js/catalogmanager')
|
|
const ConfigManager = require('./assets/js/configmanager')
|
|
const ProfileAssetManager = require('./assets/js/profileassetmanager')
|
|
|
|
const installCatalogList = document.getElementById('installCatalogList')
|
|
const installPageShell = document.querySelector('#installContainer .launcherPageShell')
|
|
|
|
let expandedProfileId = null
|
|
|
|
function createInstallBadge(text){
|
|
const badge = document.createElement('span')
|
|
badge.className = 'launcherBadge'
|
|
badge.textContent = text
|
|
return badge
|
|
}
|
|
|
|
function describeProfileFeatures(profile){
|
|
const features = ['맵']
|
|
if(profile.modsEnabled){
|
|
features.push('모드')
|
|
}
|
|
if(profile.pluginsEnabled){
|
|
features.push('플러그인')
|
|
}
|
|
if(profile.serverEnabled){
|
|
features.push('서버')
|
|
}
|
|
return features
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
if(profile.serverEnabled){
|
|
return '이 프로필은 맵을 기본으로 두고 서버 기능까지 함께 사용하는 항목입니다. 주소를 직접 넣으면 해당 서버로 접속하고, 주소를 비워두면 로컬 서버 실행 흐름을 사용할 수 있습니다.'
|
|
}
|
|
|
|
if(profile.modsEnabled){
|
|
return '이 프로필은 맵 기반 클라이언트에 모드 구성을 포함한 항목입니다. 관리자가 distribution과 월드 자료를 미리 등록해두고, 사용자는 라이브러리에 추가한 뒤 바로 실행합니다.'
|
|
}
|
|
|
|
return '이 프로필은 맵 기반 기본 항목입니다. 관리자가 distribution과 월드 자료를 미리 등록해두고, 사용자는 라이브러리에 추가한 뒤 바로 실행합니다.'
|
|
}
|
|
|
|
function toggleExpandedProfile(profileId){
|
|
expandedProfileId = expandedProfileId === profileId ? null : profileId
|
|
}
|
|
|
|
function isInstallable(profile){
|
|
return profile.launchReady
|
|
}
|
|
|
|
async function installProfile(profile){
|
|
const installedProfile = await CatalogManager.installProfile(profile.id)
|
|
await ProfileAssetManager.prefetchProfileAssets(installedProfile)
|
|
if(installedProfile.serverEnabled && installedProfile.hostReady){
|
|
await ProfileAssetManager.ensureServerJarInstalled(installedProfile)
|
|
}
|
|
|
|
if(typeof refreshSelectedProfileButton === 'function'){
|
|
refreshSelectedProfileButton()
|
|
}
|
|
if(typeof refreshServerStatus === 'function'){
|
|
refreshServerStatus(true)
|
|
}
|
|
if(typeof refreshLibraryView === 'function'){
|
|
await refreshLibraryView()
|
|
}
|
|
}
|
|
|
|
function createExpandedDetail(profile, installed){
|
|
const detailSection = document.createElement('div')
|
|
detailSection.className = 'launcherExpandableDetail'
|
|
detailSection.addEventListener('click', (event) => {
|
|
event.stopPropagation()
|
|
})
|
|
|
|
const badgeRow = document.createElement('div')
|
|
badgeRow.className = 'launcherExpandableMeta'
|
|
describeProfileFeatures(profile).forEach((label) => {
|
|
badgeRow.appendChild(createInstallBadge(label))
|
|
})
|
|
if(installed){
|
|
badgeRow.appendChild(createInstallBadge('설치됨'))
|
|
}
|
|
if(profile.serverEnabled && profile.hostReady){
|
|
badgeRow.appendChild(createInstallBadge('로컬 서버 가능'))
|
|
}
|
|
if(!profile.launchReady){
|
|
badgeRow.appendChild(createInstallBadge('설정 필요'))
|
|
}
|
|
|
|
const infoBlock = document.createElement('div')
|
|
infoBlock.className = 'launcherInfoBlock'
|
|
infoBlock.appendChild(createInfoLine('프로필 ID', profile.id))
|
|
infoBlock.appendChild(createInfoLine('구성', describeProfileFeatures(profile).join(' + ')))
|
|
infoBlock.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
|
|
infoBlock.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName || '미설정'))
|
|
|
|
if(profile.serverEnabled){
|
|
infoBlock.appendChild(createInfoLine('서버 포트', String(profile.serverPort ?? 25565)))
|
|
infoBlock.appendChild(createInfoLine('서버 메모리', `${profile.serverMemoryMb ?? 4096}MB`))
|
|
infoBlock.appendChild(createInfoLine('최대 인원수', String(profile.serverMaxPlayers ?? 20)))
|
|
infoBlock.appendChild(createInfoLine('화이트리스트', profile.serverWhitelistEnabled ? '사용' : '미사용'))
|
|
infoBlock.appendChild(createInfoLine('로컬 서버 준비', profile.hostReady ? '완료' : '버킷 JAR 필요'))
|
|
}
|
|
|
|
if(profile.launchIssues.length > 0){
|
|
infoBlock.appendChild(createInfoLine('확인 필요', profile.launchIssues.join(' / ')))
|
|
} else if(profile.hostIssues.length > 0){
|
|
infoBlock.appendChild(createInfoLine('서버 참고', profile.hostIssues.join(' / ')))
|
|
}
|
|
|
|
const bodyGroup = document.createElement('div')
|
|
bodyGroup.className = 'launcherFieldGroup'
|
|
|
|
const bodyLabel = document.createElement('label')
|
|
bodyLabel.className = 'launcherFieldLabel'
|
|
bodyLabel.textContent = '자세한 내용'
|
|
|
|
const body = document.createElement('div')
|
|
body.className = 'launcherDetailBody'
|
|
body.textContent = buildDetailText(profile)
|
|
|
|
bodyGroup.appendChild(bodyLabel)
|
|
bodyGroup.appendChild(body)
|
|
|
|
const actions = document.createElement('div')
|
|
actions.className = 'launcherCardActions'
|
|
|
|
const installButton = document.createElement('button')
|
|
installButton.className = 'launcherPrimaryButton'
|
|
installButton.textContent = installed ? '설치됨' : '라이브러리에 추가'
|
|
installButton.disabled = installed || !isInstallable(profile)
|
|
installButton.addEventListener('click', async (event) => {
|
|
event.stopPropagation()
|
|
try {
|
|
await installProfile(profile)
|
|
await renderInstallView()
|
|
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
|
|
} catch (error) {
|
|
console.error(error)
|
|
const message = error instanceof Error ? error.message : '프로필 설치 중 오류가 발생했습니다.'
|
|
showInstallMessage('설치 실패', message)
|
|
}
|
|
})
|
|
|
|
const openLibraryButton = document.createElement('button')
|
|
openLibraryButton.className = 'launcherSecondaryButton'
|
|
openLibraryButton.textContent = '라이브러리 열기'
|
|
openLibraryButton.addEventListener('click', async (event) => {
|
|
event.stopPropagation()
|
|
if(typeof refreshLibraryView === 'function'){
|
|
await refreshLibraryView()
|
|
}
|
|
switchView(getCurrentView(), VIEWS.library)
|
|
})
|
|
|
|
actions.appendChild(installButton)
|
|
actions.appendChild(openLibraryButton)
|
|
|
|
detailSection.appendChild(badgeRow)
|
|
detailSection.appendChild(infoBlock)
|
|
detailSection.appendChild(bodyGroup)
|
|
detailSection.appendChild(actions)
|
|
|
|
return detailSection
|
|
}
|
|
|
|
async function renderInstallView(){
|
|
const previousScrollTop = installPageShell != null ? installPageShell.scrollTop : 0
|
|
installCatalogList.innerHTML = ''
|
|
|
|
try {
|
|
const catalog = await CatalogManager.loadCatalog()
|
|
const installedIds = new Set(
|
|
ConfigManager.getInstalledLibraryProfiles().map((profile) => profile.id)
|
|
)
|
|
|
|
if(expandedProfileId != null && !catalog.profiles.some((profile) => profile.id === expandedProfileId)){
|
|
expandedProfileId = null
|
|
}
|
|
|
|
if(catalog.sourceError != null){
|
|
const warningCard = document.createElement('article')
|
|
warningCard.className = 'launcherCard'
|
|
warningCard.innerHTML = '<h3 class="launcherCardTitle">카탈로그 로드 경고</h3><p class="launcherCardDescription">관리자가 등록한 카탈로그를 읽지 못했습니다. 현재 보이는 목록이 없다면 관리자 사이트에서 카탈로그 파일과 배포 경로를 다시 확인하세요.</p>'
|
|
installCatalogList.appendChild(warningCard)
|
|
}
|
|
|
|
for(const profile of catalog.profiles){
|
|
const installed = installedIds.has(profile.id)
|
|
const expanded = expandedProfileId === profile.id
|
|
|
|
const row = document.createElement('article')
|
|
row.className = 'launcherListItem'
|
|
if(expanded){
|
|
row.setAttribute('selected', 'true')
|
|
}
|
|
|
|
const top = document.createElement('div')
|
|
top.className = 'launcherListItemTop'
|
|
|
|
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'
|
|
describeProfileFeatures(profile).forEach((label) => {
|
|
meta.appendChild(createInstallBadge(label))
|
|
})
|
|
if(installed){
|
|
meta.appendChild(createInstallBadge('설치됨'))
|
|
}
|
|
if(profile.serverEnabled && profile.hostReady){
|
|
meta.appendChild(createInstallBadge('로컬 서버 가능'))
|
|
}
|
|
|
|
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 = expanded ? '간단히 보기' : '자세히 보기'
|
|
detailButton.addEventListener('click', async (event) => {
|
|
event.stopPropagation()
|
|
toggleExpandedProfile(profile.id)
|
|
await renderInstallView()
|
|
})
|
|
|
|
actions.appendChild(detailButton)
|
|
textGroup.appendChild(title)
|
|
textGroup.appendChild(meta)
|
|
titleRow.appendChild(textGroup)
|
|
top.appendChild(main)
|
|
top.appendChild(actions)
|
|
main.appendChild(titleRow)
|
|
main.appendChild(description)
|
|
row.appendChild(top)
|
|
|
|
if(expanded){
|
|
row.appendChild(createExpandedDetail(profile, installed))
|
|
}
|
|
|
|
installCatalogList.appendChild(row)
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
const errorCard = document.createElement('article')
|
|
errorCard.className = 'launcherCard'
|
|
errorCard.innerHTML = '<h3 class="launcherCardTitle">설치 페이지 로드 실패</h3><p class="launcherCardDescription">프로필 목록을 읽지 못했습니다. 관리자 사이트에서 catalog 설정을 확인하세요.</p>'
|
|
installCatalogList.appendChild(errorCard)
|
|
} finally {
|
|
if(installPageShell != null){
|
|
installPageShell.scrollTop = previousScrollTop
|
|
}
|
|
}
|
|
}
|
|
|
|
document.getElementById('installBackButton').addEventListener('click', () => {
|
|
switchView(getCurrentView(), VIEWS.landing)
|
|
})
|
|
|
|
window.refreshInstallView = renderInstallView
|
|
renderInstallView()
|
|
})()
|