Refactor launcher profiles and port automation
This commit is contained in:
@@ -8,18 +8,6 @@ const installPageShell = document.querySelector('#installContainer .launcherPage
|
||||
|
||||
let expandedProfileId = 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'
|
||||
@@ -27,6 +15,20 @@ function createInstallBadge(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'
|
||||
@@ -57,26 +59,30 @@ function buildDetailText(profile){
|
||||
return profile.details.trim()
|
||||
}
|
||||
|
||||
switch(profile.kind){
|
||||
case 'map':
|
||||
return '이 프로필은 싱글플레이 월드를 바로 실행하기 위한 항목입니다. 필요한 클라이언트 배포 파일과 월드 자료는 관리자가 미리 등록해둡니다.'
|
||||
case 'server-pack':
|
||||
return '이 프로필은 서버 실행/접속 흐름을 함께 다루는 항목입니다. 클라이언트 파일과 서버 번들은 관리자가 미리 등록하며, 사용자는 라이브러리에서 실행과 접속만 진행합니다.'
|
||||
case 'modpack':
|
||||
default:
|
||||
return '이 프로필은 일반 모드팩 클라이언트입니다. 필요한 배포 파일은 관리자가 미리 등록하며, 사용자는 라이브러리에 추가한 뒤 실행만 하면 됩니다.'
|
||||
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.kind === 'server-pack' && installedProfile.hostReady){
|
||||
await ProfileAssetManager.ensureServerBundleInstalled(installedProfile)
|
||||
if(installedProfile.serverEnabled && installedProfile.hostReady){
|
||||
await ProfileAssetManager.ensureServerJarInstalled(installedProfile)
|
||||
}
|
||||
|
||||
if(typeof refreshSelectedProfileButton === 'function'){
|
||||
@@ -99,36 +105,38 @@ function createExpandedDetail(profile, installed){
|
||||
|
||||
const badgeRow = document.createElement('div')
|
||||
badgeRow.className = 'launcherExpandableMeta'
|
||||
badgeRow.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
|
||||
describeProfileFeatures(profile).forEach((label) => {
|
||||
badgeRow.appendChild(createInstallBadge(label))
|
||||
})
|
||||
if(installed){
|
||||
badgeRow.appendChild(createInstallBadge('설치됨'))
|
||||
}
|
||||
if(!profile.launchReady){
|
||||
badgeRow.appendChild(createInstallBadge('실행 준비 필요'))
|
||||
if(profile.serverEnabled && profile.hostReady){
|
||||
badgeRow.appendChild(createInstallBadge('로컬 서버 가능'))
|
||||
}
|
||||
if(profile.kind === 'server-pack' && 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('종류', describeProfileKind(profile.kind)))
|
||||
infoBlock.appendChild(createInfoLine('구성', describeProfileFeatures(profile).join(' + ')))
|
||||
infoBlock.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
|
||||
infoBlock.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName || '미설정'))
|
||||
|
||||
if(profile.defaultServerAddress){
|
||||
infoBlock.appendChild(createInfoLine('기본 주소', profile.defaultServerAddress))
|
||||
}
|
||||
if(profile.kind === 'map' && profile.worldDirectoryName){
|
||||
infoBlock.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName))
|
||||
}
|
||||
if(profile.kind === 'server-pack'){
|
||||
infoBlock.appendChild(createInfoLine('로컬 호스팅', profile.hostReady ? '가능' : '관리자 설정 필요'))
|
||||
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(' / ')))
|
||||
infoBlock.appendChild(createInfoLine('서버 참고', profile.hostIssues.join(' / ')))
|
||||
}
|
||||
|
||||
const bodyGroup = document.createElement('div')
|
||||
@@ -151,7 +159,7 @@ function createExpandedDetail(profile, installed){
|
||||
const installButton = document.createElement('button')
|
||||
installButton.className = 'launcherPrimaryButton'
|
||||
installButton.textContent = installed ? '설치됨' : '라이브러리에 추가'
|
||||
installButton.disabled = installed || !profile.launchReady
|
||||
installButton.disabled = installed || !isInstallable(profile)
|
||||
installButton.addEventListener('click', async (event) => {
|
||||
event.stopPropagation()
|
||||
try {
|
||||
@@ -204,7 +212,7 @@ async function renderInstallView(){
|
||||
if(catalog.sourceError != null){
|
||||
const warningCard = document.createElement('article')
|
||||
warningCard.className = 'launcherCard'
|
||||
warningCard.innerHTML = '<h3 class="launcherCardTitle">카탈로그 로드 경고</h3><p class="launcherCardDescription">관리자가 등록한 카탈로그를 읽지 못했습니다. 현재 보이는 목록이 없다면 배포 주소 또는 로컬 카탈로그 파일을 관리자 측에서 확인해야 합니다.</p>'
|
||||
warningCard.innerHTML = '<h3 class="launcherCardTitle">카탈로그 로드 경고</h3><p class="launcherCardDescription">관리자가 등록한 카탈로그를 읽지 못했습니다. 현재 보이는 목록이 없다면 관리자 사이트에서 카탈로그 파일과 배포 경로를 다시 확인하세요.</p>'
|
||||
installCatalogList.appendChild(warningCard)
|
||||
}
|
||||
|
||||
@@ -236,12 +244,14 @@ async function renderInstallView(){
|
||||
|
||||
const meta = document.createElement('div')
|
||||
meta.className = 'launcherListMeta'
|
||||
meta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
|
||||
describeProfileFeatures(profile).forEach((label) => {
|
||||
meta.appendChild(createInstallBadge(label))
|
||||
})
|
||||
if(installed){
|
||||
meta.appendChild(createInstallBadge('설치됨'))
|
||||
}
|
||||
if(profile.kind === 'server-pack' && profile.hostReady){
|
||||
meta.appendChild(createInstallBadge('호스팅 가능'))
|
||||
if(profile.serverEnabled && profile.hostReady){
|
||||
meta.appendChild(createInstallBadge('로컬 서버 가능'))
|
||||
}
|
||||
|
||||
const description = document.createElement('p')
|
||||
@@ -260,67 +270,31 @@ async function renderInstallView(){
|
||||
await renderInstallView()
|
||||
})
|
||||
|
||||
const installButton = document.createElement('button')
|
||||
installButton.className = 'launcherPrimaryButton'
|
||||
installButton.textContent = installed ? '설치됨' : '라이브러리에 추가'
|
||||
installButton.disabled = installed || !profile.launchReady
|
||||
installButton.addEventListener('click', async (event) => {
|
||||
event.stopPropagation()
|
||||
try {
|
||||
await installProfile(profile)
|
||||
expandedProfileId = profile.id
|
||||
await renderInstallView()
|
||||
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const message = error instanceof Error ? error.message : '프로필 설치 중 오류가 발생했습니다.'
|
||||
showInstallMessage('설치 실패', message)
|
||||
}
|
||||
})
|
||||
|
||||
textGroup.appendChild(title)
|
||||
titleRow.appendChild(textGroup)
|
||||
titleRow.appendChild(meta)
|
||||
main.appendChild(titleRow)
|
||||
main.appendChild(description)
|
||||
|
||||
actions.appendChild(detailButton)
|
||||
actions.appendChild(installButton)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
row.addEventListener('click', async () => {
|
||||
toggleExpandedProfile(profile.id)
|
||||
await renderInstallView()
|
||||
})
|
||||
|
||||
installCatalogList.appendChild(row)
|
||||
}
|
||||
|
||||
if(catalog.profiles.length === 0){
|
||||
const emptyCard = document.createElement('article')
|
||||
emptyCard.className = 'launcherCard'
|
||||
emptyCard.innerHTML = '<h3 class="launcherCardTitle">등록된 프로필이 없습니다</h3><p class="launcherCardDescription">관리자가 카탈로그에 프로필을 추가하면 여기에 표시됩니다.</p>'
|
||||
installCatalogList.appendChild(emptyCard)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const errorCard = document.createElement('article')
|
||||
errorCard.className = 'launcherCard'
|
||||
errorCard.innerHTML = '<h3 class="launcherCardTitle">카탈로그 로드 실패</h3><p class="launcherCardDescription">관리자가 등록한 카탈로그를 읽지 못했습니다.</p>'
|
||||
errorCard.innerHTML = '<h3 class="launcherCardTitle">설치 페이지 로드 실패</h3><p class="launcherCardDescription">프로필 목록을 읽지 못했습니다. 관리자 사이트에서 catalog 설정을 확인하세요.</p>'
|
||||
installCatalogList.appendChild(errorCard)
|
||||
} finally {
|
||||
if(installPageShell != null){
|
||||
requestAnimationFrame(() => {
|
||||
installPageShell.scrollTop = previousScrollTop
|
||||
})
|
||||
installPageShell.scrollTop = previousScrollTop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user