4
0
mirror of https://github.com/AltarikMC/Launcher synced 2024-11-25 06:49:51 +01:00

added vue as front-end framework

This commit is contained in:
Quentin Legot 2021-09-19 17:31:08 +02:00
parent d8ada09563
commit b959d35ee9
13 changed files with 259 additions and 235 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ game
out/
.vscode/
*.code-workspace
.idea

View File

@ -1,6 +1 @@
# Launcher
## Dependencies
- <https://www.npmjs.com/package/electron>
- <https://www.npmjs.com/package/minecraft-launcher-core>

5
package-lock.json generated
View File

@ -7500,6 +7500,11 @@
"extsprintf": "^1.2.0"
}
},
"vue": {
"version": "2.6.14",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

View File

@ -36,7 +36,8 @@
"extract-zip": "^2.0.1",
"hasha": "^5.2.2",
"minecraft-launcher-core": "^3.16.8",
"msmc": "^2.2.1"
"msmc": "^2.2.1",
"vue": "^2.6.14"
},
"config": {
"forge": "./config.forge.js"

View File

@ -148,7 +148,6 @@ h3 {
width: 100%;
height: calc(100% - 28px);
background-color: rgba(0, 0, 0, 0.6);
display: none;
z-index: 10;
}
@ -212,3 +211,10 @@ h3 {
border-width:0;
border-top: 1px solid black;
}
/* .slider {
min-width: 60%;
position:relative;
left:50%;
transform: translateX(-50%);
} */

View File

@ -65,3 +65,8 @@
position: relative;
top: 2px;
}
#vue {
width: 100vw;
height: 100vh;
}

View File

@ -1,50 +1,48 @@
const os = require('os')
const launchBtn = document.querySelector('#launch-btn')
const launchText = document.querySelector("#launch-text")
const fullProgressBar = document.querySelector('#fullprogressbar')
const progressBar = document.querySelector('#progressbar')
const loadingMessage = document.querySelector('#loading-message')
const disconnectBtn = document.querySelector('#disconnect-btn')
const fullscreen = document.querySelector('#fullscreen')
const minMem = document.querySelector('#minMem')
const maxMem = document.querySelector('#maxMem')
const outputMinMem = document.querySelector('#outputMinMem')
const outputMaxMem = document.querySelector('#outputMaxMem')
const totalMem = os.totalmem() / (1.049 * Math.pow(10, 6))
const sidebar = document.querySelector("#sidebar-content")
const invalidateButton = document.querySelector("#invalidateData")
let gameLaunching = false
let selectedChapter = -1;
document.body.onload = () => {
minMem.max = totalMem
maxMem.max = totalMem
minMem.value = localStorage.getItem("minMem") != null ? localStorage.getItem("minMem") : 1024
outputMinMem.textContent = minMem.value
maxMem.value = localStorage.getItem("maxMem") != null ? localStorage.getItem("maxMem") : 2048
outputMaxMem.textContent = maxMem.value
demandModsInformations()
}
ipcRenderer.on("nick", (event, args) => {
console.log(args)
document.querySelector("#nick").innerHTML = args.name
})
launchBtn.addEventListener("click", e => {
launchText.classList.add('hidden')
fullProgressBar.classList.remove('hidden')
loadingMessage.classList.remove('hidden')
if(Number(minMem.value) <= Number(maxMem.value)){
let app = new vue({
el: "#vue",
data: {
minMemValue: localStorage.getItem("minMem") != null ? localStorage.getItem("minMem") : 1024 ,
maxMemValue: localStorage.getItem("maxMem") != null ? localStorage.getItem("maxMem") : 2048,
memStep: 128,
memMax: totalMem,
invalidateButtonText: "Supprimer et retélécharger les bibliothèques",
invalidateButtonDisabled: false,
displayFullscreen: "none",
nick: "Chargement",
launchBtnText: "Selectionnez un chapitre",
launchBtnDisable: true,
launchBtnHidden: false,
loadingMessageHidden: true,
loadingMessageText: "Téléchargement de Minecraft en cours...",
fullprogressbarHidden: true,
progressbarWidth: 0,
sidebarContent: "<hr><p>Chargement en cours</p>"
},
mounted: function () {
this.demandModsInformations()
},
methods: {
invalidateData: function () {
this.invalidateButtonDisabled = true
this.invalidateButtonText = "Opération en cours"
ipcRenderer.send('invalidateData')
},
launchBtnClick: function () {
this.launchBtnHidden = true
this.fullprogressbarHidden = false
app.loadingMessageHidden = false
if(Number(this.minMemValue) <= Number(this.maxMemValue)){
ipcRenderer.send('launch', {
minMem: minMem.value + "M",
maxMem: maxMem.value + "M",
minMem: this.minMemValue + "M",
maxMem: this.maxMemValue + "M",
chapter: selectedChapter
})
launchBtn.disabled = true
localStorage.setItem("minMem", minMem.value)
localStorage.setItem("maxMem", maxMem.value)
app.launchBtnDisable = true
localStorage.setItem("minMem", this.minMemValue)
localStorage.setItem("maxMem", this.maxMemValue)
gameLaunching = true
} else{
ipcRenderer.send('notification', {
@ -52,92 +50,77 @@ launchBtn.addEventListener("click", e => {
body: "La mémoire minimale doit être inférieure ou égale à la mémoire maximale"
})
}
})
document.querySelector("#web").addEventListener("click", e => {
shell.openExternal("https://altarik.fr")
})
document.querySelector("#options").addEventListener("click", e => {
},
disconnectBtn: function () {
ipcRenderer.send('disconnect')
},
options: function () {
if(!gameLaunching)
fullscreen.style.display = "block"
this.displayFullscreen = "block"
},
discord: () => shell.openExternal("https://discord.gg/b923tMhmRE"),
web: () => shell.openExternal("https://altarik.fr"),
closeFullscreen: function () {
this.displayFullscreen = "none"
},
demandModsInformations: function () {
ipcRenderer.send('demandModsInformations')
}
}
})
document.querySelector("#discord").addEventListener("click", e => {
shell.openExternal("https://discord.gg/b923tMhmRE")
})
const sidebar = document.querySelector("#sidebar-content")
let gameLaunching = false
document.querySelector("#close").addEventListener("click", e => {
fullscreen.style.display = "none"
});
let selectedChapter = -1;
invalidateButton.addEventListener("click", e => {
invalidateButton.disabled = true
invalidateButton.childNodes[0].nodeValue = "Opération en cours"
ipcRenderer.send('invalidateData')
})
ipcRenderer.on("nick", (_, args) => app.nick = args.name)
ipcRenderer.on("invalidated", e => {
invalidateButton.disabled = false
invalidateButton.childNodes[0].nodeValue = "Supprimer et retélécharger les bibliothèques"
app.invalidateButtonDisabled = false
app.invalidateButtonText = "Supprimer et retélécharger les bibliothèques"
})
ipcRenderer.on("progress", (e, args) => {
progressBar.style.width = (args.task / Math.max(args.total, args.task)) * 100 + "%"
loadingMessage.innerHTML = "Téléchargement de " + args.type + ": " + args.task + " sur " + Math.max(args.total, args.task)
app.progressbarWidth = (args.task / Math.max(args.total, args.task)) * 100
app.loadingMessageText = "Téléchargement de " + args.type + ": " + args.task + " sur " + Math.max(args.total, args.task)
})
ipcRenderer.on("close", (e, args) => {
launchText.classList.remove('hidden')
fullProgressBar.classList.add('hidden')
loadingMessage.classList.add('hidden')
loadingMessage.innerHTML = "Chargement de Minecraft en cours..."
progressBar.style.width = "0"
launchBtn.disabled = false
ipcRenderer.on("close", (_e, _args) => {
app.launchBtnHidden = false
app.fullprogressbarHidden = true
app.loadingMessageHidden = true
app.loadingMessageText = "Chargement de Minecraft en cours..."
app.progressbarWidth = 0
app.launchBtnDisable = false
gameLaunching = false
})
ipcRenderer.on('launch', (e, args) => {
fullProgressBar.classList.add('hidden')
loadingMessage.classList.add('hidden')
ipcRenderer.on('launch', (_e, _args) => {
app.fullprogressbarHidden = true
app.loadingMessageText = true
})
ipcRenderer.on("modsInformations", (e, args) => {
console.log(args)
if(args === null) {
sidebar.innerHTML = "<hr><p>Une erreur est survenue lors de la récupération des informations, vérifiez votre connexion internet puis cliquez sur réessayez</p>"
+ "<button onclick=\"demandModsInformations()\">Réessayer</button>"
app.sidebarContent = "<hr><p>Une erreur est survenue lors de la récupération des informations, vérifiez votre connexion internet puis cliquez sur réessayez</p>"
+ "<button onclick=\"app.demandModsInformations()\">Réessayer</button>"
} else {
let element = ""
for(const i in args) {
element += `<hr><div data-chapter="${i}" onclick="changeSelectedChapter(this)"><h3>${args[i].title}</h3><p>${args[i].description}</p></div>`
}
sidebar.innerHTML = element
app.sidebarContent = element
}
})
function demandModsInformations() {
ipcRenderer.send('demandModsInformations')
}
function changeSelectedChapter(element) {
selectedChapter = Number(element.dataset.chapter)
document.querySelectorAll("#sidebar-content > div").forEach((v, key) => {
v.classList.remove("selected")
})
element.classList.add("selected")
launchText.innerHTML = "JOUER"
launchBtn.disabled = false
app.launchBtnText = "JOUER"
app.launchBtnDisable = false
}
disconnectBtn.addEventListener('click', e => {
ipcRenderer.send('disconnect')
})
minMem.addEventListener("input", e => {
outputMinMem.textContent = e.target.value
})
maxMem.addEventListener("input", e => {
outputMaxMem.textContent = e.target.value
})

View File

@ -1,9 +1,16 @@
const form = document.querySelector('#login-form')
const user = document.querySelector('#nickname')
const password = document.querySelector('#password')
const microsoftButton = document.querySelector("#microsoft-button")
form.addEventListener("submit", (e) => {
let app = new vue({
el: "#vue",
data: {
login: "Connexion",
email: "Email",
password: "Mot de passe",
send_credentials: "Se connecter",
microsoft_button: "Connexion avec un compte Microsoft"
},
methods: {
formSubmit: (e) => {
e.preventDefault()
if(!microsoftButton.disabled) {
form.disabled = true
@ -19,18 +26,25 @@ form.addEventListener("submit", (e) => {
})
}
}
})
microsoftButton.addEventListener("click", (e) => {
},
microsoftButton: (e) => {
e.preventDefault()
if(!form.disabled) {
microsoftButton.disabled = true
form.disabled = true
ipcRenderer.send("microsoft-login")
}
})
}
}
});
ipcRenderer.on("loginError", event => {
// theirs const are declared after vue cause vue modify them when declaring new vue instance
const form = document.querySelector('#login-form')
const user = document.querySelector('#nickname')
const password = document.querySelector('#password')
const microsoftButton = document.querySelector("#microsoft-button")
ipcRenderer.on("loginError", () => {
form.disabled = false
microsoftButton.disabled = false
})

View File

@ -1,5 +1,7 @@
'use strict';
const { ipcRenderer, shell } = require('electron');
// const isDev = require("electron-is-dev")
const vue = require(/*isDev ? */ 'vue/dist/vue'/* : 'vue' */)
window.addEventListener("DOMContentLoaded", () => {
const minimizeButton = document.getElementById("minimize-btn")

View File

@ -3,13 +3,14 @@
<head>
<meta charset="UTF-8">
<title>Altarik Launcher</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
<link href="assets/css/fonts.css" rel="stylesheet" />
<link href="assets/css/index.css" rel="stylesheet" />
<link href="assets/css/menubar.css" rel="stylesheet" />
<link rel="shortcut icon" type="image/png" href="assets/images/icon.png"/>
</head>
<body>
<div id="vue">
<div id="menubar">
<ul class="left">
<img src="../../icon.ico">
@ -19,50 +20,48 @@
<li id="minimize-btn"><i class="material-icons">minimize</i></li><!--<li id="max-unmax-btn"><i class="material-icons">crop_square</i></li>--><li id="close-btn"><i class="material-icons">close</i></li>
</ul>
</div>
<div id="fullscreen">
<div id="close"><i class="material-icons">close</i></div>
<div id="fullscreen" :style="{ display: displayFullscreen }">
<div @click="closeFullscreen" id="close"><i class="material-icons">close</i></div>
<div id="settings">
<h2>Modifier la configuration</h2>
<span href="" id="disconnect-btn">Se déconnecter</span>
<span href="" id="disconnect-btn" @click="disconnectBtn">Se déconnecter</span>
<h4>Allocation mémoire</h4>
<label for="minMem">mémoire minimale : <span id="outputMinMem"></span></label><br />
<input type="range" min="1024" max="2048" step="128" value="1" class="slider" id="minMem"><br />
<label for="maxMem">mémoire maximale : <span id="outputMaxMem"></span></label><br />
<input type="range" min="1024" max="2048" step="128" value="1" class="slider" id="maxMem"><br />
<label for="minMem">mémoire minimale : <span id="outputMinMem">{{ minMemValue }}</span></label><br />
<input type="number" min="1024" :max="memMax" :step="memStep" v-model="minMemValue" class="slider" id="minMem"><br />
<label for="maxMem">mémoire maximale : <span id="outputMaxMem">{{ maxMemValue }}</span></label><br />
<input type="number" min="1024" :max="memMax" :step="memStep" v-model="maxMemValue" class="slider" id="maxMem"><br />
<h4>Au secours, mon jeu ne démarre pas</h4>
<button id="invalidateData">Supprimer et retélécharger les bibliothèques</button>
<button @click="invalidateData" :disabled="invalidateButtonDisabled">{{ invalidateButtonText }}</button>
</div>
</div>
<div id="content">
<div id="sidebar">
<h2>Chapitres</h2>
<div id="sidebar-content">
</div>
<div id="sidebar-content" v-html="sidebarContent"></div>
</div>
<div id="media">
<div id="options">
<div @click="options">
<img src="assets/images/settings.png">
</div>
<div id="discord">
<div @click="discord">
<img src="assets/images/discord.png">
</div>
<div id="web">
<div @click="web">
<img src="assets/images/web.png">
</div>
</div>
<div id="main">
<div id="account">
<div id="nick"></div><!-- <img src=""> Head du joueur -->
<div id="nick">{{ nick }}</div><!-- <img src=""> Head du joueur -->
</div>
<button id="launch-btn" disabled>
<div id="launch-text">Selectionnez un chapitre</div>
<div id="loading-message" class="hidden">Téléchargement de Minecraft en cours...</div>
<div id="fullprogressbar" class="hidden"><div id="progressbar"></div></div>
<button @click="launchBtnClick" id="launch-btn" :disabled="launchBtnDisable">
<div id="launch-text" :class="[{hidden: launchBtnHidden}]">{{ launchBtnText }}</div>
<div id="loading-message" :class="[{hidden: loadingMessageHidden}]">{{ loadingMessageText }}</div>
<div id="fullprogressbar" :class="[{hidden: fullprogressbarHidden}]"><div id="progressbar" :style="{ width: progressbarWidth + '%' }"></div></div>
</button>
</div>
</p>
</div>
</div>
<script src="assets/js/script.js"></script>
<script src="assets/js/index.js"></script>

View File

@ -3,13 +3,14 @@
<head>
<meta charset="UTF-8">
<title>Altarik Launcher</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
<link href="assets/css/fonts.css" rel="stylesheet" />
<link href="assets/css/login.css" rel="stylesheet" />
<link href="assets/css/menubar.css" rel="stylesheet" />
<link rel="shortcut icon" type="image/png" href="assets/images/icon.png"/>
</head>
<body>
<div id="vue">
<div id="menubar">
<ul class="left">
<img src="../../icon.ico">
@ -21,19 +22,20 @@
</div>
<div id="content">
<div id="login">
<h3>Connexion</h3>
<h3>{{ login }}</h3>
<hr>
<form id="login-form">
<label for="nickname">Email:</label><br />
<form v-on:submit="formSubmit" id="login-form">
<label for="nickname">{{ email }}:</label><br />
<input type="text" name="nickname" id="nickname"><br />
<label for="password">Mot de passe:</label><br />
<label for="password">{{ password }}:</label><br />
<input type="password" name="password" id="password"><br />
<input type="submit" value="Se connecter">
<input type="submit" v-bind:value="send_credentials">
</form>
</div>
<button id="microsoft-button">Connexion avec un compte Microsoft</button>
<button id="microsoft-button" v-on:click="microsoftButton">{{ microsoft_button }}</button>
</div>
</div>
<script src="assets/js/script.js"></script>
<script src="assets/js/login.js"></script>
</body>

View File

@ -9,6 +9,7 @@ if (require('electron-squirrel-startup')) {
require('./updater.js').configUpdater(app, autoUpdater, dialog, logger)
const minecraft = require('./minecraft.js')
minecraft.showNotification = showNotification
const iconPath = join(__dirname, "icon.ico")
let win = null
@ -29,7 +30,7 @@ function createWindow () {
frame: false,
nativeWindowOpen: true
})
Menu.setApplicationMenu(null)
// Menu.setApplicationMenu(null)
win.loadFile('src/client/login.html')
win.on("close", () => {
app.quit()
@ -65,11 +66,11 @@ app.on('activate', () => {
})
ipcMain.on("login", (event, args) => {
minecraft.login(event, win, showNotification, args.user, args.pass)
minecraft.login(event, win, args.user, args.pass)
})
ipcMain.on("microsoft-login", (event, args) => {
minecraft.microsoftLogin(event, win, showNotification)
minecraft.microsoftLogin(event, win)
})
ipcMain.on("invalidateData", event => {
@ -77,7 +78,7 @@ ipcMain.on("invalidateData", event => {
})
ipcMain.on("launch", (event, args) => {
minecraft.launch(event, showNotification, args)
minecraft.launch(event, args)
})
function showNotification(title, body="") {

View File

@ -16,11 +16,16 @@ class Minecraft {
launcher = new Client()
auth = null
modsList = undefined
showNotification = undefined
setShowNotification(showNotification) {
this.showNotification = showNotification
}
/**
* Used to login through Mojang account
*/
login(event, win, showNotification, username, password) {
login(event, win, username, password) {
this.auth = null
if(isDev || password.trim() !== "") {
this.auth = Authenticator.getAuth(username, password)
@ -31,23 +36,23 @@ class Minecraft {
}).catch((err) => {
event.sender.send("loginError")
logger.error("[MJ login] User haven't purchase the game")
showNotification("Erreur de connexion")
this.showNotification("Erreur de connexion")
})
} else {
showNotification("Veuillez renseignez un mot de passe")
this.showNotification("Veuillez renseignez un mot de passe")
}
}
/**
* Used to login through a Microsoft account
*/
microsoftLogin(event, win, showNotification) {
microsoftLogin(event, win) {
msmc.fastLaunch("electron",
(update) => {
switch (update.type) {
case "Error":
event.sender.send("loginError")
showNotification("Une erreur est survenue", update.data)
this.showNotification("Une erreur est survenue", update.data)
logger.error("MC-Account error:", update.data);
break;
}
@ -55,7 +60,7 @@ class Minecraft {
if(msmc.errorCheck(result)) {
event.sender.send("loginError")
logger.error(result.reason)
showNotification("Erreur de connexion", result.reason)
this.showNotification("Erreur de connexion", result.reason)
} else {
if(!msmc.isDemoUser(result)) {
this.auth = msmc.getMCLC().getAuth(result)
@ -65,17 +70,17 @@ class Minecraft {
} else {
event.sender.send("loginError")
logger.error("[MS login] User haven't purchase the game")
showNotification("Erreur de connexion", "Vous ne possèdez pas de licence Minecraft sur ce compte")
this.showNotification("Erreur de connexion", "Vous ne possèdez pas de licence Minecraft sur ce compte")
}
}
}).catch(reason => {
event.sender.send("loginError")
logger.error(reason)
showNotification("Erreur de connexion")
this.showNotification("Erreur de connexion")
})
}
launch(event, showNotification, args) {
launch(event, args) {
this.extractJava(Number(args.chapter), event).then((javaPath) => {
this.extractMods(Number(args.chapter), event).then((chapter) => {
this.launcher.launch({
@ -108,17 +113,17 @@ class Minecraft {
if(e !== 0) {
logger.warn("Minecraft didn't close properly")
logger.warn(e)
showNotification("Une erreur est survenue", "Minecraft ne s'est pas fermé correctement")
this.showNotification("Une erreur est survenue", "Minecraft ne s'est pas fermé correctement")
}
})
}).catch((err) => {
showNotification("Impossible de lancer le jeu")
this.showNotification("Impossible de lancer le jeu")
event.sender.send("close", 1)
logger.error('Unable to launch the game')
logger.error(err)
})
}).catch(err => {
showNotification("Impossible d'intaller Java pour votre configuration")
this.showNotification("Impossible d'intaller Java pour votre configuration")
event.sender.send("close", 1)
logger.warn("Unable to install java")
logger.warn(err)
@ -132,7 +137,10 @@ class Minecraft {
let folder = join(process.env.LOCALAPPDATA, "altarik-launcher", "data")
if(!fs.existsSync(folder))
fs.mkdirSync(folder)
fs.writeFileSync(join(folder, "launcher.json"), JSON.stringify(o.data))
let file = join(folder, "launcher.json")
if(fs.existsSync(file))
fs.rmSync(file)
fs.writeFileSync(file, JSON.stringify(o.data))
event.sender.send('modsInformations', this.extractModsInformations(o.data))
} else {
event.sender.send('modsInformations', this.extractModsFromFileSystem())
@ -145,12 +153,12 @@ class Minecraft {
}
extractModsFromFileSystem() {
content = fs.readFileSync(join(process.env.LOCALAPPDATA, "altarik-launcher/data/launcher.json"))
let content = fs.readFileSync(join(process.env.LOCALAPPDATA, "altarik-launcher/data/launcher.json"))
if(content !== null) {
showNotification("Impossible de récupérer certaines informations en ligne", "utilisation des dernières données récupérées")
this.showNotification("Impossible de récupérer certaines informations en ligne", "utilisation des dernières données récupérées")
return this.extractModsInformations(JSON.parse(content))
} else {
showNotification("Impossible de récupérer certaines informations en ligne", "Veuillez réessayez en cliquant sur le bouton")
this.showNotification("Impossible de récupérer certaines informations en ligne", "Veuillez réessayez en cliquant sur le bouton")
logger.error("Unable to get chapters informations from server or filesystem")
return null
}
@ -215,9 +223,9 @@ class Minecraft {
downloadMods(link, path) {
return new Promise((resolve, reject) => {
axios.get(link, {
responseType: "stream"
}).then(res => {
if(!navigator.onLine)
reject("offline")
axios.get(link, {responseType: "stream"}).then(res => {
if(res.status === 200) {
if(fs.existsSync(path))
fs.rmSync(path)
@ -305,6 +313,7 @@ class Minecraft {
}
invalidateData(event) {
logger.info("invalidate game data...")
const assets = join(this.minecraftpath, 'assets')
const librairies = join(this.minecraftpath,'libraries')
const natives = join(this.minecraftpath, 'natives')
@ -314,6 +323,7 @@ class Minecraft {
fs.rmdirSync(librairies, { recursive: true })
if(fs.existsSync(natives))
fs.rmdirSync(natives, { recursive: true })
logger.info("Game data invalidated")
event.sender.send("invalidated")
}
}