settings.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. // Requirements
  2. const os = require('os')
  3. const semver = require('semver')
  4. const settingsState = {
  5. invalid: new Set()
  6. }
  7. /**
  8. * General Settings Functions
  9. */
  10. /**
  11. * Bind value validators to the settings UI elements. These will
  12. * validate against the criteria defined in the ConfigManager (if
  13. * and). If the value is invalid, the UI will reflect this and saving
  14. * will be disabled until the value is corrected. This is an automated
  15. * process. More complex UI may need to be bound separately.
  16. */
  17. function initSettingsValidators(){
  18. const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
  19. Array.from(sEls).map((v, index, arr) => {
  20. const vFn = ConfigManager['validate' + v.getAttribute('cValue')]
  21. if(typeof vFn === 'function'){
  22. if(v.tagName === 'INPUT'){
  23. if(v.type === 'number' || v.type === 'text'){
  24. v.addEventListener('keyup', (e) => {
  25. const v = e.target
  26. if(!vFn(v.value)){
  27. settingsState.invalid.add(v.id)
  28. v.setAttribute('error', '')
  29. settingsSaveDisabled(true)
  30. } else {
  31. if(v.hasAttribute('error')){
  32. v.removeAttribute('error')
  33. settingsState.invalid.delete(v.id)
  34. if(settingsState.invalid.size === 0){
  35. settingsSaveDisabled(false)
  36. }
  37. }
  38. }
  39. })
  40. }
  41. }
  42. }
  43. })
  44. }
  45. /**
  46. * Load configuration values onto the UI. This is an automated process.
  47. */
  48. function initSettingsValues(){
  49. const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
  50. Array.from(sEls).map((v, index, arr) => {
  51. const gFn = ConfigManager['get' + v.getAttribute('cValue')]
  52. if(typeof gFn === 'function'){
  53. if(v.tagName === 'INPUT'){
  54. if(v.type === 'number' || v.type === 'text'){
  55. // Special Conditions
  56. const cVal = v.getAttribute('cValue')
  57. if(cVal === 'JavaExecutable'){
  58. populateJavaExecDetails(v.value)
  59. v.value = gFn()
  60. } else if(cVal === 'JVMOptions'){
  61. v.value = gFn().join(' ')
  62. } else {
  63. v.value = gFn()
  64. }
  65. } else if(v.type === 'checkbox'){
  66. v.checked = gFn()
  67. }
  68. } else if(v.tagName === 'DIV'){
  69. if(v.classList.contains('rangeSlider')){
  70. // Special Conditions
  71. const cVal = v.getAttribute('cValue')
  72. if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
  73. let val = gFn()
  74. if(val.endsWith('M')){
  75. val = Number(val.substring(0, val.length-1))/1000
  76. } else {
  77. val = Number.parseFloat(val)
  78. }
  79. v.setAttribute('value', val)
  80. } else {
  81. v.setAttribute('value', Number.parseFloat(gFn()))
  82. }
  83. }
  84. }
  85. }
  86. })
  87. }
  88. /**
  89. * Save the settings values.
  90. */
  91. function saveSettingsValues(){
  92. const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
  93. Array.from(sEls).map((v, index, arr) => {
  94. const sFn = ConfigManager['set' + v.getAttribute('cValue')]
  95. if(typeof sFn === 'function'){
  96. if(v.tagName === 'INPUT'){
  97. if(v.type === 'number' || v.type === 'text'){
  98. // Special Conditions
  99. const cVal = v.getAttribute('cValue')
  100. if(cVal === 'JVMOptions'){
  101. sFn(v.value.split(' '))
  102. } else {
  103. sFn(v.value)
  104. }
  105. } else if(v.type === 'checkbox'){
  106. sFn(v.checked)
  107. // Special Conditions
  108. const cVal = v.getAttribute('cValue')
  109. if(cVal === 'AllowPrerelease'){
  110. changeAllowPrerelease(v.checked)
  111. }
  112. }
  113. } else if(v.tagName === 'DIV'){
  114. if(v.classList.contains('rangeSlider')){
  115. // Special Conditions
  116. const cVal = v.getAttribute('cValue')
  117. if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
  118. let val = Number(v.getAttribute('value'))
  119. if(val%1 > 0){
  120. val = val*1000 + 'M'
  121. } else {
  122. val = val + 'G'
  123. }
  124. sFn(val)
  125. } else {
  126. sFn(v.getAttribute('value'))
  127. }
  128. }
  129. }
  130. }
  131. })
  132. }
  133. let selectedTab = 'settingsTabAccount'
  134. /**
  135. * Modify the settings container UI when the scroll threshold reaches
  136. * a certain poin.
  137. *
  138. * @param {UIEvent} e The scroll event.
  139. */
  140. function settingsTabScrollListener(e){
  141. if(e.target.scrollTop > Number.parseFloat(getComputedStyle(e.target.firstElementChild).marginTop)){
  142. document.getElementById('settingsContainer').setAttribute('scrolled', '')
  143. } else {
  144. document.getElementById('settingsContainer').removeAttribute('scrolled')
  145. }
  146. }
  147. /**
  148. * Bind functionality for the settings navigation items.
  149. */
  150. function setupSettingsTabs(){
  151. Array.from(document.getElementsByClassName('settingsNavItem')).map((val) => {
  152. if(val.hasAttribute('rSc')){
  153. val.onclick = (e) => {
  154. if(val.hasAttribute('selected')){
  155. return
  156. }
  157. const navItems = document.getElementsByClassName('settingsNavItem')
  158. for(let i=0; i<navItems.length; i++){
  159. if(navItems[i].hasAttribute('selected')){
  160. navItems[i].removeAttribute('selected')
  161. }
  162. }
  163. val.setAttribute('selected', '')
  164. let prevTab = selectedTab
  165. selectedTab = val.getAttribute('rSc')
  166. document.getElementById(prevTab).onscroll = null
  167. document.getElementById(selectedTab).onscroll = settingsTabScrollListener
  168. $(`#${prevTab}`).fadeOut(250, () => {
  169. $(`#${selectedTab}`).fadeIn({
  170. duration: 250,
  171. start: () => {
  172. settingsTabScrollListener({
  173. target: document.getElementById(selectedTab)
  174. })
  175. }
  176. })
  177. })
  178. }
  179. }
  180. })
  181. }
  182. const settingsNavDone = document.getElementById('settingsNavDone')
  183. /**
  184. * Set if the settings save (done) button is disabled.
  185. *
  186. * @param {boolean} v True to disable, false to enable.
  187. */
  188. function settingsSaveDisabled(v){
  189. settingsNavDone.disabled = v
  190. }
  191. /* Closes the settings view and saves all data. */
  192. settingsNavDone.onclick = () => {
  193. saveSettingsValues()
  194. ConfigManager.save()
  195. switchView(getCurrentView(), VIEWS.landing)
  196. }
  197. /**
  198. * Account Management Tab
  199. */
  200. // Bind the add account button.
  201. document.getElementById('settingsAddAccount').onclick = (e) => {
  202. switchView(getCurrentView(), VIEWS.login, 500, 500, () => {
  203. loginViewOnCancel = VIEWS.settings
  204. loginViewOnSuccess = VIEWS.settings
  205. loginCancelEnabled(true)
  206. })
  207. }
  208. /**
  209. * Bind functionality for the account selection buttons. If another account
  210. * is selected, the UI of the previously selected account will be updated.
  211. */
  212. function bindAuthAccountSelect(){
  213. Array.from(document.getElementsByClassName('settingsAuthAccountSelect')).map((val) => {
  214. val.onclick = (e) => {
  215. if(val.hasAttribute('selected')){
  216. return
  217. }
  218. const selectBtns = document.getElementsByClassName('settingsAuthAccountSelect')
  219. for(let i=0; i<selectBtns.length; i++){
  220. if(selectBtns[i].hasAttribute('selected')){
  221. selectBtns[i].removeAttribute('selected')
  222. selectBtns[i].innerHTML = 'Select Account'
  223. }
  224. }
  225. val.setAttribute('selected', '')
  226. val.innerHTML = 'Selected Account &#10004;'
  227. setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid'))
  228. }
  229. })
  230. }
  231. /**
  232. * Bind functionality for the log out button. If the logged out account was
  233. * the selected account, another account will be selected and the UI will
  234. * be updated accordingly.
  235. */
  236. function bindAuthAccountLogOut(){
  237. Array.from(document.getElementsByClassName('settingsAuthAccountLogOut')).map((val) => {
  238. val.onclick = (e) => {
  239. let isLastAccount = false
  240. if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
  241. isLastAccount = true
  242. setOverlayContent(
  243. 'Warning<br>This is Your Last Account',
  244. '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?',
  245. 'I\'m Sure',
  246. 'Cancel'
  247. )
  248. setOverlayHandler(() => {
  249. processLogOut(val, isLastAccount)
  250. switchView(getCurrentView(), VIEWS.login)
  251. toggleOverlay(false)
  252. })
  253. setDismissHandler(() => {
  254. toggleOverlay(false)
  255. })
  256. toggleOverlay(true, true)
  257. } else {
  258. processLogOut(val, isLastAccount)
  259. }
  260. }
  261. })
  262. }
  263. /**
  264. * Process a log out.
  265. *
  266. * @param {Element} val The log out button element.
  267. * @param {boolean} isLastAccount If this logout is on the last added account.
  268. */
  269. function processLogOut(val, isLastAccount){
  270. const parent = val.closest('.settingsAuthAccount')
  271. const uuid = parent.getAttribute('uuid')
  272. const prevSelAcc = ConfigManager.getSelectedAccount()
  273. AuthManager.removeAccount(uuid).then(() => {
  274. if(!isLastAccount && uuid === prevSelAcc.uuid){
  275. const selAcc = ConfigManager.getSelectedAccount()
  276. refreshAuthAccountSelected(selAcc.uuid)
  277. updateSelectedAccount(selAcc)
  278. validateSelectedAccount()
  279. }
  280. })
  281. $(parent).fadeOut(250, () => {
  282. parent.remove()
  283. })
  284. }
  285. /**
  286. * Refreshes the status of the selected account on the auth account
  287. * elements.
  288. *
  289. * @param {string} uuid The UUID of the new selected account.
  290. */
  291. function refreshAuthAccountSelected(uuid){
  292. Array.from(document.getElementsByClassName('settingsAuthAccount')).map((val) => {
  293. const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0]
  294. if(uuid === val.getAttribute('uuid')){
  295. selBtn.setAttribute('selected', '')
  296. selBtn.innerHTML = 'Selected Account &#10004;'
  297. } else {
  298. if(selBtn.hasAttribute('selected')){
  299. selBtn.removeAttribute('selected')
  300. }
  301. selBtn.innerHTML = 'Select Account'
  302. }
  303. })
  304. }
  305. const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts')
  306. /**
  307. * Add auth account elements for each one stored in the authentication database.
  308. */
  309. function populateAuthAccounts(){
  310. const authAccounts = ConfigManager.getAuthAccounts()
  311. const authKeys = Object.keys(authAccounts)
  312. const selectedUUID = ConfigManager.getSelectedAccount().uuid
  313. let authAccountStr = ``
  314. authKeys.map((val) => {
  315. const acc = authAccounts[val]
  316. authAccountStr += `<div class="settingsAuthAccount" uuid="${acc.uuid}">
  317. <div class="settingsAuthAccountLeft">
  318. <img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://crafatar.com/renders/body/${acc.uuid}?scale=3&default=MHF_Steve&overlay">
  319. </div>
  320. <div class="settingsAuthAccountRight">
  321. <div class="settingsAuthAccountDetails">
  322. <div class="settingsAuthAccountDetailPane">
  323. <div class="settingsAuthAccountDetailTitle">Username</div>
  324. <div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
  325. </div>
  326. <div class="settingsAuthAccountDetailPane">
  327. <div class="settingsAuthAccountDetailTitle">${acc.displayName === acc.username ? 'UUID' : 'Email'}</div>
  328. <div class="settingsAuthAccountDetailValue">${acc.displayName === acc.username ? acc.uuid : acc.username}</div>
  329. </div>
  330. </div>
  331. <div class="settingsAuthAccountActions">
  332. <button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>Selected Account &#10004;' : '>Select Account'}</button>
  333. <div class="settingsAuthAccountWrapper">
  334. <button class="settingsAuthAccountLogOut">Log Out</button>
  335. </div>
  336. </div>
  337. </div>
  338. </div>`
  339. })
  340. settingsCurrentAccounts.innerHTML = authAccountStr
  341. }
  342. /**
  343. * Prepare the accounts tab for display.
  344. */
  345. function prepareAccountsTab() {
  346. populateAuthAccounts()
  347. bindAuthAccountSelect()
  348. bindAuthAccountLogOut()
  349. }
  350. /**
  351. * Minecraft Tab
  352. */
  353. /**
  354. * Disable decimals, negative signs, and scientific notation.
  355. */
  356. document.getElementById('settingsGameWidth').addEventListener('keydown', (e) => {
  357. if(/[-\.eE]/.test(e.key)){
  358. e.preventDefault()
  359. }
  360. })
  361. document.getElementById('settingsGameHeight').addEventListener('keydown', (e) => {
  362. if(/[-\.eE]/.test(e.key)){
  363. e.preventDefault()
  364. }
  365. })
  366. /**
  367. * Java Tab
  368. */
  369. // DOM Cache
  370. const settingsMaxRAMRange = document.getElementById('settingsMaxRAMRange')
  371. const settingsMinRAMRange = document.getElementById('settingsMinRAMRange')
  372. const settingsMaxRAMLabel = document.getElementById('settingsMaxRAMLabel')
  373. const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel')
  374. const settingsMemoryTotal = document.getElementById('settingsMemoryTotal')
  375. const settingsMemoryAvail = document.getElementById('settingsMemoryAvail')
  376. const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails')
  377. const settingsJavaExecVal = document.getElementById('settingsJavaExecVal')
  378. const settingsJavaExecSel = document.getElementById('settingsJavaExecSel')
  379. // Store maximum memory values.
  380. const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM()
  381. const SETTINGS_MIN_MEMORY = ConfigManager.getAbsoluteMinRAM()
  382. // Set the max and min values for the ranged sliders.
  383. settingsMaxRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
  384. settingsMaxRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY)
  385. settingsMinRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
  386. settingsMinRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY )
  387. // Bind on change event for min memory container.
  388. settingsMinRAMRange.onchange = (e) => {
  389. // Current range values
  390. const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
  391. const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
  392. // Get reference to range bar.
  393. const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
  394. // Calculate effective total memory.
  395. const max = (os.totalmem()-1000000000)/1000000000
  396. // Change range bar color based on the selected value.
  397. if(sMinV >= max/2){
  398. bar.style.background = '#e86060'
  399. } else if(sMinV >= max/4) {
  400. bar.style.background = '#e8e18b'
  401. } else {
  402. bar.style.background = null
  403. }
  404. // Increase maximum memory if the minimum exceeds its value.
  405. if(sMaxV < sMinV){
  406. const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
  407. updateRangedSlider(settingsMaxRAMRange, sMinV,
  408. ((sMinV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
  409. settingsMaxRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
  410. }
  411. // Update label
  412. settingsMinRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
  413. }
  414. // Bind on change event for max memory container.
  415. settingsMaxRAMRange.onchange = (e) => {
  416. // Current range values
  417. const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
  418. const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
  419. // Get reference to range bar.
  420. const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
  421. // Calculate effective total memory.
  422. const max = (os.totalmem()-1000000000)/1000000000
  423. // Change range bar color based on the selected value.
  424. if(sMaxV >= max/2){
  425. bar.style.background = '#e86060'
  426. } else if(sMaxV >= max/4) {
  427. bar.style.background = '#e8e18b'
  428. } else {
  429. bar.style.background = null
  430. }
  431. // Decrease the minimum memory if the maximum value is less.
  432. if(sMaxV < sMinV){
  433. const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
  434. updateRangedSlider(settingsMinRAMRange, sMaxV,
  435. ((sMaxV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
  436. settingsMinRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G'
  437. }
  438. settingsMaxRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G'
  439. }
  440. /**
  441. * Calculate common values for a ranged slider.
  442. *
  443. * @param {Element} v The range slider to calculate against.
  444. * @returns {Object} An object with meta values for the provided ranged slider.
  445. */
  446. function calculateRangeSliderMeta(v){
  447. const val = {
  448. max: Number(v.getAttribute('max')),
  449. min: Number(v.getAttribute('min')),
  450. step: Number(v.getAttribute('step')),
  451. }
  452. val.ticks = (val.max-val.min)/val.step
  453. val.inc = 100/val.ticks
  454. return val
  455. }
  456. /**
  457. * Binds functionality to the ranged sliders. They're more than
  458. * just divs now :').
  459. */
  460. function bindRangeSlider(){
  461. Array.from(document.getElementsByClassName('rangeSlider')).map((v) => {
  462. // Reference the track (thumb).
  463. const track = v.getElementsByClassName('rangeSliderTrack')[0]
  464. // Set the initial slider value.
  465. const value = v.getAttribute('value')
  466. const sliderMeta = calculateRangeSliderMeta(v)
  467. updateRangedSlider(v, value, ((value-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
  468. // The magic happens when we click on the track.
  469. track.onmousedown = (e) => {
  470. // Stop moving the track on mouse up.
  471. document.onmouseup = (e) => {
  472. document.onmousemove = null
  473. document.onmouseup = null
  474. }
  475. // Move slider according to the mouse position.
  476. document.onmousemove = (e) => {
  477. // Distance from the beginning of the bar in pixels.
  478. const diff = e.pageX - v.offsetLeft - track.offsetWidth/2
  479. // Don't move the track off the bar.
  480. if(diff >= 0 && diff <= v.offsetWidth-track.offsetWidth/2){
  481. // Convert the difference to a percentage.
  482. const perc = (diff/v.offsetWidth)*100
  483. // Calculate the percentage of the closest notch.
  484. const notch = Number(perc/sliderMeta.inc).toFixed(0)*sliderMeta.inc
  485. // If we're close to that notch, stick to it.
  486. if(Math.abs(perc-notch) < sliderMeta.inc/2){
  487. updateRangedSlider(v, sliderMeta.min+(sliderMeta.step*(notch/sliderMeta.inc)), notch)
  488. }
  489. }
  490. }
  491. }
  492. })
  493. }
  494. /**
  495. * Update a ranged slider's value and position.
  496. *
  497. * @param {Element} element The ranged slider to update.
  498. * @param {string | number} value The new value for the ranged slider.
  499. * @param {number} notch The notch that the slider should now be at.
  500. */
  501. function updateRangedSlider(element, value, notch){
  502. const oldVal = element.getAttribute('value')
  503. const bar = element.getElementsByClassName('rangeSliderBar')[0]
  504. const track = element.getElementsByClassName('rangeSliderTrack')[0]
  505. element.setAttribute('value', value)
  506. const event = new MouseEvent('change', {
  507. target: element,
  508. type: 'change',
  509. bubbles: false,
  510. cancelable: true
  511. })
  512. let cancelled = !element.dispatchEvent(event)
  513. if(!cancelled){
  514. track.style.left = notch + '%'
  515. bar.style.width = notch + '%'
  516. } else {
  517. element.setAttribute('value', oldVal)
  518. }
  519. }
  520. /**
  521. * Display the total and available RAM.
  522. */
  523. function populateMemoryStatus(){
  524. settingsMemoryTotal.innerHTML = Number((os.totalmem()-1000000000)/1000000000).toFixed(1) + 'G'
  525. settingsMemoryAvail.innerHTML = Number(os.freemem()/1000000000).toFixed(1) + 'G'
  526. }
  527. // Bind the executable file input to the display text input.
  528. settingsJavaExecSel.onchange = (e) => {
  529. settingsJavaExecVal.value = settingsJavaExecSel.files[0].path
  530. populateJavaExecDetails(settingsJavaExecVal.value)
  531. }
  532. /**
  533. * Validate the provided executable path and display the data on
  534. * the UI.
  535. *
  536. * @param {string} execPath The executable path to populate against.
  537. */
  538. function populateJavaExecDetails(execPath){
  539. AssetGuard._validateJavaBinary(execPath).then(v => {
  540. if(v.valid){
  541. settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major} Update ${v.version.update} (x${v.arch})`
  542. } else {
  543. settingsJavaExecDetails.innerHTML = 'Invalid Selection'
  544. }
  545. })
  546. }
  547. /**
  548. * Prepare the Java tab for display.
  549. */
  550. function prepareJavaTab(){
  551. bindRangeSlider()
  552. populateMemoryStatus()
  553. }
  554. /**
  555. * About Tab
  556. */
  557. const settingsAboutCurrentVersionCheck = document.getElementById('settingsAboutCurrentVersionCheck')
  558. const settingsAboutCurrentVersionTitle = document.getElementById('settingsAboutCurrentVersionTitle')
  559. const settingsAboutCurrentVersionValue = document.getElementById('settingsAboutCurrentVersionValue')
  560. const settingsChangelogTitle = document.getElementById('settingsChangelogTitle')
  561. const settingsChangelogText = document.getElementById('settingsChangelogText')
  562. const settingsChangelogButton = document.getElementById('settingsChangelogButton')
  563. // Bind the devtools toggle button.
  564. document.getElementById('settingsAboutDevToolsButton').onclick = (e) => {
  565. let window = remote.getCurrentWindow()
  566. window.toggleDevTools()
  567. }
  568. /**
  569. * Retrieve the version information and display it on the UI.
  570. */
  571. function populateVersionInformation(){
  572. const version = remote.app.getVersion()
  573. settingsAboutCurrentVersionValue.innerHTML = version
  574. const preRelComp = semver.prerelease(version)
  575. if(preRelComp != null && preRelComp.length > 0){
  576. settingsAboutCurrentVersionTitle.innerHTML = 'Pre-release'
  577. settingsAboutCurrentVersionTitle.style.color = '#ff886d'
  578. settingsAboutCurrentVersionCheck.style.background = '#ff886d'
  579. } else {
  580. settingsAboutCurrentVersionTitle.innerHTML = 'Stable Release'
  581. settingsAboutCurrentVersionTitle.style.color = null
  582. settingsAboutCurrentVersionCheck.style.background = null
  583. }
  584. }
  585. /**
  586. * Fetches the GitHub atom release feed and parses it for the release notes
  587. * of the current version. This value is displayed on the UI.
  588. */
  589. function populateReleaseNotes(){
  590. $.ajax({
  591. url: 'https://github.com/WesterosCraftCode/ElectronLauncher/releases.atom',
  592. success: (data) => {
  593. const version = 'v' + remote.app.getVersion()
  594. const entries = $(data).find('entry')
  595. for(let i=0; i<entries.length; i++){
  596. const entry = $(entries[i])
  597. let id = entry.find('id').text()
  598. id = id.substring(id.lastIndexOf('/')+1)
  599. if(id === version){
  600. settingsChangelogTitle.innerHTML = entry.find('title').text()
  601. settingsChangelogText.innerHTML = entry.find('content').text()
  602. settingsChangelogButton.href = entry.find('link').attr('href')
  603. }
  604. }
  605. },
  606. timeout: 2500
  607. }).catch(err => {
  608. settingsChangelogText.innerHTML = 'Failed to load release notes.'
  609. })
  610. }
  611. /**
  612. * Prepare account tab for display.
  613. */
  614. function prepareAboutTab(){
  615. populateVersionInformation()
  616. populateReleaseNotes()
  617. }
  618. /**
  619. * Settings preparation functions.
  620. */
  621. /**
  622. * Prepare the entire settings UI.
  623. *
  624. * @param {boolean} first Whether or not it is the first load.
  625. */
  626. function prepareSettings(first = false) {
  627. if(first){
  628. setupSettingsTabs()
  629. initSettingsValidators()
  630. }
  631. initSettingsValues()
  632. prepareAccountsTab()
  633. prepareJavaTab()
  634. prepareAboutTab()
  635. }
  636. // Prepare the settings UI on startup.
  637. prepareSettings(true)