uibinder.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /**
  2. * Initialize UI functions which depend on internal modules.
  3. * Loaded after core UI functions are initialized in uicore.js.
  4. */
  5. // Requirements
  6. const path = require('path')
  7. const AuthManager = require('./assets/js/authmanager.js')
  8. const {AssetGuard} = require('./assets/js/assetguard.js')
  9. const ConfigManager = require('./assets/js/configmanager.js')
  10. let rscShouldLoad = false
  11. let fatalStartupError = false
  12. // Mapping of each view to their container IDs.
  13. const VIEWS = {
  14. landing: '#landingContainer',
  15. login: '#loginContainer',
  16. settings: '#settingsContainer',
  17. welcome: '#welcomeContainer'
  18. }
  19. // The currently shown view container.
  20. let currentView
  21. /**
  22. * Switch launcher views.
  23. *
  24. * @param {string} current The ID of the current view container.
  25. * @param {*} next The ID of the next view container.
  26. * @param {*} currentFadeTime Optional. The fade out time for the current view.
  27. * @param {*} nextFadeTime Optional. The fade in time for the next view.
  28. * @param {*} onCurrentFade Optional. Callback function to execute when the current
  29. * view fades out.
  30. * @param {*} onNextFade Optional. Callback function to execute when the next view
  31. * fades in.
  32. */
  33. function switchView(current, next, currentFadeTime = 500, nextFadeTime = 500, onCurrentFade = () => {}, onNextFade = () => {}){
  34. currentView = next
  35. $(`${current}`).fadeOut(currentFadeTime, () => {
  36. onCurrentFade()
  37. $(`${next}`).fadeIn(nextFadeTime, () => {
  38. onNextFade()
  39. })
  40. })
  41. }
  42. /**
  43. * Get the currently shown view container.
  44. *
  45. * @returns {string} The currently shown view container.
  46. */
  47. function getCurrentView(){
  48. return currentView
  49. }
  50. function showMainUI(){
  51. if(!isDev){
  52. console.log('%c[AutoUpdater]', 'color: #a02d2a; font-weight: bold', 'Initializing..')
  53. ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
  54. }
  55. updateSelectedServer(AssetGuard.getServerById(ConfigManager.getSelectedServer()).name)
  56. refreshServerStatus()
  57. setTimeout(() => {
  58. document.getElementById('frameBar').style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
  59. document.body.style.backgroundImage = `url('assets/images/backgrounds/${document.body.getAttribute('bkid')}.jpg')`
  60. $('#main').show()
  61. const isLoggedIn = Object.keys(ConfigManager.getAuthAccounts()).length > 0
  62. // If this is enabled in a development environment we'll get ratelimited.
  63. // The relaunch frequency is usually far too high.
  64. if(!isDev && isLoggedIn){
  65. validateSelectedAccount().then((v) => {
  66. if(v){
  67. console.log('%c[AuthManager]', 'color: #209b07; font-weight: bold', 'Account access token validated.')
  68. } else {
  69. console.log('%c[AuthManager]', 'color: #a02d2a; font-weight: bold', 'Account access token is invalid.')
  70. }
  71. })
  72. }
  73. if(ConfigManager.isFirstLaunch()){
  74. currentView = VIEWS.welcome
  75. $(VIEWS.welcome).fadeIn(1000)
  76. } else {
  77. if(isLoggedIn){
  78. currentView = VIEWS.landing
  79. $(VIEWS.landing).fadeIn(1000)
  80. } else {
  81. currentView = VIEWS.login
  82. $(VIEWS.login).fadeIn(1000)
  83. }
  84. }
  85. setTimeout(() => {
  86. $('#loadingContainer').fadeOut(500, () => {
  87. $('#loadSpinnerImage').removeClass('rotating')
  88. })
  89. }, 250)
  90. }, 750)
  91. // Disable tabbing to the news container.
  92. initNews().then(() => {
  93. $("#newsContainer *").attr('tabindex', '-1')
  94. })
  95. }
  96. function showFatalStartupError(){
  97. setTimeout(() => {
  98. $('#loadingContainer').fadeOut(250, () => {
  99. document.getElementById('overlayContainer').style.background = 'none'
  100. setOverlayContent(
  101. 'Fatal Error: Unable to Load Distribution Index',
  102. 'A connection could not be established to our servers to download the distribution index. No local copies were available to load. <br><br>The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application.',
  103. 'Close'
  104. )
  105. setOverlayHandler(() => {
  106. const window = remote.getCurrentWindow()
  107. window.close()
  108. })
  109. toggleOverlay(true)
  110. })
  111. }, 750)
  112. }
  113. /**
  114. * Common functions to perform after refreshing the distro index.
  115. *
  116. * @param {Object} data The distro index object.
  117. */
  118. function onDistroRefresh(data){
  119. updateSelectedServer(AssetGuard.getServerById(ConfigManager.getSelectedServer()).name)
  120. refreshServerStatus()
  121. initNews()
  122. syncModConfigurations(data)
  123. }
  124. /**
  125. * Sync the mod configurations with the distro index.
  126. *
  127. * @param {Object} data The distro index object.
  128. */
  129. function syncModConfigurations(data){
  130. const syncedCfgs = []
  131. const servers = data.servers
  132. for(let i=0; i<servers.length; i++){
  133. const id = servers[i].id
  134. const mdls = servers[i].modules
  135. const cfg = ConfigManager.getModConfiguration(servers[i].id)
  136. if(cfg != null){
  137. const modsOld = cfg.mods
  138. const mods = {}
  139. for(let j=0; j<mdls.length; j++){
  140. const mdl = mdls[j]
  141. if(mdl.type === 'forgemod' || mdl.type === 'litemod' || mdl.type === 'liteloader'){
  142. if(mdl.required != null && mdl.required.value != null && mdl.required.value === false){
  143. const mdlID = AssetGuard._resolveWithoutVersion(mdl.id)
  144. if(modsOld[mdlID] == null){
  145. mods[mdlID] = scanOptionalSubModules(mdl.sub_modules, mdl)
  146. } else {
  147. mods[mdlID] = mergeModConfiguration(modsOld[mdlID], scanOptionalSubModules(mdl.sub_modules, mdl))
  148. }
  149. }
  150. }
  151. }
  152. syncedCfgs.push({
  153. id,
  154. mods
  155. })
  156. } else {
  157. const mods = {}
  158. for(let j=0; j<mdls.length; j++){
  159. const mdl = mdls[j]
  160. if(mdl.type === 'forgemod' || mdl.type === 'litemod' || mdl.type === 'liteloader'){
  161. if(mdl.required != null && mdl.required.value != null && mdl.required.value === false){
  162. mods[AssetGuard._resolveWithoutVersion(mdl.id)] = scanOptionalSubModules(mdl.sub_modules, mdl)
  163. }
  164. }
  165. }
  166. syncedCfgs.push({
  167. id,
  168. mods
  169. })
  170. }
  171. }
  172. ConfigManager.setModConfigurations(syncedCfgs)
  173. ConfigManager.save()
  174. }
  175. /**
  176. * Recursively scan for optional sub modules. If none are found,
  177. * this function returns a boolean. If optional sub modules do exist,
  178. * a recursive configuration object is returned.
  179. *
  180. * @returns {boolean | Object} The resolved mod configuration.
  181. */
  182. function scanOptionalSubModules(mdls, origin){
  183. if(mdls != null){
  184. const mods = {}
  185. for(let i=0; i<mdls.length; i++){
  186. const mdl = mdls[i]
  187. // Optional types.
  188. if(mdl.type === 'forgemod' || mdl.type === 'litemod' || mdl.type === 'liteloader'){
  189. // It is optional.
  190. if(mdl.required != null && mdl.required.value != null && mdl.required.value === false){
  191. mods[AssetGuard._resolveWithoutVersion(mdl.id)] = scanOptionalSubModules(mdl.sub_modules, mdl)
  192. }
  193. }
  194. }
  195. if(Object.keys(mods).length > 0){
  196. return {
  197. value: origin.required != null && origin.required.def != null ? origin.required.def : true,
  198. mods
  199. }
  200. }
  201. }
  202. return origin.required != null && origin.required.def != null ? origin.required.def : true
  203. }
  204. /**
  205. * Recursively merge an old configuration into a new configuration.
  206. *
  207. * @param {boolean | Object} o The old configuration value.
  208. * @param {boolean | Object} n The new configuration value.
  209. *
  210. * @returns {boolean | Object} The merged configuration.
  211. */
  212. function mergeModConfiguration(o, n){
  213. if(typeof o === 'boolean'){
  214. if(typeof n === 'boolean') return o
  215. else if(typeof n === 'object'){
  216. n.value = o
  217. return n
  218. }
  219. } else if(typeof o === 'object'){
  220. if(typeof n === 'boolean') return o.value
  221. else if(typeof n === 'object'){
  222. n.value = o.value
  223. const newMods = Object.keys(n.mods)
  224. for(let i=0; i<newMods.length; i++){
  225. const mod = newMods[i]
  226. if(o.mods[mod] != null){
  227. n.mods[mod] = mergeModConfiguration(o.mods[mod], n.mods[mod])
  228. }
  229. }
  230. return n
  231. }
  232. }
  233. // If for some reason we haven't been able to merge,
  234. // wipe the old value and use the new one. Just to be safe
  235. return n
  236. }
  237. function refreshDistributionIndex(remote, onSuccess, onError){
  238. if(remote){
  239. AssetGuard.refreshDistributionDataRemote(ConfigManager.getLauncherDirectory())
  240. .then(onSuccess)
  241. .catch(onError)
  242. } else {
  243. AssetGuard.refreshDistributionDataLocal(ConfigManager.getLauncherDirectory())
  244. .then(onSuccess)
  245. .catch(onError)
  246. }
  247. }
  248. async function validateSelectedAccount(){
  249. const selectedAcc = ConfigManager.getSelectedAccount()
  250. if(selectedAcc != null){
  251. const val = await AuthManager.validateSelected()
  252. if(!val){
  253. ConfigManager.removeAuthAccount(selectedAcc.uuid)
  254. ConfigManager.save()
  255. const accLen = Object.keys(ConfigManager.getAuthAccounts()).length
  256. setOverlayContent(
  257. 'Failed to Refresh Login',
  258. `We were unable to refresh the login for <strong>${selectedAcc.displayName}</strong>. Please ${accLen > 0 ? 'select another account or ' : ''} login again.`,
  259. 'Login',
  260. 'Select Another Account'
  261. )
  262. setOverlayHandler(() => {
  263. document.getElementById('loginUsername').value = selectedAcc.username
  264. validateEmail(selectedAcc.username)
  265. loginViewOnSuccess = getCurrentView()
  266. switchView(getCurrentView(), VIEWS.login)
  267. toggleOverlay(false)
  268. })
  269. setDismissHandler(() => {
  270. if(accLen > 1){
  271. prepareAccountSelectionList()
  272. $('#overlayContent').fadeOut(250, () => {
  273. $('#accountSelectContent').fadeIn(250)
  274. })
  275. } else {
  276. const accountsObj = ConfigManager.getAuthAccounts()
  277. const accounts = Array.from(Object.keys(accountsObj), v => accountsObj[v]);
  278. // This function validates the account switch.
  279. setSelectedAccount(accounts[0].uuid)
  280. toggleOverlay(false)
  281. }
  282. })
  283. toggleOverlay(true, accLen > 0)
  284. } else {
  285. return true
  286. }
  287. } else {
  288. return true
  289. }
  290. }
  291. /**
  292. * Temporary function to update the selected account along
  293. * with the relevent UI elements.
  294. *
  295. * @param {string} uuid The UUID of the account.
  296. */
  297. function setSelectedAccount(uuid){
  298. const authAcc = ConfigManager.setSelectedAccount(uuid)
  299. ConfigManager.save()
  300. updateSelectedAccount(authAcc)
  301. validateSelectedAccount()
  302. }
  303. // Synchronous Listener
  304. document.addEventListener('readystatechange', function(){
  305. if (document.readyState === 'complete'){
  306. if(rscShouldLoad){
  307. if(!fatalStartupError){
  308. showMainUI()
  309. } else {
  310. showFatalStartupError()
  311. }
  312. }
  313. } else if(document.readyState === 'interactive'){
  314. //toggleOverlay(true, 'loadingContent')
  315. }
  316. /*if (document.readyState === 'interactive'){
  317. }*/
  318. }, false)
  319. // Actions that must be performed after the distribution index is downloaded.
  320. ipcRenderer.on('distributionIndexDone', (event, data) => {
  321. if(data != null) {
  322. syncModConfigurations(data)
  323. if(document.readyState === 'complete'){
  324. showMainUI()
  325. } else {
  326. rscShouldLoad = true
  327. }
  328. } else {
  329. fatalStartupError = true
  330. if(document.readyState === 'complete'){
  331. showFatalStartupError()
  332. } else {
  333. rscShouldLoad = true
  334. }
  335. }
  336. })