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. */
#settingsContainerLeft {
display: flex;
flex-direction: column;
padding-top: 4%;
height: 100%;
width: 25%;
box-sizing: border-box;
}
.settingsTopActions {
padding: 0 0 18px 24px;
}
/* Settings navigation container. */
#settingsNavContainer {
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
@@ -2469,13 +2476,14 @@ input:checked + .toggleSwitchSlider:before {
}
#landingContainer > #upper > #content {
display: inline-flex;
width: 70%;
width: 63%;
height: 100%;
}
#landingContainer > #upper > #right {
display: inline-flex;
width: 15%;
width: 22%;
height: 100%;
justify-content: flex-end;
}
/* Lower content container. */
@@ -2956,6 +2964,9 @@ input:checked + .toggleSwitchSlider:before {
position: relative;
top: 50px;
align-items: flex-end;
width: 100%;
padding-right: 26px;
box-sizing: border-box;
height: calc(100% - 50px);
}
@@ -2970,6 +2981,35 @@ input:checked + .toggleSwitchSlider:before {
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 {
padding: 0;
border: none;
@@ -2997,8 +3037,8 @@ input:checked + .toggleSwitchSlider:before {
overflow: hidden;
position: relative;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-repeat: no-repeat, no-repeat;
background-size: cover, 60%;
}
#avatarMenuButton:hover #avatarContainer,
@@ -4025,6 +4065,11 @@ input:checked + .toggleSwitchSlider:before {
overflow-y: auto;
}
.launcherPageTopBar {
display: flex;
justify-content: flex-start;
}
.launcherPageHeader {
display: flex;
align-items: flex-start;
@@ -4055,6 +4100,36 @@ input:checked + .toggleSwitchSlider:before {
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,
.launcherCardActions,
#landingNavButtons {
@@ -4076,6 +4151,80 @@ input:checked + .toggleSwitchSlider:before {
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 {
display: flex;
flex-direction: column;

View File

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

View File

@@ -394,6 +394,10 @@ document.getElementById('libraryOpenInstallButton').addEventListener('click', as
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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