Refine install list and responsive scaling
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:22:09 +09:00
parent bc6bef0c46
commit 010dbc30a4
6 changed files with 243 additions and 162 deletions

View File

@@ -4,15 +4,8 @@ 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
let expandedProfileId = null
function describeProfileKind(kind){
switch(kind){
@@ -74,98 +67,123 @@ function buildDetailText(profile){
}
}
function renderDetailPanel(profile){
const installedIds = new Set(
ConfigManager.getInstalledLibraryProfiles().map((installedProfile) => installedProfile.id)
)
const installed = installedIds.has(profile.id)
function toggleExpandedProfile(profileId){
expandedProfileId = expandedProfileId === profileId ? null : profileId
}
installDetailTitle.textContent = profile.name
installDetailSummary.textContent = profile.description || '설명이 없습니다.'
installDetailMeta.innerHTML = ''
installDetailMeta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
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(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'
badgeRow.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
if(installed){
installDetailMeta.appendChild(createInstallBadge('라이브러리 보유'))
badgeRow.appendChild(createInstallBadge('설치됨'))
}
if(!profile.launchReady){
installDetailMeta.appendChild(createInstallBadge('실행 준비 필요'))
badgeRow.appendChild(createInstallBadge('실행 준비 필요'))
}
if(profile.kind === 'server-pack' && profile.hostReady){
installDetailMeta.appendChild(createInstallBadge('로컬 호스팅 가능'))
badgeRow.appendChild(createInstallBadge('호스팅 가능'))
}
installDetailInfo.innerHTML = ''
installDetailInfo.appendChild(createInfoLine('프로필 ID', profile.id))
installDetailInfo.appendChild(createInfoLine('종류', describeProfileKind(profile.kind)))
installDetailInfo.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
const infoBlock = document.createElement('div')
infoBlock.className = 'launcherInfoBlock'
infoBlock.appendChild(createInfoLine('프로필 ID', profile.id))
infoBlock.appendChild(createInfoLine('종류', describeProfileKind(profile.kind)))
infoBlock.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
if(profile.defaultServerAddress){
installDetailInfo.appendChild(createInfoLine('기본 주소', profile.defaultServerAddress))
infoBlock.appendChild(createInfoLine('기본 주소', profile.defaultServerAddress))
}
if(profile.kind === 'map' && profile.worldDirectoryName){
installDetailInfo.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName))
infoBlock.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName))
}
if(profile.kind === 'server-pack'){
installDetailInfo.appendChild(createInfoLine('로컬 호스팅', profile.hostReady ? '가능' : '관리자 설정 필요'))
infoBlock.appendChild(createInfoLine('로컬 호스팅', profile.hostReady ? '가능' : '관리자 설정 필요'))
}
if(profile.launchIssues.length > 0){
installDetailInfo.appendChild(createInfoLine('확인 필요', profile.launchIssues.join(' / ')))
infoBlock.appendChild(createInfoLine('확인 필요', profile.launchIssues.join(' / ')))
} else if(profile.hostIssues.length > 0){
installDetailInfo.appendChild(createInfoLine('호스팅 참고', profile.hostIssues.join(' / ')))
infoBlock.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()
}
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 || !profile.launchReady
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)
}
}
}
})
function renderEmptyDetailPanel(){
installDetailTitle.textContent = '프로필을 선택하세요'
installDetailSummary.textContent = '아래 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.'
installDetailMeta.innerHTML = ''
installDetailInfo.innerHTML = ''
installDetailBody.textContent = '관리자가 등록한 프로필 상세 설명이 여기에 표시됩니다.'
installDetailAddButton.disabled = true
installDetailAddButton.textContent = '라이브러리에 추가'
installDetailAddButton.onclick = null
}
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)
})
function selectProfile(profileId){
selectedProfileId = profileId
if(latestCatalog == null){
renderEmptyDetailPanel()
return
}
actions.appendChild(installButton)
actions.appendChild(openLibraryButton)
const profile = latestCatalog.profiles.find((entry) => entry.id === profileId)
if(profile == null){
renderEmptyDetailPanel()
return
}
detailSection.appendChild(badgeRow)
detailSection.appendChild(infoBlock)
detailSection.appendChild(bodyGroup)
detailSection.appendChild(actions)
renderDetailPanel(profile)
return detailSection
}
async function renderInstallView(){
@@ -173,11 +191,14 @@ async function renderInstallView(){
try {
const catalog = await CatalogManager.loadCatalog()
latestCatalog = catalog
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'
@@ -186,12 +207,18 @@ async function renderInstallView(){
}
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(profile.id === selectedProfileId){
if(expanded){
row.setAttribute('selected', 'true')
}
const top = document.createElement('div')
top.className = 'launcherListItemTop'
const main = document.createElement('div')
main.className = 'launcherListItemMain'
@@ -208,15 +235,13 @@ async function renderInstallView(){
const meta = document.createElement('div')
meta.className = 'launcherListMeta'
meta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
if(installedIds.has(profile.id)){
if(installed){
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 || '설명이 없습니다.'
@@ -226,36 +251,23 @@ async function renderInstallView(){
const detailButton = document.createElement('button')
detailButton.className = 'launcherSecondaryButton'
detailButton.textContent = '자세히 보기'
detailButton.textContent = expanded ? '간단히 보기' : '자세히 보기'
detailButton.addEventListener('click', async (event) => {
event.stopPropagation()
selectProfile(profile.id)
toggleExpandedProfile(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.textContent = installed ? '설치됨' : '라이브러리에 추가'
installButton.disabled = installed || !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 installProfile(profile)
expandedProfileId = profile.id
await renderInstallView()
if(typeof refreshLibraryView === 'function'){
await refreshLibraryView()
}
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
} catch (error) {
console.error(error)
@@ -264,37 +276,39 @@ async function renderInstallView(){
}
})
actions.appendChild(detailButton)
actions.appendChild(installButton)
textGroup.appendChild(title)
titleRow.appendChild(textGroup)
titleRow.appendChild(meta)
main.appendChild(titleRow)
main.appendChild(description)
row.appendChild(main)
row.appendChild(actions)
actions.appendChild(detailButton)
actions.appendChild(installButton)
top.appendChild(main)
top.appendChild(actions)
row.appendChild(top)
if(expanded){
row.appendChild(createExpandedDetail(profile, installed))
}
row.addEventListener('click', async () => {
selectProfile(profile.id)
toggleExpandedProfile(profile.id)
await renderInstallView()
})
installCatalogList.appendChild(row)
}
if(catalog.profiles.length === 0){
renderEmptyDetailPanel()
return
const emptyCard = document.createElement('article')
emptyCard.className = 'launcherCard'
emptyCard.innerHTML = '<h3 class="launcherCardTitle">등록된 프로필이 없습니다</h3><p class="launcherCardDescription">관리자가 카탈로그에 프로필을 추가하면 여기에 표시됩니다.</p>'
installCatalogList.appendChild(emptyCard)
}
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'
@@ -307,14 +321,6 @@ 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()
})()