Refine launcher navigation and install list
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 14:41:04 +09:00
parent f6f716acd0
commit 0f6c0563db
10 changed files with 226 additions and 32 deletions

View File

@@ -1073,15 +1073,22 @@ body, button {
/* Left hand side of the settings UI, for navigation. */ /* Left hand side of the settings UI, for navigation. */
#settingsContainerLeft { #settingsContainerLeft {
display: flex;
flex-direction: column;
padding-top: 4%; padding-top: 4%;
height: 100%; height: 100%;
width: 25%; width: 25%;
box-sizing: border-box; box-sizing: border-box;
} }
.settingsTopActions {
padding: 0 0 18px 24px;
}
/* Settings navigation container. */ /* Settings navigation container. */
#settingsNavContainer { #settingsNavContainer {
height: 100%; height: 100%;
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -2469,13 +2476,14 @@ input:checked + .toggleSwitchSlider:before {
} }
#landingContainer > #upper > #content { #landingContainer > #upper > #content {
display: inline-flex; display: inline-flex;
width: 70%; width: 63%;
height: 100%; height: 100%;
} }
#landingContainer > #upper > #right { #landingContainer > #upper > #right {
display: inline-flex; display: inline-flex;
width: 15%; width: 22%;
height: 100%; height: 100%;
justify-content: flex-end;
} }
/* Lower content container. */ /* Lower content container. */
@@ -2956,6 +2964,9 @@ input:checked + .toggleSwitchSlider:before {
position: relative; position: relative;
top: 50px; top: 50px;
align-items: flex-end; align-items: flex-end;
width: 100%;
padding-right: 26px;
box-sizing: border-box;
height: calc(100% - 50px); height: calc(100% - 50px);
} }
@@ -2970,6 +2981,35 @@ input:checked + .toggleSwitchSlider:before {
position: relative; position: relative;
} }
#accountTriggerGroup {
display: flex;
align-items: flex-end;
gap: 14px;
}
#accountPreviewText {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
min-width: 150px;
}
#accountPreviewLabel {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.58);
}
#accountPreviewName {
font-size: 13px;
font-weight: 800;
color: #ffffff;
text-align: right;
text-shadow: 0px 0px 18px rgba(0, 0, 0, 0.6);
}
#avatarMenuButton { #avatarMenuButton {
padding: 0; padding: 0;
border: none; border: none;
@@ -2997,8 +3037,8 @@ input:checked + .toggleSwitchSlider:before {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat, no-repeat;
background-size: cover; background-size: cover, 60%;
} }
#avatarMenuButton:hover #avatarContainer, #avatarMenuButton:hover #avatarContainer,
@@ -4025,6 +4065,11 @@ input:checked + .toggleSwitchSlider:before {
overflow-y: auto; overflow-y: auto;
} }
.launcherPageTopBar {
display: flex;
justify-content: flex-start;
}
.launcherPageHeader { .launcherPageHeader {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@@ -4055,6 +4100,36 @@ input:checked + .toggleSwitchSlider:before {
color: rgba(255, 255, 255, 0.74); color: rgba(255, 255, 255, 0.74);
} }
.launcherBackButton {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0;
border: none;
background: none;
color: rgba(255, 255, 255, 0.88);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
cursor: pointer;
}
.launcherBackButton::before {
content: '←';
font-size: 16px;
}
.launcherBackButton:hover,
.launcherBackButton:focus {
color: #ffffff;
text-shadow: 0px 0px 16px rgba(255, 255, 255, 0.45);
outline: none;
}
.launcherBackButton:active {
color: rgba(255, 255, 255, 0.72);
}
.launcherPageActions, .launcherPageActions,
.launcherCardActions, .launcherCardActions,
#landingNavButtons { #landingNavButtons {
@@ -4076,6 +4151,80 @@ input:checked + .toggleSwitchSlider:before {
gap: 16px; gap: 16px;
} }
.launcherListContainer {
display: flex;
flex-direction: column;
gap: 12px;
}
.launcherListItem {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 18px 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 18px;
background: rgba(15, 15, 15, 0.56);
backdrop-filter: blur(10px);
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
cursor: pointer;
}
.launcherListItem[selected="true"] {
border-color: rgba(253, 197, 78, 0.7);
box-shadow: 0 22px 60px rgba(253, 197, 78, 0.14);
}
.launcherListItemMain {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.launcherListTitleRow {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.launcherListTextGroup {
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.launcherListTitle {
margin: 0;
font-size: 20px;
color: #ffffff;
}
.launcherListMeta {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.launcherListDescription {
margin: 0;
line-height: 1.5;
color: rgba(255, 255, 255, 0.76);
}
.launcherListActions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
flex-wrap: wrap;
}
.launcherCard { .launcherCard {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -140,7 +140,7 @@ function renderDetailPanel(profile){
function renderEmptyDetailPanel(){ function renderEmptyDetailPanel(){
installDetailTitle.textContent = '프로필을 선택하세요' installDetailTitle.textContent = '프로필을 선택하세요'
installDetailSummary.textContent = '왼쪽 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.' installDetailSummary.textContent = '아래 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.'
installDetailMeta.innerHTML = '' installDetailMeta.innerHTML = ''
installDetailInfo.innerHTML = '' installDetailInfo.innerHTML = ''
installDetailBody.textContent = '관리자가 등록한 프로필 상세 설명이 여기에 표시됩니다.' installDetailBody.textContent = '관리자가 등록한 프로필 상세 설명이 여기에 표시됩니다.'
@@ -183,24 +183,27 @@ async function renderInstallView(){
} }
for(const profile of catalog.profiles){ for(const profile of catalog.profiles){
const card = document.createElement('article') const row = document.createElement('article')
card.className = 'launcherCard' row.className = 'launcherListItem'
if(profile.id === selectedProfileId){ if(profile.id === selectedProfileId){
card.setAttribute('selected', 'true') row.setAttribute('selected', 'true')
} }
const header = document.createElement('div') const main = document.createElement('div')
header.className = 'launcherCardHeader' main.className = 'launcherListItemMain'
const titleGroup = document.createElement('div') const titleRow = document.createElement('div')
titleGroup.className = 'launcherCardTitleGroup' titleRow.className = 'launcherListTitleRow'
const textGroup = document.createElement('div')
textGroup.className = 'launcherListTextGroup'
const title = document.createElement('h3') const title = document.createElement('h3')
title.className = 'launcherCardTitle' title.className = 'launcherListTitle'
title.textContent = profile.name title.textContent = profile.name
const meta = document.createElement('div') const meta = document.createElement('div')
meta.className = 'launcherCardMeta' meta.className = 'launcherListMeta'
meta.appendChild(createInstallBadge(describeProfileKind(profile.kind))) meta.appendChild(createInstallBadge(describeProfileKind(profile.kind)))
if(installedIds.has(profile.id)){ if(installedIds.has(profile.id)){
meta.appendChild(createInstallBadge('설치됨')) meta.appendChild(createInstallBadge('설치됨'))
@@ -209,21 +212,20 @@ async function renderInstallView(){
meta.appendChild(createInstallBadge('호스팅 가능')) meta.appendChild(createInstallBadge('호스팅 가능'))
} }
titleGroup.appendChild(title) textGroup.appendChild(title)
titleGroup.appendChild(meta)
header.appendChild(titleGroup)
const description = document.createElement('p') const description = document.createElement('p')
description.className = 'launcherCardDescription' description.className = 'launcherListDescription'
description.textContent = profile.description || '설명이 없습니다.' description.textContent = profile.description || '설명이 없습니다.'
const actions = document.createElement('div') const actions = document.createElement('div')
actions.className = 'launcherCardActions' actions.className = 'launcherListActions'
const detailButton = document.createElement('button') const detailButton = document.createElement('button')
detailButton.className = 'launcherSecondaryButton' detailButton.className = 'launcherSecondaryButton'
detailButton.textContent = '자세히 보기' detailButton.textContent = '자세히 보기'
detailButton.addEventListener('click', () => { detailButton.addEventListener('click', (event) => {
event.stopPropagation()
selectProfile(profile.id) selectProfile(profile.id)
renderInstallView() renderInstallView()
}) })
@@ -232,7 +234,8 @@ async function renderInstallView(){
installButton.className = 'launcherPrimaryButton' installButton.className = 'launcherPrimaryButton'
installButton.textContent = installedIds.has(profile.id) ? '설치됨' : '라이브러리에 추가' installButton.textContent = installedIds.has(profile.id) ? '설치됨' : '라이브러리에 추가'
installButton.disabled = installedIds.has(profile.id) || !profile.launchReady installButton.disabled = installedIds.has(profile.id) || !profile.launchReady
installButton.addEventListener('click', async () => { installButton.addEventListener('click', async (event) => {
event.stopPropagation()
try { try {
const installedProfile = await CatalogManager.installProfile(profile.id) const installedProfile = await CatalogManager.installProfile(profile.id)
await ProfileAssetManager.prefetchProfileAssets(installedProfile) await ProfileAssetManager.prefetchProfileAssets(installedProfile)
@@ -255,10 +258,17 @@ async function renderInstallView(){
actions.appendChild(detailButton) actions.appendChild(detailButton)
actions.appendChild(installButton) actions.appendChild(installButton)
card.appendChild(header) titleRow.appendChild(textGroup)
card.appendChild(description) titleRow.appendChild(meta)
card.appendChild(actions) main.appendChild(titleRow)
installCatalogList.appendChild(card) main.appendChild(description)
row.appendChild(main)
row.appendChild(actions)
row.addEventListener('click', () => {
selectProfile(profile.id)
renderInstallView()
})
installCatalogList.appendChild(row)
} }
if(catalog.profiles.length === 0){ if(catalog.profiles.length === 0){
@@ -289,6 +299,10 @@ document.getElementById('installOpenSettingsButton').addEventListener('click', a
switchView(getCurrentView(), VIEWS.settings) switchView(getCurrentView(), VIEWS.settings)
}) })
document.getElementById('installBackButton').addEventListener('click', () => {
switchView(getCurrentView(), VIEWS.landing)
})
document.getElementById('installBackToLibraryButton').addEventListener('click', async () => { document.getElementById('installBackToLibraryButton').addEventListener('click', async () => {
if(typeof refreshLibraryView === 'function'){ if(typeof refreshLibraryView === 'function'){
await refreshLibraryView() await refreshLibraryView()

View File

@@ -40,6 +40,7 @@ const launch_progress = document.getElementById('launch_progress')
const launch_progress_label = document.getElementById('launch_progress_label') const launch_progress_label = document.getElementById('launch_progress_label')
const launch_details_text = document.getElementById('launch_details_text') const launch_details_text = document.getElementById('launch_details_text')
const server_selection_button = document.getElementById('server_selection_button') const server_selection_button = document.getElementById('server_selection_button')
const accountPreviewName = document.getElementById('accountPreviewName')
const avatarMenuButton = document.getElementById('avatarMenuButton') const avatarMenuButton = document.getElementById('avatarMenuButton')
const avatarContainer = document.getElementById('avatarContainer') const avatarContainer = document.getElementById('avatarContainer')
const accountMenu = document.getElementById('accountMenu') const accountMenu = document.getElementById('accountMenu')
@@ -245,11 +246,12 @@ function updateSelectedAccount(authUser){
username = authUser.displayName username = authUser.displayName
} }
if(authUser.uuid != null){ if(authUser.uuid != null){
avatarContainer.style.backgroundImage = `url('https://mc-heads.net/avatar/${authUser.uuid}/64')` avatarContainer.style.backgroundImage = `url('https://mc-heads.net/avatar/${authUser.uuid}/64'), url('assets/images/Icon.png')`
} }
} else { } else {
avatarContainer.style.backgroundImage = `url('assets/images/Icon.png')` avatarContainer.style.backgroundImage = `url('assets/images/Icon.png')`
} }
accountPreviewName.textContent = username
accountMenuName.textContent = username accountMenuName.textContent = username
avatarMenuButton.disabled = authUser == null avatarMenuButton.disabled = authUser == null
if(authUser == null){ if(authUser == null){

View File

@@ -394,6 +394,10 @@ document.getElementById('libraryOpenInstallButton').addEventListener('click', as
switchView(getCurrentView(), VIEWS.install) switchView(getCurrentView(), VIEWS.install)
}) })
document.getElementById('libraryBackButton').addEventListener('click', () => {
switchView(getCurrentView(), VIEWS.landing)
})
document.getElementById('libraryOpenSettingsButton').addEventListener('click', async () => { document.getElementById('libraryOpenSettingsButton').addEventListener('click', async () => {
await prepareSettings() await prepareSettings()
switchView(getCurrentView(), VIEWS.settings) switchView(getCurrentView(), VIEWS.settings)

View File

@@ -8,6 +8,7 @@ const {
const DropinModUtil = require('./assets/js/dropinmodutil') const DropinModUtil = require('./assets/js/dropinmodutil')
const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants')
const settingsBackButton = document.getElementById('settingsBackButton')
const settingsState = { const settingsState = {
invalid: new Set() invalid: new Set()
@@ -339,6 +340,11 @@ settingsNavDone.onclick = () => {
switchView(getCurrentView(), VIEWS.library) switchView(getCurrentView(), VIEWS.library)
} }
settingsBackButton.onclick = () => {
fullSettingsSave()
switchView(getCurrentView(), VIEWS.landing)
}
/** /**
* Account Management Tab * Account Management Tab
*/ */

View File

@@ -12,10 +12,12 @@ mediaXURL = "#"
mediaInstagramURL = "#" mediaInstagramURL = "#"
mediaYouTubeURL = "#" mediaYouTubeURL = "#"
mediaDiscordURL = "https://discord.gg/Z8j6ahF4MJ" mediaDiscordURL = "https://discord.gg/Z8j6ahF4MJ"
accountPreviewLabel = "계정"
libraryButton = "라이브러리" libraryButton = "라이브러리"
installButton = "설치" installButton = "설치"
[ejs.settings] [ejs.settings]
backButton = "메인으로"
sourceGithubLink = "https://github.com/peunsu/MRSLauncher" sourceGithubLink = "https://github.com/peunsu/MRSLauncher"
supportLink = "https://github.com/peunsu/MRSLauncher/issues" supportLink = "https://github.com/peunsu/MRSLauncher/issues"
@@ -23,6 +25,7 @@ supportLink = "https://github.com/peunsu/MRSLauncher/issues"
eyebrow = "Library" eyebrow = "Library"
title = "내 라이브러리" title = "내 라이브러리"
subtitle = "설치한 프로필을 선택하고 바로 실행하거나, 서버 주소를 입력해 자동 접속을 준비할 수 있습니다." subtitle = "설치한 프로필을 선택하고 바로 실행하거나, 서버 주소를 입력해 자동 접속을 준비할 수 있습니다."
backButton = "메인으로"
settingsButton = "설정" settingsButton = "설정"
installPageButton = "설치 페이지" installPageButton = "설치 페이지"
launchPageButton = "실행 화면" launchPageButton = "실행 화면"
@@ -33,6 +36,7 @@ emptyDescription = "설치 페이지에서 네가 배포한 모드팩이나 서
eyebrow = "Install" eyebrow = "Install"
title = "설치 페이지" title = "설치 페이지"
subtitle = "관리자가 미리 등록한 프로필을 둘러보고, 설명과 상세 내용을 확인한 뒤 내 라이브러리에 추가합니다." subtitle = "관리자가 미리 등록한 프로필을 둘러보고, 설명과 상세 내용을 확인한 뒤 내 라이브러리에 추가합니다."
backButton = "메인으로"
libraryPageButton = "라이브러리" libraryPageButton = "라이브러리"
notice = "설치 페이지는 읽기 전용 카탈로그입니다. 프로필 제목, 요약, 상세 설명은 관리자가 미리 등록하며, 클라이언트는 라이브러리에 추가만 할 수 있습니다." notice = "설치 페이지는 읽기 전용 카탈로그입니다. 프로필 제목, 요약, 상세 설명은 관리자가 미리 등록하며, 클라이언트는 라이브러리에 추가만 할 수 있습니다."

View File

@@ -1,5 +1,8 @@
<div id="installContainer" style="display: none;"> <div id="installContainer" style="display: none;">
<div class="launcherPageShell"> <div class="launcherPageShell">
<div class="launcherPageTopBar">
<button id="installBackButton" class="launcherBackButton"><%- lang('install.backButton') %></button>
</div>
<div class="launcherPageHeader"> <div class="launcherPageHeader">
<div> <div>
<span class="launcherPageEyebrow"><%- lang('install.eyebrow') %></span> <span class="launcherPageEyebrow"><%- lang('install.eyebrow') %></span>
@@ -18,7 +21,7 @@
<div class="launcherEditorHeader"> <div class="launcherEditorHeader">
<div> <div>
<span id="installDetailTitle" class="launcherEditorTitle">프로필을 선택하세요</span> <span id="installDetailTitle" class="launcherEditorTitle">프로필을 선택하세요</span>
<span id="installDetailSummary" class="launcherEditorDescription">왼쪽 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.</span> <span id="installDetailSummary" class="launcherEditorDescription">아래 목록에서 모드팩, 맵, 서버팩을 고르면 자세한 설명과 설치 조건을 볼 수 있습니다.</span>
</div> </div>
<div id="installDetailMeta" class="launcherPageActions"></div> <div id="installDetailMeta" class="launcherPageActions"></div>
</div> </div>
@@ -32,7 +35,7 @@
<button id="installDetailOpenLibraryButton" class="launcherSecondaryButton">라이브러리 열기</button> <button id="installDetailOpenLibraryButton" class="launcherSecondaryButton">라이브러리 열기</button>
</div> </div>
</div> </div>
<div id="installCatalogList" class="launcherCardGrid"></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>
</div> </div>

View File

@@ -15,9 +15,15 @@
<div id="right"> <div id="right">
<div id="rightContainer"> <div id="rightContainer">
<div id="user_content"> <div id="user_content">
<button id="avatarMenuButton" type="button" aria-haspopup="true" aria-expanded="false"> <div id="accountTriggerGroup">
<div id="avatarContainer"></div> <div id="accountPreviewText">
</button> <span id="accountPreviewLabel"><%- lang('landing.accountPreviewLabel') %></span>
<span id="accountPreviewName"><%- lang('landing.usernamePlaceholder') %></span>
</div>
<button id="avatarMenuButton" type="button" aria-haspopup="true" aria-expanded="false">
<div id="avatarContainer"></div>
</button>
</div>
<div id="accountMenu" hidden> <div id="accountMenu" hidden>
<span id="accountMenuName"><%- lang('landing.usernamePlaceholder') %></span> <span id="accountMenuName"><%- lang('landing.usernamePlaceholder') %></span>
<button id="accountMenuLogoutButton" class="launcherGhostButton"><%- lang('landing.accountMenuLogout') %></button> <button id="accountMenuLogoutButton" class="launcherGhostButton"><%- lang('landing.accountMenuLogout') %></button>

View File

@@ -1,5 +1,8 @@
<div id="libraryContainer" style="display: none;"> <div id="libraryContainer" style="display: none;">
<div class="launcherPageShell"> <div class="launcherPageShell">
<div class="launcherPageTopBar">
<button id="libraryBackButton" class="launcherBackButton"><%- lang('library.backButton') %></button>
</div>
<div class="launcherPageHeader"> <div class="launcherPageHeader">
<div> <div>
<span class="launcherPageEyebrow"><%- lang('library.eyebrow') %></span> <span class="launcherPageEyebrow"><%- lang('library.eyebrow') %></span>

View File

@@ -1,5 +1,8 @@
<div id="settingsContainer" style="display: none;"> <div id="settingsContainer" style="display: none;">
<div id="settingsContainerLeft"> <div id="settingsContainerLeft">
<div class="settingsTopActions">
<button id="settingsBackButton" class="launcherBackButton"><%- lang('settings.backButton') %></button>
</div>
<div id="settingsNavContainer"> <div id="settingsNavContainer">
<div id="settingsNavHeader"> <div id="settingsNavHeader">
<span id="settingsNavHeaderText"><%- lang('settings.navHeaderText') %></span> <span id="settingsNavHeaderText"><%- lang('settings.navHeaderText') %></span>
@@ -390,4 +393,4 @@
</div> </div>
</div> </div>
<script src="./assets/js/scripts/settings.js"></script> <script src="./assets/js/scripts/settings.js"></script>
</div> </div>