import $ from 'jquery'; import { gt, coerce } from 'semver'; import { inflate, deflate } from 'pako'; // @ts-expect-error import idbExportImport from 'indexeddb-export-import'; declare global { interface Window { console: Console; gameWindow: Window | null; gameWindowIframe: Window | null; game: { play: (version?: string) => void; stop: (error?: string) => void; }; } } let selectedVersion: string | undefined = undefined; let articleAnimationLock = false; const theme = { load: function (themeToLoad?: string) { const themeElement = document.querySelector( '#theme', ) as HTMLLinkElement | null; if (themeElement) themeElement.href = themeToLoad ? `/resources/styles/themes/${themeToLoad}.css` : `/resources/styles/themes/${storage.local.get('theme') ?? 'default'}.css`; }, set: function (newTheme: string) { storage.local.set('theme', newTheme); theme.load(); }, }; const versionSelector = { open: function () { const options = document.querySelector('.installations div .options'); const selector = document.querySelector('.installations div .selector'); if (options && selector) { options.classList.add('open'); selector.classList.add('open'); } }, close: function () { const options = document.querySelector('.installations div .options'); const selector = document.querySelector('.installations div .selector'); if (options && selector) { options.classList.remove('open'); selector.classList.remove('open'); } }, toggle: function () { const options = document.querySelector('.installations div .options'); const selector = document.querySelector('.installations div .selector'); if (options && selector) { options.classList.toggle('open'); selector.classList.toggle('open'); } }, }; function consoleLog( type: 'debug' | 'log' | 'info' | 'warn' | 'error', msg: string, ) { if (type === 'info') { if (msg.includes('DEBUG')) type = 'debug'; else if (msg.includes('LOG')) type = 'log'; else if (msg.includes('INFO')) type = 'info'; else if (msg.includes('WARN')) type = 'warn'; else if (msg.includes('ERROR')) type = 'error'; } const consoleElement = document.querySelector('.console'); if (consoleElement) { const scrolledToBottom = Math.abs( consoleElement.scrollHeight - consoleElement.scrollTop - consoleElement.clientHeight, ) === 0; const messageElement = document.createElement('span'); messageElement.classList.add(type); messageElement.innerText = msg; consoleElement.append(messageElement); storage.session.set('console', consoleElement.innerHTML); if (scrolledToBottom) consoleElement.scroll(0, consoleElement.scrollHeight); } } const game = { play: function (version?: string) { if (window !== window.top) window.top?.game.play(version ?? selectedVersion); else { version = version ?? selectedVersion; if (!version) { alert('Please select a version to play.'); return; } storage.session.set('lastGame', selectedVersion); storage.session.set('playingGame', true); const console = document.querySelector('.console') as HTMLElement | null; if (console) console.style.display = 'flex'; if (!window.gameWindow || window.gameWindow.closed) { window.gameWindow = window.open('about:blank', '_blank', 'popup'); if (window.gameWindow) { window.gameWindow.document.title = 'MineXLauncher'; const icon = window.gameWindow.document.createElement('link'); icon.rel = 'icon'; icon.href = '/resources/images/icons/favicon.webp'; window.gameWindow.document.head.append(icon); const iframe = window.gameWindow.document.createElement('iframe'); iframe.src = version; iframe.width = '100%'; iframe.height = '100%'; iframe.style.position = 'fixed'; iframe.style.top = '0'; iframe.style.left = '0'; iframe.style.border = 'none'; window.gameWindow.document.body.append(iframe); if (iframe.contentWindow) { ( ['debug', 'log', 'info', 'warn', 'error'] as ( | 'debug' | 'log' | 'info' | 'warn' | 'error' )[] ).forEach((type) => { if (iframe.contentWindow) iframe.contentWindow.console[type] = (msg: string) => consoleLog(type, msg); }); ['keydown', 'keyup'].forEach((eventType) => window.gameWindow?.addEventListener(eventType, (event) => iframe.contentWindow?.dispatchEvent( new KeyboardEvent(eventType, event), ), ), ); window.gameWindow.focus(); window.gameWindow.document.documentElement.requestFullscreen(); } window.gameWindowIframe = iframe.contentWindow; } } else { window.gameWindow.focus(); const console = document.querySelector( '.console', ) as HTMLElement | null; if (console) console.style.display = 'flex'; } const waitForCrash = setInterval(() => { if (window.gameWindow?.closed) { clearInterval(waitForCrash); game.stop(undefined, { killed: true }); } else { window.gameWindowIframe?.document .querySelectorAll('div') .forEach((element: HTMLElement) => { if ( element.innerText.includes( "Game Crashed! I have fallen and I can't get up!", ) ) { clearInterval(waitForCrash); game.stop(element.innerText); } }); } }, 50); } }, stop: function (error?: string, options?: { killed: boolean }) { if (window !== window.top) window.top?.game.stop(error); else { if ( window.gameWindow && (!window.gameWindow.closed || options?.killed) && window.gameWindowIframe ) { window.gameWindow.onbeforeunload = null; window.gameWindowIframe.onbeforeunload = null; window.gameWindow.close(); storage.session.set('playingGame', false); if (error) { consoleLog('error', 'MineXLauncher: Game crashed with error:'); consoleLog('error', error); } else { consoleLog('error', 'MineXLauncher: Game process killed by user'); } window.focus(); } } }, select: function (path: string, name?: string) { selectedVersion = path; const selector = document.querySelector('.installations div .selector'); if (selector?.textContent) { if (name) selector.textContent = `Selected: ${name}`; else selector.textContent = `Selected: ${path}`; } versionSelector.close(); }, archive: function (client: string) { const clients: Record = { '1.8': '18-client-version', '1.5': '15-client-version', 'b1.3': 'b13-client-version', }; const dropdown = clients[client] ? (document.querySelector( `select[id='${clients[client]}']`, ) as HTMLSelectElement | null) : null; if (dropdown?.value) { selectedVersion = `https://archive.eaglercraft.rip/Eaglercraft${client === '1.8' ? 'X_1.8' : `_${client}`}/client/${dropdown.value}/index.html`; game.play(); } }, }; const navigate = { home: { game: function () { document.body.style.display = 'none'; const navUrl = '/home/game/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, clients: function () { document.body.style.display = 'none'; const navUrl = '/home/clients/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, archive: function () { document.body.style.display = 'none'; const navUrl = '/home/archive/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, downloads: function () { document.body.style.display = 'none'; const navUrl = '/home/downloads/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, }, mods: { mods: function () { document.body.style.display = 'none'; const navUrl = '/mods/mods/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, resourcepacks: function () { document.body.style.display = 'none'; const navUrl = '/mods/resourcepacks/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, }, settings: { general: function () { const navUrl = '/settings/general/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, backups: function () { const navUrl = '/settings/backups/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, }, articles: function () { const navUrl = '/articles/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, mobile: function () { const navUrl = '/mobile/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, updates: function () { const navUrl = '/updates/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, servers: function () { const navUrl = '/servers/'; storage.session.set('lastPage', navUrl); window.location.href = navUrl; }, }; function openAboutBlank( url: string, options?: { title?: string; favicon?: string }, ): Window | null { const newWindow = window.open('about:blank', '_blank', 'popup'); if (newWindow) { if (options?.title) newWindow.document.title = options.title; if (options?.favicon) { const icon = newWindow.document.createElement('link'); icon.rel = 'icon'; icon.href = options.favicon; newWindow.document.head.append(icon); } const iframe = newWindow.document.createElement('iframe'); iframe.src = url; iframe.width = '100%'; iframe.height = '100%'; iframe.style.position = 'fixed'; iframe.style.top = '0'; iframe.style.left = '0'; iframe.style.border = 'none'; newWindow.onbeforeunload = () => newWindow.close(); newWindow.document.body.append(iframe); } return newWindow; } const article = { open: function (articleId: string) { const modal = document.querySelector( `.article[data-article-id='${articleId}']`, ) as HTMLElement | null; const modalContent = document.querySelector( `.article[data-article-id='${articleId}'] > div`, ) as HTMLElement | null; if (!articleAnimationLock && modal && modalContent) { articleAnimationLock = true; modal.style.animation = '0.5s ease-in-out 1 normal article'; modal.style.display = 'flex'; modalContent.scroll({ top: 0, left: 0, behavior: 'instant' }); modal.addEventListener( 'animationend', () => { const closeArticleHandler = (event: Event) => { if (event.target === modal) { modal.removeEventListener('click', closeArticleHandler); article.close(articleId); } }; modal.addEventListener('click', closeArticleHandler); articleAnimationLock = false; }, { once: true }, ); } }, close: function (articleId: string) { const modal = document.querySelector( `.article[data-article-id='${articleId}']`, ) as HTMLElement | null; if (!articleAnimationLock && modal) { articleAnimationLock = true; modal.style.animation = '0.5s ease-in-out 1 reverse article-tempfix'; modal.addEventListener( 'animationend', () => { modal.style.display = 'none'; articleAnimationLock = false; }, { once: true }, ); } }, }; /*const cookie = { set: function (key: string, value: string | number | object | [] | boolean | null | undefined, days: number) { let maxAge; if (days) { maxAge = days * 60 * 60 * 24; } else { maxAge = 31536000; } document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; SameSite=Lax; Secure`; }, get: function (key: string) { for (const cookie of document.cookie.split('; ')) { const cookiePair = cookie.split('='); if (encodeURIComponent(key) === cookiePair[0]) { return decodeURIComponent(cookiePair[1]); } } return undefined; }, delete: function (key: string) { document.cookie = `${encodeURIComponent(key)}=; Max-Age=0; Path=/`; }, };*/ const storage = { local: { get: function (key: string) { const item = localStorage.getItem('minexlauncher'); if (item !== null) { const json = JSON.parse(base64Gzip.decompress(item)); if (json[key] !== undefined) { return json[key]; } return undefined; } return undefined; }, set: function ( key: string, value: string | number | object | boolean | null | undefined, ) { const item = localStorage.getItem('minexlauncher'); if (item === null) { const json: Record = {}; json[key] = value; localStorage.setItem( 'minexlauncher', base64Gzip.compress(JSON.stringify(json)), ); } else { const json = JSON.parse(base64Gzip.decompress(item)); json[key] = value; localStorage.setItem( 'minexlauncher', base64Gzip.compress(JSON.stringify(json)), ); } }, }, session: { get: function (key: string) { const item = sessionStorage.getItem('minexlauncher'); if (item !== null) { const json = JSON.parse(base64Gzip.decompress(item)); if (json[key] !== undefined) { return json[key]; } return undefined; } return undefined; }, set: function ( key: string, value: string | number | object | boolean | null | undefined, ) { const item = sessionStorage.getItem('minexlauncher'); if (item === null) { const json: Record = {}; json[key] = value; sessionStorage.setItem( 'minexlauncher', base64Gzip.compress(JSON.stringify(json)), ); } else { const json = JSON.parse(base64Gzip.decompress(item)); json[key] = value; sessionStorage.setItem( 'minexlauncher', base64Gzip.compress(JSON.stringify(json)), ); } }, }, }; const query = { get: function (name: string) { return new URLSearchParams(window.top?.location.search).get(name); }, }; const detect = { mobile: function (): boolean { try { document.exitPointerLock(); return /Mobi/i.test(window.navigator.userAgent); } catch { return true; } }, landscape: function () { return window.innerWidth > window.innerHeight; }, }; const mods = { toggle: function (modId: string): void { const mod = `/resources/mods/downloads/${modId}.js`; const mods: string[] = storage.local.get('mods') ?? []; const modIndex = mods.indexOf(mod); if (modIndex === -1) { mods.push(mod); mods.sort(); storage.local.set('mods', mods); const modInstallElem = document.querySelector( `.mod-list > div .links .install[data-mod-id='${modId}']`, ); if (modInstallElem) { modInstallElem.textContent = 'Uninstall'; modInstallElem.classList.add('installed'); } } else { mods.splice(modIndex, 1); storage.local.set('mods', mods); const modInstallElem = document.querySelector( `.mod-list > div .links .install[data-mod-id='${modId}']`, ); if (modInstallElem) { modInstallElem.textContent = 'Install'; modInstallElem.classList.remove('installed'); } } }, }; const backups = { export: async function () { const exportData: { cookies: Record; localStorage: Record; indexedDb: Record; } = { cookies: {}, localStorage: { ...localStorage }, indexedDb: {}, }; document.cookie.split('; ').forEach((cookie) => { const pair = cookie.split('='); if (pair[0] !== undefined && pair[1] !== undefined) exportData.cookies[pair[0]] = pair[1]; }); for (const dbInfo of await indexedDB.databases()) { if (dbInfo.name) indexedDB.open(dbInfo.name).onsuccess = (event) => (exportData.indexedDb[dbInfo.name ?? ''] = idbExportImport.exportToJsonString( (event.target as IDBOpenDBRequest).result, )); } const url = URL.createObjectURL( new Blob([base64Gzip.compress(JSON.stringify(exportData))]), ); const a = document.createElement('a'); a.href = url; a.target = '_blank'; a.download = `MineXLauncher_${new Date().toLocaleDateString('en-GB').replace(/\//g, '-')}.backup`; document.body.append(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, import: async function () { await new Promise((resolve, reject) => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.backup'; fileInput.multiple = false; fileInput.onchange = (event) => { const file = (event.target as HTMLInputElement).files?.[0]; if (file) resolve(file); else reject(); }; fileInput.oncancel = () => reject(); document.body.append(fileInput); fileInput.click(); document.body.removeChild(fileInput); }) .then(async (file) => { const importData = JSON.parse(base64Gzip.decompress(await file.text())); // @ts-expect-error if (typeof cookieStore !== 'undefined') // @ts-expect-error await cookieStore.getAll().then((cookies: object[]) => // @ts-expect-error cookies.forEach((cookie) => cookieStore.delete(cookie)), ); else document.cookie .split('; ') .forEach( (cookie) => (document.cookie = `${cookie.split('=')[0]}=; Path=/; Max-Age=0`), ); localStorage.clear(); for (const dbInfo of await indexedDB.databases()) if (dbInfo.name) indexedDB.deleteDatabase(dbInfo.name); for (const key in importData.localStorage) if (importData.localStorage[key] !== undefined) localStorage.setItem(key, importData.localStorage[key]); for (const key in importData.cookies) document.cookie = `${key}=${importData.cookies[key]}; Max-Age=${365 * 24 * 60 * 60}; Path=/; SameSite=Lax; Secure`; if (Object.keys(importData.indexedDb).length > 0) for (const dbName in importData.indexedDb) { const dbRequest = indexedDB.open(dbName); dbRequest.onsuccess = async (event) => { const db = (event.target as IDBOpenDBRequest).result; const transaction = db.transaction( Array.from(db.objectStoreNames), 'readwrite', ); for (const storeName of Array.from(db.objectStoreNames)) transaction.objectStore(storeName).clear(); transaction.oncomplete = () => idbExportImport.importFromJsonString( db, importData.indexedDb[dbName], ); }; } window.location.reload(); }) .catch((error) => console.error(error)); }, }; const sw = { register: function (url: string) { if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register(url).then(() => { navigator.serviceWorker.addEventListener('message', (event) => { if (event.origin === window.location.origin) { if (event.data.title === 'sw-install-complete') { alert('MineXLauncher is now ready for offline use!'); } } }); }); }); } }, }; const base64Gzip = { compress: function (string: string): string { return btoa( String.fromCharCode(...deflate(new TextEncoder().encode(string))), ); }, decompress: function (string: string): string { return inflate( Uint8Array.from(atob(string), (c) => c.charCodeAt(0)), { to: 'string' }, ); }, }; function downloadFile(url: string, name?: string) { const a = document.createElement('a'); a.href = url; a.download = name ?? ''; document.body.append(a); a.click(); document.body.removeChild(a); } if (window.location.pathname === '/') { const lastPage = storage.session.get('lastPage'); const isMobile = detect.mobile(); const iframe = document.createElement('iframe'); iframe.src = !storage.local.get('lastVersion') ? '/welcome/' : lastPage ? lastPage : isMobile ? '/mobile/' : '/home/game/'; document.addEventListener('DOMContentLoaded', () => { document.body.append(iframe); const consoleElement = document.querySelector( '.console', ) as HTMLElement | null; if (consoleElement) { const consoleHistory = storage.session.get('console'); if (consoleHistory) { consoleElement.innerHTML = consoleHistory; consoleElement.style.display = 'flex'; consoleElement.scroll(0, consoleElement.scrollHeight); } consoleElement.style.display = storage.session.get('playingGame') ? 'flex' : ''; } }); /* document.addEventListener('load', () => { if (storage.local.get('offlineCache')) { sw.register('/sw-full.js'); } else { sw.register('/sw.js'); } }); */ document.addEventListener('load', () => sw.register('/sw.js')); window.addEventListener('beforeunload', () => game.stop()); window.addEventListener('beforeinstallprompt', (event) => { // @ts-expect-error if (iframe.contentWindow) iframe.contentWindow.installPwaEvent = event; }); } else { if (detect.mobile()) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = '/resources/styles/mobile.css'; document.head.append(link); } theme.load(); document.addEventListener('DOMContentLoaded', async () => { const profileName = document.querySelector('.username'); const titleBarText = document.querySelector('.title-bar'); const lastVersion = storage.local.get('lastVersion'); const updateData = (await (await fetch('/resources/data/main.json')).json()) .updates; const currentVersion = updateData[0].version; const changelog = updateData[0].changelog .map((change: string) => ` - ${change}`) .join('\n'); if (profileName) profileName.textContent = storage.local.get('username'); if (titleBarText) titleBarText.textContent += ` ${currentVersion}`; if ( lastVersion && gt( // @ts-expect-error coerce(currentVersion, { includePrerelease: true }), coerce(lastVersion, { includePrerelease: true }), ) ) { alert( `MineXLauncher has been updated to v${currentVersion}!\n\nChanges in v${currentVersion}:\n${changelog}`, ); storage.local.set('lastVersion', currentVersion); } }); /* if (storage.local.get('showAds')) { const googleAdsScript = document.createElement('script'); googleAdsScript.async = true; googleAdsScript.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1132419379737567'; googleAdsScript.crossOrigin = 'anonymous'; document.head.append(googleAdsScript); document.addEventListener('DOMContentLoaded', () => { const googleAdsPush = document.createElement('script'); googleAdsPush.text = '(adsbygoogle = window.adsbygoogle || []).push({});'; document.body.append(googleAdsPush); const adsContainers = document.querySelectorAll('.ads-container'); adsContainers.forEach((adsContainer) => { adsContainer.style.display = 'flex'; }); }); } */ } if (window.location.pathname === '/settings/general/') { document.addEventListener('DOMContentLoaded', async () => { const profileName = document.querySelector('.username'); const usernameInput = document.querySelector( '#username-input', ) as HTMLInputElement | null; const themeSelect = document.querySelector( '#theme-select', ) as HTMLSelectElement | null; const offlineCheckbox = document.querySelector( '#offline-checkbox', ) as HTMLInputElement | null; const adsCheckbox = document.querySelector( '#ads-checkbox', ) as HTMLInputElement | null; const themeData: { id: string; name: string }[] = ( await (await fetch('/resources/data/main.json')).json() ).themes; if (usernameInput) { usernameInput.placeholder = storage.local.get('username') ?? ''; usernameInput.addEventListener('input', () => { let username = usernameInput.value .replace(/[^A-Za-z0-9]/g, '_') .substring(0, 16); usernameInput.value = username; while (username.length < 3) username += '_'; storage.local.set('username', username); if (profileName) profileName.textContent = username; }); } if (themeSelect) { themeData.forEach((theme: { id: string; name: string }) => { const option = document.createElement('option'); option.value = theme.id; option.textContent = theme.name; themeSelect?.append(option); }); themeSelect.value = storage.local.get('theme') ?? ''; themeSelect.addEventListener('change', () => theme.set(themeSelect.value ?? 'default'), ); } if (offlineCheckbox) { offlineCheckbox.checked = storage.local.get('offlineCache') ?? false; offlineCheckbox.addEventListener('change', () => { storage.local.set('offlineCache', offlineCheckbox.checked); if (offlineCheckbox.checked) { sw.register('/sw-full.js'); alert( 'Offline cache is now downloading.\nThe download size is about 1GB, so it may take a while.\n\nPlease do not leave this page while the download is in progress.\nYou will be notified when the download is complete.', ); } else { sw.register('/sw.js'); alert('Offline cache has been deleted.'); } }); } if (adsCheckbox) { adsCheckbox.checked = storage.local.get('showAds'); adsCheckbox.addEventListener('change', () => { storage.local.set('showAds', adsCheckbox.checked); const adsContainers = document.querySelectorAll('.ads-container'); adsContainers.forEach( (adsContainer: Element) => ((adsContainer as HTMLElement).style.display = 'none'), ); }); } }); } else if (window.location.pathname === '/welcome/') { document.addEventListener('DOMContentLoaded', async () => { const setupForm = document.querySelector( '#setup-form', ) as HTMLFormElement | null; const usernameInput = document.querySelector( '#username-input', ) as HTMLInputElement | null; const themeSelect = document.querySelector( '#theme-select', ) as HTMLSelectElement | null; const offlineCheckbox = document.querySelector( '#offline-checkbox', ) as HTMLInputElement | null; const themeData: { id: string; name: string }[] = ( await (await fetch('/resources/data/main.json')).json() ).themes; if (setupForm) { if (usernameInput) { usernameInput.addEventListener('input', () => { const username = usernameInput.value .replace(/[^A-Za-z0-9]/g, '_') .substring(0, 16); usernameInput.value = username; }); } if (themeSelect) { themeData.forEach((theme: { id: string; name: string }) => { const option = document.createElement('option'); option.value = theme.id; option.textContent = theme.name; themeSelect?.append(option); }); themeSelect.addEventListener('change', () => theme.load(themeSelect.value ?? 'default'), ); } setupForm.addEventListener('submit', async (event) => { event.preventDefault(); let username = usernameInput?.value .replace(/[^A-Za-z0-9]/g, '_') .substring(0, 16); if (usernameInput) usernameInput.value = username ?? ''; if (!username) { alert('Please type a username.'); return; } else { while (username.length < 3) username += '_'; storage.local.set('username', username); storage.local.set('theme', themeSelect?.value ?? 'default'); // storage.local.set('offlineCache', offlineCheckbox?.checked ?? false); // storage.local.set('showAds', true); storage.local.set('mods', []); storage.local.set( 'lastVersion', (await (await fetch('/resources/data/main.json')).json()).updates[0] .version, ); if (offlineCheckbox?.checked) { sw.register('/sw-full.js'); alert( 'Offline cache is now downloading.\nThe download size is about 1GB, so it may take a while.\n\nPlease do not leave this page while the download is in progress.\nYou will be notified when the download is complete.', ); } else sw.register('/sw.js'); // @ts-expect-error window.top.location.href = '/'; } }); } }); } else if ( window.location.pathname === '/mods/mods/' || window.location.pathname === '/mods/resourcepacks/' ) { document.addEventListener('DOMContentLoaded', async () => { const addonType: 'mods' | 'resourcepacks' = window.location.pathname === '/mods/mods/' ? 'mods' : 'resourcepacks'; const data: { id: string; name: string; description: string; author: string; authorLink: string; source: string; }[] = (await (await fetch('/resources/data/main.json')).json()).addons; const modList = document.querySelector('.mod-list'); // @ts-expect-error data[addonType].forEach((addon) => { const modItem = document.createElement('div'); modItem.innerHTML = `
${ addon.name }

By ${addon.author}

${addon.description}

`; modList?.append(modItem); }); if (addonType === 'mods') { const installedMods = storage.local.get('mods') ?? []; const modElements = document.querySelectorAll( '.mod-list > div .links .install', ); modElements.forEach((element) => { const modId = (element as HTMLElement).dataset['modId']; if (installedMods.includes(`/resources/mods/downloads/${modId}.js`)) { element.textContent = 'Uninstall'; element.classList.add('installed'); } }); } }); } else if (window.location.pathname === '/updates/') { document.addEventListener('DOMContentLoaded', async () => { const updatesContainer = document.querySelector('.updates-container'); const data: { version: string; changelog: string[] }[] = ( await (await fetch('/resources/data/main.json')).json() ).updates; data.forEach((update) => { const version = document.createElement('div'); const name = document.createElement('strong'); name.textContent = `MineXLauncher ${update.version}`; const changes = document.createElement('ul'); update.changelog.forEach((change) => { const item = document.createElement('li'); item.textContent = change; changes.append(item); }); version.append(name); version.append(changes); updatesContainer?.append(version); }); }); } else if ( window.location.pathname === '/home/game/' || window.location.pathname === '/home/clients/' ) { const lastGame = storage.session.get('lastGame'); if (lastGame) game.select(lastGame); } if (window.location.hostname === null) { // Stop the minifier from removing these functions console.debug([ navigate, query, versionSelector, game, mods, base64Gzip, article, downloadFile, backups, consoleLog, openAboutBlank, ]); }