diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index e5fad61..484b2de 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -2955,19 +2955,36 @@ input:checked + .toggleSwitchSlider:before { flex-direction: column; position: relative; top: 50px; - align-items: flex-start; + align-items: flex-end; height: calc(100% - 50px); } /* Right hand user content container. */ #user_content { display: flex; - align-items: center; + flex-direction: column; + align-items: flex-end; justify-content: center; + gap: 10px; box-sizing: border-box; position: relative; } +#avatarMenuButton { + padding: 0; + border: none; + background: none; + cursor: pointer; +} + +#avatarMenuButton:focus { + outline: none; +} + +#avatarMenuButton:disabled { + cursor: default; +} + /* User profile avatar container. */ #avatarContainer { border-radius: 50%; @@ -2981,47 +2998,44 @@ input:checked + .toggleSwitchSlider:before { position: relative; background-position: center; background-repeat: no-repeat; - background-size: contain; + background-size: cover; } -/* Avatar edit overlay. */ -#avatarOverlay { - opacity: 0; - position: absolute; - z-index: 1; +#avatarMenuButton:hover #avatarContainer, +#avatarMenuButton[aria-expanded="true"] #avatarContainer, +#avatarMenuButton:focus #avatarContainer { + border-color: #f1c15a; + box-shadow: 0px 0px 18px 0px rgba(241, 193, 90, 0.55); +} + +#accountMenu { + min-width: 190px; + padding: 14px; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(10, 10, 10, 0.9); + backdrop-filter: blur(12px); display: flex; - justify-content: center; - align-items: center; - transition: 0.25s ease; - font-weight: bold; - letter-spacing: 2px; - background-color: rgba(0, 0, 0, 0.35); - -webkit-user-select: none; - border: none; - cursor: pointer; - width: 100%; - height: 100%; - border-radius: 50%; -} -#avatarOverlay:hover, -#avatarOverlay:focus { - opacity: 1; -} -#avatarOverlay:active { - background-color: rgba(0, 0, 0, 0.45); + flex-direction: column; + align-items: stretch; + gap: 12px; + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.28); } -/* User profile name text. */ -#user_text { - font-size: 12px; - min-width: 135px; - font-weight: 900; - letter-spacing: 1px; - text-shadow: 0px 0px 20px black; - position: absolute; - right: 95px; +#accountMenu[hidden] { + display: none; +} + +#accountMenuName { + font-size: 13px; + font-weight: 800; + letter-spacing: 0.04em; + color: #ffffff; text-align: right; - -webkit-user-select: initial; +} + +#accountMenuLogoutButton { + width: 100%; } /* Social media icon content container. */ diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index abbccb7..595cb33 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -1,6 +1,7 @@ /** * Script for landing.ejs */ +(() => { // Requirements const { URL } = require('url') const { @@ -28,6 +29,7 @@ const { } = require('helios-core/java') // Internal Requirements +const AuthManager = require('./assets/js/authmanager') const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') @@ -38,10 +40,79 @@ 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 user_text = document.getElementById('user_text') +const avatarMenuButton = document.getElementById('avatarMenuButton') +const avatarContainer = document.getElementById('avatarContainer') +const accountMenu = document.getElementById('accountMenu') +const accountMenuName = document.getElementById('accountMenuName') +const accountMenuLogoutButton = document.getElementById('accountMenuLogoutButton') const loggerLanding = LoggerUtil.getLogger('Landing') +function setAccountMenuOpen(open){ + if(open && ConfigManager.getSelectedAccount() == null){ + return + } + + avatarMenuButton.setAttribute('aria-expanded', open ? 'true' : 'false') + if(open){ + accountMenu.removeAttribute('hidden') + } else { + accountMenu.setAttribute('hidden', '') + } +} + +function showAccountError(message){ + if(typeof setOverlayContent === 'function'){ + setOverlayContent( + Lang.queryJS('settings.msftLogout.errorTitle'), + message, + Lang.queryJS('landing.launch.okay') + ) + setOverlayHandler(() => toggleOverlay(false)) + toggleOverlay(true) + } +} + +async function logoutSelectedAccount(){ + const selectedAccount = ConfigManager.getSelectedAccount() + if(selectedAccount == null){ + return + } + + const accountCount = Object.keys(ConfigManager.getAuthAccounts()).length + accountMenuLogoutButton.disabled = true + + try { + if(selectedAccount.type === 'microsoft'){ + await AuthManager.removeMicrosoftAccount(selectedAccount.uuid) + } else { + await AuthManager.removeMojangAccount(selectedAccount.uuid) + } + + setAccountMenuOpen(false) + + const nextAccount = ConfigManager.getSelectedAccount() + if(nextAccount != null){ + updateSelectedAccount(nextAccount) + validateSelectedAccount() + } else if(accountCount === 1){ + updateSelectedAccount(null) + loginOptionsCancelEnabled(false) + loginOptionsViewOnLoginSuccess = VIEWS.landing + loginOptionsViewOnLoginCancel = VIEWS.loginOptions + switchView(getCurrentView(), VIEWS.loginOptions) + } + } catch (error) { + console.error(error) + const message = selectedAccount.type === 'microsoft' + ? Lang.queryJS('settings.msftLogout.errorMessage') + : Lang.queryJS('landing.selectedAccount.logoutFailed') + showAccountError(message) + } finally { + accountMenuLogoutButton.disabled = false + } +} + /* Launch Progress Wrapper Functions */ /** @@ -147,13 +218,24 @@ document.getElementById('landingInstallButton').onclick = async () => { switchView(getCurrentView(), VIEWS.install) } -// Bind avatar overlay button. -document.getElementById('avatarOverlay').onclick = async e => { - await prepareSettings() - switchView(getCurrentView(), VIEWS.settings, 500, 500, () => { - settingsNavItemListener(document.getElementById('settingsNavAccount'), false) - }) -} +avatarMenuButton.addEventListener('click', (e) => { + e.stopPropagation() + setAccountMenuOpen(accountMenu.hasAttribute('hidden')) +}) + +accountMenu.addEventListener('click', (e) => { + e.stopPropagation() +}) + +accountMenuLogoutButton.addEventListener('click', async () => { + await logoutSelectedAccount() +}) + +document.addEventListener('click', () => { + if(!accountMenu.hasAttribute('hidden')){ + setAccountMenuOpen(false) + } +}) // Bind selected account function updateSelectedAccount(authUser){ @@ -163,10 +245,16 @@ function updateSelectedAccount(authUser){ username = authUser.displayName } if(authUser.uuid != null){ - document.getElementById('avatarContainer').style.backgroundImage = `url('https://mc-heads.net/body/${authUser.uuid}/right')` + avatarContainer.style.backgroundImage = `url('https://mc-heads.net/avatar/${authUser.uuid}/64')` } + } else { + avatarContainer.style.backgroundImage = `url('assets/images/Icon.png')` + } + accountMenuName.textContent = username + avatarMenuButton.disabled = authUser == null + if(authUser == null){ + setAccountMenuOpen(false) } - user_text.innerHTML = username } updateSelectedAccount(ConfigManager.getSelectedAccount()) @@ -1038,3 +1126,9 @@ async function loadNews(){ return await promise } + +window.updateSelectedAccount = updateSelectedAccount +window.updateSelectedServer = updateSelectedServer +window.refreshServerStatus = refreshServerStatus +window.initNews = initNews +})() diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 02db564..45163c9 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -1,6 +1,10 @@ // Requirements const os = require('os') const semver = require('semver') +const { + validateSelectedJvm, + ensureJavaDirIsRoot +} = require('helios-core/java') const DropinModUtil = require('./assets/js/dropinmodutil') const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml index 1ed42c1..15041ed 100644 --- a/app/assets/lang/en_US.toml +++ b/app/assets/lang/en_US.toml @@ -2,6 +2,7 @@ updateAvailableTooltip = "Update Available" usernamePlaceholder = "Username" usernameEditButton = "Edit" +accountMenuLogout = "Log Out" settingsTooltip = "Settings" serverStatus = "SERVER" serverStatusPlaceholder = "OFFLINE" @@ -161,6 +162,7 @@ okay = "Okay" [js.landing.selectedAccount] noAccountSelected = "No Account Selected" +logoutFailed = "Failed to log out of the selected account. Please try again." [js.landing.selectedServer] noSelection = "No Server Selected" diff --git a/app/assets/lang/ko_KR.toml b/app/assets/lang/ko_KR.toml index ba40ad7..f8a600c 100644 --- a/app/assets/lang/ko_KR.toml +++ b/app/assets/lang/ko_KR.toml @@ -2,6 +2,7 @@ updateAvailableTooltip = "업데이트 가능" usernamePlaceholder = "사용자 이름" usernameEditButton = "편집" +accountMenuLogout = "로그아웃" settingsTooltip = "설정" serverStatus = "서버" serverStatusPlaceholder = "오프라인" @@ -161,6 +162,7 @@ okay = "확인" [js.landing.selectedAccount] noAccountSelected = "선택된 계정 없음" +logoutFailed = "계정을 로그아웃하지 못했습니다. 다시 시도해 주세요." [js.landing.selectedServer] noSelection = "선택된 서버 없음" diff --git a/app/landing.ejs b/app/landing.ejs index b990364..688b4b6 100644 --- a/app/landing.ejs +++ b/app/landing.ejs @@ -15,9 +15,12 @@