commit
68b99f9a18
@ -53,9 +53,9 @@ If you download from the [Releases](https://github.com/dscalzi/HeliosLauncher/re
|
||||
|
||||
| Platform | File |
|
||||
| -------- | ---- |
|
||||
| Windows x64 | `helioslauncher-setup-VERSION.exe` |
|
||||
| macOS | `helioslauncher-VERSION.dmg` |
|
||||
| Linux x64 | `helioslauncher-VERSION-x86_64.AppImage` |
|
||||
| Windows x64 | `Helios-Launcher-setup-VERSION.exe` |
|
||||
| macOS | `Helios-Launcher-setup-VERSION.dmg` |
|
||||
| Linux x64 | `Helios-Launcher-setup-VERSION.AppImage` |
|
||||
|
||||
## Console
|
||||
|
||||
|
@ -618,8 +618,7 @@ body, button {
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
border: none;
|
||||
padding: 15px 5px;
|
||||
margin: 10px 0px;
|
||||
margin: 15px 0px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
right: -20px;
|
||||
@ -694,6 +693,21 @@ body, button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.circle-loader-big {
|
||||
margin:auto;
|
||||
border: 5px solid rgba(255, 255, 255, 0.5);
|
||||
border-left-color: #ffffff;
|
||||
animation-name: loader-spin;
|
||||
animation-duration: 1s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
position: relative;
|
||||
display: block;
|
||||
align-self: center;
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.load-complete {
|
||||
animation: none;
|
||||
border-color: #ffffff;
|
||||
@ -864,6 +878,28 @@ body, button {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
#loginMSButton {
|
||||
background: none;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
}
|
||||
#loginMSButton:disabled {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#loginMSButton:hover,
|
||||
#loginMSButton:focus {
|
||||
text-shadow: 0px 0px 20px #fff;
|
||||
outline: none;
|
||||
}
|
||||
#loginMSButton:active {
|
||||
color: #c7c7c7;
|
||||
text-shadow: 0px 0px 20px #c7c7c7;
|
||||
}
|
||||
|
||||
/*
|
||||
#login_filter {
|
||||
height: calc(100% - 22px);
|
||||
|
@ -12,9 +12,66 @@
|
||||
const ConfigManager = require('./configmanager')
|
||||
const LoggerUtil = require('./loggerutil')
|
||||
const Mojang = require('./mojang')
|
||||
const Microsoft = require('./microsoft')
|
||||
const logger = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
||||
const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
|
||||
|
||||
async function validateSelectedMojang() {
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
||||
if(!isValid){
|
||||
try {
|
||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
||||
ConfigManager.save()
|
||||
} catch(err) {
|
||||
logger.debug('Error while validating selected profile:', err)
|
||||
if(err && err.error === 'ForbiddenOperationException'){
|
||||
// What do we do?
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async function validateSelectedMicrosoft() {
|
||||
try {
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const now = new Date().getTime()
|
||||
const MCExpiresAt = Date.parse(current.expiresAt)
|
||||
const MCExpired = now > MCExpiresAt
|
||||
|
||||
if(MCExpired) {
|
||||
const MSExpiresAt = Date.parse(current.microsoft.expires_at)
|
||||
const MSExpired = now > MSExpiresAt
|
||||
|
||||
if (MSExpired) {
|
||||
const newAccessToken = await Microsoft.refreshAccessToken(current.microsoft.refresh_token)
|
||||
const newMCAccessToken = await Microsoft.authMinecraft(newAccessToken.access_token)
|
||||
ConfigManager.updateAuthAccount(current.uuid, newMCAccessToken.access_token, newAccessToken.expires_at)
|
||||
ConfigManager.save()
|
||||
return true
|
||||
}
|
||||
const newMCAccessToken = await Microsoft.authMinecraft(current.microsoft.access_token)
|
||||
ConfigManager.updateAuthAccount(current.uuid, newMCAccessToken.access_token, current.microsoft.access_token, current.microsoft.expires_at, newMCAccessToken.expires_at)
|
||||
ConfigManager.save()
|
||||
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Exports
|
||||
// Functions
|
||||
|
||||
/**
|
||||
@ -55,6 +112,11 @@ exports.addAccount = async function(username, password){
|
||||
exports.removeAccount = async function(uuid){
|
||||
try {
|
||||
const authAcc = ConfigManager.getAuthAccount(uuid)
|
||||
if(authAcc.type === 'microsoft'){
|
||||
ConfigManager.removeAuthAccount(uuid)
|
||||
ConfigManager.save()
|
||||
return Promise.resolve()
|
||||
}
|
||||
await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.removeAuthAccount(uuid)
|
||||
ConfigManager.save()
|
||||
@ -78,22 +140,35 @@ exports.validateSelected = async function(){
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
||||
if(!isValid){
|
||||
try {
|
||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
||||
ConfigManager.save()
|
||||
} catch(err) {
|
||||
logger.debug('Error while validating selected profile:', err)
|
||||
if(err && err.error === 'ForbiddenOperationException'){
|
||||
// What do we do?
|
||||
try{
|
||||
if (ConfigManager.getSelectedAccount() === 'microsoft') {
|
||||
const validate = await validateSelectedMicrosoft()
|
||||
return validate
|
||||
} else {
|
||||
const validate = await validateSelectedMojang()
|
||||
return validate
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else return true
|
||||
}
|
||||
|
||||
exports.addMSAccount = async authCode => {
|
||||
try {
|
||||
const accessToken = await Microsoft.getAccessToken(authCode)
|
||||
const MCAccessToken = await Microsoft.authMinecraft(accessToken.access_token)
|
||||
const minecraftBuyed = await Microsoft.checkMCStore(MCAccessToken.access_token)
|
||||
if(!minecraftBuyed)
|
||||
return Promise.reject({
|
||||
message: 'You didn\'t buy Minecraft! Please use another Microsoft account or buy Minecraft.'
|
||||
})
|
||||
const MCProfile = await Microsoft.getMCProfile(MCAccessToken.access_token)
|
||||
const ret = ConfigManager.addMsAuthAccount(MCProfile.id, MCAccessToken.access_token, MCProfile.name, MCAccessToken.expires_at, accessToken.access_token, accessToken.refresh_token)
|
||||
ConfigManager.save()
|
||||
|
||||
return ret
|
||||
} catch(error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
@ -101,7 +101,8 @@ const DEFAULT_CONFIG = {
|
||||
selectedServer: null, // Resolved
|
||||
selectedAccount: null,
|
||||
authenticationDatabase: {},
|
||||
modConfigurations: []
|
||||
modConfigurations: [],
|
||||
microsoftAuth: {}
|
||||
}
|
||||
|
||||
let config = null
|
||||
@ -329,7 +330,28 @@ exports.updateAuthAccount = function(uuid, accessToken){
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticated account to the database to be stored.
|
||||
* Update the tokens of an authenticated microsoft account.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The new Access Token.
|
||||
* @param {string} msAccessToken The new Microsoft Access Token
|
||||
* @param {string} msRefreshToken The new Microsoft Refresh Token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
* @param {date} mcExpires The date when the mojang access token expires
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
exports.updateAuthAccount = function(uuid, accessToken, msAccessToken, msRefreshToken, msExpires, mcExpires){
|
||||
config.authenticationDatabase[uuid].accessToken = accessToken
|
||||
config.authenticationDatabase[uuid].expiresAt = mcExpires
|
||||
config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken
|
||||
config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken
|
||||
config.authenticationDatabase[uuid].microsoft.expires_at = msRefreshToken
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticated mojang account to the database to be stored.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The accessToken of the authenticated account.
|
||||
@ -344,7 +366,39 @@ exports.addAuthAccount = function(uuid, accessToken, username, displayName){
|
||||
accessToken,
|
||||
username: username.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: displayName.trim()
|
||||
displayName: displayName.trim(),
|
||||
type: 'mojang'
|
||||
}
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authenticated microsoft account to the database to be stored.
|
||||
*
|
||||
* @param {string} uuid The uuid of the authenticated account.
|
||||
* @param {string} accessToken The accessToken of the authenticated account.
|
||||
* @param {string} name The in game name of the authenticated account.
|
||||
* @param {date} mcExpires The date when the mojang access token expires
|
||||
* @param {string} msAccessToken The microsoft access token
|
||||
* @param {string} msRefreshToken The microsoft refresh token
|
||||
* @param {date} msExpires The date when the microsoft access token expires
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
exports.addMsAuthAccount = function(uuid, accessToken, name, mcExpires, msAccessToken, msRefreshToken, msExpires){
|
||||
config.selectedAccount = uuid
|
||||
config.authenticationDatabase[uuid] = {
|
||||
accessToken,
|
||||
username: name.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: name.trim(),
|
||||
expiresAt: mcExpires,
|
||||
type: 'microsoft',
|
||||
microsoft: {
|
||||
access_token: msAccessToken,
|
||||
refresh_token: msRefreshToken,
|
||||
expires_at: msExpires
|
||||
}
|
||||
}
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
227
app/assets/js/microsoft.js
Normal file
227
app/assets/js/microsoft.js
Normal file
@ -0,0 +1,227 @@
|
||||
require('dotenv').config()
|
||||
|
||||
// Requirements
|
||||
const request = require('request')
|
||||
|
||||
// Constants
|
||||
const clientId = 'ce9c7ade-7cee-4c4c-83bc-0c0edafdcaea'
|
||||
|
||||
const tokenUri = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token'
|
||||
const authXBLUri = 'https://user.auth.xboxlive.com/user/authenticate'
|
||||
const authXSTSUri = 'https://xsts.auth.xboxlive.com/xsts/authorize'
|
||||
const authMCUri = 'https://api.minecraftservices.com/authentication/login_with_xbox'
|
||||
const profileURI = 'https://api.minecraftservices.com/minecraft/profile'
|
||||
|
||||
// Functions
|
||||
function requestPromise(uri, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(uri, options, (error, response, body) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else if (response.statusCode !== 200) {
|
||||
reject([response.statusCode, response.statusMessage, response])
|
||||
} else {
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getXBLToken(accessToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
Properties: {
|
||||
AuthMethod: 'RPS',
|
||||
SiteName: 'user.auth.xboxlive.com',
|
||||
RpsTicket: `d=${accessToken}`
|
||||
},
|
||||
RelyingParty: 'http://auth.xboxlive.com',
|
||||
TokenType: 'JWT'
|
||||
}
|
||||
}
|
||||
requestPromise(authXBLUri, options).then(response => {
|
||||
const body = response.body
|
||||
|
||||
data.token = body.Token
|
||||
data.uhs = body.DisplayClaims.xui[0].uhs
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getXSTSToken(XBLToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
Properties: {
|
||||
SandboxId: 'RETAIL',
|
||||
UserTokens: [XBLToken]
|
||||
},
|
||||
RelyingParty: 'rp://api.minecraftservices.com/',
|
||||
TokenType: 'JWT'
|
||||
}
|
||||
}
|
||||
requestPromise(authXSTSUri, options).then(response => {
|
||||
if (response.body.XErr) {
|
||||
switch (response.body.XErr) {
|
||||
case 2148916233:
|
||||
reject({
|
||||
message: 'Your Microsoft account is not connected to an Xbox account. Please create one.<br>'
|
||||
})
|
||||
return
|
||||
|
||||
case 2148916238:
|
||||
reject({
|
||||
message: 'Since you are not yet 18 years old, an adult must add you to a family in order for you to use NumaLauncher!'
|
||||
})
|
||||
return
|
||||
|
||||
}
|
||||
reject(response.body)
|
||||
}
|
||||
resolve(response.body.Token)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getMCAccessToken(UHS, XSTSToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new Object()
|
||||
const expiresAt = new Date()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
identityToken: `XBL3.0 x=${UHS};${XSTSToken}`
|
||||
}
|
||||
}
|
||||
requestPromise(authMCUri, options).then(response => {
|
||||
const body = response.body
|
||||
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.access_token = body.access_token
|
||||
data.expires_at = expiresAt
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Exports
|
||||
exports.getAccessToken = authCode => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expiresAt = new Date()
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
client_id: clientId,
|
||||
code: authCode,
|
||||
scope: 'XboxLive.signin',
|
||||
redirect_uri: 'https://login.microsoftonline.com/common/oauth2/nativeclient',
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
}
|
||||
requestPromise(tokenUri, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.expires_at = expiresAt
|
||||
data.access_token = body.access_token
|
||||
data.refresh_token = body.refresh_token
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.refreshAccessToken = refreshToken => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expiresAt = new Date()
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
client_id: clientId,
|
||||
refresh_token: refreshToken,
|
||||
scope: 'XboxLive.signin',
|
||||
redirect_uri: 'https://login.microsoftonline.com/common/oauth2/nativeclient',
|
||||
grant_type: 'refresh_token'
|
||||
}
|
||||
}
|
||||
requestPromise(tokenUri, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.expires_at = expiresAt
|
||||
data.access_token = body.access_token
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.authMinecraft = async accessToken => {
|
||||
try {
|
||||
const XBLToken = await getXBLToken(accessToken)
|
||||
const XSTSToken = await getXSTSToken(XBLToken.token)
|
||||
const MCToken = await getMCAccessToken(XBLToken.uhs, XSTSToken)
|
||||
|
||||
return MCToken
|
||||
} catch (error) {
|
||||
Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
exports.checkMCStore = async function(access_token){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get({
|
||||
url: 'https://api.minecraftservices.com/entitlements/mcstore',
|
||||
json: true,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + access_token
|
||||
}
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
if(body.items && body.items.length > 0) resolve(true)
|
||||
else resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.getMCProfile = MCAccessToken => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: 'get',
|
||||
headers: {
|
||||
Authorization: `Bearer ${MCAccessToken}`
|
||||
}
|
||||
}
|
||||
requestPromise(profileURI, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
resolve(body)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
@ -175,8 +175,8 @@ const refreshMojangStatuses = async function(){
|
||||
for(let i=0; i<statuses.length; i++){
|
||||
const service = statuses[i]
|
||||
|
||||
// Mojang API is broken for these two. https://bugs.mojang.com/browse/WEB-2303
|
||||
if(service.service === 'sessionserver.mojang.com' || service.service === 'minecraft.net') {
|
||||
// Mojang API is broken for sessionserver. https://bugs.mojang.com/browse/WEB-2303
|
||||
if(service.service === 'sessionserver.mojang.com') {
|
||||
service.status = 'green'
|
||||
}
|
||||
|
||||
@ -1185,6 +1185,8 @@ notion.contentWindow.addEventListener('DOMContentLoaded', event => {
|
||||
style.innerHTML += '.notion-collection_view-block { overflow-x: hidden !important; }'
|
||||
style.innerHTML += '.notion-collection_view-block > .notion-scroller { overflow: hidden !important; }'
|
||||
style.innerHTML += 'body { background: transparent !important; }'
|
||||
style.innerHTML += '.notion-page-content { color: #ffffff !important; }'
|
||||
style.innerHTML += '.notion-selectable { color: #ffffff !important; }'
|
||||
|
||||
// Add the <style> element to the page
|
||||
notionDoc.head.appendChild(style)
|
||||
|
@ -17,6 +17,7 @@ const checkmarkContainer = document.getElementById('checkmarkContainer')
|
||||
const loginRememberOption = document.getElementById('loginRememberOption')
|
||||
const loginButton = document.getElementById('loginButton')
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const loginMSButton = document.getElementById('loginMSButton')
|
||||
|
||||
// Control variables.
|
||||
let lu = false, lp = false
|
||||
@ -297,4 +298,97 @@ loginButton.addEventListener('click', () => {
|
||||
loggerLogin.log('Error while logging in.', err)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
loginMSButton.addEventListener('click', (event) => {
|
||||
// Show loading stuff.
|
||||
toggleOverlay(true, false, 'msOverlay')
|
||||
loginMSButton.disabled = true
|
||||
ipcRenderer.send('openMSALoginWindow', 'open')
|
||||
})
|
||||
|
||||
ipcRenderer.on('MSALoginWindowReply', (event, ...args) => {
|
||||
if (args[0] === 'error') {
|
||||
|
||||
loginMSButton.disabled = false
|
||||
loginLoading(false)
|
||||
switch (args[1]){
|
||||
case 'AlreadyOpenException': {
|
||||
setOverlayContent('ERROR', 'すでにログインウィンドウが開いています!', 'OK')
|
||||
setOverlayHandler(() => {
|
||||
toggleOverlay(false)
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
case 'AuthNotFinished': {
|
||||
setOverlayContent('ERROR', 'NumaLauncherを使用するには、ログインが必要です。ログインに成功すると、ウィンドウは自動的に閉じます。', 'OK')
|
||||
setOverlayHandler(() => {
|
||||
toggleOverlay(false)
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
const queryMap = args[0]
|
||||
if (queryMap.has('error')) {
|
||||
let error = queryMap.get('error')
|
||||
let errorDesc = queryMap.get('error_description')
|
||||
if(error === 'access_denied'){
|
||||
error = 'ERRPR'
|
||||
errorDesc = 'To use the Helios Launcher, you must agree to the required permissions! Otherwise you can\'t use this launcher with Microsoft accounts.<br><br>Despite agreeing to the permissions you don\'t give us the possibility to do anything with your account, because all data will always be sent back to you (the launcher) IMMEDIATELY and WITHOUT WAY.'
|
||||
}
|
||||
setOverlayContent(error, errorDesc, 'OK')
|
||||
setOverlayHandler(() => {
|
||||
loginMSButton.disabled = false
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Disable form.
|
||||
formDisabled(true)
|
||||
|
||||
const authCode = queryMap.get('code')
|
||||
AuthManager.addMSAccount(authCode).then(account => {
|
||||
updateSelectedAccount(account)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
setTimeout(() => {
|
||||
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
|
||||
// Temporary workaround
|
||||
if (loginViewOnSuccess === VIEWS.settings) {
|
||||
prepareSettings()
|
||||
}
|
||||
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
|
||||
loginCancelEnabled(false) // Reset this for good measure.
|
||||
loginViewCancelHandler = null // Reset this for good measure.
|
||||
loginUsername.value = ''
|
||||
loginPassword.value = ''
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
loginLoading(false)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login'))
|
||||
formDisabled(false)
|
||||
})
|
||||
}, 1000)
|
||||
}).catch(error => {
|
||||
loginMSButton.disabled = false
|
||||
loginLoading(false)
|
||||
setOverlayContent('ERROR', error.message ? error.message : 'Microsoftでのログイン中にエラーが発生しました!詳細については、ログを確認してください。 CTRL + SHIFT + Iで開くことができます。', Lang.queryJS('login.tryAgain'))
|
||||
setOverlayHandler(() => {
|
||||
formDisabled(false)
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
loggerLogin.error(error)
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
* @returns {boolean} Whether or not the overlay is visible.
|
||||
*/
|
||||
function isOverlayVisible(){
|
||||
function isOverlayVisible() {
|
||||
return document.getElementById('main').hasAttribute('overlay')
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@ let overlayHandlerContent
|
||||
*
|
||||
* @param {KeyboardEvent} e The keydown event.
|
||||
*/
|
||||
function overlayKeyHandler (e){
|
||||
if(e.key === 'Enter' || e.key === 'Escape'){
|
||||
function overlayKeyHandler(e) {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEnter')[0].click()
|
||||
}
|
||||
}
|
||||
@ -30,10 +30,10 @@ function overlayKeyHandler (e){
|
||||
*
|
||||
* @param {KeyboardEvent} e The keydown event.
|
||||
*/
|
||||
function overlayKeyDismissableHandler (e){
|
||||
if(e.key === 'Enter'){
|
||||
function overlayKeyDismissableHandler(e) {
|
||||
if (e.key === 'Enter') {
|
||||
document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEnter')[0].click()
|
||||
} else if(e.key === 'Escape'){
|
||||
} else if (e.key === 'Escape') {
|
||||
document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEsc')[0].click()
|
||||
}
|
||||
}
|
||||
@ -45,12 +45,12 @@ function overlayKeyDismissableHandler (e){
|
||||
* @param {string} content The overlay content which will be shown.
|
||||
* @param {boolean} dismissable Whether or not the overlay is dismissable
|
||||
*/
|
||||
function bindOverlayKeys(state, content, dismissable){
|
||||
function bindOverlayKeys(state, content, dismissable) {
|
||||
overlayHandlerContent = content
|
||||
document.removeEventListener('keydown', overlayKeyHandler)
|
||||
document.removeEventListener('keydown', overlayKeyDismissableHandler)
|
||||
if(state){
|
||||
if(dismissable){
|
||||
if (state) {
|
||||
if (dismissable) {
|
||||
document.addEventListener('keydown', overlayKeyDismissableHandler)
|
||||
} else {
|
||||
document.addEventListener('keydown', overlayKeyHandler)
|
||||
@ -65,22 +65,22 @@ function bindOverlayKeys(state, content, dismissable){
|
||||
* @param {boolean} dismissable Optional. True to show the dismiss option, otherwise false.
|
||||
* @param {string} content Optional. The content div to be shown.
|
||||
*/
|
||||
function toggleOverlay(toggleState, dismissable = false, content = 'overlayContent'){
|
||||
if(toggleState == null){
|
||||
function toggleOverlay(toggleState, dismissable = false, content = 'overlayContent') {
|
||||
if (toggleState == null) {
|
||||
toggleState = !document.getElementById('main').hasAttribute('overlay')
|
||||
}
|
||||
if(typeof dismissable === 'string'){
|
||||
if (typeof dismissable === 'string') {
|
||||
content = dismissable
|
||||
dismissable = false
|
||||
}
|
||||
bindOverlayKeys(toggleState, content, dismissable)
|
||||
if(toggleState){
|
||||
if (toggleState) {
|
||||
document.getElementById('main').setAttribute('overlay', true)
|
||||
// Make things untabbable.
|
||||
$('#main *').attr('tabindex', '-1')
|
||||
$('#' + content).parent().children().hide()
|
||||
$('#' + content).show()
|
||||
if(dismissable){
|
||||
if (dismissable) {
|
||||
$('#overlayDismiss').show()
|
||||
} else {
|
||||
$('#overlayDismiss').hide()
|
||||
@ -88,7 +88,7 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
||||
$('#overlayContainer').fadeIn({
|
||||
duration: 250,
|
||||
start: () => {
|
||||
if(getCurrentView() === VIEWS.settings){
|
||||
if (getCurrentView() === VIEWS.settings) {
|
||||
document.getElementById('settingsContainer').style.backgroundColor = 'transparent'
|
||||
}
|
||||
}
|
||||
@ -100,14 +100,14 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
||||
$('#overlayContainer').fadeOut({
|
||||
duration: 250,
|
||||
start: () => {
|
||||
if(getCurrentView() === VIEWS.settings){
|
||||
if (getCurrentView() === VIEWS.settings) {
|
||||
document.getElementById('settingsContainer').style.backgroundColor = 'rgba(0, 0, 0, 0.50)'
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
$('#' + content).parent().children().hide()
|
||||
$('#' + content).show()
|
||||
if(dismissable){
|
||||
if (dismissable) {
|
||||
$('#overlayDismiss').show()
|
||||
} else {
|
||||
$('#overlayDismiss').hide()
|
||||
@ -117,7 +117,7 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
||||
}
|
||||
}
|
||||
|
||||
function toggleServerSelection(toggleState){
|
||||
function toggleServerSelection(toggleState) {
|
||||
prepareServerSelectionList()
|
||||
toggleOverlay(toggleState, true, 'serverSelectContent')
|
||||
}
|
||||
@ -130,7 +130,7 @@ function toggleServerSelection(toggleState){
|
||||
* @param {string} acknowledge Acknowledge button text.
|
||||
* @param {string} dismiss Dismiss button text.
|
||||
*/
|
||||
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss'){
|
||||
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss') {
|
||||
document.getElementById('overlayTitle').innerHTML = title
|
||||
document.getElementById('overlayDesc').innerHTML = description
|
||||
document.getElementById('overlayAcknowledge').innerHTML = acknowledge
|
||||
@ -143,8 +143,8 @@ function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss')
|
||||
*
|
||||
* @param {function} handler
|
||||
*/
|
||||
function setOverlayHandler(handler){
|
||||
if(handler == null){
|
||||
function setOverlayHandler(handler) {
|
||||
if (handler == null) {
|
||||
document.getElementById('overlayAcknowledge').onclick = () => {
|
||||
toggleOverlay(false)
|
||||
}
|
||||
@ -159,8 +159,8 @@ function setOverlayHandler(handler){
|
||||
*
|
||||
* @param {function} handler
|
||||
*/
|
||||
function setDismissHandler(handler){
|
||||
if(handler == null){
|
||||
function setDismissHandler(handler) {
|
||||
if (handler == null) {
|
||||
document.getElementById('overlayDismiss').onclick = () => {
|
||||
toggleOverlay(false)
|
||||
}
|
||||
@ -173,8 +173,8 @@ function setDismissHandler(handler){
|
||||
|
||||
document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
||||
const listings = document.getElementsByClassName('serverListing')
|
||||
for(let i=0; i<listings.length; i++){
|
||||
if(listings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < listings.length; i++) {
|
||||
if (listings[i].hasAttribute('selected')) {
|
||||
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
|
||||
updateSelectedServer(serv)
|
||||
refreshServerStatus(true)
|
||||
@ -183,7 +183,7 @@ document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
||||
}
|
||||
}
|
||||
// None are selected? Not possible right? Meh, handle it.
|
||||
if(listings.length > 0){
|
||||
if (listings.length > 0) {
|
||||
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
|
||||
updateSelectedServer(serv)
|
||||
toggleOverlay(false)
|
||||
@ -192,8 +192,8 @@ document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
||||
|
||||
document.getElementById('accountSelectConfirm').addEventListener('click', () => {
|
||||
const listings = document.getElementsByClassName('accountListing')
|
||||
for(let i=0; i<listings.length; i++){
|
||||
if(listings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < listings.length; i++) {
|
||||
if (listings[i].hasAttribute('selected')) {
|
||||
const authAcc = ConfigManager.setSelectedAccount(listings[i].getAttribute('uuid'))
|
||||
ConfigManager.save()
|
||||
updateSelectedAccount(authAcc)
|
||||
@ -203,7 +203,7 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
|
||||
}
|
||||
}
|
||||
// None are selected? Not possible right? Meh, handle it.
|
||||
if(listings.length > 0){
|
||||
if (listings.length > 0) {
|
||||
const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid'))
|
||||
ConfigManager.save()
|
||||
updateSelectedAccount(authAcc)
|
||||
@ -223,16 +223,16 @@ document.getElementById('accountSelectCancel').addEventListener('click', () => {
|
||||
})
|
||||
})
|
||||
|
||||
function setServerListingHandlers(){
|
||||
function setServerListingHandlers() {
|
||||
const listings = Array.from(document.getElementsByClassName('serverListing'))
|
||||
listings.map((val) => {
|
||||
val.onclick = e => {
|
||||
if(val.hasAttribute('selected')){
|
||||
if (val.hasAttribute('selected')) {
|
||||
return
|
||||
}
|
||||
const cListings = document.getElementsByClassName('serverListing')
|
||||
for(let i=0; i<cListings.length; i++){
|
||||
if(cListings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < cListings.length; i++) {
|
||||
if (cListings[i].hasAttribute('selected')) {
|
||||
cListings[i].removeAttribute('selected')
|
||||
}
|
||||
}
|
||||
@ -242,16 +242,16 @@ function setServerListingHandlers(){
|
||||
})
|
||||
}
|
||||
|
||||
function setAccountListingHandlers(){
|
||||
function setAccountListingHandlers() {
|
||||
const listings = Array.from(document.getElementsByClassName('accountListing'))
|
||||
listings.map((val) => {
|
||||
val.onclick = e => {
|
||||
if(val.hasAttribute('selected')){
|
||||
if (val.hasAttribute('selected')) {
|
||||
return
|
||||
}
|
||||
const cListings = document.getElementsByClassName('accountListing')
|
||||
for(let i=0; i<cListings.length; i++){
|
||||
if(cListings[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < cListings.length; i++) {
|
||||
if (cListings[i].hasAttribute('selected')) {
|
||||
cListings[i].removeAttribute('selected')
|
||||
}
|
||||
}
|
||||
@ -261,12 +261,12 @@ function setAccountListingHandlers(){
|
||||
})
|
||||
}
|
||||
|
||||
function populateServerListings(){
|
||||
function populateServerListings() {
|
||||
const distro = DistroManager.getDistribution()
|
||||
const giaSel = ConfigManager.getSelectedServer()
|
||||
const servers = distro.getServers()
|
||||
let htmlString = ''
|
||||
for(const serv of servers){
|
||||
for (const serv of servers) {
|
||||
htmlString += `<button class="serverListing" servid="${serv.getID()}" ${serv.getID() === giaSel ? 'selected' : ''}>
|
||||
<img class="serverListingImg" src="${serv.getIcon()}"/>
|
||||
<div class="serverListingDetails">
|
||||
@ -293,12 +293,12 @@ function populateServerListings(){
|
||||
|
||||
}
|
||||
|
||||
function populateAccountListings(){
|
||||
function populateAccountListings() {
|
||||
const accountsObj = ConfigManager.getAuthAccounts()
|
||||
const accounts = Array.from(Object.keys(accountsObj), v=>accountsObj[v])
|
||||
const accounts = Array.from(Object.keys(accountsObj), v => accountsObj[v])
|
||||
let htmlString = ''
|
||||
for(let i=0; i<accounts.length; i++){
|
||||
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i===0 ? 'selected' : ''}>
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i === 0 ? 'selected' : ''}>
|
||||
<img src="https://crafatar.com/renders/head/${accounts[i].uuid}?scale=2&default=MHF_Steve&overlay">
|
||||
<div class="accountListingName">${accounts[i].displayName}</div>
|
||||
</button>`
|
||||
@ -307,12 +307,12 @@ function populateAccountListings(){
|
||||
|
||||
}
|
||||
|
||||
function prepareServerSelectionList(){
|
||||
function prepareServerSelectionList() {
|
||||
populateServerListings()
|
||||
setServerListingHandlers()
|
||||
}
|
||||
|
||||
function prepareAccountSelectionList(){
|
||||
function prepareAccountSelectionList() {
|
||||
populateAccountListings()
|
||||
setAccountListingHandlers()
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
// Requirements
|
||||
const os = require('os')
|
||||
const os = require('os')
|
||||
const semver = require('semver')
|
||||
|
||||
const { JavaGuard } = require('./assets/js/assetguard')
|
||||
const DropinModUtil = require('./assets/js/dropinmodutil')
|
||||
const DropinModUtil = require('./assets/js/dropinmodutil')
|
||||
|
||||
const settingsState = {
|
||||
invalid: new Set()
|
||||
}
|
||||
|
||||
function bindSettingsSelect(){
|
||||
for(let ele of document.getElementsByClassName('settingsSelectContainer')) {
|
||||
function bindSettingsSelect() {
|
||||
for (let ele of document.getElementsByClassName('settingsSelectContainer')) {
|
||||
const selectedDiv = ele.getElementsByClassName('settingsSelectSelected')[0]
|
||||
|
||||
selectedDiv.onclick = (e) => {
|
||||
@ -22,12 +22,12 @@ function bindSettingsSelect(){
|
||||
}
|
||||
}
|
||||
|
||||
function closeSettingsSelect(el){
|
||||
for(let ele of document.getElementsByClassName('settingsSelectContainer')) {
|
||||
function closeSettingsSelect(el) {
|
||||
for (let ele of document.getElementsByClassName('settingsSelectContainer')) {
|
||||
const selectedDiv = ele.getElementsByClassName('settingsSelectSelected')[0]
|
||||
const optionsDiv = ele.getElementsByClassName('settingsSelectOptions')[0]
|
||||
|
||||
if(!(selectedDiv === el)) {
|
||||
if (!(selectedDiv === el)) {
|
||||
selectedDiv.classList.remove('select-arrow-active')
|
||||
optionsDiv.setAttribute('hidden', '')
|
||||
}
|
||||
@ -41,9 +41,9 @@ document.addEventListener('click', closeSettingsSelect)
|
||||
bindSettingsSelect()
|
||||
|
||||
|
||||
function bindFileSelectors(){
|
||||
for(let ele of document.getElementsByClassName('settingsFileSelButton')){
|
||||
|
||||
function bindFileSelectors() {
|
||||
for (let ele of document.getElementsByClassName('settingsFileSelButton')) {
|
||||
|
||||
ele.onclick = async e => {
|
||||
const isJavaExecSel = ele.id === 'settingsJavaExecSel'
|
||||
const directoryDialog = ele.hasAttribute('dialogDirectory') && ele.getAttribute('dialogDirectory') == 'true'
|
||||
@ -53,11 +53,11 @@ function bindFileSelectors(){
|
||||
properties
|
||||
}
|
||||
|
||||
if(ele.hasAttribute('dialogTitle')) {
|
||||
if (ele.hasAttribute('dialogTitle')) {
|
||||
options.title = ele.getAttribute('dialogTitle')
|
||||
}
|
||||
|
||||
if(isJavaExecSel && process.platform === 'win32') {
|
||||
if (isJavaExecSel && process.platform === 'win32') {
|
||||
options.filters = [
|
||||
{ name: '実行可能ファイル', extensions: ['exe'] },
|
||||
{ name: 'すべてのファイル', extensions: ['*'] }
|
||||
@ -65,9 +65,9 @@ function bindFileSelectors(){
|
||||
}
|
||||
|
||||
const res = await remote.dialog.showOpenDialog(remote.getCurrentWindow(), options)
|
||||
if(!res.canceled) {
|
||||
if (!res.canceled) {
|
||||
ele.previousElementSibling.value = res.filePaths[0]
|
||||
if(isJavaExecSel) {
|
||||
if (isJavaExecSel) {
|
||||
populateJavaExecDetails(ele.previousElementSibling.value)
|
||||
}
|
||||
}
|
||||
@ -89,24 +89,24 @@ bindFileSelectors()
|
||||
* will be disabled until the value is corrected. This is an automated
|
||||
* process. More complex UI may need to be bound separately.
|
||||
*/
|
||||
function initSettingsValidators(){
|
||||
function initSettingsValidators() {
|
||||
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
|
||||
Array.from(sEls).map((v, index, arr) => {
|
||||
const vFn = ConfigManager['validate' + v.getAttribute('cValue')]
|
||||
if(typeof vFn === 'function'){
|
||||
if(v.tagName === 'INPUT'){
|
||||
if(v.type === 'number' || v.type === 'text'){
|
||||
if (typeof vFn === 'function') {
|
||||
if (v.tagName === 'INPUT') {
|
||||
if (v.type === 'number' || v.type === 'text') {
|
||||
v.addEventListener('keyup', (e) => {
|
||||
const v = e.target
|
||||
if(!vFn(v.value)){
|
||||
if (!vFn(v.value)) {
|
||||
settingsState.invalid.add(v.id)
|
||||
v.setAttribute('error', '')
|
||||
settingsSaveDisabled(true)
|
||||
} else {
|
||||
if(v.hasAttribute('error')){
|
||||
if (v.hasAttribute('error')) {
|
||||
v.removeAttribute('error')
|
||||
settingsState.invalid.delete(v.id)
|
||||
if(settingsState.invalid.size === 0){
|
||||
if (settingsState.invalid.size === 0) {
|
||||
settingsSaveDisabled(false)
|
||||
}
|
||||
}
|
||||
@ -122,35 +122,35 @@ function initSettingsValidators(){
|
||||
/**
|
||||
* Load configuration values onto the UI. This is an automated process.
|
||||
*/
|
||||
function initSettingsValues(){
|
||||
function initSettingsValues() {
|
||||
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
|
||||
Array.from(sEls).map((v, index, arr) => {
|
||||
const cVal = v.getAttribute('cValue')
|
||||
const gFn = ConfigManager['get' + cVal]
|
||||
if(typeof gFn === 'function'){
|
||||
if(v.tagName === 'INPUT'){
|
||||
if(v.type === 'number' || v.type === 'text'){
|
||||
if (typeof gFn === 'function') {
|
||||
if (v.tagName === 'INPUT') {
|
||||
if (v.type === 'number' || v.type === 'text') {
|
||||
// Special Conditions
|
||||
if(cVal === 'JavaExecutable'){
|
||||
if (cVal === 'JavaExecutable') {
|
||||
populateJavaExecDetails(v.value)
|
||||
v.value = gFn()
|
||||
} else if (cVal === 'DataDirectory'){
|
||||
} else if (cVal === 'DataDirectory') {
|
||||
v.value = gFn()
|
||||
} else if(cVal === 'JVMOptions'){
|
||||
} else if (cVal === 'JVMOptions') {
|
||||
v.value = gFn().join(' ')
|
||||
} else {
|
||||
v.value = gFn()
|
||||
}
|
||||
} else if(v.type === 'checkbox'){
|
||||
} else if (v.type === 'checkbox') {
|
||||
v.checked = gFn()
|
||||
}
|
||||
} else if(v.tagName === 'DIV'){
|
||||
if(v.classList.contains('rangeSlider')){
|
||||
} else if (v.tagName === 'DIV') {
|
||||
if (v.classList.contains('rangeSlider')) {
|
||||
// Special Conditions
|
||||
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
|
||||
if (cVal === 'MinRAM' || cVal === 'MaxRAM') {
|
||||
let val = gFn()
|
||||
if(val.endsWith('M')){
|
||||
val = Number(val.substring(0, val.length-1))/1000
|
||||
if (val.endsWith('M')) {
|
||||
val = Number(val.substring(0, val.length - 1)) / 1000
|
||||
} else {
|
||||
val = Number.parseFloat(val)
|
||||
}
|
||||
@ -169,34 +169,34 @@ function initSettingsValues(){
|
||||
/**
|
||||
* Save the settings values.
|
||||
*/
|
||||
function saveSettingsValues(){
|
||||
function saveSettingsValues() {
|
||||
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
|
||||
Array.from(sEls).map((v, index, arr) => {
|
||||
const cVal = v.getAttribute('cValue')
|
||||
const sFn = ConfigManager['set' + cVal]
|
||||
if(typeof sFn === 'function'){
|
||||
if(v.tagName === 'INPUT'){
|
||||
if(v.type === 'number' || v.type === 'text'){
|
||||
if (typeof sFn === 'function') {
|
||||
if (v.tagName === 'INPUT') {
|
||||
if (v.type === 'number' || v.type === 'text') {
|
||||
// Special Conditions
|
||||
if(cVal === 'JVMOptions'){
|
||||
if (cVal === 'JVMOptions') {
|
||||
sFn(v.value.split(' '))
|
||||
} else {
|
||||
sFn(v.value)
|
||||
}
|
||||
} else if(v.type === 'checkbox'){
|
||||
} else if (v.type === 'checkbox') {
|
||||
sFn(v.checked)
|
||||
// Special Conditions
|
||||
if(cVal === 'AllowPrerelease'){
|
||||
if (cVal === 'AllowPrerelease') {
|
||||
changeAllowPrerelease(v.checked)
|
||||
}
|
||||
}
|
||||
} else if(v.tagName === 'DIV'){
|
||||
if(v.classList.contains('rangeSlider')){
|
||||
} else if (v.tagName === 'DIV') {
|
||||
if (v.classList.contains('rangeSlider')) {
|
||||
// Special Conditions
|
||||
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
|
||||
if (cVal === 'MinRAM' || cVal === 'MaxRAM') {
|
||||
let val = Number(v.getAttribute('value'))
|
||||
if(val%1 > 0){
|
||||
val = val*1000 + 'M'
|
||||
if (val % 1 > 0) {
|
||||
val = val * 1000 + 'M'
|
||||
} else {
|
||||
val = val + 'G'
|
||||
}
|
||||
@ -219,8 +219,8 @@ let selectedSettingsTab = 'settingsTabAccount'
|
||||
*
|
||||
* @param {UIEvent} e The scroll event.
|
||||
*/
|
||||
function settingsTabScrollListener(e){
|
||||
if(e.target.scrollTop > Number.parseFloat(getComputedStyle(e.target.firstElementChild).marginTop)){
|
||||
function settingsTabScrollListener(e) {
|
||||
if (e.target.scrollTop > Number.parseFloat(getComputedStyle(e.target.firstElementChild).marginTop)) {
|
||||
document.getElementById('settingsContainer').setAttribute('scrolled', '')
|
||||
} else {
|
||||
document.getElementById('settingsContainer').removeAttribute('scrolled')
|
||||
@ -230,9 +230,9 @@ function settingsTabScrollListener(e){
|
||||
/**
|
||||
* Bind functionality for the settings navigation items.
|
||||
*/
|
||||
function setupSettingsTabs(){
|
||||
function setupSettingsTabs() {
|
||||
Array.from(document.getElementsByClassName('settingsNavItem')).map((val) => {
|
||||
if(val.hasAttribute('rSc')){
|
||||
if (val.hasAttribute('rSc')) {
|
||||
val.onclick = () => {
|
||||
settingsNavItemListener(val)
|
||||
}
|
||||
@ -247,13 +247,13 @@ function setupSettingsTabs(){
|
||||
* @param {Element} ele The nav item which has been clicked.
|
||||
* @param {boolean} fade Optional. True to fade transition.
|
||||
*/
|
||||
function settingsNavItemListener(ele, fade = true){
|
||||
if(ele.hasAttribute('selected')){
|
||||
function settingsNavItemListener(ele, fade = true) {
|
||||
if (ele.hasAttribute('selected')) {
|
||||
return
|
||||
}
|
||||
const navItems = document.getElementsByClassName('settingsNavItem')
|
||||
for(let i=0; i<navItems.length; i++){
|
||||
if(navItems[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < navItems.length; i++) {
|
||||
if (navItems[i].hasAttribute('selected')) {
|
||||
navItems[i].removeAttribute('selected')
|
||||
}
|
||||
}
|
||||
@ -264,7 +264,7 @@ function settingsNavItemListener(ele, fade = true){
|
||||
document.getElementById(prevTab).onscroll = null
|
||||
document.getElementById(selectedSettingsTab).onscroll = settingsTabScrollListener
|
||||
|
||||
if(fade){
|
||||
if (fade) {
|
||||
$(`#${prevTab}`).fadeOut(250, () => {
|
||||
$(`#${selectedSettingsTab}`).fadeIn({
|
||||
duration: 250,
|
||||
@ -296,7 +296,7 @@ const settingsNavDone = document.getElementById('settingsNavDone')
|
||||
*
|
||||
* @param {boolean} v True to disable, false to enable.
|
||||
*/
|
||||
function settingsSaveDisabled(v){
|
||||
function settingsSaveDisabled(v) {
|
||||
settingsNavDone.disabled = v
|
||||
}
|
||||
|
||||
@ -327,15 +327,15 @@ document.getElementById('settingsAddAccount').onclick = (e) => {
|
||||
* Bind functionality for the account selection buttons. If another account
|
||||
* is selected, the UI of the previously selected account will be updated.
|
||||
*/
|
||||
function bindAuthAccountSelect(){
|
||||
function bindAuthAccountSelect() {
|
||||
Array.from(document.getElementsByClassName('settingsAuthAccountSelect')).map((val) => {
|
||||
val.onclick = (e) => {
|
||||
if(val.hasAttribute('selected')){
|
||||
if (val.hasAttribute('selected')) {
|
||||
return
|
||||
}
|
||||
const selectBtns = document.getElementsByClassName('settingsAuthAccountSelect')
|
||||
for(let i=0; i<selectBtns.length; i++){
|
||||
if(selectBtns[i].hasAttribute('selected')){
|
||||
for (let i = 0; i < selectBtns.length; i++) {
|
||||
if (selectBtns[i].hasAttribute('selected')) {
|
||||
selectBtns[i].removeAttribute('selected')
|
||||
selectBtns[i].innerHTML = 'このアカウントを選択する'
|
||||
}
|
||||
@ -352,11 +352,11 @@ function bindAuthAccountSelect(){
|
||||
* the selected account, another account will be selected and the UI will
|
||||
* be updated accordingly.
|
||||
*/
|
||||
function bindAuthAccountLogOut(){
|
||||
function bindAuthAccountLogOut() {
|
||||
Array.from(document.getElementsByClassName('settingsAuthAccountLogOut')).map((val) => {
|
||||
val.onclick = (e) => {
|
||||
let isLastAccount = false
|
||||
if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
|
||||
if (Object.keys(ConfigManager.getAuthAccounts()).length === 1) {
|
||||
isLastAccount = true
|
||||
setOverlayContent(
|
||||
'注意<br>これはログインしている最後のアカウントです',
|
||||
@ -376,23 +376,36 @@ function bindAuthAccountLogOut(){
|
||||
} else {
|
||||
processLogOut(val, isLastAccount)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let data = null
|
||||
|
||||
/**
|
||||
* Process a log out.
|
||||
*
|
||||
* @param {Element} val The log out button element.
|
||||
* @param {boolean} isLastAccount If this logout is on the last added account.
|
||||
*/
|
||||
function processLogOut(val, isLastAccount){
|
||||
const parent = val.closest('.settingsAuthAccount')
|
||||
const uuid = parent.getAttribute('uuid')
|
||||
function processLogOut(val, isLastAccount, skip = false) {
|
||||
data = {
|
||||
val,
|
||||
isLastAccount
|
||||
}
|
||||
if (!skip) {
|
||||
const parent = val.closest('.settingsAuthAccount')
|
||||
const uuid = parent.getAttribute('uuid')
|
||||
const account = ConfigManager.getAuthAccount(uuid)
|
||||
if (account.type === 'microsoft') {
|
||||
toggleOverlay(true, false, 'msOverlay')
|
||||
ipcRenderer.send('openMSALogoutWindow', 'open')
|
||||
}
|
||||
}
|
||||
const prevSelAcc = ConfigManager.getSelectedAccount()
|
||||
AuthManager.removeAccount(uuid).then(() => {
|
||||
if(!isLastAccount && uuid === prevSelAcc.uuid){
|
||||
if (!isLastAccount && uuid === prevSelAcc.uuid) {
|
||||
const selAcc = ConfigManager.getSelectedAccount()
|
||||
refreshAuthAccountSelected(selAcc.uuid)
|
||||
updateSelectedAccount(selAcc)
|
||||
@ -404,20 +417,25 @@ function processLogOut(val, isLastAccount){
|
||||
})
|
||||
}
|
||||
|
||||
ipcRenderer.on('MSALogoutWindowReply', (event, ...args) => {
|
||||
toggleOverlay(false, false, 'msOverlay')
|
||||
processLogOut(data.val, data.isLastAccount, true)
|
||||
})
|
||||
|
||||
/**
|
||||
* Refreshes the status of the selected account on the auth account
|
||||
* elements.
|
||||
*
|
||||
* @param {string} uuid The UUID of the new selected account.
|
||||
*/
|
||||
function refreshAuthAccountSelected(uuid){
|
||||
function refreshAuthAccountSelected(uuid) {
|
||||
Array.from(document.getElementsByClassName('settingsAuthAccount')).map((val) => {
|
||||
const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0]
|
||||
if(uuid === val.getAttribute('uuid')){
|
||||
if (uuid === val.getAttribute('uuid')) {
|
||||
selBtn.setAttribute('selected', '')
|
||||
selBtn.innerHTML = '選択中のアカウント ✔'
|
||||
} else {
|
||||
if(selBtn.hasAttribute('selected')){
|
||||
if (selBtn.hasAttribute('selected')) {
|
||||
selBtn.removeAttribute('selected')
|
||||
}
|
||||
selBtn.innerHTML = 'このアカウントを選択する'
|
||||
@ -430,10 +448,10 @@ const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts
|
||||
/**
|
||||
* Add auth account elements for each one stored in the authentication database.
|
||||
*/
|
||||
function populateAuthAccounts(){
|
||||
function populateAuthAccounts() {
|
||||
const authAccounts = ConfigManager.getAuthAccounts()
|
||||
const authKeys = Object.keys(authAccounts)
|
||||
if(authKeys.length === 0){
|
||||
if (authKeys.length === 0) {
|
||||
return
|
||||
}
|
||||
const selectedUUID = ConfigManager.getSelectedAccount().uuid
|
||||
@ -487,12 +505,12 @@ function prepareAccountsTab() {
|
||||
* Disable decimals, negative signs, and scientific notation.
|
||||
*/
|
||||
document.getElementById('settingsGameWidth').addEventListener('keydown', (e) => {
|
||||
if(/^[-.eE]$/.test(e.key)){
|
||||
if (/^[-.eE]$/.test(e.key)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
document.getElementById('settingsGameHeight').addEventListener('keydown', (e) => {
|
||||
if(/^[-.eE]$/.test(e.key)){
|
||||
if (/^[-.eE]$/.test(e.key)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
@ -506,7 +524,7 @@ const settingsModsContainer = document.getElementById('settingsModsContainer')
|
||||
/**
|
||||
* Resolve and update the mods on the UI.
|
||||
*/
|
||||
function resolveModsForUI(){
|
||||
function resolveModsForUI() {
|
||||
const serv = ConfigManager.getSelectedServer()
|
||||
|
||||
const distro = DistroManager.getDistribution()
|
||||
@ -525,16 +543,16 @@ function resolveModsForUI(){
|
||||
* @param {boolean} submodules Whether or not we are parsing submodules.
|
||||
* @param {Object} servConf The server configuration object for this module level.
|
||||
*/
|
||||
function parseModulesForUI(mdls, submodules, servConf){
|
||||
function parseModulesForUI(mdls, submodules, servConf) {
|
||||
|
||||
let reqMods = ''
|
||||
let optMods = ''
|
||||
|
||||
for(const mdl of mdls){
|
||||
for (const mdl of mdls) {
|
||||
|
||||
if(mdl.getType() === DistroManager.Types.ForgeMod || mdl.getType() === DistroManager.Types.LiteMod || mdl.getType() === DistroManager.Types.LiteLoader){
|
||||
if (mdl.getType() === DistroManager.Types.ForgeMod || mdl.getType() === DistroManager.Types.LiteMod || mdl.getType() === DistroManager.Types.LiteLoader) {
|
||||
|
||||
if(mdl.getRequired().isRequired()){
|
||||
if (mdl.getRequired().isRequired()) {
|
||||
|
||||
reqMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" enabled>
|
||||
<div class="settingsModContent">
|
||||
@ -594,11 +612,11 @@ function parseModulesForUI(mdls, submodules, servConf){
|
||||
* Bind functionality to mod config toggle switches. Switching the value
|
||||
* will also switch the status color on the left of the mod UI.
|
||||
*/
|
||||
function bindModsToggleSwitch(){
|
||||
function bindModsToggleSwitch() {
|
||||
const sEls = settingsModsContainer.querySelectorAll('[formod]')
|
||||
Array.from(sEls).map((v, index, arr) => {
|
||||
v.onchange = () => {
|
||||
if(v.checked) {
|
||||
if (v.checked) {
|
||||
document.getElementById(v.getAttribute('formod')).setAttribute('enabled', '')
|
||||
} else {
|
||||
document.getElementById(v.getAttribute('formod')).removeAttribute('enabled')
|
||||
@ -611,7 +629,7 @@ function bindModsToggleSwitch(){
|
||||
/**
|
||||
* Save the mod configuration based on the UI values.
|
||||
*/
|
||||
function saveModConfiguration(){
|
||||
function saveModConfiguration() {
|
||||
const serv = ConfigManager.getSelectedServer()
|
||||
const modConf = ConfigManager.getModConfiguration(serv)
|
||||
modConf.mods = _saveModConfiguration(modConf.mods)
|
||||
@ -623,15 +641,15 @@ function saveModConfiguration(){
|
||||
*
|
||||
* @param {Object} modConf Mod config object to save.
|
||||
*/
|
||||
function _saveModConfiguration(modConf){
|
||||
for(let m of Object.entries(modConf)){
|
||||
function _saveModConfiguration(modConf) {
|
||||
for (let m of Object.entries(modConf)) {
|
||||
const tSwitch = settingsModsContainer.querySelectorAll(`[formod='${m[0]}']`)
|
||||
if(!tSwitch[0].hasAttribute('dropin')){
|
||||
if(typeof m[1] === 'boolean'){
|
||||
if (!tSwitch[0].hasAttribute('dropin')) {
|
||||
if (typeof m[1] === 'boolean') {
|
||||
modConf[m[0]] = tSwitch[0].checked
|
||||
} else {
|
||||
if(m[1] != null){
|
||||
if(tSwitch.length > 0){
|
||||
if (m[1] != null) {
|
||||
if (tSwitch.length > 0) {
|
||||
modConf[m[0]].value = tSwitch[0].checked
|
||||
}
|
||||
modConf[m[0]].mods = _saveModConfiguration(modConf[m[0]].mods)
|
||||
@ -651,14 +669,14 @@ let CACHE_DROPIN_MODS
|
||||
* Resolve any located drop-in mods for this server and
|
||||
* populate the results onto the UI.
|
||||
*/
|
||||
function resolveDropinModsForUI(){
|
||||
function resolveDropinModsForUI() {
|
||||
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
|
||||
CACHE_SETTINGS_MODS_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID(), 'mods')
|
||||
CACHE_DROPIN_MODS = DropinModUtil.scanForDropinMods(CACHE_SETTINGS_MODS_DIR, serv.getMinecraftVersion())
|
||||
|
||||
let dropinMods = ''
|
||||
|
||||
for(dropin of CACHE_DROPIN_MODS){
|
||||
for (dropin of CACHE_DROPIN_MODS) {
|
||||
dropinMods += `<div id="${dropin.fullName}" class="settingsBaseMod settingsDropinMod" ${!dropin.disabled ? 'enabled' : ''}>
|
||||
<div class="settingsModContent">
|
||||
<div class="settingsModMainWrapper">
|
||||
@ -684,13 +702,13 @@ function resolveDropinModsForUI(){
|
||||
/**
|
||||
* Bind the remove button for each loaded drop-in mod.
|
||||
*/
|
||||
function bindDropinModsRemoveButton(){
|
||||
function bindDropinModsRemoveButton() {
|
||||
const sEls = settingsModsContainer.querySelectorAll('[remmod]')
|
||||
Array.from(sEls).map((v, index, arr) => {
|
||||
v.onclick = () => {
|
||||
const fullName = v.getAttribute('remmod')
|
||||
const res = DropinModUtil.deleteDropinMod(CACHE_SETTINGS_MODS_DIR, fullName)
|
||||
if(res){
|
||||
if (res) {
|
||||
document.getElementById(fullName).remove()
|
||||
} else {
|
||||
setOverlayContent(
|
||||
@ -709,7 +727,7 @@ function bindDropinModsRemoveButton(){
|
||||
* Bind functionality to the file system button for the selected
|
||||
* server configuration.
|
||||
*/
|
||||
function bindDropinModFileSystemButton(){
|
||||
function bindDropinModFileSystemButton() {
|
||||
const fsBtn = document.getElementById('settingsDropinFileSystemButton')
|
||||
fsBtn.onclick = () => {
|
||||
DropinModUtil.validateDir(CACHE_SETTINGS_MODS_DIR)
|
||||
@ -740,14 +758,14 @@ function bindDropinModFileSystemButton(){
|
||||
* Save drop-in mod states. Enabling and disabling is just a matter
|
||||
* of adding/removing the .disabled extension.
|
||||
*/
|
||||
function saveDropinModConfiguration(){
|
||||
for(dropin of CACHE_DROPIN_MODS){
|
||||
function saveDropinModConfiguration() {
|
||||
for (dropin of CACHE_DROPIN_MODS) {
|
||||
const dropinUI = document.getElementById(dropin.fullName)
|
||||
if(dropinUI != null){
|
||||
if (dropinUI != null) {
|
||||
const dropinUIEnabled = dropinUI.hasAttribute('enabled')
|
||||
if(DropinModUtil.isDropinModEnabled(dropin.fullName) != dropinUIEnabled){
|
||||
if (DropinModUtil.isDropinModEnabled(dropin.fullName) != dropinUIEnabled) {
|
||||
DropinModUtil.toggleDropinMod(CACHE_SETTINGS_MODS_DIR, dropin.fullName, dropinUIEnabled).catch(err => {
|
||||
if(!isOverlayVisible()){
|
||||
if (!isOverlayVisible()) {
|
||||
setOverlayContent(
|
||||
'1つ以上のドロップ・インMod<br>のON/OFFに失敗しました',
|
||||
err.message,
|
||||
@ -765,8 +783,8 @@ function saveDropinModConfiguration(){
|
||||
// Refresh the drop-in mods when F5 is pressed.
|
||||
// Only active on the mods tab.
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if(getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods'){
|
||||
if(e.key === 'F5'){
|
||||
if (getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods') {
|
||||
if (e.key === 'F5') {
|
||||
reloadDropinMods()
|
||||
saveShaderpackSettings()
|
||||
resolveShaderpacksForUI()
|
||||
@ -774,7 +792,7 @@ document.addEventListener('keydown', (e) => {
|
||||
}
|
||||
})
|
||||
|
||||
function reloadDropinMods(){
|
||||
function reloadDropinMods() {
|
||||
resolveDropinModsForUI()
|
||||
bindDropinModsRemoveButton()
|
||||
bindDropinModFileSystemButton()
|
||||
@ -790,7 +808,7 @@ let CACHE_SELECTED_SHADERPACK
|
||||
/**
|
||||
* Load shaderpack information.
|
||||
*/
|
||||
function resolveShaderpacksForUI(){
|
||||
function resolveShaderpacksForUI() {
|
||||
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
|
||||
CACHE_SETTINGS_INSTANCE_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID())
|
||||
CACHE_SHADERPACKS = DropinModUtil.scanForShaderpacks(CACHE_SETTINGS_INSTANCE_DIR)
|
||||
@ -799,20 +817,20 @@ function resolveShaderpacksForUI(){
|
||||
setShadersOptions(CACHE_SHADERPACKS, CACHE_SELECTED_SHADERPACK)
|
||||
}
|
||||
|
||||
function setShadersOptions(arr, selected){
|
||||
function setShadersOptions(arr, selected) {
|
||||
const cont = document.getElementById('settingsShadersOptions')
|
||||
cont.innerHTML = ''
|
||||
for(let opt of arr) {
|
||||
for (let opt of arr) {
|
||||
const d = document.createElement('DIV')
|
||||
d.innerHTML = opt.name
|
||||
d.setAttribute('value', opt.fullName)
|
||||
if(opt.fullName === selected) {
|
||||
if (opt.fullName === selected) {
|
||||
d.setAttribute('selected', '')
|
||||
document.getElementById('settingsShadersSelected').innerHTML = opt.name
|
||||
}
|
||||
d.addEventListener('click', function(e) {
|
||||
d.addEventListener('click', function (e) {
|
||||
this.parentNode.previousElementSibling.innerHTML = this.innerHTML
|
||||
for(let sib of this.parentNode.children){
|
||||
for (let sib of this.parentNode.children) {
|
||||
sib.removeAttribute('selected')
|
||||
}
|
||||
this.setAttribute('selected', '')
|
||||
@ -822,10 +840,10 @@ function setShadersOptions(arr, selected){
|
||||
}
|
||||
}
|
||||
|
||||
function saveShaderpackSettings(){
|
||||
function saveShaderpackSettings() {
|
||||
let sel = 'OFF'
|
||||
for(let opt of document.getElementById('settingsShadersOptions').childNodes){
|
||||
if(opt.hasAttribute('selected')){
|
||||
for (let opt of document.getElementById('settingsShadersOptions').childNodes) {
|
||||
if (opt.hasAttribute('selected')) {
|
||||
sel = opt.getAttribute('value')
|
||||
}
|
||||
}
|
||||
@ -866,7 +884,7 @@ function bindShaderpackButton() {
|
||||
/**
|
||||
* Load the currently selected server information onto the mods tab.
|
||||
*/
|
||||
function loadSelectedServerOnModsTab(){
|
||||
function loadSelectedServerOnModsTab() {
|
||||
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
|
||||
|
||||
document.getElementById('settingsSelServContent').innerHTML = `
|
||||
@ -901,7 +919,7 @@ document.getElementById('settingsSwitchServerButton').addEventListener('click',
|
||||
/**
|
||||
* Save mod configuration for the current selected server.
|
||||
*/
|
||||
function saveAllModConfigurations(){
|
||||
function saveAllModConfigurations() {
|
||||
saveModConfiguration()
|
||||
ConfigManager.save()
|
||||
saveDropinModConfiguration()
|
||||
@ -911,7 +929,7 @@ function saveAllModConfigurations(){
|
||||
* Function to refresh the mods tab whenever the selected
|
||||
* server is changed.
|
||||
*/
|
||||
function animateModsTabRefresh(){
|
||||
function animateModsTabRefresh() {
|
||||
$('#settingsTabMods').fadeOut(500, () => {
|
||||
prepareModsTab()
|
||||
$('#settingsTabMods').fadeIn(500)
|
||||
@ -921,7 +939,7 @@ function animateModsTabRefresh(){
|
||||
/**
|
||||
* Prepare the Mods tab for display.
|
||||
*/
|
||||
function prepareModsTab(first){
|
||||
function prepareModsTab(first) {
|
||||
resolveModsForUI()
|
||||
resolveDropinModsForUI()
|
||||
resolveShaderpacksForUI()
|
||||
@ -937,12 +955,12 @@ function prepareModsTab(first){
|
||||
*/
|
||||
|
||||
// DOM Cache
|
||||
const settingsMaxRAMRange = document.getElementById('settingsMaxRAMRange')
|
||||
const settingsMinRAMRange = document.getElementById('settingsMinRAMRange')
|
||||
const settingsMaxRAMLabel = document.getElementById('settingsMaxRAMLabel')
|
||||
const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel')
|
||||
const settingsMemoryTotal = document.getElementById('settingsMemoryTotal')
|
||||
const settingsMemoryAvail = document.getElementById('settingsMemoryAvail')
|
||||
const settingsMaxRAMRange = document.getElementById('settingsMaxRAMRange')
|
||||
const settingsMinRAMRange = document.getElementById('settingsMinRAMRange')
|
||||
const settingsMaxRAMLabel = document.getElementById('settingsMaxRAMLabel')
|
||||
const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel')
|
||||
const settingsMemoryTotal = document.getElementById('settingsMemoryTotal')
|
||||
const settingsMemoryAvail = document.getElementById('settingsMemoryAvail')
|
||||
const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails')
|
||||
|
||||
// Store maximum memory values.
|
||||
@ -953,7 +971,7 @@ const SETTINGS_MIN_MEMORY = ConfigManager.getAbsoluteMinRAM()
|
||||
settingsMaxRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
|
||||
settingsMaxRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY)
|
||||
settingsMinRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
|
||||
settingsMinRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY )
|
||||
settingsMinRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY)
|
||||
|
||||
// Bind on change event for min memory container.
|
||||
settingsMinRAMRange.onchange = (e) => {
|
||||
@ -965,22 +983,22 @@ settingsMinRAMRange.onchange = (e) => {
|
||||
// Get reference to range bar.
|
||||
const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
|
||||
// Calculate effective total memory.
|
||||
const max = (os.totalmem()-1000000000)/1000000000
|
||||
const max = (os.totalmem() - 1000000000) / 1000000000
|
||||
|
||||
// Change range bar color based on the selected value.
|
||||
if(sMinV >= max/2){
|
||||
if (sMinV >= max / 2) {
|
||||
bar.style.background = '#e86060'
|
||||
} else if(sMinV >= max/4) {
|
||||
} else if (sMinV >= max / 4) {
|
||||
bar.style.background = '#e8e18b'
|
||||
} else {
|
||||
bar.style.background = null
|
||||
}
|
||||
|
||||
// Increase maximum memory if the minimum exceeds its value.
|
||||
if(sMaxV < sMinV){
|
||||
if (sMaxV < sMinV) {
|
||||
const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
|
||||
updateRangedSlider(settingsMaxRAMRange, sMinV,
|
||||
((sMinV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
|
||||
((sMinV - sliderMeta.min) / sliderMeta.step) * sliderMeta.inc)
|
||||
settingsMaxRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
|
||||
}
|
||||
|
||||
@ -997,22 +1015,22 @@ settingsMaxRAMRange.onchange = (e) => {
|
||||
// Get reference to range bar.
|
||||
const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
|
||||
// Calculate effective total memory.
|
||||
const max = (os.totalmem()-1000000000)/1000000000
|
||||
const max = (os.totalmem() - 1000000000) / 1000000000
|
||||
|
||||
// Change range bar color based on the selected value.
|
||||
if(sMaxV >= max/2){
|
||||
if (sMaxV >= max / 2) {
|
||||
bar.style.background = '#e86060'
|
||||
} else if(sMaxV >= max/4) {
|
||||
} else if (sMaxV >= max / 4) {
|
||||
bar.style.background = '#e8e18b'
|
||||
} else {
|
||||
bar.style.background = null
|
||||
}
|
||||
|
||||
// Decrease the minimum memory if the maximum value is less.
|
||||
if(sMaxV < sMinV){
|
||||
if (sMaxV < sMinV) {
|
||||
const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
|
||||
updateRangedSlider(settingsMinRAMRange, sMaxV,
|
||||
((sMaxV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
|
||||
((sMaxV - sliderMeta.min) / sliderMeta.step) * sliderMeta.inc)
|
||||
settingsMinRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G'
|
||||
}
|
||||
settingsMaxRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G'
|
||||
@ -1024,14 +1042,14 @@ settingsMaxRAMRange.onchange = (e) => {
|
||||
* @param {Element} v The range slider to calculate against.
|
||||
* @returns {Object} An object with meta values for the provided ranged slider.
|
||||
*/
|
||||
function calculateRangeSliderMeta(v){
|
||||
function calculateRangeSliderMeta(v) {
|
||||
const val = {
|
||||
max: Number(v.getAttribute('max')),
|
||||
min: Number(v.getAttribute('min')),
|
||||
step: Number(v.getAttribute('step')),
|
||||
}
|
||||
val.ticks = (val.max-val.min)/val.step
|
||||
val.inc = 100/val.ticks
|
||||
val.ticks = (val.max - val.min) / val.step
|
||||
val.inc = 100 / val.ticks
|
||||
return val
|
||||
}
|
||||
|
||||
@ -1039,7 +1057,7 @@ function calculateRangeSliderMeta(v){
|
||||
* Binds functionality to the ranged sliders. They're more than
|
||||
* just divs now :').
|
||||
*/
|
||||
function bindRangeSlider(){
|
||||
function bindRangeSlider() {
|
||||
Array.from(document.getElementsByClassName('rangeSlider')).map((v) => {
|
||||
|
||||
// Reference the track (thumb).
|
||||
@ -1049,7 +1067,7 @@ function bindRangeSlider(){
|
||||
const value = v.getAttribute('value')
|
||||
const sliderMeta = calculateRangeSliderMeta(v)
|
||||
|
||||
updateRangedSlider(v, value, ((value-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
|
||||
updateRangedSlider(v, value, ((value - sliderMeta.min) / sliderMeta.step) * sliderMeta.inc)
|
||||
|
||||
// The magic happens when we click on the track.
|
||||
track.onmousedown = (e) => {
|
||||
@ -1064,24 +1082,24 @@ function bindRangeSlider(){
|
||||
document.onmousemove = (e) => {
|
||||
|
||||
// Distance from the beginning of the bar in pixels.
|
||||
const diff = e.pageX - v.offsetLeft - track.offsetWidth/2
|
||||
|
||||
const diff = e.pageX - v.offsetLeft - track.offsetWidth / 2
|
||||
|
||||
// Don't move the track off the bar.
|
||||
if(diff >= 0 && diff <= v.offsetWidth-track.offsetWidth/2){
|
||||
if (diff >= 0 && diff <= v.offsetWidth - track.offsetWidth / 2) {
|
||||
|
||||
// Convert the difference to a percentage.
|
||||
const perc = (diff/v.offsetWidth)*100
|
||||
const perc = (diff / v.offsetWidth) * 100
|
||||
// Calculate the percentage of the closest notch.
|
||||
const notch = Number(perc/sliderMeta.inc).toFixed(0)*sliderMeta.inc
|
||||
const notch = Number(perc / sliderMeta.inc).toFixed(0) * sliderMeta.inc
|
||||
|
||||
// If we're close to that notch, stick to it.
|
||||
if(Math.abs(perc-notch) < sliderMeta.inc/2){
|
||||
updateRangedSlider(v, sliderMeta.min+(sliderMeta.step*(notch/sliderMeta.inc)), notch)
|
||||
if (Math.abs(perc - notch) < sliderMeta.inc / 2) {
|
||||
updateRangedSlider(v, sliderMeta.min + (sliderMeta.step * (notch / sliderMeta.inc)), notch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1091,16 +1109,16 @@ function bindRangeSlider(){
|
||||
* @param {string | number} value The new value for the ranged slider.
|
||||
* @param {number} notch The notch that the slider should now be at.
|
||||
*/
|
||||
function updateRangedSlider(element, value, notch){
|
||||
function updateRangedSlider(element, value, notch) {
|
||||
const oldVal = element.getAttribute('value')
|
||||
const bar = element.getElementsByClassName('rangeSliderBar')[0]
|
||||
const track = element.getElementsByClassName('rangeSliderTrack')[0]
|
||||
|
||||
|
||||
element.setAttribute('value', value)
|
||||
|
||||
if(notch < 0){
|
||||
if (notch < 0) {
|
||||
notch = 0
|
||||
} else if(notch > 100) {
|
||||
} else if (notch > 100) {
|
||||
notch = 100
|
||||
}
|
||||
|
||||
@ -1113,7 +1131,7 @@ function updateRangedSlider(element, value, notch){
|
||||
|
||||
let cancelled = !element.dispatchEvent(event)
|
||||
|
||||
if(!cancelled){
|
||||
if (!cancelled) {
|
||||
track.style.left = notch + '%'
|
||||
bar.style.width = notch + '%'
|
||||
} else {
|
||||
@ -1124,9 +1142,9 @@ function updateRangedSlider(element, value, notch){
|
||||
/**
|
||||
* Display the total and available RAM.
|
||||
*/
|
||||
function populateMemoryStatus(){
|
||||
settingsMemoryTotal.innerHTML = Number((os.totalmem()-1000000000)/1000000000).toFixed(1) + 'G'
|
||||
settingsMemoryAvail.innerHTML = Number(os.freemem()/1000000000).toFixed(1) + 'G'
|
||||
function populateMemoryStatus() {
|
||||
settingsMemoryTotal.innerHTML = Number((os.totalmem() - 1000000000) / 1000000000).toFixed(1) + 'G'
|
||||
settingsMemoryAvail.innerHTML = Number(os.freemem() / 1000000000).toFixed(1) + 'G'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1135,12 +1153,12 @@ function populateMemoryStatus(){
|
||||
*
|
||||
* @param {string} execPath The executable path to populate against.
|
||||
*/
|
||||
function populateJavaExecDetails(execPath){
|
||||
function populateJavaExecDetails(execPath) {
|
||||
const jg = new JavaGuard(DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion())
|
||||
jg._validateJavaBinary(execPath).then(v => {
|
||||
if(v.valid){
|
||||
if (v.valid) {
|
||||
const vendor = v.vendor != null ? ` (${v.vendor})` : ''
|
||||
if(v.version.major < 9) {
|
||||
if (v.version.major < 9) {
|
||||
settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major} Update ${v.version.update} (x${v.arch})${vendor}`
|
||||
} else {
|
||||
settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major}.${v.version.minor}.${v.version.revision} (x${v.arch})${vendor}`
|
||||
@ -1154,7 +1172,7 @@ function populateJavaExecDetails(execPath){
|
||||
/**
|
||||
* Prepare the Java tab for display.
|
||||
*/
|
||||
function prepareJavaTab(){
|
||||
function prepareJavaTab() {
|
||||
bindRangeSlider()
|
||||
populateMemoryStatus()
|
||||
}
|
||||
@ -1163,9 +1181,9 @@ function prepareJavaTab(){
|
||||
* About Tab
|
||||
*/
|
||||
|
||||
const settingsTabAbout = document.getElementById('settingsTabAbout')
|
||||
const settingsAboutChangelogTitle = settingsTabAbout.getElementsByClassName('settingsChangelogTitle')[0]
|
||||
const settingsAboutChangelogText = settingsTabAbout.getElementsByClassName('settingsChangelogText')[0]
|
||||
const settingsTabAbout = document.getElementById('settingsTabAbout')
|
||||
const settingsAboutChangelogTitle = settingsTabAbout.getElementsByClassName('settingsChangelogTitle')[0]
|
||||
const settingsAboutChangelogText = settingsTabAbout.getElementsByClassName('settingsChangelogText')[0]
|
||||
const settingsAboutChangelogButton = settingsTabAbout.getElementsByClassName('settingsChangelogButton')[0]
|
||||
|
||||
// Bind the devtools toggle button.
|
||||
@ -1180,7 +1198,7 @@ document.getElementById('settingsAboutDevToolsButton').onclick = (e) => {
|
||||
* @param {string} version The semver version to test.
|
||||
* @returns {boolean} True if the version is a prerelease, otherwise false.
|
||||
*/
|
||||
function isPrerelease(version){
|
||||
function isPrerelease(version) {
|
||||
const preRelComp = semver.prerelease(version)
|
||||
return preRelComp != null && preRelComp.length > 0
|
||||
}
|
||||
@ -1194,7 +1212,7 @@ function isPrerelease(version){
|
||||
* @param {Element} titleElement The title element.
|
||||
* @param {Element} checkElement The check mark element.
|
||||
*/
|
||||
function populateVersionInformation(version, valueElement, titleElement, checkElement){
|
||||
function populateVersionInformation(version, valueElement, titleElement, checkElement) {
|
||||
valueElement.innerHTML = version
|
||||
if(isPrerelease(version)){
|
||||
titleElement.innerHTML = 'プレリリース'
|
||||
@ -1210,7 +1228,7 @@ function populateVersionInformation(version, valueElement, titleElement, checkEl
|
||||
/**
|
||||
* Retrieve the version information and display it on the UI.
|
||||
*/
|
||||
function populateAboutVersionInformation(){
|
||||
function populateAboutVersionInformation() {
|
||||
populateVersionInformation(remote.app.getVersion(), document.getElementById('settingsAboutCurrentVersionValue'), document.getElementById('settingsAboutCurrentVersionTitle'), document.getElementById('settingsAboutCurrentVersionCheck'))
|
||||
}
|
||||
|
||||
@ -1218,19 +1236,19 @@ function populateAboutVersionInformation(){
|
||||
* Fetches the GitHub atom release feed and parses it for the release notes
|
||||
* of the current version. This value is displayed on the UI.
|
||||
*/
|
||||
function populateReleaseNotes(){
|
||||
function populateReleaseNotes() {
|
||||
$.ajax({
|
||||
url: 'https://github.com/TeamKun/NumaLauncher/releases.atom',
|
||||
success: (data) => {
|
||||
const version = 'v' + remote.app.getVersion()
|
||||
const entries = $(data).find('entry')
|
||||
|
||||
for(let i=0; i<entries.length; i++){
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = $(entries[i])
|
||||
let id = entry.find('id').text()
|
||||
id = id.substring(id.lastIndexOf('/')+1)
|
||||
id = id.substring(id.lastIndexOf('/') + 1)
|
||||
|
||||
if(id === version){
|
||||
if (id === version) {
|
||||
settingsAboutChangelogTitle.innerHTML = entry.find('title').text()
|
||||
settingsAboutChangelogText.innerHTML = entry.find('content').text()
|
||||
settingsAboutChangelogButton.href = entry.find('link').attr('href')
|
||||
@ -1247,7 +1265,7 @@ function populateReleaseNotes(){
|
||||
/**
|
||||
* Prepare account tab for display.
|
||||
*/
|
||||
function prepareAboutTab(){
|
||||
function prepareAboutTab() {
|
||||
populateAboutVersionInformation()
|
||||
populateReleaseNotes()
|
||||
}
|
||||
@ -1256,15 +1274,15 @@ function prepareAboutTab(){
|
||||
* Update Tab
|
||||
*/
|
||||
|
||||
const settingsTabUpdate = document.getElementById('settingsTabUpdate')
|
||||
const settingsUpdateTitle = document.getElementById('settingsUpdateTitle')
|
||||
const settingsUpdateVersionCheck = document.getElementById('settingsUpdateVersionCheck')
|
||||
const settingsUpdateVersionTitle = document.getElementById('settingsUpdateVersionTitle')
|
||||
const settingsUpdateVersionValue = document.getElementById('settingsUpdateVersionValue')
|
||||
const settingsTabUpdate = document.getElementById('settingsTabUpdate')
|
||||
const settingsUpdateTitle = document.getElementById('settingsUpdateTitle')
|
||||
const settingsUpdateVersionCheck = document.getElementById('settingsUpdateVersionCheck')
|
||||
const settingsUpdateVersionTitle = document.getElementById('settingsUpdateVersionTitle')
|
||||
const settingsUpdateVersionValue = document.getElementById('settingsUpdateVersionValue')
|
||||
const settingsUpdateChangelogTitle = settingsTabUpdate.getElementsByClassName('settingsChangelogTitle')[0]
|
||||
const settingsUpdateChangelogText = settingsTabUpdate.getElementsByClassName('settingsChangelogText')[0]
|
||||
const settingsUpdateChangelogCont = settingsTabUpdate.getElementsByClassName('settingsChangelogContainer')[0]
|
||||
const settingsUpdateActionButton = document.getElementById('settingsUpdateActionButton')
|
||||
const settingsUpdateChangelogText = settingsTabUpdate.getElementsByClassName('settingsChangelogText')[0]
|
||||
const settingsUpdateChangelogCont = settingsTabUpdate.getElementsByClassName('settingsChangelogContainer')[0]
|
||||
const settingsUpdateActionButton = document.getElementById('settingsUpdateActionButton')
|
||||
|
||||
/**
|
||||
* Update the properties of the update action button.
|
||||
@ -1273,10 +1291,10 @@ const settingsUpdateActionButton = document.getElementById('settingsUpdateActi
|
||||
* @param {boolean} disabled Optional. Disable or enable the button
|
||||
* @param {function} handler Optional. New button event handler.
|
||||
*/
|
||||
function settingsUpdateButtonStatus(text, disabled = false, handler = null){
|
||||
function settingsUpdateButtonStatus(text, disabled = false, handler = null) {
|
||||
settingsUpdateActionButton.innerHTML = text
|
||||
settingsUpdateActionButton.disabled = disabled
|
||||
if(handler != null){
|
||||
if (handler != null) {
|
||||
settingsUpdateActionButton.onclick = handler
|
||||
}
|
||||
}
|
||||
@ -1319,7 +1337,7 @@ function populateSettingsUpdateInformation(data){
|
||||
*
|
||||
* @param {Object} data The update data.
|
||||
*/
|
||||
function prepareUpdateTab(data = null){
|
||||
function prepareUpdateTab(data = null) {
|
||||
populateSettingsUpdateInformation(data)
|
||||
}
|
||||
|
||||
@ -1333,7 +1351,7 @@ function prepareUpdateTab(data = null){
|
||||
* @param {boolean} first Whether or not it is the first load.
|
||||
*/
|
||||
function prepareSettings(first = false) {
|
||||
if(first){
|
||||
if (first) {
|
||||
setupSettingsTabs()
|
||||
initSettingsValidators()
|
||||
prepareUpdateTab()
|
||||
|
168
app/login.ejs
168
app/login.ejs
@ -1,69 +1,99 @@
|
||||
<div id="loginContainer" style="display: none;">
|
||||
<div id="loginCancelContainer" style="display: none;">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.svg"/>
|
||||
<span id="loginSubheader">MINECRAFTへログイン</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<input id="loginUsername" class="loginField" type="text" placeholder="メールアドレス"/>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* 無効なメールアドレス</span>
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<input id="loginPassword" class="loginField" type="password" placeholder="パスワード"/>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* 入力してください</span>
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<!--
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/">パスワードを忘れちゃった?</a>
|
||||
</span>
|
||||
-->
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked>
|
||||
<span id="loginRememberText" class="loginSpanDim">パスワードを記憶する?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
ログイン
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
<div id="loginDisclaimer">
|
||||
<!--
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://minecraft.net/en-us/store/minecraft/">Need an Account?</a>
|
||||
</span>
|
||||
-->
|
||||
<p class="loginDisclaimerText">パスワードは直接Mojangに送信され、保存されることはありません。</p>
|
||||
<p class="loginDisclaimerText">沼ランチャーは Mojang AB とは関係ありません。</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/login.js"></script>
|
||||
</div>
|
||||
<div id="loginContainer" style="display: none">
|
||||
<div id="loginCancelContainer" style="display: none">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.svg" />
|
||||
<span id="loginSubheader">MINECRAFTへログイン</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path
|
||||
d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* 無効なメールアドレス</span>
|
||||
<input
|
||||
id="loginUsername"
|
||||
class="loginField"
|
||||
type="text"
|
||||
placeholder="EMAIL OR USERNAME"
|
||||
/>
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path
|
||||
d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* 入力してください</span>
|
||||
<input
|
||||
id="loginPassword"
|
||||
class="loginField"
|
||||
type="password"
|
||||
placeholder="PASSWORD"
|
||||
/>
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/"
|
||||
>パスワードを忘れちゃった?</a
|
||||
>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked />
|
||||
<span id="loginRememberText" class="loginSpanDim">パスワードを記憶する?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
ログイン
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>
|
||||
.arrowLine {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-width: 2px;
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<polyline
|
||||
class="arrowLine"
|
||||
points="0.71 13.26 12.56 1.41 24.16 13.02"
|
||||
/>
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
または <br />
|
||||
<button id="loginMSButton">Microsoft Login</button>
|
||||
<div id="loginDisclaimer">
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://www.minecraft.net/en-us/store/minecraft-java-edition"
|
||||
>Need an Account?</a
|
||||
>
|
||||
</span>
|
||||
<p class="loginDisclaimerText">
|
||||
パスワードは直接Mojangに送信され、保存されることはありません。
|
||||
</p>
|
||||
<p class="loginDisclaimerText">
|
||||
沼ランチャーは Mojang AB とは関係ありません。
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/login.js"></script>
|
||||
</div>
|
||||
|
@ -28,6 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="overlayContent">
|
||||
<div class="circle-loader" style="display: none;"></div>
|
||||
<span id="overlayTitle">Lorem Ipsum:<br>Finis Illud</span>
|
||||
<span id="overlayDesc">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud..</span>
|
||||
<div id="overlayActionContainer">
|
||||
@ -37,5 +38,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="msOverlay" style="display: none;">
|
||||
<span><div class="circle-loader-big"></div></span><br>
|
||||
<span style="font-size: 2em;">Waiting on Microsoft...</span>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/overlay.js"></script>
|
||||
</div>
|
114
index.js
114
index.js
@ -1,28 +1,33 @@
|
||||
require('dotenv').config()
|
||||
|
||||
// Requirements
|
||||
const { app, BrowserWindow, ipcMain, Menu, shell } = require('electron')
|
||||
const autoUpdater = require('electron-updater').autoUpdater
|
||||
const ejse = require('ejs-electron')
|
||||
const fs = require('fs')
|
||||
const isDev = require('./app/assets/js/isdev')
|
||||
const path = require('path')
|
||||
const semver = require('semver')
|
||||
const url = require('url')
|
||||
const autoUpdater = require('electron-updater').autoUpdater
|
||||
const ejse = require('ejs-electron')
|
||||
const fs = require('fs')
|
||||
const isDev = require('./app/assets/js/isdev')
|
||||
const path = require('path')
|
||||
const semver = require('semver')
|
||||
const url = require('url')
|
||||
|
||||
const redirectUriPrefix = 'https://login.microsoftonline.com/common/oauth2/nativeclient?'
|
||||
const clientID = 'ce9c7ade-7cee-4c4c-83bc-0c0edafdcaea'
|
||||
|
||||
// Setup auto updater.
|
||||
function initAutoUpdater(event, data) {
|
||||
|
||||
if(data){
|
||||
if (data) {
|
||||
autoUpdater.allowPrerelease = true
|
||||
} else {
|
||||
// Defaults to true if application version contains prerelease components (e.g. 0.12.1-alpha.1)
|
||||
// autoUpdater.allowPrerelease = true
|
||||
}
|
||||
|
||||
if(isDev){
|
||||
|
||||
if (isDev) {
|
||||
autoUpdater.autoInstallOnAppQuit = false
|
||||
autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml')
|
||||
}
|
||||
if(process.platform === 'darwin'){
|
||||
if (process.platform === 'darwin') {
|
||||
autoUpdater.autoDownload = false
|
||||
}
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
@ -39,12 +44,12 @@ function initAutoUpdater(event, data) {
|
||||
})
|
||||
autoUpdater.on('error', (err) => {
|
||||
event.sender.send('autoUpdateNotification', 'realerror', err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Open channel to listen for update actions.
|
||||
ipcMain.on('autoUpdateAction', (event, arg, data) => {
|
||||
switch(arg){
|
||||
switch (arg) {
|
||||
case 'initAutoUpdater':
|
||||
console.log('Initializing auto updater.')
|
||||
initAutoUpdater(event, data)
|
||||
@ -57,9 +62,9 @@ ipcMain.on('autoUpdateAction', (event, arg, data) => {
|
||||
})
|
||||
break
|
||||
case 'allowPrereleaseChange':
|
||||
if(!data){
|
||||
if (!data) {
|
||||
const preRelComp = semver.prerelease(app.getVersion())
|
||||
if(preRelComp != null && preRelComp.length > 0){
|
||||
if (preRelComp != null && preRelComp.length > 0) {
|
||||
autoUpdater.allowPrerelease = true
|
||||
} else {
|
||||
autoUpdater.allowPrerelease = data
|
||||
@ -85,6 +90,77 @@ ipcMain.on('distributionIndexDone', (event, res) => {
|
||||
// https://electronjs.org/docs/tutorial/offscreen-rendering
|
||||
app.disableHardwareAcceleration()
|
||||
|
||||
let MSALoginWindow = null
|
||||
|
||||
// Open the Microsoft Account Login window
|
||||
ipcMain.on('openMSALoginWindow', (ipcEvent, args) => {
|
||||
if (MSALoginWindow != null) {
|
||||
ipcEvent.reply('MSALoginWindowReply', 'error', 'AlreadyOpenException')
|
||||
return
|
||||
}
|
||||
MSALoginWindow = new BrowserWindow({
|
||||
title: 'Microsoft Login',
|
||||
backgroundColor: '#222222',
|
||||
width: 520,
|
||||
height: 600,
|
||||
frame: true,
|
||||
icon: getPlatformIcon('SealCircle')
|
||||
})
|
||||
|
||||
MSALoginWindow.on('closed', () => {
|
||||
|
||||
MSALoginWindow = null
|
||||
})
|
||||
|
||||
MSALoginWindow.on('close', event => {
|
||||
ipcEvent.reply('MSALoginWindowReply', 'error', 'AuthNotFinished')
|
||||
|
||||
})
|
||||
|
||||
MSALoginWindow.webContents.on('did-navigate', (event, uri, responseCode, statusText) => {
|
||||
if (uri.startsWith(redirectUriPrefix)) {
|
||||
let querys = uri.substring(redirectUriPrefix.length).split('#', 1).toString().split('&')
|
||||
let queryMap = new Map()
|
||||
|
||||
querys.forEach(query => {
|
||||
let arr = query.split('=')
|
||||
queryMap.set(arr[0], decodeURI(arr[1]))
|
||||
})
|
||||
|
||||
ipcEvent.reply('MSALoginWindowReply', queryMap)
|
||||
|
||||
MSALoginWindow.close()
|
||||
MSALoginWindow = null
|
||||
}
|
||||
})
|
||||
|
||||
MSALoginWindow.removeMenu()
|
||||
console.log(clientID)
|
||||
MSALoginWindow.loadURL('https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=consent&client_id=' + clientID + '&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient')
|
||||
})
|
||||
|
||||
let MSALogoutWindow = null
|
||||
|
||||
ipcMain.on('openMSALogoutWindow', (ipcEvent, args) => {
|
||||
if (MSALogoutWindow == null) {
|
||||
MSALogoutWindow = new BrowserWindow({
|
||||
title: 'Microsoft Logout',
|
||||
backgroundColor: '#222222',
|
||||
width: 520,
|
||||
height: 600,
|
||||
frame: true,
|
||||
icon: getPlatformIcon('SealCircle')
|
||||
})
|
||||
MSALogoutWindow.loadURL('https://login.microsoftonline.com/common/oauth2/v2.0/logout')
|
||||
MSALogoutWindow.webContents.on('did-navigate', (e) => {
|
||||
setTimeout(() => {
|
||||
ipcEvent.reply('MSALogoutWindowReply')
|
||||
}, 5000)
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
app.allowRendererProcessReuse = true
|
||||
|
||||
@ -161,8 +237,8 @@ function createWindow() {
|
||||
}
|
||||
|
||||
function createMenu() {
|
||||
|
||||
if(process.platform === 'darwin') {
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
|
||||
// Extend default included application menu to continue support for quit keyboard shortcut
|
||||
let applicationSubMenu = {
|
||||
@ -224,9 +300,9 @@ function createMenu() {
|
||||
|
||||
}
|
||||
|
||||
function getPlatformIcon(filename){
|
||||
function getPlatformIcon(filename) {
|
||||
let ext
|
||||
switch(process.platform) {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
ext = 'ico'
|
||||
break
|
||||
|
4241
package-lock.json
generated
4241
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -25,9 +25,10 @@
|
||||
"node": "12.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.4.16",
|
||||
"adm-zip": "^0.5.1",
|
||||
"async": "^3.2.0",
|
||||
"discord-rpc": "^3.1.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"ejs": "^3.1.5",
|
||||
"ejs-electron": "^2.1.1",
|
||||
"electron-updater": "^4.3.5",
|
||||
@ -35,15 +36,15 @@
|
||||
"github-syntax-dark": "^0.5.0",
|
||||
"jquery": "^3.5.1",
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.3.2",
|
||||
"semver": "^7.3.4",
|
||||
"tar-fs": "^2.1.1",
|
||||
"winreg": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^11.0.5",
|
||||
"electron-builder": "^22.9.1",
|
||||
"eslint": "^7.13.0"
|
||||
"eslint": "^7.15.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
Loading…
Reference in New Issue
Block a user