Refine install list and responsive scaling
This commit is contained in:
@@ -3293,6 +3293,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
text-shadow: 0px 0px 20px #949494;
|
text-shadow: 0px 0px 20px #949494;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wrapper container for the mojang status bar. */
|
/* Wrapper container for the mojang status bar. */
|
||||||
@@ -3463,6 +3464,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
display: flex;
|
display: flex;
|
||||||
transition: 0.25s ease;
|
transition: 0.25s ease;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * *
|
/* * *
|
||||||
@@ -3488,6 +3490,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
transition: 0.25s ease;
|
transition: 0.25s ease;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
#launch_button:hover,
|
#launch_button:hover,
|
||||||
#launch_button:focus {
|
#launch_button:focus {
|
||||||
@@ -3543,6 +3546,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
transition: 0.25s ease;
|
transition: 0.25s ease;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
#server_selection_button:hover,
|
#server_selection_button:hover,
|
||||||
#server_selection_button:focus {
|
#server_selection_button:focus {
|
||||||
@@ -4150,8 +4154,8 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.launcherCardGrid {
|
.launcherCardGrid {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4163,9 +4167,9 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
|
|
||||||
.launcherListItem {
|
.launcherListItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
align-items: stretch;
|
||||||
gap: 20px;
|
gap: 18px;
|
||||||
padding: 22px 24px;
|
padding: 22px 24px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -4188,6 +4192,13 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.launcherListItemTop {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.launcherListTitleRow {
|
.launcherListTitleRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -4206,6 +4217,9 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcherListMeta {
|
.launcherListMeta {
|
||||||
@@ -4228,6 +4242,22 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcherExpandableDetail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
padding-top: 18px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcherExpandableMeta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcherCard {
|
.launcherCard {
|
||||||
@@ -4241,6 +4271,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
background: rgba(15, 15, 15, 0.56);
|
background: rgba(15, 15, 15, 0.56);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
|
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcherCard[selected="true"] {
|
.launcherCard[selected="true"] {
|
||||||
@@ -4264,6 +4295,9 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcherCardMeta {
|
.launcherCardMeta {
|
||||||
@@ -4283,6 +4317,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcherCardDescription,
|
.launcherCardDescription,
|
||||||
@@ -4428,6 +4463,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s ease, opacity 0.2s ease, border-color 0.2s ease, background 0.2s ease;
|
transition: transform 0.2s ease, opacity 0.2s ease, border-color 0.2s ease, background 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcherPrimaryButton {
|
.launcherPrimaryButton {
|
||||||
|
|||||||
@@ -4,15 +4,8 @@ const ConfigManager = require('./assets/js/configmanager')
|
|||||||
const ProfileAssetManager = require('./assets/js/profileassetmanager')
|
const ProfileAssetManager = require('./assets/js/profileassetmanager')
|
||||||
|
|
||||||
const installCatalogList = document.getElementById('installCatalogList')
|
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 expandedProfileId = null
|
||||||
let latestCatalog = null
|
|
||||||
|
|
||||||
function describeProfileKind(kind){
|
function describeProfileKind(kind){
|
||||||
switch(kind){
|
switch(kind){
|
||||||
@@ -74,98 +67,123 @@ function buildDetailText(profile){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDetailPanel(profile){
|
function toggleExpandedProfile(profileId){
|
||||||
const installedIds = new Set(
|
expandedProfileId = expandedProfileId === profileId ? null : profileId
|
||||||
ConfigManager.getInstalledLibraryProfiles().map((installedProfile) => installedProfile.id)
|
}
|
||||||
)
|
|
||||||
const installed = installedIds.has(profile.id)
|
|
||||||
|
|
||||||
installDetailTitle.textContent = profile.name
|
async function installProfile(profile){
|
||||||
installDetailSummary.textContent = profile.description || '설명이 없습니다.'
|
|
||||||
installDetailMeta.innerHTML = ''
|
|
||||||
installDetailMeta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
|
|
||||||
if(installed){
|
|
||||||
installDetailMeta.appendChild(createInstallBadge('라이브러리 보유'))
|
|
||||||
}
|
|
||||||
if(!profile.launchReady){
|
|
||||||
installDetailMeta.appendChild(createInstallBadge('실행 준비 필요'))
|
|
||||||
}
|
|
||||||
if(profile.kind === 'server-pack' && profile.hostReady){
|
|
||||||
installDetailMeta.appendChild(createInstallBadge('로컬 호스팅 가능'))
|
|
||||||
}
|
|
||||||
|
|
||||||
installDetailInfo.innerHTML = ''
|
|
||||||
installDetailInfo.appendChild(createInfoLine('프로필 ID', profile.id))
|
|
||||||
installDetailInfo.appendChild(createInfoLine('종류', describeProfileKind(profile.kind)))
|
|
||||||
installDetailInfo.appendChild(createInfoLine('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
|
|
||||||
|
|
||||||
if(profile.defaultServerAddress){
|
|
||||||
installDetailInfo.appendChild(createInfoLine('기본 주소', profile.defaultServerAddress))
|
|
||||||
}
|
|
||||||
if(profile.kind === 'map' && profile.worldDirectoryName){
|
|
||||||
installDetailInfo.appendChild(createInfoLine('월드 폴더', profile.worldDirectoryName))
|
|
||||||
}
|
|
||||||
if(profile.kind === 'server-pack'){
|
|
||||||
installDetailInfo.appendChild(createInfoLine('로컬 호스팅', profile.hostReady ? '가능' : '관리자 설정 필요'))
|
|
||||||
}
|
|
||||||
if(profile.launchIssues.length > 0){
|
|
||||||
installDetailInfo.appendChild(createInfoLine('확인 필요', profile.launchIssues.join(' / ')))
|
|
||||||
} else if(profile.hostIssues.length > 0){
|
|
||||||
installDetailInfo.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)
|
const installedProfile = await CatalogManager.installProfile(profile.id)
|
||||||
await ProfileAssetManager.prefetchProfileAssets(installedProfile)
|
await ProfileAssetManager.prefetchProfileAssets(installedProfile)
|
||||||
if(installedProfile.kind === 'server-pack' && installedProfile.hostReady){
|
if(installedProfile.kind === 'server-pack' && installedProfile.hostReady){
|
||||||
await ProfileAssetManager.ensureServerBundleInstalled(installedProfile)
|
await ProfileAssetManager.ensureServerBundleInstalled(installedProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof refreshSelectedProfileButton === 'function'){
|
if(typeof refreshSelectedProfileButton === 'function'){
|
||||||
refreshSelectedProfileButton()
|
refreshSelectedProfileButton()
|
||||||
}
|
}
|
||||||
renderDetailPanel(profile)
|
if(typeof refreshServerStatus === 'function'){
|
||||||
await renderInstallView()
|
refreshServerStatus(true)
|
||||||
|
}
|
||||||
if(typeof refreshLibraryView === 'function'){
|
if(typeof refreshLibraryView === 'function'){
|
||||||
await refreshLibraryView()
|
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){
|
||||||
|
badgeRow.appendChild(createInstallBadge('설치됨'))
|
||||||
|
}
|
||||||
|
if(!profile.launchReady){
|
||||||
|
badgeRow.appendChild(createInstallBadge('실행 준비 필요'))
|
||||||
|
}
|
||||||
|
if(profile.kind === 'server-pack' && profile.hostReady){
|
||||||
|
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('실행 준비', profile.launchReady ? '완료' : '추가 설정 필요'))
|
||||||
|
|
||||||
|
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.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 || !profile.launchReady
|
||||||
|
installButton.addEventListener('click', async (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
try {
|
||||||
|
await installProfile(profile)
|
||||||
|
await renderInstallView()
|
||||||
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
|
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
const message = error instanceof Error ? error.message : '프로필 설치 중 오류가 발생했습니다.'
|
const message = error instanceof Error ? error.message : '프로필 설치 중 오류가 발생했습니다.'
|
||||||
showInstallMessage('설치 실패', 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)
|
||||||
|
})
|
||||||
|
|
||||||
function renderEmptyDetailPanel(){
|
actions.appendChild(installButton)
|
||||||
installDetailTitle.textContent = '프로필을 선택하세요'
|
actions.appendChild(openLibraryButton)
|
||||||
installDetailSummary.textContent = '아래 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.'
|
|
||||||
installDetailMeta.innerHTML = ''
|
|
||||||
installDetailInfo.innerHTML = ''
|
|
||||||
installDetailBody.textContent = '관리자가 등록한 프로필 상세 설명이 여기에 표시됩니다.'
|
|
||||||
installDetailAddButton.disabled = true
|
|
||||||
installDetailAddButton.textContent = '라이브러리에 추가'
|
|
||||||
installDetailAddButton.onclick = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectProfile(profileId){
|
detailSection.appendChild(badgeRow)
|
||||||
selectedProfileId = profileId
|
detailSection.appendChild(infoBlock)
|
||||||
if(latestCatalog == null){
|
detailSection.appendChild(bodyGroup)
|
||||||
renderEmptyDetailPanel()
|
detailSection.appendChild(actions)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const profile = latestCatalog.profiles.find((entry) => entry.id === profileId)
|
return detailSection
|
||||||
if(profile == null){
|
|
||||||
renderEmptyDetailPanel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDetailPanel(profile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderInstallView(){
|
async function renderInstallView(){
|
||||||
@@ -173,11 +191,14 @@ async function renderInstallView(){
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const catalog = await CatalogManager.loadCatalog()
|
const catalog = await CatalogManager.loadCatalog()
|
||||||
latestCatalog = catalog
|
|
||||||
const installedIds = new Set(
|
const installedIds = new Set(
|
||||||
ConfigManager.getInstalledLibraryProfiles().map((profile) => profile.id)
|
ConfigManager.getInstalledLibraryProfiles().map((profile) => profile.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(expandedProfileId != null && !catalog.profiles.some((profile) => profile.id === expandedProfileId)){
|
||||||
|
expandedProfileId = null
|
||||||
|
}
|
||||||
|
|
||||||
if(catalog.sourceError != null){
|
if(catalog.sourceError != null){
|
||||||
const warningCard = document.createElement('article')
|
const warningCard = document.createElement('article')
|
||||||
warningCard.className = 'launcherCard'
|
warningCard.className = 'launcherCard'
|
||||||
@@ -186,12 +207,18 @@ async function renderInstallView(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(const profile of catalog.profiles){
|
for(const profile of catalog.profiles){
|
||||||
|
const installed = installedIds.has(profile.id)
|
||||||
|
const expanded = expandedProfileId === profile.id
|
||||||
|
|
||||||
const row = document.createElement('article')
|
const row = document.createElement('article')
|
||||||
row.className = 'launcherListItem'
|
row.className = 'launcherListItem'
|
||||||
if(profile.id === selectedProfileId){
|
if(expanded){
|
||||||
row.setAttribute('selected', 'true')
|
row.setAttribute('selected', 'true')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const top = document.createElement('div')
|
||||||
|
top.className = 'launcherListItemTop'
|
||||||
|
|
||||||
const main = document.createElement('div')
|
const main = document.createElement('div')
|
||||||
main.className = 'launcherListItemMain'
|
main.className = 'launcherListItemMain'
|
||||||
|
|
||||||
@@ -208,15 +235,13 @@ async function renderInstallView(){
|
|||||||
const meta = document.createElement('div')
|
const meta = document.createElement('div')
|
||||||
meta.className = 'launcherListMeta'
|
meta.className = 'launcherListMeta'
|
||||||
meta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
|
meta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
|
||||||
if(installedIds.has(profile.id)){
|
if(installed){
|
||||||
meta.appendChild(createInstallBadge('설치됨'))
|
meta.appendChild(createInstallBadge('설치됨'))
|
||||||
}
|
}
|
||||||
if(profile.kind === 'server-pack' && profile.hostReady){
|
if(profile.kind === 'server-pack' && profile.hostReady){
|
||||||
meta.appendChild(createInstallBadge('호스팅 가능'))
|
meta.appendChild(createInstallBadge('호스팅 가능'))
|
||||||
}
|
}
|
||||||
|
|
||||||
textGroup.appendChild(title)
|
|
||||||
|
|
||||||
const description = document.createElement('p')
|
const description = document.createElement('p')
|
||||||
description.className = 'launcherListDescription'
|
description.className = 'launcherListDescription'
|
||||||
description.textContent = profile.description || '설명이 없습니다.'
|
description.textContent = profile.description || '설명이 없습니다.'
|
||||||
@@ -226,36 +251,23 @@ async function renderInstallView(){
|
|||||||
|
|
||||||
const detailButton = document.createElement('button')
|
const detailButton = document.createElement('button')
|
||||||
detailButton.className = 'launcherSecondaryButton'
|
detailButton.className = 'launcherSecondaryButton'
|
||||||
detailButton.textContent = '자세히 보기'
|
detailButton.textContent = expanded ? '간단히 보기' : '자세히 보기'
|
||||||
detailButton.addEventListener('click', async (event) => {
|
detailButton.addEventListener('click', async (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
selectProfile(profile.id)
|
toggleExpandedProfile(profile.id)
|
||||||
await renderInstallView()
|
await renderInstallView()
|
||||||
})
|
})
|
||||||
|
|
||||||
const installButton = document.createElement('button')
|
const installButton = document.createElement('button')
|
||||||
installButton.className = 'launcherPrimaryButton'
|
installButton.className = 'launcherPrimaryButton'
|
||||||
installButton.textContent = installedIds.has(profile.id) ? '설치됨' : '라이브러리에 추가'
|
installButton.textContent = installed ? '설치됨' : '라이브러리에 추가'
|
||||||
installButton.disabled = installedIds.has(profile.id) || !profile.launchReady
|
installButton.disabled = installed || !profile.launchReady
|
||||||
installButton.addEventListener('click', async (event) => {
|
installButton.addEventListener('click', async (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
try {
|
try {
|
||||||
const installedProfile = await CatalogManager.installProfile(profile.id)
|
await installProfile(profile)
|
||||||
await ProfileAssetManager.prefetchProfileAssets(installedProfile)
|
expandedProfileId = profile.id
|
||||||
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 renderInstallView()
|
await renderInstallView()
|
||||||
if(typeof refreshLibraryView === 'function'){
|
|
||||||
await refreshLibraryView()
|
|
||||||
}
|
|
||||||
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
|
showInstallMessage('추가 완료', `${profile.name} 프로필을 라이브러리에 추가했습니다.`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
@@ -264,37 +276,39 @@ async function renderInstallView(){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
actions.appendChild(detailButton)
|
textGroup.appendChild(title)
|
||||||
actions.appendChild(installButton)
|
|
||||||
|
|
||||||
titleRow.appendChild(textGroup)
|
titleRow.appendChild(textGroup)
|
||||||
titleRow.appendChild(meta)
|
titleRow.appendChild(meta)
|
||||||
main.appendChild(titleRow)
|
main.appendChild(titleRow)
|
||||||
main.appendChild(description)
|
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 () => {
|
row.addEventListener('click', async () => {
|
||||||
selectProfile(profile.id)
|
toggleExpandedProfile(profile.id)
|
||||||
await renderInstallView()
|
await renderInstallView()
|
||||||
})
|
})
|
||||||
|
|
||||||
installCatalogList.appendChild(row)
|
installCatalogList.appendChild(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(catalog.profiles.length === 0){
|
if(catalog.profiles.length === 0){
|
||||||
renderEmptyDetailPanel()
|
const emptyCard = document.createElement('article')
|
||||||
return
|
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) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
latestCatalog = null
|
|
||||||
renderEmptyDetailPanel()
|
|
||||||
|
|
||||||
const errorCard = document.createElement('article')
|
const errorCard = document.createElement('article')
|
||||||
errorCard.className = 'launcherCard'
|
errorCard.className = 'launcherCard'
|
||||||
@@ -307,14 +321,6 @@ document.getElementById('installBackButton').addEventListener('click', () => {
|
|||||||
switchView(getCurrentView(), VIEWS.landing)
|
switchView(getCurrentView(), VIEWS.landing)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById('installDetailOpenLibraryButton').addEventListener('click', async () => {
|
|
||||||
if(typeof refreshLibraryView === 'function'){
|
|
||||||
await refreshLibraryView()
|
|
||||||
}
|
|
||||||
switchView(getCurrentView(), VIEWS.library)
|
|
||||||
})
|
|
||||||
|
|
||||||
window.refreshInstallView = renderInstallView
|
window.refreshInstallView = renderInstallView
|
||||||
renderEmptyDetailPanel()
|
|
||||||
renderInstallView()
|
renderInstallView()
|
||||||
})()
|
})()
|
||||||
|
|||||||
@@ -35,6 +35,69 @@ remote.getCurrentWebContents().on('devtools-opened', () => {
|
|||||||
webFrame.setZoomLevel(0)
|
webFrame.setZoomLevel(0)
|
||||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||||
|
|
||||||
|
const BASE_WINDOW_WIDTH = 1400
|
||||||
|
const BASE_WINDOW_HEIGHT = 860
|
||||||
|
|
||||||
|
let responsiveLayoutFrame = null
|
||||||
|
|
||||||
|
function clamp(value, min, max){
|
||||||
|
return Math.min(Math.max(value, min), max)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLaunchDetailWidths(){
|
||||||
|
const launchContent = document.getElementById('launch_content')
|
||||||
|
const launchDetails = document.getElementById('launch_details')
|
||||||
|
const launchButton = document.getElementById('launch_button')
|
||||||
|
const launchProgress = document.getElementById('launch_progress')
|
||||||
|
const launchDetailsRight = document.getElementById('launch_details_right')
|
||||||
|
const launchProgressLabel = document.getElementById('launch_progress_label')
|
||||||
|
|
||||||
|
if(!launchContent || !launchDetails || !launchButton || !launchProgress || !launchDetailsRight || !launchProgressLabel){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const launchContentWidth = launchContent.getBoundingClientRect().width
|
||||||
|
const launchButtonWidth = launchButton.getBoundingClientRect().width
|
||||||
|
const labelWidth = Math.max(64, Math.ceil(launchButtonWidth * 0.48))
|
||||||
|
const progressWidth = Math.max(220, Math.floor(launchContentWidth - labelWidth - 48))
|
||||||
|
|
||||||
|
launchDetails.style.maxWidth = `${Math.ceil(launchContentWidth)}px`
|
||||||
|
launchProgress.style.width = `${progressWidth}px`
|
||||||
|
launchDetailsRight.style.maxWidth = `${progressWidth}px`
|
||||||
|
launchProgressLabel.style.width = `${labelWidth}px`
|
||||||
|
launchProgressLabel.style.minWidth = `${labelWidth}px`
|
||||||
|
launchProgressLabel.style.maxWidth = `${labelWidth}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyResponsiveLayout(){
|
||||||
|
const scale = clamp(
|
||||||
|
Math.min(window.innerWidth / BASE_WINDOW_WIDTH, window.innerHeight / BASE_WINDOW_HEIGHT),
|
||||||
|
0.72,
|
||||||
|
1.45
|
||||||
|
)
|
||||||
|
|
||||||
|
webFrame.setZoomFactor(scale)
|
||||||
|
document.documentElement.style.setProperty('--launcher-scale', scale.toFixed(3))
|
||||||
|
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
syncLaunchDetailWidths()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueResponsiveLayout(){
|
||||||
|
if(responsiveLayoutFrame != null){
|
||||||
|
cancelAnimationFrame(responsiveLayoutFrame)
|
||||||
|
}
|
||||||
|
responsiveLayoutFrame = requestAnimationFrame(() => {
|
||||||
|
responsiveLayoutFrame = null
|
||||||
|
applyResponsiveLayout()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
queueResponsiveLayout()
|
||||||
|
})
|
||||||
|
|
||||||
// Initialize auto updates in production environments.
|
// Initialize auto updates in production environments.
|
||||||
let updateCheckListener
|
let updateCheckListener
|
||||||
if(!isDev){
|
if(!isDev){
|
||||||
@@ -174,20 +237,10 @@ document.addEventListener('readystatechange', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
} else if(document.readyState === 'complete'){
|
} else if(document.readyState === 'complete'){
|
||||||
|
queueResponsiveLayout()
|
||||||
//266.01
|
setTimeout(() => {
|
||||||
//170.8
|
queueResponsiveLayout()
|
||||||
//53.21
|
}, 150)
|
||||||
// Bind progress bar length to length of bot wrapper
|
|
||||||
//const targetWidth = document.getElementById("launch_content").getBoundingClientRect().width
|
|
||||||
//const targetWidth2 = document.getElementById("server_selection").getBoundingClientRect().width
|
|
||||||
//const targetWidth3 = document.getElementById("launch_button").getBoundingClientRect().width
|
|
||||||
|
|
||||||
document.getElementById('launch_details').style.maxWidth = 266.01
|
|
||||||
document.getElementById('launch_progress').style.width = 170.8
|
|
||||||
document.getElementById('launch_details_right').style.maxWidth = 170.8
|
|
||||||
document.getElementById('launch_progress_label').style.width = 53.21
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, false)
|
}, false)
|
||||||
|
|||||||
@@ -13,24 +13,6 @@
|
|||||||
<div id="installCatalogNotice" class="launcherNotice">
|
<div id="installCatalogNotice" class="launcherNotice">
|
||||||
<span><%- lang('install.notice') %></span>
|
<span><%- lang('install.notice') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="installProfileDetails" class="launcherNotice">
|
|
||||||
<div class="launcherEditorHeader">
|
|
||||||
<div>
|
|
||||||
<span id="installDetailTitle" class="launcherEditorTitle">프로필을 선택하세요</span>
|
|
||||||
<span id="installDetailSummary" class="launcherEditorDescription">아래 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.</span>
|
|
||||||
</div>
|
|
||||||
<div id="installDetailMeta" class="launcherPageActions"></div>
|
|
||||||
</div>
|
|
||||||
<div id="installDetailInfo" class="launcherInfoBlock"></div>
|
|
||||||
<div class="launcherFieldGroup">
|
|
||||||
<label class="launcherFieldLabel">자세한 내용</label>
|
|
||||||
<div id="installDetailBody" class="launcherDetailBody">관리자가 등록한 프로필 상세 설명이 여기에 표시됩니다.</div>
|
|
||||||
</div>
|
|
||||||
<div id="installDetailActions" class="launcherCardActions">
|
|
||||||
<button id="installDetailAddButton" class="launcherPrimaryButton" disabled>라이브러리에 추가</button>
|
|
||||||
<button id="installDetailOpenLibraryButton" class="launcherSecondaryButton">라이브러리 열기</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="installCatalogList" class="launcherListContainer"></div>
|
<div id="installCatalogList" class="launcherListContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="./assets/js/scripts/install.js"></script>
|
<script src="./assets/js/scripts/install.js"></script>
|
||||||
|
|||||||
@@ -227,8 +227,10 @@ let win
|
|||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
width: 980,
|
width: 1400,
|
||||||
height: 552,
|
height: 860,
|
||||||
|
minWidth: 1120,
|
||||||
|
minHeight: 700,
|
||||||
icon: getPlatformIcon('Icon'),
|
icon: getPlatformIcon('Icon'),
|
||||||
frame: false,
|
frame: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
|||||||
@@ -228,8 +228,10 @@ let win
|
|||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
width: 980,
|
width: 1400,
|
||||||
height: 552,
|
height: 860,
|
||||||
|
minWidth: 1120,
|
||||||
|
minHeight: 700,
|
||||||
icon: getPlatformIcon('Icon'),
|
icon: getPlatformIcon('Icon'),
|
||||||
frame: false,
|
frame: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
|||||||
Reference in New Issue
Block a user