diff --git a/build.js b/build.js index e96e733e..81955ee4 100644 --- a/build.js +++ b/build.js @@ -57,7 +57,8 @@ builder.build({ '!{dist,.gitignore,.vscode,docs,dev-app-update.yml,.travis.yml,.nvmrc,.eslintrc.json,build.js}' ], extraResources: [ - 'libraries' + 'libraries', + 'static' ], asar: true } diff --git a/package-lock.json b/package-lock.json index a25cbe22..bd7fbbed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1686,6 +1686,12 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.152", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.152.tgz", + "integrity": "sha512-Vwf9YF2x1GE3WNeUMjT5bTHa2DqgUo87ocdgTScupY2JclZ5Nn7W2RLM/N0+oreexUk8uaVugR81NnTY/jNNXg==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1740,6 +1746,15 @@ "redux": "^4.0.0" } }, + "@types/react-transition-group": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz", + "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/request": { "version": "2.48.5", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", @@ -4464,6 +4479,33 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", + "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^2.6.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -10831,6 +10873,18 @@ "react-is": "^16.9.0" } }, + "react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-config-file": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.0.0.tgz", diff --git a/package.json b/package.json index d7affa3e..3773a3e5 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "github-syntax-dark": "^0.5.0", "got": "^11.1.4", "jquery": "^3.5.1", + "lodash": "^4.17.15", "moment": "^2.26.0", "request": "^2.88.2", "semver": "^7.3.2", @@ -53,11 +54,13 @@ "@types/discord-rpc": "^3.0.4", "@types/fs-extra": "^9.0.1", "@types/jquery": "^3.3.38", + "@types/lodash": "^4.14.152", "@types/mocha": "^7.0.2", "@types/node": "^12.12.42", "@types/react": "^16.9.35", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", + "@types/react-transition-group": "^4.4.0", "@types/request": "^2.48.5", "@types/tar-fs": "^2.0.0", "@types/triple-beam": "^1.3.1", @@ -79,6 +82,7 @@ "react-dom": "^16.13.0", "react-hot-loader": "^4.12.21", "react-redux": "^7.2.0", + "react-transition-group": "^4.4.1", "redux": "^4.0.5", "rimraf": "^3.0.2", "ts-node": "^8.10.1", diff --git a/src/renderer/components/Application.css b/src/renderer/components/Application.css index 737009e3..d10ca79b 100644 --- a/src/renderer/components/Application.css +++ b/src/renderer/components/Application.css @@ -1,4 +1,38 @@ .appWrapper { height: calc(100vh - 22px); background: linear-gradient(to top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0) 100%); +} + +.loader-enter { + opacity: 0; + transform: scale(0.9); +} + +.loader-enter-active { + opacity: 1; + transform: translateX(0); + transition: opacity 300ms, transform 300ms; +} +.loader-exit { + opacity: 1; +} +.loader-exit-active { + opacity: 0; + transform: scale(0.9); + transition: opacity 300ms, transform 300ms; +} + +.appWrapper-enter { + opacity: 0; +} +.appWrapper-enter-active { + opacity: 1; + transition: opacity 500ms, transform 500ms; +} +.appWrapper-exit { + opacity: 1; +} +.appWrapper-exit-active { + opacity: 0; + transition: opacity 500ms, transform 500ms; } \ No newline at end of file diff --git a/src/renderer/components/Application.tsx b/src/renderer/components/Application.tsx index 3a43037e..4af53865 100644 --- a/src/renderer/components/Application.tsx +++ b/src/renderer/components/Application.tsx @@ -6,18 +6,61 @@ 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 './Application.css' +import { StoreType } from '../redux/store' +import { CSSTransition } from 'react-transition-group' +import { setCurrentView } from '../redux/actions/viewActions' +import { throttle } from 'lodash' +import { readdir } from 'fs-extra' +import { join } from 'path' -type ApplicationProps = { +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 } -class Application extends React.Component { +interface ApplicationState { + loading: boolean + showMain: boolean + renderMain: boolean + workingView: View +} + +const mapState = (state: StoreType) => { + return { + currentView: state.currentView + } +} +const mapDispatch = { + setView: (x: View) => setCurrentView(x) +} + +class Application extends React.Component { + + private bkid!: number + + constructor(props: ApplicationProps & typeof mapDispatch) { + super(props) + this.state = { + loading: true, + showMain: false, + renderMain: false, + workingView: props.currentView + } + } getViewElement(): JSX.Element { - switch(this.props.currentView) { + switch(this.state.workingView) { case View.WELCOME: return <> @@ -38,21 +81,86 @@ class Application extends React.Component { } } - render() { + private updateWorkingView = throttle(() => { + this.setState({ + ...this.state, + workingView: this.props.currentView + }) + + }, 200) + + private showMain = (): void => { + setBackground(this.bkid) + this.setState({ + ...this.state, + showMain: true + }) + } + + private initLoad = async (): Promise => { + if(this.state.loading) { + const MIN_LOAD = 800 + const start = Date.now() + this.bkid = Math.floor((Math.random() * (await readdir(join(__static, 'images', 'backgrounds'))).length)) + const endLoad = () => { + this.setState({ + ...this.state, + loading: false + }) + // TODO temp + setTimeout(() => { + this.props.setView(View.WELCOME) + }, 5000) + } + const diff = Date.now() - start + if(diff < MIN_LOAD) { + setTimeout(endLoad, MIN_LOAD-diff) + } else { + endLoad() + } + } + } + + render(): JSX.Element { return ( <> -
- {this.getViewElement()} -
+ +
+ + {this.getViewElement()} + + +
+
+ + + ) } } -const connected = connect((state: any) => ({ - currentView: state.currentView -}), undefined)(Application) - -export default hot(connected) \ No newline at end of file +export default hot(connect(mapState, mapDispatch)(Application)) \ No newline at end of file diff --git a/src/renderer/components/loader/Loader.css b/src/renderer/components/loader/Loader.css new file mode 100644 index 00000000..94792274 --- /dev/null +++ b/src/renderer/components/loader/Loader.css @@ -0,0 +1,62 @@ +/******************************************************************************* + * * + * Loading Element (app.ejs) * + * * + ******************************************************************************/ + +/* Loading container, placed above everything. */ +#loadingContainer { + position: absolute; + z-index: 400; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: calc(100% - 22px); +} + +/* Loading content container. */ +#loadingContent { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +/* Spinner container. */ +#loadSpinnerContainer { + position: relative; + display: flex; + align-items: center; + justify-content: center; +} + +/* Stationary image for the spinner. */ +#loadCenterImage { + position: absolute; + width: 277px; + height: auto; +} + +/* Rotating image for the spinner. */ +#loadSpinnerImage { + width: 280px; + height: auto; + z-index: 400; +} + +/* Rotating animation for the spinner. */ +@keyframes rotating { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* Class which is applied when the spinner image is spinning. */ +.rotating { + animation: rotating 10s linear infinite; +} \ No newline at end of file diff --git a/src/renderer/components/loader/Loader.tsx b/src/renderer/components/loader/Loader.tsx new file mode 100644 index 00000000..e932b2cc --- /dev/null +++ b/src/renderer/components/loader/Loader.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' + +import './Loader.css' + +import LoadingSeal from '../../../../static/images/LoadingSeal.png' +import LoadingText from '../../../../static/images/LoadingText.png' + +export default class Loader extends React.Component { + + render(): JSX.Element { + return <> +
+
+
+ + +
+
+
+ + } + +} \ No newline at end of file diff --git a/src/renderer/components/login/Login.css b/src/renderer/components/login/Login.css index bbe80ef7..7e2e062b 100644 --- a/src/renderer/components/login/Login.css +++ b/src/renderer/components/login/Login.css @@ -19,7 +19,6 @@ align-items: center; height: 100%; width: 100%; - transition: filter 0.25s ease; background: rgba(0, 0, 0, 0.50); } diff --git a/src/renderer/components/welcome/Welcome.css b/src/renderer/components/welcome/Welcome.css index a30b20ad..5276ba24 100644 --- a/src/renderer/components/welcome/Welcome.css +++ b/src/renderer/components/welcome/Welcome.css @@ -5,6 +5,7 @@ align-items: center; height: 100%; width: 100%; + background: rgba(0, 0, 0, 0.50); } #welcomeContent { diff --git a/src/renderer/index.css b/src/renderer/index.css index 7a394e81..81d23bbb 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -28,6 +28,8 @@ /* TODO: Temp for development */ body { /* background-image: url('../../static/images/backgrounds/3.jpg'); */ + transition: background-image 1s ease; + background-image: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBkZWZhdWx0IHF1YWxpdHkK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAPwBwAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8VooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9k='); background-size: cover; } diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 26813709..d5e1528b 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -6,19 +6,6 @@ import './index.css' import Application from './components/Application' import { Provider } from 'react-redux' -import { readdirSync } from 'fs-extra' -import { join } from 'path' - -declare const __static: string - -function setBackground(id: number) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const bk = require('../../static/images/backgrounds/' + id + '.jpg') - document.body.style.backgroundImage = `url('${bk.default}')` -} - -const id = Math.floor((Math.random() * readdirSync(join(__static, 'images', 'backgrounds')).length)) -setBackground(id) // Create main element const mainElement = document.createElement('div') diff --git a/src/renderer/redux/actions/appActions.ts b/src/renderer/redux/actions/appActions.ts new file mode 100644 index 00000000..d83de66f --- /dev/null +++ b/src/renderer/redux/actions/appActions.ts @@ -0,0 +1,19 @@ +import { Action } from 'redux' + +export enum AppActionType { + ChangeLoadState = 'SET_LOADING' +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AppAction extends Action {} + +export interface ChangeLoadStateAction extends AppAction { + payload: boolean +} + +export function setLoadingState(state: boolean): ChangeLoadStateAction { + return { + type: AppActionType.ChangeLoadState, + payload: state + } +} \ No newline at end of file diff --git a/src/renderer/redux/reducers/appReducer.ts b/src/renderer/redux/reducers/appReducer.ts new file mode 100644 index 00000000..c08e3cd7 --- /dev/null +++ b/src/renderer/redux/reducers/appReducer.ts @@ -0,0 +1,24 @@ +import { ChangeLoadStateAction, AppActionType, AppAction } from '../actions/appActions' +import { Reducer } from 'redux' + +export interface AppState { + loading: boolean +} + +const defaultAppState: AppState = { + loading: true +} + +// TODO remove loading from global state. Keeping as an example... +const AppReducer: Reducer = (state = defaultAppState, action) => { + switch(action.type) { + case AppActionType.ChangeLoadState: + return { + ...state, + loading: (action as ChangeLoadStateAction).payload + } + } + return state +} + +export default AppReducer \ No newline at end of file diff --git a/src/renderer/redux/reducers/index.ts b/src/renderer/redux/reducers/index.ts index 197b150c..d5cc9281 100644 --- a/src/renderer/redux/reducers/index.ts +++ b/src/renderer/redux/reducers/index.ts @@ -1,6 +1,8 @@ import { combineReducers } from 'redux' import ViewReducer from './viewReducer' +import AppReducer from './appReducer' export default combineReducers({ - currentView: ViewReducer + currentView: ViewReducer, + app: AppReducer }) \ No newline at end of file diff --git a/src/renderer/redux/store.ts b/src/renderer/redux/store.ts index 24106446..8d11c98e 100644 --- a/src/renderer/redux/store.ts +++ b/src/renderer/redux/store.ts @@ -1,4 +1,6 @@ import { createStore } from 'redux' import reducer from './reducers' +export type StoreType = ReturnType + export default createStore(reducer) \ No newline at end of file diff --git a/assets/images/LoadingSeal.png b/static/images/LoadingSeal.png similarity index 100% rename from assets/images/LoadingSeal.png rename to static/images/LoadingSeal.png diff --git a/assets/images/LoadingText.png b/static/images/LoadingText.png similarity index 100% rename from assets/images/LoadingText.png rename to static/images/LoadingText.png diff --git a/typings/redux-augment.d.ts b/typings/redux-augment.d.ts new file mode 100644 index 00000000..07551af5 --- /dev/null +++ b/typings/redux-augment.d.ts @@ -0,0 +1,6 @@ +import { StoreType } from '../src/renderer/redux/store' + +declare module 'react-redux' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface DefaultRootState extends StoreType {} +} diff --git a/typings/static-import.d.ts b/typings/static-import.d.ts new file mode 100644 index 00000000..1b3e98bd --- /dev/null +++ b/typings/static-import.d.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +declare module '*.png' { + const value: any + export default value +} + +declare module '*.jpg' { + const value: any + export default value +} + +declare module '*.gif' { + const value: any + export default value +} \ No newline at end of file