From 0f6c0563dbfb0107db8ca6b306fe28d29cbbdeb1 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 4 May 2026 14:41:04 +0900 Subject: [PATCH] Refine launcher navigation and install list --- app/assets/css/launcher.css | 157 +++++++++++++++++++++++++++++- app/assets/js/scripts/install.js | 56 +++++++---- app/assets/js/scripts/landing.js | 4 +- app/assets/js/scripts/library.js | 4 + app/assets/js/scripts/settings.js | 6 ++ app/assets/lang/_custom.toml | 4 + app/install.ejs | 7 +- app/landing.ejs | 12 ++- app/library.ejs | 3 + app/settings.ejs | 5 +- 10 files changed, 226 insertions(+), 32 deletions(-) diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index 484b2de..67891f2 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -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; diff --git a/app/assets/js/scripts/install.js b/app/assets/js/scripts/install.js index 56000c0..37a613f 100644 --- a/app/assets/js/scripts/install.js +++ b/app/assets/js/scripts/install.js @@ -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() diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 595cb33..b50c8ff 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -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){ diff --git a/app/assets/js/scripts/library.js b/app/assets/js/scripts/library.js index 0257dba..29ced75 100644 --- a/app/assets/js/scripts/library.js +++ b/app/assets/js/scripts/library.js @@ -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) diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 45163c9..f96e7ee 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -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 */ diff --git a/app/assets/lang/_custom.toml b/app/assets/lang/_custom.toml index e9ebba3..a1a10f5 100644 --- a/app/assets/lang/_custom.toml +++ b/app/assets/lang/_custom.toml @@ -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 = "설치 페이지는 읽기 전용 카탈로그입니다. 프로필 제목, 요약, 상세 설명은 관리자가 미리 등록하며, 클라이언트는 라이브러리에 추가만 할 수 있습니다." diff --git a/app/install.ejs b/app/install.ejs index 93850e3..a78f3be 100644 --- a/app/install.ejs +++ b/app/install.ejs @@ -1,5 +1,8 @@ diff --git a/app/landing.ejs b/app/landing.ejs index 688b4b6..60aa0a7 100644 --- a/app/landing.ejs +++ b/app/landing.ejs @@ -15,9 +15,15 @@