Application.tsx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { hot } from 'react-hot-loader/root'
  2. import * as React from 'react'
  3. import Frame from './frame/Frame'
  4. import Welcome from './welcome/Welcome'
  5. import { connect } from 'react-redux'
  6. import { View } from '../meta/Views'
  7. import Landing from './landing/Landing'
  8. import Login from './login/Login'
  9. import Loader from './loader/Loader'
  10. import Settings from './settings/Settings'
  11. import Overlay from './overlay/Overlay'
  12. import Fatal from './fatal/Fatal'
  13. import { StoreType } from '../redux/store'
  14. import { CSSTransition } from 'react-transition-group'
  15. import { ViewActionDispatch } from '../redux/actions/viewActions'
  16. import { throttle } from 'lodash'
  17. import { readdir } from 'fs-extra'
  18. import { join } from 'path'
  19. import { AppActionDispatch } from '../redux/actions/appActions'
  20. import { OverlayPushAction, OverlayActionDispatch } from '../redux/actions/overlayActions'
  21. import { LoggerUtil } from 'common/logging/loggerutil'
  22. import { DistributionAPI } from 'common/distribution/DistributionAPI'
  23. import { getServerStatus, ServerStatus } from 'common/mojang/net/ServerStatusAPI'
  24. import { Distribution } from 'helios-distribution-types'
  25. import { HeliosDistribution, HeliosServer } from 'common/distribution/DistributionFactory'
  26. import './Application.css'
  27. declare const __static: string
  28. function setBackground(id: number) {
  29. import(`../../../static/images/backgrounds/${id}.jpg`).then(mdl => {
  30. document.body.style.backgroundImage = `url('${mdl.default}')`
  31. })
  32. }
  33. interface ApplicationProps {
  34. currentView: View
  35. overlayQueue: OverlayPushAction<unknown>[]
  36. distribution: HeliosDistribution
  37. selectedServer: HeliosServer
  38. selectedServerStatus: ServerStatus
  39. }
  40. interface ApplicationState {
  41. loading: boolean
  42. showMain: boolean
  43. renderMain: boolean
  44. workingView: View
  45. }
  46. const mapState = (state: StoreType): Partial<ApplicationProps> => {
  47. return {
  48. currentView: state.currentView,
  49. overlayQueue: state.overlayQueue,
  50. distribution: state.app.distribution!,
  51. selectedServer: state.app.selectedServer!
  52. }
  53. }
  54. const mapDispatch = {
  55. ...AppActionDispatch,
  56. ...ViewActionDispatch,
  57. ...OverlayActionDispatch
  58. }
  59. class Application extends React.Component<ApplicationProps & typeof mapDispatch, ApplicationState> {
  60. private static readonly logger = LoggerUtil.getLogger('ApplicationTSX')
  61. private bkid!: number
  62. constructor(props: ApplicationProps & typeof mapDispatch) {
  63. super(props)
  64. this.state = {
  65. loading: true,
  66. showMain: false,
  67. renderMain: false,
  68. workingView: props.currentView
  69. }
  70. }
  71. private getViewElement = (): JSX.Element => {
  72. // TODO debug remove
  73. console.log('loading', this.props.currentView, this.state.workingView)
  74. switch(this.state.workingView) {
  75. case View.WELCOME:
  76. return <>
  77. <Welcome />
  78. </>
  79. case View.LANDING:
  80. return <>
  81. <Landing
  82. distribution={this.props.distribution}
  83. selectedServer={this.props.selectedServer}
  84. selectedServerStatus={this.props.selectedServerStatus}
  85. />
  86. </>
  87. case View.LOGIN:
  88. return <>
  89. <Login cancelable={false} />
  90. </>
  91. case View.SETTINGS:
  92. return <>
  93. <Settings />
  94. </>
  95. case View.FATAL:
  96. return <>
  97. <Fatal />
  98. </>
  99. case View.NONE:
  100. return <></>
  101. }
  102. }
  103. private hasOverlay = (): boolean => {
  104. return this.props.overlayQueue.length > 0
  105. }
  106. private updateWorkingView = throttle(() => {
  107. // TODO debug remove
  108. console.log('Setting to', this.props.currentView)
  109. this.setState({
  110. ...this.state,
  111. workingView: this.props.currentView
  112. })
  113. }, 200)
  114. private finishLoad = (): void => {
  115. if(this.props.currentView !== View.FATAL) {
  116. setBackground(this.bkid)
  117. }
  118. this.showMain()
  119. }
  120. private showMain = (): void => {
  121. this.setState({
  122. ...this.state,
  123. showMain: true
  124. })
  125. }
  126. private initLoad = async (): Promise<void> => {
  127. if(this.state.loading) {
  128. const MIN_LOAD = 800
  129. const start = Date.now()
  130. // Initial distribution load.
  131. const distroAPI = new DistributionAPI('C:\\Users\\user\\AppData\\Roaming\\Helios Launcher')
  132. let rawDisto: Distribution
  133. try {
  134. rawDisto = await distroAPI.testLoad()
  135. console.log('distro', distroAPI)
  136. } catch(err) {
  137. console.log('EXCEPTION IN DISTRO LOAD TODO TODO TODO', err)
  138. rawDisto = null!
  139. }
  140. // Fatal error
  141. if(rawDisto == null) {
  142. this.props.setView(View.FATAL)
  143. this.setState({
  144. ...this.state,
  145. loading: false,
  146. workingView: View.FATAL
  147. })
  148. return
  149. } else {
  150. const distro = new HeliosDistribution(rawDisto)
  151. // TODO TEMP USE CONFIG
  152. // TODO TODO TODO TODO
  153. const selectedServer: HeliosServer = distro.servers[0]
  154. const { hostname, port } = selectedServer
  155. let selectedServerStatus
  156. try {
  157. selectedServerStatus = await getServerStatus(47, hostname, port)
  158. } catch(err) {
  159. Application.logger.error('Failed to refresh server status', selectedServerStatus)
  160. }
  161. this.props.setDistribution(distro)
  162. this.props.setSelectedServer(selectedServer)
  163. this.props.setSelectedServerStatus(selectedServerStatus)
  164. }
  165. // TODO Setup hook for distro refresh every ~ 5 mins.
  166. // Pick a background id.
  167. this.bkid = Math.floor((Math.random() * (await readdir(join(__static, 'images', 'backgrounds'))).length))
  168. const endLoad = () => {
  169. // TODO determine correct view
  170. // either welcome, landing, or login
  171. this.props.setView(View.LANDING)
  172. this.setState({
  173. ...this.state,
  174. loading: false,
  175. workingView: View.LANDING
  176. })
  177. // TODO temp
  178. setTimeout(() => {
  179. // this.props.setView(View.WELCOME)
  180. // this.props.pushGenericOverlay({
  181. // title: 'Load Distribution',
  182. // description: 'This is a test.',
  183. // dismissible: false,
  184. // acknowledgeCallback: async () => {
  185. // const serverStatus = await getServerStatus(47, 'play.hypixel.net', 25565)
  186. // console.log(serverStatus)
  187. // }
  188. // })
  189. // this.props.pushGenericOverlay({
  190. // title: 'Test Title 2',
  191. // description: 'Test Description',
  192. // dismissible: true
  193. // })
  194. // this.props.pushGenericOverlay({
  195. // title: 'Test Title IMPORTANT',
  196. // description: 'Test Description',
  197. // dismissible: true
  198. // }, true)
  199. }, 5000)
  200. }
  201. const diff = Date.now() - start
  202. if(diff < MIN_LOAD) {
  203. setTimeout(endLoad, MIN_LOAD-diff)
  204. } else {
  205. endLoad()
  206. }
  207. }
  208. }
  209. render(): JSX.Element {
  210. return (
  211. <>
  212. <Frame />
  213. <CSSTransition
  214. in={this.state.showMain}
  215. appear={true}
  216. timeout={500}
  217. classNames="appWrapper"
  218. unmountOnExit
  219. >
  220. <div className="appWrapper" {...(this.hasOverlay() ? {overlay: 'true'} : {})}>
  221. <CSSTransition
  222. in={this.props.currentView == this.state.workingView}
  223. appear={true}
  224. timeout={500}
  225. classNames="appWrapper"
  226. unmountOnExit
  227. onExited={this.updateWorkingView}
  228. >
  229. {this.getViewElement()}
  230. </CSSTransition>
  231. </div>
  232. </CSSTransition>
  233. <CSSTransition
  234. in={this.hasOverlay()}
  235. appear={true}
  236. timeout={500}
  237. classNames="appWrapper"
  238. unmountOnExit
  239. >
  240. <Overlay overlayQueue={this.props.overlayQueue} />
  241. </CSSTransition>
  242. <CSSTransition
  243. in={this.state.loading}
  244. appear={true}
  245. timeout={300}
  246. classNames="loader"
  247. unmountOnExit
  248. onEnter={this.initLoad}
  249. onExited={this.finishLoad}
  250. >
  251. <Loader />
  252. </CSSTransition>
  253. </>
  254. )
  255. }
  256. }
  257. export default hot(connect<unknown, typeof mapDispatch>(mapState, mapDispatch)(Application))