import { hot } from 'react-hot-loader/root' import * as React from 'react' import Frame from './frame/Frame' import Welcome from './welcome/Welcome' import { connect } from 'react-redux' import { View } from '../meta/Views' import Landing from './landing/Landing' import Login from './login/Login' import Loader from './loader/Loader' import Settings from './settings/Settings' import Overlay from './overlay/Overlay' import Fatal from './fatal/Fatal' import { StoreType } from '../redux/store' import { CSSTransition } from 'react-transition-group' import { ViewActionDispatch } from '../redux/actions/viewActions' import { throttle } from 'lodash' import { readdir } from 'fs-extra' import { join } from 'path' import { AppActionDispatch } from '../redux/actions/appActions' import { OverlayPushAction, OverlayActionDispatch } from '../redux/actions/overlayActions' import { LoggerUtil } from 'common/logging/loggerutil' import { DistributionAPI } from 'common/distribution/DistributionAPI' import { getServerStatus, ServerStatus } from 'common/mojang/net/ServerStatusAPI' import { Distribution } from 'helios-distribution-types' import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory' import { MojangResponse } from 'common/mojang/rest/internal/MojangResponse' import { MojangStatus, MojangStatusColor } from 'common/mojang/rest/internal/MojangStatus' import { MojangRestAPI } from 'common/mojang/rest/MojangRestAPI' import { RestResponseStatus } from 'common/got/RestResponse' import './Application.css' declare const __static: string function setBackground(id: number) { import(`../../../static/images/backgrounds/${id}.jpg`).then(mdl => { document.body.style.backgroundImage = `url('${mdl.default}')` }) } interface ApplicationProps { currentView: View overlayQueue: OverlayPushAction[] distribution: HeliosDistribution selectedServer?: HeliosServer selectedServerStatus?: ServerStatus mojangStatuses: MojangStatus[] } interface ApplicationState { loading: boolean showMain: boolean renderMain: boolean workingView: View } const mapState = (state: StoreType): Partial => { return { currentView: state.currentView, overlayQueue: state.overlayQueue, distribution: state.app.distribution, selectedServer: state.app.selectedServer, mojangStatuses: state.app.mojangStatuses } } const mapDispatch = { ...AppActionDispatch, ...ViewActionDispatch, ...OverlayActionDispatch } type InternalApplicationProps = ApplicationProps & typeof mapDispatch class Application extends React.Component { private static readonly logger = LoggerUtil.getLogger('ApplicationTSX') private mojangStatusInterval!: NodeJS.Timeout private serverStatusInterval!: NodeJS.Timeout private bkid!: number constructor(props: InternalApplicationProps) { super(props) this.state = { loading: true, showMain: false, renderMain: false, workingView: props.currentView } } async componentDidMount(): Promise { this.mojangStatusInterval = setInterval(async () => { Application.logger.info('Refreshing Mojang Statuses..') await this.loadMojangStatuses() }, 300000) this.serverStatusInterval = setInterval(async () => { Application.logger.info('Refreshing selected server status..') await this.syncServerStatus() }, 300000) } componentWillUnmount(): void { // Clean up intervals. clearInterval(this.mojangStatusInterval) clearInterval(this.serverStatusInterval) } async componentDidUpdate(prevProps: InternalApplicationProps): Promise { if(this.props.selectedServer?.rawServer.id !== prevProps.selectedServer?.rawServer.id) { await this.syncServerStatus() } } /** * Load the mojang statuses and add them to the global store. */ private loadMojangStatuses = async (): Promise => { const response: MojangResponse = await MojangRestAPI.status() if(response.responseStatus !== RestResponseStatus.SUCCESS) { Application.logger.warn('Failed to retrieve Mojang Statuses.') } // TODO Temp workaround because their status checker always shows // this as red. https://bugs.mojang.com/browse/WEB-2303 const statuses = response.data for(const status of statuses) { if(status.service === 'sessionserver.mojang.com' || status.service === 'minecraft.net') { status.status = MojangStatusColor.GREEN } } this.props.setMojangStatuses(response.data) } /** * Fetch the status of the selected server and store it in the global store. */ private syncServerStatus = async (): Promise => { let serverStatus: ServerStatus | undefined if(this.props.selectedServer != null) { const { hostname, port } = this.props.selectedServer try { serverStatus = await getServerStatus( 47, hostname, port ) } catch(err) { Application.logger.error('Error while refreshing server status', err) } } else { serverStatus = undefined } this.props.setSelectedServerStatus(serverStatus) } private getViewElement = (): JSX.Element => { // TODO debug remove console.log('loading', this.props.currentView, this.state.workingView) switch(this.state.workingView) { case View.WELCOME: return <> case View.LANDING: return <> case View.LOGIN: return <> case View.SETTINGS: return <> case View.FATAL: return <> case View.NONE: return <> } } private hasOverlay = (): boolean => { return this.props.overlayQueue.length > 0 } private updateWorkingView = throttle(() => { // TODO debug remove console.log('Setting to', this.props.currentView) this.setState({ ...this.state, workingView: this.props.currentView }) }, 200) private finishLoad = (): void => { if(this.props.currentView !== View.FATAL) { setBackground(this.bkid) } this.showMain() } private showMain = (): void => { this.setState({ ...this.state, showMain: true }) } private initLoad = async (): Promise => { if(this.state.loading) { const MIN_LOAD = 800 const start = Date.now() // Initial distribution load. const distroAPI = new DistributionAPI('C:\\Users\\user\\AppData\\Roaming\\Helios Launcher') let rawDisto: Distribution try { rawDisto = await distroAPI.testLoad() console.log('distro', distroAPI) } catch(err) { console.log('EXCEPTION IN DISTRO LOAD TODO TODO TODO', err) rawDisto = null! } // Fatal error if(rawDisto == null) { this.props.setView(View.FATAL) this.setState({ ...this.state, loading: false, workingView: View.FATAL }) return } else { const distro = new HeliosDistribution(rawDisto) // TODO TEMP USE CONFIG // TODO TODO TODO TODO 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) } // Load initial mojang statuses. Application.logger.info('Loading mojang statuses..') await this.loadMojangStatuses() // TODO Setup hook for distro refresh every ~ 5 mins. // Pick a background id. this.bkid = Math.floor((Math.random() * (await readdir(join(__static, 'images', 'backgrounds'))).length)) const endLoad = () => { // TODO determine correct view // either welcome, landing, or login this.props.setView(View.LANDING) this.setState({ ...this.state, loading: false, workingView: View.LANDING }) // TODO temp setTimeout(() => { // this.props.setView(View.WELCOME) // this.props.pushGenericOverlay({ // title: 'Load Distribution', // description: 'This is a test.', // dismissible: false, // acknowledgeCallback: async () => { // const serverStatus = await getServerStatus(47, 'play.hypixel.net', 25565) // console.log(serverStatus) // } // }) this.props.pushGenericOverlay({ title: 'Test Title 2', description: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.', dismissible: true }) // this.props.pushGenericOverlay({ // title: 'Test Title IMPORTANT', // description: 'Test Description', // dismissible: true // }, true) }, 5000) } const diff = Date.now() - start if(diff < MIN_LOAD) { setTimeout(endLoad, MIN_LOAD-diff) } else { endLoad() } } } render(): JSX.Element { return ( <>
{this.getViewElement()}
) } } export default hot(connect(mapState, mapDispatch)(Application))