diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index 5cf3c496..6873d1a8 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -387,6 +387,68 @@ body, button { background: rgba(0, 0, 0, 0.50); } +/* Login cancel button styles. */ +#loginCancelContainer { + position: absolute; + top: 5%; + right: 5%; +} + +/* Login cancel button styles. */ +#loginCancelButton { + background: none; + border: none; + outline: none; + cursor: pointer; + transition: 0.25s ease; +} +#loginCancelButton:hover #loginCancelIcon, +#loginCancelButton:hover #loginCancelText, +#loginCancelButton:focus #loginCancelIcon, +#loginCancelButton:focus #loginCancelText { + text-shadow: 0px 0px 20px white; +} +#loginCancelButton:hover #loginCancelIcon, +#loginCancelButton:focus #loginCancelIcon { + box-shadow: 0px 0px 20px white; +} +#loginCancelButton:active #loginCancelIcon, +#loginCancelButton:active #loginCancelText { + text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75); + color: rgba(255, 255, 255, 0.75); + border-color: rgba(255, 255, 255, 0.75); +} +#loginCancelButton:active #loginCancelIcon { + box-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75); +} +#loginCancelButton:disabled { + pointer-events: none; +} +#loginCancelButton:disabled #loginCancelIcon, +#loginCancelButton:disabled #loginCancelText { + color: rgba(255, 255, 255, 0.75); + border-color: rgba(255, 255, 255, 0.75); +} + +/* The X in a circle icon for the cancel button. */ +#loginCancelIcon { + border-radius: 50%; + border: 1px solid white; + box-sizing: border-box; + height: 30px; + width: 30px; + font-size: 19px; + line-height: 30px; + margin: 0 auto; + margin-bottom: 5px; + transition: 0.25s ease; +} +/* Text for the login cancel button. */ +#loginCancelText { + font-size: 15px; + transition: 0.25s ease; +} + /* Login content wrapper. */ #loginContent { display: flex; @@ -816,6 +878,7 @@ body, button { * * ******************************************************************************/ +/* Main settings container. */ #settingsContainer { position: relative; height: 100%; @@ -823,6 +886,7 @@ body, button { background: rgba(0, 0, 0, 0.50); } +/* Left hand side of the settings UI, for navigation. */ #settingsContainerLeft { height: 100%; width: 25%; @@ -830,20 +894,22 @@ body, button { box-sizing: border-box; } +/* Settings navigation container. */ #settingsNavContainer { display: flex; flex-direction: column; } +/* Navigation header styles. */ #settingsNavHeader { display: flex; justify-content: center; } - #settingsNavHeaderText { font-size: 20px; } +/* Navigation items outer container. */ #settingsNavItemsContainer { height: 100%; display: flex; @@ -852,11 +918,13 @@ body, button { box-sizing: border-box; } +/* Navigation items content container. */ #settingsNavItemsContent { display: flex; flex-direction: column; } +/* Navigation item shared styles. */ .settingsNavItem { background: none; border: none; @@ -880,23 +948,36 @@ body, button { text-shadow: none; } +/* Right hand side of the settings container, for tabs. */ #settingsContainerRight { height: 100%; width: 75%; - padding: 5% 0%; + padding-top: 5%; box-sizing: border-box; } +/* Settings tab shared styles. */ .settingsTab { width: 100%; height: 100%; + overflow-y: auto; +} +.settingsTab::-webkit-scrollbar { + width: 2px; +} +.settingsTab::-webkit-scrollbar-track { + display: none; +} +.settingsTab::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.50); } +/* Tab header shared styles. */ .settingsTabHeader { display: flex; flex-direction: column; } - .settingsTabHeaderText { font-size: 20px; font-family: 'Avenir Medium'; @@ -905,6 +986,163 @@ body, button { font-size: 12px; } +/* * * +* Settings View (Account Tab) +* * */ + +/* Add account button styles. */ +#settingsAddAccountContainer { + margin-top: 20px; +} +#settingsAddAccount { + background: rgba(0, 0, 0, 0.25); + border: 1px solid rgba(126, 126, 126, 0.57); + border-radius: 3px; + height: 50px; + width: 75%; + text-align: left; + padding: 0px 50px; + cursor: pointer; + outline: none; + transition: 0.25s ease; +} +#settingsAddAccount:hover, +#settingsAddAccount:focus { + background: rgba(54, 54, 54, 0.25); + text-shadow: 0px 0px 20px white; +} + +/* Settings auth accounts header. */ +#settingsCurrentAccountsHeader { + margin: 20px 0px; +} +#settingsCurrentAccountsHeaderText { + font-size: 16px; +} + +/* Auth account list container styles. */ +#settingsCurrentAccounts { + margin-bottom: 5%; +} +#settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) { + margin-bottom: 10px; +} +#settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) { + margin-top: 10px; +} + +/* Auth account shared styles. */ +.settingsAuthAccount { + display: flex; + width: 75%; + background: rgba(0, 0, 0, 0.25); + border-radius: 3px; + border: 1px solid rgba(126, 126, 126, 0.57); +} + +/* Left hand side of an auth account element, for the skin image. */ +.settingsAuthAccountLeft { + padding: 5px 5px 5px 20px; +} + +/* Image of the auth account's skin. */ +.settingsAuthAccountImage { + height: 115px; +} + +/* Right hand side of the auth account, for info + actions. */ +.settingsAuthAccountRight { + display: flex; + width: 100%; +} + +/* Account details container. */ +.settingsAuthAccountDetails { + display: flex; + flex-direction: column; + justify-content: center; + margin-left: 20px; + width: 100%; +} +.settingsAuthAccountDetails > *:not(:last-child) { + margin-bottom: 20px; +} + +/* Account detail element styles. */ +.settingsAuthAccountDetailPane { + display: flex; + flex-direction: column; +} +.settingsAuthAccountDetailTitle { + font-size: 12px; + color: grey; + font-weight: bold; + font-family: 'Avenir Medium'; +} +.settingsAuthAccountDetailValue { + font-size: 14px; +} + +/* Account actions container. */ +.settingsAuthAccountActions { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-end; + padding: 10px; +} + +/* Account select button shared styles. */ +.settingsAuthAccountSelect { + opacity: 0; + border: none; + white-space: nowrap; + background: none; + font-family: 'Avenir Medium'; + outline: none; + transition: 0.25s ease; +} +.settingsAuthAccountSelect:hover:not([selected]), +.settingsAuthAccountSelect:focus:not([selected]) { + text-shadow: 0px 0px 20px white, 0px 0px 20px white; + cursor: pointer; +} +.settingsAuthAccount:hover .settingsAuthAccountSelect:not([selected]), +.settingsAuthAccountSelect[selected] { + opacity: 1; +} +.settingsAuthAccountSelect[selected] { + pointer-events: none; +} + +/* Account logout button shared styles. */ +.settingsAuthAccountLogOut { + opacity: 0; + border: 1px solid rgb(241, 55, 55); + color: rgb(241, 55, 55); + background: none; + font-size: 12px; + border-radius: 3px; + font-family: 'Avenir Medium'; + transition: 0.25s ease; + cursor: pointer; + outline: none; +} +.settingsAuthAccountLogOut:hover, +.settingsAuthAccountLogOut:focus { + box-shadow: 0px 0px 20px rgb(241, 55, 55); + background: rgba(241, 55, 55, 0.25); +} +.settingsAuthAccountLogOut:active { + box-shadow: 0px 0px 20px rgb(185, 47, 47); + background: rgba(185, 47, 47, 0.25); + border: 1px solid rgb(185, 47, 47); + color: rgb(185, 47, 47); +} +.settingsAuthAccount:hover .settingsAuthAccountLogOut { + opacity: 1; +} + /******************************************************************************* * * * Landing View (Structural Styles) * diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index dce50401..4d00c007 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -116,6 +116,7 @@ document.getElementById('launch_button').addEventListener('click', function(e){ // Bind settings button document.getElementById('settingsMediaButton').onclick = (e) => { + prepareSettings() switchView(getCurrentView(), VIEWS.settings) } diff --git a/app/assets/js/scripts/login.js b/app/assets/js/scripts/login.js index ac96a08f..61ea765b 100644 --- a/app/assets/js/scripts/login.js +++ b/app/assets/js/scripts/login.js @@ -7,6 +7,8 @@ const basicEmail = /^\S+@\S+\.\S+$/ //const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i // Login Elements +const loginCancelContainer = document.getElementById('loginCancelContainer') +const loginCancelButton = document.getElementById('loginCancelButton') const loginEmailError = document.getElementById('loginEmailError') const loginUsername = document.getElementById('loginUsername') const loginPasswordError = document.getElementById('loginPasswordError') @@ -139,6 +141,7 @@ function loginLoading(v){ */ function formDisabled(v){ loginDisabled(v) + loginCancelButton.disabled = v loginUsername.disabled = v loginPassword.disabled = v if(v){ @@ -215,6 +218,23 @@ function resolveError(err){ } } +let loginViewOnSuccess = VIEWS.landing +let loginViewOnCancel = VIEWS.settings + +function loginCancelEnabled(val){ + if(val){ + $(loginCancelContainer).show() + } else { + $(loginCancelContainer).hide() + } +} + +loginCancelButton.onclick = (e) => { + switchView(getCurrentView(), loginViewOnCancel, 500, 500, () => { + loginCancelEnabled(false) + }) +} + // Disable default form behavior. loginForm.onsubmit = () => { return false } @@ -233,7 +253,13 @@ loginButton.addEventListener('click', () => { $('.checkmark').toggle() //console.log(value) setTimeout(() => { - switchView(VIEWS.login, VIEWS.landing, 500, 500, () => { + switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => { + // Temporary workaround + if(loginViewOnSuccess === VIEWS.settings){ + prepareSettings() + } + loginViewOnSuccess = VIEWS.landing // Reset this for good measure. + loginCancelEnabled(false) // Reset this for good measure. loginUsername.value = '' loginPassword.value = '' $('.circle-loader').toggleClass('load-complete') diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index f9e7e468..f6b7f687 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -1,5 +1,15 @@ +const settingsAddAccount = document.getElementById('settingsAddAccount') +const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts') + +/** + * General Settings Functions + */ + let selectedTab = 'settingsTabAccount' +/** + * Bind functionality for the settings navigation items. + */ function setupSettingsTabs(){ Array.from(document.getElementsByClassName('settingsNavItem')).map((val) => { val.onclick = (e) => { @@ -22,4 +32,148 @@ function setupSettingsTabs(){ }) } -setupSettingsTabs() \ No newline at end of file +/** + * Account Management Tab + */ + +// Bind the add account button. +settingsAddAccount.onclick = (e) => { + switchView(getCurrentView(), VIEWS.login, 500, 500, () => { + loginViewOnCancel = VIEWS.settings + loginViewOnSuccess = VIEWS.settings + loginCancelEnabled(true) + }) +} + +/** + * Bind functionality for the account selection buttons. If another account + * is selected, the UI of the previously selected account will be updated. + */ +function bindAuthAccountSelect(){ + Array.from(document.getElementsByClassName('settingsAuthAccountSelect')).map((val) => { + val.onclick = (e) => { + if(val.hasAttribute('selected')){ + return + } + const selectBtns = document.getElementsByClassName('settingsAuthAccountSelect') + for(let i=0; i { + val.onclick = (e) => { + const parent = val.closest('.settingsAuthAccount') + const uuid = parent.getAttribute('uuid') + const prevSelAcc = ConfigManager.getSelectedAccount() + AuthManager.removeAccount(uuid).then(() => { + if(uuid === prevSelAcc.uuid){ + const selAcc = ConfigManager.getSelectedAccount() + refreshAuthAccountSelected(selAcc.uuid) + updateSelectedAccount(selAcc) + } + }) + $(parent).fadeOut(250, () => { + parent.remove() + }) + } + }) +} + +/** + * Refreshes the status of the selected account on the auth account + * elements. + * + * @param {string} uuid The UUID of the new selected account. + */ +function refreshAuthAccountSelected(uuid){ + Array.from(document.getElementsByClassName('settingsAuthAccount')).map((val) => { + const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0] + if(uuid === val.getAttribute('uuid')){ + selBtn.setAttribute('selected', '') + selBtn.innerHTML = 'Selected Account ✔' + } else { + if(selBtn.hasAttribute('selected')){ + selBtn.removeAttribute('selected') + } + selBtn.innerHTML = 'Select Account' + } + }) +} + +/** + * Add auth account elements for each one stored in the authentication database. + */ +function populateAuthAccounts(){ + const authAccounts = ConfigManager.getAuthAccounts() + const authKeys = Object.keys(authAccounts) + const selectedUUID = ConfigManager.getSelectedAccount().uuid + + let authAccountStr = `` + + authKeys.map((val) => { + const acc = authAccounts[val] + authAccountStr += `
+
+ ${acc.displayName} +
+
+
+
+
Username
+
${acc.displayName}
+
+
+
${acc.displayName === acc.username ? 'UUID' : 'Email'}
+
${acc.displayName === acc.username ? acc.uuid : acc.username}
+
+
+
+ +
+ +
+
+
+
` + }) + + settingsCurrentAccounts.innerHTML = authAccountStr +} + +function prepareAccountsTab() { + populateAuthAccounts() + bindAuthAccountSelect() + bindAuthAccountLogOut() +} + +/** + * Settings preparation functions. + */ + + /** + * Prepare the entire settings UI. + */ +function prepareSettings() { + setupSettingsTabs() + prepareAccountsTab() +} + +// Prepare the settings UI on startup. +prepareSettings() \ No newline at end of file diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js index 2e1555ca..ada4bd97 100644 --- a/app/assets/js/scripts/uibinder.js +++ b/app/assets/js/scripts/uibinder.js @@ -145,6 +145,7 @@ async function validateSelectedAccount(){ setOverlayHandler(() => { document.getElementById('loginUsername').value = selectedAcc.username validateEmail(selectedAcc.username) + loginViewOnSuccess = getCurrentView() switchView(getCurrentView(), VIEWS.login) toggleOverlay(false) }) diff --git a/app/login.ejs b/app/login.ejs index 12a10d15..552e5b83 100644 --- a/app/login.ejs +++ b/app/login.ejs @@ -1,4 +1,10 @@