mirror of
https://github.com/dscalzi/HeliosLauncher.git
synced 2024-12-22 11:42:14 -08:00
Show player count on landing page.
The server status needs to be stored in the redux store as it needs to be determined before mounting the Landing component.
This commit is contained in:
parent
cb68c34abe
commit
67e42ead78
63
package-lock.json
generated
63
package-lock.json
generated
@ -1362,14 +1362,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@eslint/eslintrc": {
|
"@eslint/eslintrc": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
|
||||||
"integrity": "sha512-bfL5365QSCmH6cPeFT7Ywclj8C7LiF7sO6mUGzZhtAMV7iID1Euq6740u/SRi4C80NOnVz/CEfK8/HO+nCAPJg==",
|
"integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
|
"espree": "^7.3.0",
|
||||||
|
"globals": "^12.1.0",
|
||||||
|
"ignore": "^4.0.6",
|
||||||
"import-fresh": "^3.2.1",
|
"import-fresh": "^3.2.1",
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
"strip-json-comments": "^3.1.1"
|
"strip-json-comments": "^3.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1385,11 +1391,26 @@
|
|||||||
"uri-js": "^4.2.2"
|
"uri-js": "^4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"globals": {
|
||||||
|
"version": "12.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
|
||||||
|
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"type-fest": "^0.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"strip-json-comments": {
|
"strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"type-fest": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1577,9 +1598,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "12.12.54",
|
"version": "12.12.55",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.55.tgz",
|
||||||
"integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w=="
|
"integrity": "sha512-Vd6xQUVvPCTm7Nx1N7XHcpX6t047ltm7TgcsOr4gFHjeYgwZevo+V7I1lfzHnj5BT5frztZ42+RTG4MwYw63dw=="
|
||||||
},
|
},
|
||||||
"@types/prop-types": {
|
"@types/prop-types": {
|
||||||
"version": "15.7.3",
|
"version": "15.7.3",
|
||||||
@ -4722,13 +4743,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "7.8.0",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz",
|
||||||
"integrity": "sha512-qgtVyLZqKd2ZXWnLQA4NtVbOyH56zivOAdBFWE54RFkSZjokzNrcP4Z0eVWsZ+84ByXv+jL9k/wE1ENYe8xRFw==",
|
"integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
"@eslint/eslintrc": "^0.1.0",
|
"@eslint/eslintrc": "^0.1.3",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@ -6072,18 +6093,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"got": {
|
"got": {
|
||||||
"version": "11.5.2",
|
"version": "11.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-11.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-11.6.0.tgz",
|
||||||
"integrity": "sha512-yUhpEDLeuGiGJjRSzEq3kvt4zJtAcjKmhIiwNp/eUs75tRlXfWcHo5tcBaMQtnjHWC7nQYT5HkY/l0QOQTkVww==",
|
"integrity": "sha512-ErhWb4IUjQzJ3vGs3+RR12NWlBDDkRciFpAkQ1LPUxi6OnwhGj07gQxjPsyIk69s7qMihwKrKquV6VQq7JNYLA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@sindresorhus/is": "^3.0.0",
|
"@sindresorhus/is": "^3.1.1",
|
||||||
"@szmarczak/http-timer": "^4.0.5",
|
"@szmarczak/http-timer": "^4.0.5",
|
||||||
"@types/cacheable-request": "^6.0.1",
|
"@types/cacheable-request": "^6.0.1",
|
||||||
"@types/responselike": "^1.0.0",
|
"@types/responselike": "^1.0.0",
|
||||||
"cacheable-lookup": "^5.0.3",
|
"cacheable-lookup": "^5.0.3",
|
||||||
"cacheable-request": "^7.0.1",
|
"cacheable-request": "^7.0.1",
|
||||||
"decompress-response": "^6.0.0",
|
"decompress-response": "^6.0.0",
|
||||||
"http2-wrapper": "^1.0.0-beta.5.0",
|
"http2-wrapper": "^1.0.0-beta.5.2",
|
||||||
"lowercase-keys": "^2.0.0",
|
"lowercase-keys": "^2.0.0",
|
||||||
"p-cancelable": "^2.0.0",
|
"p-cancelable": "^2.0.0",
|
||||||
"responselike": "^2.0.0"
|
"responselike": "^2.0.0"
|
||||||
@ -11440,9 +11461,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"version": "8.10.2",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
|
||||||
"integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==",
|
"integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"arg": "^4.1.0",
|
"arg": "^4.1.0",
|
||||||
@ -11564,9 +11585,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "3.9.7",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
|
||||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"unicode-canonical-property-names-ecmascript": {
|
"unicode-canonical-property-names-ecmascript": {
|
||||||
|
10
package.json
10
package.json
@ -35,7 +35,7 @@
|
|||||||
"electron-updater": "^4.3.4",
|
"electron-updater": "^4.3.4",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"github-syntax-dark": "^0.5.0",
|
"github-syntax-dark": "^0.5.0",
|
||||||
"got": "^11.5.2",
|
"got": "^11.6.0",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"moment": "^2.27.0",
|
"moment": "^2.27.0",
|
||||||
@ -58,7 +58,7 @@
|
|||||||
"@types/jquery": "^3.5.1",
|
"@types/jquery": "^3.5.1",
|
||||||
"@types/lodash": "^4.14.161",
|
"@types/lodash": "^4.14.161",
|
||||||
"@types/mocha": "^8.0.3",
|
"@types/mocha": "^8.0.3",
|
||||||
"@types/node": "^12.12.54",
|
"@types/node": "^12.12.55",
|
||||||
"@types/react": "^16.9.49",
|
"@types/react": "^16.9.49",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/react-redux": "^7.1.9",
|
"@types/react-redux": "^7.1.9",
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"electron-devtools-installer": "^3.1.1",
|
"electron-devtools-installer": "^3.1.1",
|
||||||
"electron-webpack": "^2.8.2",
|
"electron-webpack": "^2.8.2",
|
||||||
"electron-webpack-ts": "^4.0.1",
|
"electron-webpack-ts": "^4.0.1",
|
||||||
"eslint": "^7.8.0",
|
"eslint": "^7.8.1",
|
||||||
"eslint-plugin-react": "^7.20.6",
|
"eslint-plugin-react": "^7.20.6",
|
||||||
"helios-distribution-types": "1.0.0-pre.1",
|
"helios-distribution-types": "1.0.0-pre.1",
|
||||||
"mocha": "^8.1.3",
|
"mocha": "^8.1.3",
|
||||||
@ -89,9 +89,9 @@
|
|||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^9.0.0",
|
||||||
"tsconfig-paths": "^3.9.0",
|
"tsconfig-paths": "^3.9.0",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^4.0.2",
|
||||||
"webpack": "^4.44.1"
|
"webpack": "^4.44.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -53,13 +53,34 @@ export class HeliosDistribution {
|
|||||||
export class HeliosServer {
|
export class HeliosServer {
|
||||||
|
|
||||||
public readonly modules: HeliosModule[]
|
public readonly modules: HeliosModule[]
|
||||||
|
public readonly hostname: string
|
||||||
|
public readonly port: number
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly rawServer: Server
|
public readonly rawServer: Server
|
||||||
) {
|
) {
|
||||||
|
const { hostname, port } = this.parseAddress()
|
||||||
|
this.hostname = hostname
|
||||||
|
this.port = port
|
||||||
this.modules = rawServer.modules.map(m => new HeliosModule(m, rawServer.id))
|
this.modules = rawServer.modules.map(m => new HeliosModule(m, rawServer.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseAddress(): { hostname: string, port: number } {
|
||||||
|
// Srv record lookup here if needed.
|
||||||
|
if(this.rawServer.address.includes(':')) {
|
||||||
|
const pieces = this.rawServer.address.split(':')
|
||||||
|
const port = Number(pieces[1])
|
||||||
|
|
||||||
|
if(!Number.isInteger(port)) {
|
||||||
|
throw new Error(`Malformed server address for ${this.rawServer.id}. Port must be an integer!`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hostname: pieces[0], port }
|
||||||
|
} else {
|
||||||
|
return { hostname: this.rawServer.address, port: 25565 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HeliosModule {
|
export class HeliosModule {
|
||||||
|
@ -35,17 +35,17 @@ export interface ServerStatus {
|
|||||||
* Get the handshake packet.
|
* Get the handshake packet.
|
||||||
*
|
*
|
||||||
* @param protocol The client's protocol version.
|
* @param protocol The client's protocol version.
|
||||||
* @param address The server address.
|
* @param hostname The server hostname.
|
||||||
* @param port The server port.
|
* @param port The server port.
|
||||||
*
|
*
|
||||||
* @see https://wiki.vg/Server_List_Ping#Handshake
|
* @see https://wiki.vg/Server_List_Ping#Handshake
|
||||||
*/
|
*/
|
||||||
function getHandshakePacket(protocol: number, address: string, port: number): Buffer {
|
function getHandshakePacket(protocol: number, hostname: string, port: number): Buffer {
|
||||||
|
|
||||||
return ServerBoundPacket.build()
|
return ServerBoundPacket.build()
|
||||||
.writeVarInt(0x00) // Packet Id
|
.writeVarInt(0x00) // Packet Id
|
||||||
.writeVarInt(protocol)
|
.writeVarInt(protocol)
|
||||||
.writeString(address)
|
.writeString(hostname)
|
||||||
.writeUnsignedShort(port)
|
.writeUnsignedShort(port)
|
||||||
.writeVarInt(1) // State, 1 = status
|
.writeVarInt(1) // State, 1 = status
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
@ -80,19 +80,19 @@ function unifyStatusResponse(resp: ServerStatus): ServerStatus {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServerStatus(protocol: number, address: string, port = 25565): Promise<ServerStatus | null> {
|
export function getServerStatus(protocol: number, hostname: string, port = 25565): Promise<ServerStatus | undefined> {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const socket = connect(port, address, () => {
|
const socket = connect(port, hostname, () => {
|
||||||
socket.write(getHandshakePacket(protocol, address, port))
|
socket.write(getHandshakePacket(protocol, hostname, port))
|
||||||
socket.write(getRequestPacket())
|
socket.write(getRequestPacket())
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.setTimeout(5000, () => {
|
socket.setTimeout(5000, () => {
|
||||||
socket.destroy()
|
socket.destroy()
|
||||||
logger.error(`Server Status Socket timed out (${address}:${port})`)
|
logger.error(`Server Status Socket timed out (${hostname}:${port})`)
|
||||||
reject(new Error(`Server Status Socket timed out (${address}:${port})`))
|
reject(new Error(`Server Status Socket timed out (${hostname}:${port})`))
|
||||||
})
|
})
|
||||||
|
|
||||||
const maxTries = 2
|
const maxTries = 2
|
||||||
@ -122,7 +122,7 @@ export function getServerStatus(protocol: number, address: string, port = 25565)
|
|||||||
|
|
||||||
if(iterations > maxTries) {
|
if(iterations > maxTries) {
|
||||||
socket.destroy()
|
socket.destroy()
|
||||||
reject(new Error(`Data read from ${address}:${port} exceeded ${maxTries} iterations, closing connection.`))
|
reject(new Error(`Data read from ${hostname}:${port} exceeded ${maxTries} iterations, closing connection.`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
++iterations
|
++iterations
|
||||||
@ -141,7 +141,7 @@ export function getServerStatus(protocol: number, address: string, port = 25565)
|
|||||||
const result = inboundPacket.readString()
|
const result = inboundPacket.readString()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(result)
|
const parsed: ServerStatus = JSON.parse(result)
|
||||||
socket.end()
|
socket.end()
|
||||||
resolve(unifyStatusResponse(parsed))
|
resolve(unifyStatusResponse(parsed))
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@ -164,17 +164,17 @@ export function getServerStatus(protocol: number, address: string, port = 25565)
|
|||||||
|
|
||||||
if(err.code === 'ENOTFOUND') {
|
if(err.code === 'ENOTFOUND') {
|
||||||
// ENOTFOUND = Unable to resolve.
|
// ENOTFOUND = Unable to resolve.
|
||||||
logger.error(`Server ${address}:${port} not found!`)
|
logger.error(`Server ${hostname}:${port} not found!`)
|
||||||
resolve(null)
|
resolve(undefined)
|
||||||
return
|
return
|
||||||
} else if(err.code === 'ECONNREFUSED') {
|
} else if(err.code === 'ECONNREFUSED') {
|
||||||
// ECONNREFUSED = Unable to connect to port.
|
// ECONNREFUSED = Unable to connect to port.
|
||||||
logger.error(`Server ${address}:${port} refused to connect, is the port correct?`)
|
logger.error(`Server ${hostname}:${port} refused to connect, is the port correct?`)
|
||||||
resolve(null)
|
resolve(undefined)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.error(`Error trying to pull server status (${address}:${port})`, err)
|
logger.error(`Error trying to pull server status (${hostname}:${port})`, err)
|
||||||
resolve(null)
|
resolve(undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ import { OverlayPushAction, OverlayActionDispatch } from '../redux/actions/overl
|
|||||||
|
|
||||||
import { LoggerUtil } from 'common/logging/loggerutil'
|
import { LoggerUtil } from 'common/logging/loggerutil'
|
||||||
import { DistributionAPI } from 'common/distribution/DistributionAPI'
|
import { DistributionAPI } from 'common/distribution/DistributionAPI'
|
||||||
import { getServerStatus } from 'common/mojang/net/ServerStatusAPI'
|
import { getServerStatus, ServerStatus } from 'common/mojang/net/ServerStatusAPI'
|
||||||
import { Distribution } from 'helios-distribution-types'
|
import { Distribution } from 'helios-distribution-types'
|
||||||
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ interface ApplicationProps {
|
|||||||
overlayQueue: OverlayPushAction<unknown>[]
|
overlayQueue: OverlayPushAction<unknown>[]
|
||||||
distribution: HeliosDistribution
|
distribution: HeliosDistribution
|
||||||
selectedServer: HeliosServer
|
selectedServer: HeliosServer
|
||||||
|
selectedServerStatus: ServerStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApplicationState {
|
interface ApplicationState {
|
||||||
@ -79,7 +80,7 @@ class Application extends React.Component<ApplicationProps & typeof mapDispatch,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewElement(): JSX.Element {
|
private getViewElement = (): JSX.Element => {
|
||||||
// TODO debug remove
|
// TODO debug remove
|
||||||
console.log('loading', this.props.currentView, this.state.workingView)
|
console.log('loading', this.props.currentView, this.state.workingView)
|
||||||
switch(this.state.workingView) {
|
switch(this.state.workingView) {
|
||||||
@ -89,7 +90,11 @@ class Application extends React.Component<ApplicationProps & typeof mapDispatch,
|
|||||||
</>
|
</>
|
||||||
case View.LANDING:
|
case View.LANDING:
|
||||||
return <>
|
return <>
|
||||||
<Landing distribution={this.props.distribution} selectedServer={this.props.selectedServer} />
|
<Landing
|
||||||
|
distribution={this.props.distribution}
|
||||||
|
selectedServer={this.props.selectedServer}
|
||||||
|
selectedServerStatus={this.props.selectedServerStatus}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
case View.LOGIN:
|
case View.LOGIN:
|
||||||
return <>
|
return <>
|
||||||
@ -164,10 +169,19 @@ class Application extends React.Component<ApplicationProps & typeof mapDispatch,
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
const distro = new HeliosDistribution(rawDisto)
|
const distro = new HeliosDistribution(rawDisto)
|
||||||
this.props.setDistribution(distro)
|
|
||||||
// TODO TEMP USE CONFIG
|
// TODO TEMP USE CONFIG
|
||||||
// TODO TODO TODO TODO
|
// TODO TODO TODO TODO
|
||||||
this.props.setSelectedServer(distro.servers[0])
|
const selectedServer: HeliosServer = distro.servers[0]
|
||||||
|
const { hostname, port } = selectedServer
|
||||||
|
let selectedServerStatus
|
||||||
|
try {
|
||||||
|
selectedServerStatus = await getServerStatus(47, hostname, port)
|
||||||
|
} catch(err) {
|
||||||
|
Application.logger.error('Failed to refresh server status', selectedServerStatus)
|
||||||
|
}
|
||||||
|
this.props.setDistribution(distro)
|
||||||
|
this.props.setSelectedServer(selectedServer)
|
||||||
|
this.props.setSelectedServerStatus(selectedServerStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Setup hook for distro refresh every ~ 5 mins.
|
// TODO Setup hook for distro refresh every ~ 5 mins.
|
||||||
@ -186,16 +200,16 @@ class Application extends React.Component<ApplicationProps & typeof mapDispatch,
|
|||||||
})
|
})
|
||||||
// TODO temp
|
// TODO temp
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
//this.props.setView(View.WELCOME)
|
// this.props.setView(View.WELCOME)
|
||||||
this.props.pushGenericOverlay({
|
// this.props.pushGenericOverlay({
|
||||||
title: 'Load Distribution',
|
// title: 'Load Distribution',
|
||||||
description: 'This is a test.',
|
// description: 'This is a test.',
|
||||||
dismissible: false,
|
// dismissible: false,
|
||||||
acknowledgeCallback: async () => {
|
// acknowledgeCallback: async () => {
|
||||||
const serverStatus = await getServerStatus(47, 'play.hypixel.net', 25565)
|
// const serverStatus = await getServerStatus(47, 'play.hypixel.net', 25565)
|
||||||
console.log(serverStatus)
|
// console.log(serverStatus)
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
// this.props.pushGenericOverlay({
|
// this.props.pushGenericOverlay({
|
||||||
// title: 'Test Title 2',
|
// title: 'Test Title 2',
|
||||||
// description: 'Test Description',
|
// description: 'Test Description',
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
.serverStatusWrapper-enter {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.serverStatusWrapper-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 500ms, transform 500ms;
|
||||||
|
}
|
||||||
|
.serverStatusWrapper-exit {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.serverStatusWrapper-exit-active {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 500ms, transform 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* Landing View (Structural Styles) *
|
* Landing View (Structural Styles) *
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import { CSSTransition } from 'react-transition-group'
|
||||||
|
|
||||||
import { StoreType } from '../../redux/store'
|
import { StoreType } from '../../redux/store'
|
||||||
import { AppActionDispatch } from '../..//redux/actions/appActions'
|
import { AppActionDispatch } from '../..//redux/actions/appActions'
|
||||||
import { OverlayActionDispatch } from '../../redux/actions/overlayActions'
|
import { OverlayActionDispatch } from '../../redux/actions/overlayActions'
|
||||||
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
||||||
|
import { ServerStatus, getServerStatus } from 'common/mojang/net/ServerStatusAPI'
|
||||||
import { MojangStatus, MojangStatusColor } from 'common/mojang/rest/internal/MojangStatus'
|
import { MojangStatus, MojangStatusColor } from 'common/mojang/rest/internal/MojangStatus'
|
||||||
import { MojangResponse } from 'common/mojang/rest/internal/MojangResponse'
|
import { MojangResponse } from 'common/mojang/rest/internal/MojangResponse'
|
||||||
import { MojangRestAPI } from 'common/mojang/rest/MojangRestAPI'
|
import { MojangRestAPI } from 'common/mojang/rest/MojangRestAPI'
|
||||||
@ -18,16 +20,19 @@ import './Landing.css'
|
|||||||
interface LandingProps {
|
interface LandingProps {
|
||||||
distribution: HeliosDistribution
|
distribution: HeliosDistribution
|
||||||
selectedServer: HeliosServer
|
selectedServer: HeliosServer
|
||||||
|
selectedServerStatus: ServerStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LandingState {
|
interface LandingState {
|
||||||
mojangStatuses: MojangStatus[]
|
mojangStatuses: MojangStatus[]
|
||||||
|
outdatedServerStatus: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapState = (state: StoreType): Partial<LandingProps> => {
|
const mapState = (state: StoreType): Partial<LandingProps> => {
|
||||||
return {
|
return {
|
||||||
distribution: state.app.distribution!,
|
distribution: state.app.distribution!,
|
||||||
selectedServer: state.app.selectedServer!
|
selectedServer: state.app.selectedServer!,
|
||||||
|
selectedServerStatus: state.app.selectedServerStatus!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const mapDispatch = {
|
const mapDispatch = {
|
||||||
@ -42,11 +47,13 @@ class Landing extends React.Component<InternalLandingProps, LandingState> {
|
|||||||
private static readonly logger = LoggerUtil.getLogger('LandingTSX')
|
private static readonly logger = LoggerUtil.getLogger('LandingTSX')
|
||||||
|
|
||||||
private mojangStatusInterval!: NodeJS.Timeout
|
private mojangStatusInterval!: NodeJS.Timeout
|
||||||
|
private serverStatusInterval!: NodeJS.Timeout
|
||||||
|
|
||||||
constructor(props: InternalLandingProps) {
|
constructor(props: InternalLandingProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
mojangStatuses: []
|
mojangStatuses: [],
|
||||||
|
outdatedServerStatus: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,12 +67,21 @@ class Landing extends React.Component<InternalLandingProps, LandingState> {
|
|||||||
await this.loadMojangStatuses()
|
await this.loadMojangStatuses()
|
||||||
}, 300000)
|
}, 300000)
|
||||||
|
|
||||||
|
this.serverStatusInterval = setInterval(async () => {
|
||||||
|
Landing.logger.info('Refreshing selected server status..')
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
outdatedServerStatus: true
|
||||||
|
})
|
||||||
|
}, 300000)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
|
|
||||||
// Clean up intervals.
|
// Clean up intervals.
|
||||||
clearInterval(this.mojangStatusInterval)
|
clearInterval(this.mojangStatusInterval)
|
||||||
|
clearInterval(this.serverStatusInterval)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +108,34 @@ class Landing extends React.Component<InternalLandingProps, LandingState> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private syncServerStatus = async (): Promise<void> => {
|
||||||
|
let serverStatus: ServerStatus | undefined
|
||||||
|
|
||||||
|
if(this.props.selectedServer != null) {
|
||||||
|
const { hostname, port } = this.props.selectedServer
|
||||||
|
try {
|
||||||
|
serverStatus = await getServerStatus(
|
||||||
|
47,
|
||||||
|
hostname,
|
||||||
|
port
|
||||||
|
)
|
||||||
|
} catch(err) {
|
||||||
|
Landing.logger.error('Error while refreshing server status', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
serverStatus = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.setSelectedServerStatus(serverStatus)
|
||||||
|
}
|
||||||
|
private finishServerSync = async (): Promise<void> => {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
outdatedServerStatus: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private getMainMojangStatusColor = (): string => {
|
private getMainMojangStatusColor = (): string => {
|
||||||
const essential = this.state.mojangStatuses.filter(s => s.essential)
|
const essential = this.state.mojangStatuses.filter(s => s.essential)
|
||||||
|
|
||||||
@ -137,9 +181,14 @@ class Landing extends React.Component<InternalLandingProps, LandingState> {
|
|||||||
this.props.pushServerSelectOverlay({
|
this.props.pushServerSelectOverlay({
|
||||||
servers: this.props.distribution.servers,
|
servers: this.props.distribution.servers,
|
||||||
selectedId: this.props.selectedServer.rawServer.id,
|
selectedId: this.props.selectedServer.rawServer.id,
|
||||||
onSelection: (serverId: string) => {
|
onSelection: async (serverId: string) => {
|
||||||
Landing.logger.info('Server Selection Change:', serverId)
|
Landing.logger.info('Server Selection Change:', serverId)
|
||||||
this.props.setSelectedServer(this.props.distribution.getServerById(serverId)!)
|
const next: HeliosServer = this.props.distribution.getServerById(serverId)!
|
||||||
|
this.props.setSelectedServer(next)
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
outdatedServerStatus: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -152,6 +201,19 @@ class Landing extends React.Component<InternalLandingProps, LandingState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSelectedServerStatusText = (): string => {
|
||||||
|
return this.props.selectedServerStatus != null ? 'PLAYERS' : 'SERVER'
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSelectedServerCount = (): string => {
|
||||||
|
if(this.props.selectedServerStatus != null) {
|
||||||
|
const { online, max } = this.props.selectedServerStatus.players
|
||||||
|
return `${online}/${max}`
|
||||||
|
} else {
|
||||||
|
return 'OFFLINE'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return <>
|
return <>
|
||||||
|
|
||||||
@ -252,10 +314,21 @@ class Landing extends React.Component<InternalLandingProps, LandingState> {
|
|||||||
<div id="left">
|
<div id="left">
|
||||||
<div className="bot_wrapper">
|
<div className="bot_wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
|
||||||
|
<CSSTransition
|
||||||
|
in={!this.state.outdatedServerStatus}
|
||||||
|
timeout={500}
|
||||||
|
classNames="serverStatusWrapper"
|
||||||
|
unmountOnExit
|
||||||
|
onEnter={this.syncServerStatus}
|
||||||
|
onExited={this.finishServerSync}
|
||||||
|
>
|
||||||
<div id="server_status_wrapper">
|
<div id="server_status_wrapper">
|
||||||
<span className="bot_label" id="landingPlayerLabel">SERVER</span>
|
<span className="bot_label" id="landingPlayerLabel">{this.getSelectedServerStatusText()}</span>
|
||||||
<span id="player_count">OFFLINE</span>
|
<span id="player_count">{this.getSelectedServerCount()}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
|
||||||
<div className="bot_divider"></div>
|
<div className="bot_divider"></div>
|
||||||
<div id="mojangStatusWrapper">
|
<div id="mojangStatusWrapper">
|
||||||
<span className="bot_label">MOJANG STATUS</span>
|
<span className="bot_label">MOJANG STATUS</span>
|
||||||
|
@ -10,7 +10,7 @@ import '../shared-select/SharedSelect.css'
|
|||||||
export interface ServerSelectOverlayProps {
|
export interface ServerSelectOverlayProps {
|
||||||
servers: HeliosServer[]
|
servers: HeliosServer[]
|
||||||
selectedId: string
|
selectedId: string
|
||||||
onSelection: (serverId: string) => void
|
onSelection: (serverId: string) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServerSelectOverlayState {
|
interface ServerSelectOverlayState {
|
||||||
@ -36,7 +36,7 @@ class ServerSelectOverlay extends React.Component<InternalServerSelectOverlayPro
|
|||||||
|
|
||||||
private onSelectClick = async (): Promise<void> => {
|
private onSelectClick = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
this.props.onSelection(this.state.selectedId)
|
await this.props.onSelection(this.state.selectedId)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.logger.error('Uncaught error in server select confirmation.', err)
|
this.logger.error('Uncaught error in server select confirmation.', err)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ ReactDOM.render(
|
|||||||
overlayQueue={store.getState().overlayQueue}
|
overlayQueue={store.getState().overlayQueue}
|
||||||
distribution={store.getState().app.distribution!}
|
distribution={store.getState().app.distribution!}
|
||||||
selectedServer={store.getState().app.selectedServer!}
|
selectedServer={store.getState().app.selectedServer!}
|
||||||
|
selectedServerStatus={store.getState().app.selectedServerStatus!}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</AppContainer>,
|
</AppContainer>,
|
||||||
|
@ -1,37 +1,51 @@
|
|||||||
import { Action } from 'redux'
|
import { Action } from 'redux'
|
||||||
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
||||||
|
import { ServerStatus } from 'common/mojang/net/ServerStatusAPI'
|
||||||
|
|
||||||
export enum AppActionType {
|
export enum AppActionType {
|
||||||
SetDistribution = 'SET_DISTRIBUTION',
|
SetDistribution = 'SET_DISTRIBUTION',
|
||||||
SetSelectedServer = 'SET_SELECTED_SERVER'
|
SetSelectedServer = 'SET_SELECTED_SERVER',
|
||||||
|
SetSelectedServerStatus = 'SET_SELECTED_SERVER_STATUS'
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface AppAction extends Action {}
|
export interface AppAction extends Action {}
|
||||||
|
|
||||||
export interface SetDistributionAction extends AppAction {
|
export interface SetDistributionAction extends AppAction {
|
||||||
payload: HeliosDistribution
|
payload?: HeliosDistribution
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetSelectedServerAction extends AppAction {
|
export interface SetSelectedServerAction extends AppAction {
|
||||||
payload: HeliosServer
|
payload?: HeliosServer
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDistribution(distribution: HeliosDistribution): SetDistributionAction {
|
export interface SetSelectedServerStatusAction extends AppAction {
|
||||||
|
payload?: ServerStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDistribution(distribution?: HeliosDistribution): SetDistributionAction {
|
||||||
return {
|
return {
|
||||||
type: AppActionType.SetDistribution,
|
type: AppActionType.SetDistribution,
|
||||||
payload: distribution
|
payload: distribution
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setSelectedServer(server: HeliosServer): SetSelectedServerAction {
|
export function setSelectedServer(server?: HeliosServer): SetSelectedServerAction {
|
||||||
return {
|
return {
|
||||||
type: AppActionType.SetSelectedServer,
|
type: AppActionType.SetSelectedServer,
|
||||||
payload: server
|
payload: server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppActionDispatch = {
|
export function setSelectedServerStatus(serverStatus?: ServerStatus): SetSelectedServerStatusAction {
|
||||||
setDistribution: (d: HeliosDistribution): SetDistributionAction => setDistribution(d),
|
return {
|
||||||
setSelectedServer: (s: HeliosServer): SetSelectedServerAction => setSelectedServer(s)
|
type: AppActionType.SetSelectedServerStatus,
|
||||||
|
payload: serverStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppActionDispatch = {
|
||||||
|
setDistribution: (d?: HeliosDistribution): SetDistributionAction => setDistribution(d),
|
||||||
|
setSelectedServer: (s?: HeliosServer): SetSelectedServerAction => setSelectedServer(s),
|
||||||
|
setSelectedServerStatus: (ss?: ServerStatus): SetSelectedServerStatusAction => setSelectedServerStatus(ss)
|
||||||
}
|
}
|
@ -1,15 +1,18 @@
|
|||||||
import { AppActionType, AppAction, SetDistributionAction, SetSelectedServerAction } from '../actions/appActions'
|
import { AppActionType, AppAction, SetDistributionAction, SetSelectedServerAction, SetSelectedServerStatusAction } from '../actions/appActions'
|
||||||
import { Reducer } from 'redux'
|
import { Reducer } from 'redux'
|
||||||
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
|
||||||
|
import { ServerStatus } from 'common/mojang/net/ServerStatusAPI'
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
distribution: HeliosDistribution | null
|
distribution?: HeliosDistribution
|
||||||
selectedServer: HeliosServer | null
|
selectedServer?: HeliosServer
|
||||||
|
selectedServerStatus?: ServerStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultAppState: AppState = {
|
const defaultAppState: AppState = {
|
||||||
distribution: null,
|
distribution: undefined,
|
||||||
selectedServer: null
|
selectedServer: undefined,
|
||||||
|
selectedServerStatus: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppReducer: Reducer<AppState, AppAction> = (state = defaultAppState, action) => {
|
const AppReducer: Reducer<AppState, AppAction> = (state = defaultAppState, action) => {
|
||||||
@ -24,6 +27,11 @@ const AppReducer: Reducer<AppState, AppAction> = (state = defaultAppState, actio
|
|||||||
...state,
|
...state,
|
||||||
selectedServer: (action as SetSelectedServerAction).payload
|
selectedServer: (action as SetSelectedServerAction).payload
|
||||||
}
|
}
|
||||||
|
case AppActionType.SetSelectedServerStatus:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedServerStatus: (action as SetSelectedServerStatusAction).payload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user