Polish launcher main and library flows
Some checks failed
Build / release (macos-latest) (push) Has been cancelled
Build / release (ubuntu-latest) (push) Has been cancelled
Build / release (windows-latest) (push) Has been cancelled
Windows Smoke Test / windows-smoke (push) Has been cancelled

This commit is contained in:
2026-05-04 15:10:36 +09:00
parent 27c1453aa8
commit bc6bef0c46
13 changed files with 255 additions and 291 deletions

View File

@@ -89,6 +89,20 @@ function describeAssetState(profile){
return '추가 자산 없음'
}
function isProfileInstalled(profile){
const state = ConfigManager.getLibraryProfileAssetState(profile.id)
if(profile.kind === 'map'){
return state.prefetchedAt != null || profile.worldArchiveUrl == null
}
if(profile.kind === 'server-pack'){
return state.serverBundleInstalledAt != null || state.prefetchedAt != null || profile.serverBundleUrl == null
}
return true
}
async function prepareProfileAssets(profile){
try {
await ProfileAssetManager.prefetchProfileAssets(profile)
@@ -103,6 +117,27 @@ async function prepareProfileAssets(profile){
}
}
async function applyProfileSelection(profile){
CatalogManager.selectProfile(profile.id)
CatalogManager.applyConfiguredProfile()
const distro = await DistroAPI.refreshDistributionOrFallback()
if(distro == null){
throw new Error('Distribution refresh returned null.')
}
const currentServer = distro.getServerById(ConfigManager.getSelectedServer())
if(currentServer == null && typeof distro.getMainServer === 'function'){
const mainServer = distro.getMainServer()
if(mainServer != null){
ConfigManager.setSelectedServer(mainServer.rawServer.id)
ConfigManager.save()
}
}
onDistroRefresh(distro)
}
async function activateProfile(profile, launchNow = false){
if(!profile.configured){
const firstIssue = profile.launchIssues?.[0] ?? '이 프로필은 아직 실행 조건이 충족되지 않았습니다.'
@@ -264,101 +299,39 @@ async function renderLibraryView(){
const description = createParagraph('launcherCardDescription', profile.description || '설명이 없습니다.')
const infoBlock = document.createElement('div')
infoBlock.className = 'launcherInfoBlock'
infoBlock.appendChild(createInfoLine('자료 상태', describeAssetState(profile)))
infoBlock.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
if(profile.defaultServerAddress){
infoBlock.appendChild(createInfoLine('기본 주소', profile.defaultServerAddress))
}
if(profile.kind === 'server-pack'){
infoBlock.appendChild(createInfoLine('로컬 호스팅', profile.hostReady ? '가능' : '서버 번들 필요'))
}
if(hostState.running){
infoBlock.appendChild(createInfoLine('호스트 상태', hostState.tunneling ? '터널 연결 중' : '로컬 서버 실행 중'))
}
if(profile.launchIssues?.length > 0){
infoBlock.appendChild(createInfoLine('확인 필요', profile.launchIssues[0]))
} else if(profile.hostIssues?.length > 0){
infoBlock.appendChild(createInfoLine('호스팅 확인', profile.hostIssues[0]))
}
const fieldGroup = document.createElement('div')
fieldGroup.className = 'launcherFieldGroup'
appendAddressOverrideField(profile, fieldGroup)
appendPublishedAddressField(profile, hostState, fieldGroup)
const actions = document.createElement('div')
actions.className = 'launcherCardActions'
const prepareButton = document.createElement('button')
prepareButton.className = 'launcherSecondaryButton'
prepareButton.textContent = '자료 준비'
prepareButton.addEventListener('click', async () => {
const installButton = document.createElement('button')
installButton.className = 'launcherSecondaryButton'
installButton.textContent = isProfileInstalled(profile) ? '설치됨' : '설치'
installButton.disabled = isProfileInstalled(profile) || !profile.configured
installButton.addEventListener('click', async () => {
await prepareProfileAssets(profile)
})
const selectButton = document.createElement('button')
selectButton.className = 'launcherSecondaryButton'
selectButton.textContent = '프로필 선택'
selectButton.disabled = !profile.configured
selectButton.textContent = profile.id === selectedProfileId ? '선택됨' : '선택'
selectButton.disabled = profile.id === selectedProfileId
selectButton.addEventListener('click', async () => {
CatalogManager.selectProfile(profile.id)
CatalogManager.applyConfiguredProfile()
if(typeof refreshSelectedProfileButton === 'function'){
refreshSelectedProfileButton()
}
await renderLibraryView()
})
const openButton = document.createElement('button')
openButton.className = 'launcherSecondaryButton'
openButton.textContent = '실행 화면'
openButton.disabled = !profile.configured
openButton.addEventListener('click', async () => {
await activateProfile(profile, false)
})
const launchButton = document.createElement('button')
launchButton.className = 'launcherPrimaryButton'
launchButton.textContent = profile.kind === 'map' ? '맵 실행' : '바로 실행'
launchButton.disabled = !profile.configured
launchButton.addEventListener('click', async () => {
await activateProfile(profile, true)
})
actions.appendChild(prepareButton)
actions.appendChild(selectButton)
actions.appendChild(openButton)
actions.appendChild(launchButton)
if(profile.kind === 'server-pack'){
const startHostButton = document.createElement('button')
startHostButton.className = 'launcherSecondaryButton'
startHostButton.textContent = '서버 실행'
startHostButton.disabled = hostState.running || !profile.hostReady
startHostButton.addEventListener('click', async () => {
try {
await ServerRuntime.startHostedProfile(profile)
await renderLibraryView()
} catch (error) {
console.error(error)
showLibraryMessage('서버 실행 실패', '서버 번들이 준비되지 않았거나 시작 명령을 찾지 못했습니다.')
try {
await applyProfileSelection(profile)
if(typeof refreshSelectedProfileButton === 'function'){
refreshSelectedProfileButton()
}
if(typeof refreshServerStatus === 'function'){
refreshServerStatus(true)
}
})
const stopHostButton = document.createElement('button')
stopHostButton.className = 'launcherGhostButton'
stopHostButton.textContent = '서버 중지'
stopHostButton.disabled = !hostState.running
stopHostButton.addEventListener('click', async () => {
ServerRuntime.stopHostedProfile(profile.id)
await renderLibraryView()
})
} catch (error) {
console.error(error)
showLibraryMessage('프로필 선택 실패', '선택한 프로필의 배포 정보 또는 서버 정보를 불러오지 못했습니다.')
}
})
actions.appendChild(startHostButton)
actions.appendChild(stopHostButton)
}
actions.appendChild(installButton)
actions.appendChild(selectButton)
const removeButton = document.createElement('button')
removeButton.className = 'launcherGhostButton'
@@ -369,6 +342,9 @@ async function renderLibraryView(){
if(typeof refreshSelectedProfileButton === 'function'){
refreshSelectedProfileButton()
}
if(typeof refreshServerStatus === 'function'){
refreshServerStatus(true)
}
await renderLibraryView()
if(typeof refreshInstallView === 'function'){
await refreshInstallView()
@@ -379,10 +355,6 @@ async function renderLibraryView(){
card.appendChild(header)
card.appendChild(description)
card.appendChild(infoBlock)
if(fieldGroup.childNodes.length > 0){
card.appendChild(fieldGroup)
}
card.appendChild(actions)
libraryList.appendChild(card)
}
@@ -396,31 +368,10 @@ async function renderLibraryView(){
}
}
document.getElementById('libraryOpenInstallButton').addEventListener('click', async () => {
if(typeof refreshInstallView === 'function'){
await refreshInstallView()
}
switchView(getCurrentView(), VIEWS.install)
})
document.getElementById('libraryBackButton').addEventListener('click', () => {
switchView(getCurrentView(), VIEWS.landing)
})
document.getElementById('libraryOpenSettingsButton').addEventListener('click', async () => {
await prepareSettings()
switchView(getCurrentView(), VIEWS.settings)
})
document.getElementById('libraryOpenLaunchButton').addEventListener('click', async () => {
const selectedProfile = CatalogManager.getSelectedProfileSync()
if(selectedProfile == null){
switchView(getCurrentView(), VIEWS.install)
return
}
await activateProfile(selectedProfile, false)
})
setInterval(() => {
if(getCurrentView() === VIEWS.library && ServerRuntime.hasRunningProfiles()){
renderLibraryView()