Files
minecraft_launcher/app/assets/js/scripts/library.js
claude-bot 9786cfe031
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
Refactor launcher profiles and port automation
2026-05-05 21:52:17 +09:00

268 lines
10 KiB
JavaScript

(() => {
const CatalogManager = require('./assets/js/catalogmanager')
const ConfigManager = require('./assets/js/configmanager')
const ProfileAssetManager = require('./assets/js/profileassetmanager')
const ServerRuntime = require('./assets/js/serverruntime')
const { DistroAPI } = require('./assets/js/distromanager')
const libraryList = document.getElementById('libraryList')
const libraryEmptyState = document.getElementById('libraryEmptyState')
function renderLibraryEmptyState(isEmpty){
libraryEmptyState.style.display = isEmpty ? 'flex' : 'none'
}
function createBadge(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 createParagraph(className, text){
const element = document.createElement('p')
element.className = className
element.textContent = text
return element
}
function showLibraryMessage(title, message){
if(typeof setOverlayContent === 'function'){
setOverlayContent(title, message, '확인')
setOverlayHandler(() => toggleOverlay(false))
toggleOverlay(true)
}
}
function isProfileInstalled(profile){
const state = ConfigManager.getLibraryProfileAssetState(profile.id)
const mapReady = state.worldInstalledAt != null || state.prefetchedAt != null || profile.worldArchiveUrl == null
const serverReady = profile.serverEnabled !== true || profile.hostReady !== true || state.serverInstalledAt != null || state.prefetchedAt != null || profile.serverJarUrl == null
return mapReady && serverReady
}
async function prepareProfileAssets(profile){
try {
await ProfileAssetManager.prefetchProfileAssets(profile)
if(profile.serverEnabled && profile.hostReady){
await ProfileAssetManager.ensureServerJarInstalled(profile)
}
await renderLibraryView()
showLibraryMessage('자료 준비 완료', `${profile.name} 자료를 준비했습니다.`)
} catch (error) {
console.error(error)
showLibraryMessage('자료 준비 실패', '프로필 자료를 내려받는 중 오류가 발생했습니다.')
}
}
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)
}
function appendAddressOverrideField(profile, container){
if(profile.serverEnabled !== true){
return
}
const fieldGroup = document.createElement('div')
fieldGroup.className = 'launcherFieldGroup'
const label = document.createElement('label')
label.className = 'launcherFieldLabel'
label.textContent = '접속 주소'
const input = document.createElement('input')
input.className = 'launcherFieldInput'
input.type = 'text'
input.placeholder = '비워두면 로컬 서버 실행'
input.value = ConfigManager.getLibraryServerAddressOverride(profile.id) ?? ''
input.addEventListener('change', () => {
CatalogManager.setServerAddressOverride(profile.id, input.value)
if(typeof refreshServerStatus === 'function'){
refreshServerStatus(true)
}
})
const help = document.createElement('div')
help.className = 'launcherFieldHint'
help.textContent = profile.hostReady
? '주소를 비워두면 PLAY 시 로컬 서버를 실행하고, 값을 넣으면 해당 주소로 바로 접속합니다.'
: '주소를 비워두면 로컬 서버 실행을 시도합니다. 지금은 버킷 JAR이 없어 직접 실행 준비가 부족합니다.'
fieldGroup.appendChild(label)
fieldGroup.appendChild(input)
fieldGroup.appendChild(help)
container.appendChild(fieldGroup)
}
async function renderLibraryView(){
libraryList.innerHTML = ''
try {
const installedProfiles = await CatalogManager.getInstalledProfiles()
const selectedProfileId = CatalogManager.getSelectedProfileId()
renderLibraryEmptyState(installedProfiles.length === 0)
for(const profile of installedProfiles){
const hostState = ServerRuntime.getHostedProfileState(profile.id)
const card = document.createElement('article')
card.className = 'launcherCard'
if(profile.id === selectedProfileId){
card.setAttribute('selected', 'true')
}
const header = document.createElement('div')
header.className = 'launcherCardHeader'
const titleGroup = document.createElement('div')
titleGroup.className = 'launcherCardTitleGroup'
const title = document.createElement('h3')
title.className = 'launcherCardTitle'
title.textContent = profile.name
const meta = document.createElement('div')
meta.className = 'launcherCardMeta'
describeProfileFeatures(profile).forEach((label) => {
meta.appendChild(createBadge(label))
})
if(profile.id === selectedProfileId){
meta.appendChild(createBadge('선택됨'))
}
if(!profile.launchReady){
meta.appendChild(createBadge('실행 준비 필요'))
}
if(profile.serverEnabled && profile.hostReady){
meta.appendChild(createBadge('로컬 서버 가능'))
}
if(hostState.running){
meta.appendChild(createBadge(hostState.ready ? '서버 실행 중' : '서버 시작 중'))
}
titleGroup.appendChild(title)
titleGroup.appendChild(meta)
header.appendChild(titleGroup)
const description = createParagraph('launcherCardDescription', profile.description || '설명이 없습니다.')
const extra = document.createElement('div')
extra.className = 'launcherCardContent'
appendAddressOverrideField(profile, extra)
const actions = document.createElement('div')
actions.className = 'launcherCardActions'
const installButton = document.createElement('button')
installButton.className = 'launcherSecondaryButton'
installButton.textContent = isProfileInstalled(profile) ? '설치됨' : '설치'
installButton.disabled = isProfileInstalled(profile) || !profile.launchReady
installButton.addEventListener('click', async () => {
await prepareProfileAssets(profile)
})
const selectButton = document.createElement('button')
selectButton.className = 'launcherSecondaryButton'
selectButton.textContent = profile.id === selectedProfileId ? '선택됨' : '선택'
selectButton.disabled = profile.id === selectedProfileId
selectButton.addEventListener('click', async () => {
try {
await applyProfileSelection(profile)
if(typeof refreshSelectedProfileButton === 'function'){
refreshSelectedProfileButton()
}
if(typeof refreshServerStatus === 'function'){
refreshServerStatus(true)
}
await renderLibraryView()
} catch (error) {
console.error(error)
showLibraryMessage('프로필 선택 실패', '선택한 프로필의 배포 정보 또는 서버 정보를 불러오지 못했습니다.')
}
})
const removeButton = document.createElement('button')
removeButton.className = 'launcherGhostButton'
removeButton.textContent = '제거'
removeButton.addEventListener('click', async () => {
await ServerRuntime.stopHostedProfile(profile.id)
CatalogManager.removeProfile(profile.id)
if(typeof refreshSelectedProfileButton === 'function'){
refreshSelectedProfileButton()
}
if(typeof refreshServerStatus === 'function'){
refreshServerStatus(true)
}
await renderLibraryView()
if(typeof refreshInstallView === 'function'){
await refreshInstallView()
}
})
actions.appendChild(installButton)
actions.appendChild(selectButton)
actions.appendChild(removeButton)
card.appendChild(header)
card.appendChild(description)
if(profile.serverEnabled){
card.appendChild(extra)
}
card.appendChild(actions)
libraryList.appendChild(card)
}
} catch (error) {
console.error(error)
renderLibraryEmptyState(false)
const errorCard = document.createElement('article')
errorCard.className = 'launcherCard'
errorCard.innerHTML = '<h3 class="launcherCardTitle">라이브러리 로드 실패</h3><p class="launcherCardDescription">선택한 카탈로그를 읽지 못했습니다. 관리자 사이트에서 카탈로그 경로를 다시 확인하세요.</p>'
libraryList.appendChild(errorCard)
}
}
document.getElementById('libraryBackButton').addEventListener('click', () => {
switchView(getCurrentView(), VIEWS.landing)
})
setInterval(() => {
if(getCurrentView() === VIEWS.library && ServerRuntime.hasRunningProfiles()){
renderLibraryView()
}
}, 3000)
window.refreshLibraryView = renderLibraryView
renderLibraryView()
})()