settings.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. const settingsNavDone = document.getElementById('settingsNavDone')
  2. // Account Management Tab
  3. const settingsAddAccount = document.getElementById('settingsAddAccount')
  4. const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts')
  5. // Minecraft Tab
  6. const settingsGameWidth = document.getElementById('settingsGameWidth')
  7. const settingsGameHeight = document.getElementById('settingsGameHeight')
  8. const settingsState = {
  9. invalid: new Set()
  10. }
  11. /**
  12. * General Settings Functions
  13. */
  14. /**
  15. * Bind value validators to the settings UI elements. These will
  16. * validate against the criteria defined in the ConfigManager (if
  17. * and). If the value is invalid, the UI will reflect this and saving
  18. * will be disabled until the value is corrected. This is an automated
  19. * process. More complex UI may need to be bound separately.
  20. */
  21. function initSettingsValidators(){
  22. const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
  23. Array.from(sEls).map((v, index, arr) => {
  24. const vFn = ConfigManager['validate' + v.getAttribute('cValue')]
  25. if(typeof vFn === 'function'){
  26. if(v.tagName === 'INPUT'){
  27. if(v.type === 'number' || v.type === 'text'){
  28. v.addEventListener('keyup', (e) => {
  29. const v = e.target
  30. if(!vFn(v.value)){
  31. settingsState.invalid.add(v.id)
  32. v.setAttribute('error', '')
  33. settingsSaveDisabled(true)
  34. } else {
  35. if(v.hasAttribute('error')){
  36. v.removeAttribute('error')
  37. settingsState.invalid.delete(v.id)
  38. if(settingsState.invalid.size === 0){
  39. settingsSaveDisabled(false)
  40. }
  41. }
  42. }
  43. })
  44. }
  45. }
  46. }
  47. })
  48. }
  49. /**
  50. * Load configuration values onto the UI. This is an automated process.
  51. */
  52. function initSettingsValues(){
  53. const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
  54. Array.from(sEls).map((v, index, arr) => {
  55. const gFn = ConfigManager['get' + v.getAttribute('cValue')]
  56. if(typeof gFn === 'function'){
  57. if(v.tagName === 'INPUT'){
  58. if(v.type === 'number' || v.type === 'text'){
  59. v.value = gFn()
  60. } else if(v.type === 'checkbox'){
  61. v.checked = gFn()
  62. }
  63. }
  64. }
  65. })
  66. }
  67. /**
  68. * Save the settings values.
  69. */
  70. function saveSettingsValues(){
  71. const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
  72. Array.from(sEls).map((v, index, arr) => {
  73. const sFn = ConfigManager['set' + v.getAttribute('cValue')]
  74. if(typeof sFn === 'function'){
  75. if(v.tagName === 'INPUT'){
  76. if(v.type === 'number' || v.type === 'text'){
  77. sFn(v.value)
  78. } else if(v.type === 'checkbox'){
  79. sFn(v.checked)
  80. }
  81. }
  82. }
  83. })
  84. }
  85. let selectedTab = 'settingsTabAccount'
  86. /**
  87. * Bind functionality for the settings navigation items.
  88. */
  89. function setupSettingsTabs(){
  90. Array.from(document.getElementsByClassName('settingsNavItem')).map((val) => {
  91. val.onclick = (e) => {
  92. if(val.hasAttribute('selected')){
  93. return
  94. }
  95. const navItems = document.getElementsByClassName('settingsNavItem')
  96. for(let i=0; i<navItems.length; i++){
  97. if(navItems[i].hasAttribute('selected')){
  98. navItems[i].removeAttribute('selected')
  99. }
  100. }
  101. val.setAttribute('selected', '')
  102. let prevTab = selectedTab
  103. selectedTab = val.getAttribute('rSc')
  104. $(`#${prevTab}`).fadeOut(250, () => {
  105. $(`#${selectedTab}`).fadeIn(250)
  106. })
  107. }
  108. })
  109. }
  110. /**
  111. * Set if the settings save (done) button is disabled.
  112. *
  113. * @param {boolean} v True to disable, false to enable.
  114. */
  115. function settingsSaveDisabled(v){
  116. settingsNavDone.disabled = v
  117. }
  118. /* Closes the settings view and saves all data. */
  119. settingsNavDone.onclick = () => {
  120. saveSettingsValues()
  121. ConfigManager.save()
  122. switchView(getCurrentView(), VIEWS.landing)
  123. }
  124. /**
  125. * Account Management Tab
  126. */
  127. // Bind the add account button.
  128. settingsAddAccount.onclick = (e) => {
  129. switchView(getCurrentView(), VIEWS.login, 500, 500, () => {
  130. loginViewOnCancel = VIEWS.settings
  131. loginViewOnSuccess = VIEWS.settings
  132. loginCancelEnabled(true)
  133. })
  134. }
  135. /**
  136. * Bind functionality for the account selection buttons. If another account
  137. * is selected, the UI of the previously selected account will be updated.
  138. */
  139. function bindAuthAccountSelect(){
  140. Array.from(document.getElementsByClassName('settingsAuthAccountSelect')).map((val) => {
  141. val.onclick = (e) => {
  142. if(val.hasAttribute('selected')){
  143. return
  144. }
  145. const selectBtns = document.getElementsByClassName('settingsAuthAccountSelect')
  146. for(let i=0; i<selectBtns.length; i++){
  147. if(selectBtns[i].hasAttribute('selected')){
  148. selectBtns[i].removeAttribute('selected')
  149. selectBtns[i].innerHTML = 'Select Account'
  150. }
  151. }
  152. val.setAttribute('selected', '')
  153. val.innerHTML = 'Selected Account &#10004;'
  154. setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid'))
  155. }
  156. })
  157. }
  158. /**
  159. * Bind functionality for the log out button. If the logged out account was
  160. * the selected account, another account will be selected and the UI will
  161. * be updated accordingly.
  162. */
  163. function bindAuthAccountLogOut(){
  164. Array.from(document.getElementsByClassName('settingsAuthAccountLogOut')).map((val) => {
  165. val.onclick = (e) => {
  166. let isLastAccount = false
  167. if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
  168. isLastAccount = true
  169. setOverlayContent(
  170. 'Warning<br>This is Your Last Account',
  171. 'In order to use the launcher you must be logged into at least one account. You will need to login again after.<br><br>Are you sure you want to log out?',
  172. 'I\'m Sure',
  173. 'Cancel'
  174. )
  175. setOverlayHandler(() => {
  176. processLogOut(val, isLastAccount)
  177. switchView(getCurrentView(), VIEWS.login)
  178. toggleOverlay(false)
  179. })
  180. setDismissHandler(() => {
  181. toggleOverlay(false)
  182. })
  183. toggleOverlay(true, true)
  184. } else {
  185. processLogOut(val, isLastAccount)
  186. }
  187. }
  188. })
  189. }
  190. /**
  191. * Process a log out.
  192. *
  193. * @param {Element} val The log out button element.
  194. * @param {boolean} isLastAccount If this logout is on the last added account.
  195. */
  196. function processLogOut(val, isLastAccount){
  197. const parent = val.closest('.settingsAuthAccount')
  198. const uuid = parent.getAttribute('uuid')
  199. const prevSelAcc = ConfigManager.getSelectedAccount()
  200. AuthManager.removeAccount(uuid).then(() => {
  201. if(!isLastAccount && uuid === prevSelAcc.uuid){
  202. const selAcc = ConfigManager.getSelectedAccount()
  203. refreshAuthAccountSelected(selAcc.uuid)
  204. updateSelectedAccount(selAcc)
  205. validateSelectedAccount()
  206. }
  207. })
  208. $(parent).fadeOut(250, () => {
  209. parent.remove()
  210. })
  211. }
  212. /**
  213. * Refreshes the status of the selected account on the auth account
  214. * elements.
  215. *
  216. * @param {string} uuid The UUID of the new selected account.
  217. */
  218. function refreshAuthAccountSelected(uuid){
  219. Array.from(document.getElementsByClassName('settingsAuthAccount')).map((val) => {
  220. const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0]
  221. if(uuid === val.getAttribute('uuid')){
  222. selBtn.setAttribute('selected', '')
  223. selBtn.innerHTML = 'Selected Account &#10004;'
  224. } else {
  225. if(selBtn.hasAttribute('selected')){
  226. selBtn.removeAttribute('selected')
  227. }
  228. selBtn.innerHTML = 'Select Account'
  229. }
  230. })
  231. }
  232. /**
  233. * Add auth account elements for each one stored in the authentication database.
  234. */
  235. function populateAuthAccounts(){
  236. const authAccounts = ConfigManager.getAuthAccounts()
  237. const authKeys = Object.keys(authAccounts)
  238. const selectedUUID = ConfigManager.getSelectedAccount().uuid
  239. let authAccountStr = ``
  240. authKeys.map((val) => {
  241. const acc = authAccounts[val]
  242. authAccountStr += `<div class="settingsAuthAccount" uuid="${acc.uuid}">
  243. <div class="settingsAuthAccountLeft">
  244. <img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://crafatar.com/renders/body/${acc.uuid}?scale=3&default=MHF_Steve&overlay">
  245. </div>
  246. <div class="settingsAuthAccountRight">
  247. <div class="settingsAuthAccountDetails">
  248. <div class="settingsAuthAccountDetailPane">
  249. <div class="settingsAuthAccountDetailTitle">Username</div>
  250. <div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
  251. </div>
  252. <div class="settingsAuthAccountDetailPane">
  253. <div class="settingsAuthAccountDetailTitle">${acc.displayName === acc.username ? 'UUID' : 'Email'}</div>
  254. <div class="settingsAuthAccountDetailValue">${acc.displayName === acc.username ? acc.uuid : acc.username}</div>
  255. </div>
  256. </div>
  257. <div class="settingsAuthAccountActions">
  258. <button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>Selected Account &#10004;' : '>Select Account'}</button>
  259. <div class="settingsAuthAccountWrapper">
  260. <button class="settingsAuthAccountLogOut">Log Out</button>
  261. </div>
  262. </div>
  263. </div>
  264. </div>`
  265. })
  266. settingsCurrentAccounts.innerHTML = authAccountStr
  267. }
  268. function prepareAccountsTab() {
  269. populateAuthAccounts()
  270. bindAuthAccountSelect()
  271. bindAuthAccountLogOut()
  272. }
  273. /**
  274. * Minecraft Tab
  275. */
  276. /**
  277. * Disable decimals, negative signs, and scientific notation.
  278. */
  279. settingsGameWidth.addEventListener('keydown', (e) => {
  280. if(/[-\.eE]/.test(e.key)){
  281. e.preventDefault()
  282. }
  283. })
  284. settingsGameHeight.addEventListener('keydown', (e) => {
  285. if(/[-\.eE]/.test(e.key)){
  286. e.preventDefault()
  287. }
  288. })
  289. /**
  290. * Settings preparation functions.
  291. */
  292. /**
  293. * Prepare the entire settings UI.
  294. *
  295. * @param {boolean} first Whether or not it is the first load.
  296. */
  297. function prepareSettings(first = false) {
  298. if(first){
  299. setupSettingsTabs()
  300. initSettingsValidators()
  301. }
  302. initSettingsValues()
  303. prepareAccountsTab()
  304. }
  305. // Prepare the settings UI on startup.
  306. prepareSettings(true)