From c670b7d88dc65ee69af8ed20e8a25ffedc35b496 Mon Sep 17 00:00:00 2001 From: Daniel Scalzi Date: Fri, 12 Jun 2020 20:58:07 -0400 Subject: [PATCH] Initial work on overlay system. The overlay display is driven by an array in the global redux store. Overlay content is now queueabale, so alerts can be asynchronously dispatched without interferring with existing displayed content. The server/account select overlay components are yet to be completed. Some usability features also need to be implemented, such as keybinds for the acknowledge/dismiss buttons. --- src/renderer/components/Application.css | 3 + src/renderer/components/Application.tsx | 59 +++- src/renderer/components/landing/Landing.css | 90 +++--- src/renderer/components/overlay/Overlay.css | 12 + src/renderer/components/overlay/Overlay.tsx | 79 +++++ .../account-select/AccountSelectOverlay.tsx | 28 ++ .../generic-overlay/GenericOverlay.css | 104 +++++++ .../generic-overlay/GenericOverlay.tsx | 67 +++++ .../server-select/ServerSelectOverlay.tsx | 33 ++ .../overlay/shared-select/SharedSelect.css | 284 ++++++++++++++++++ src/renderer/index.tsx | 5 +- src/renderer/redux/actions/overlayActions.ts | 66 ++++ src/renderer/redux/actions/viewActions.ts | 4 + src/renderer/redux/reducers/index.ts | 2 + src/renderer/redux/reducers/overlayReducer.ts | 30 ++ src/renderer/redux/reducers/viewReducer.ts | 2 +- test/assets/MojangIndexProcessorTest.ts | 1 + test/mojang/mojangTest.ts | 10 +- 18 files changed, 819 insertions(+), 60 deletions(-) create mode 100644 src/renderer/components/overlay/Overlay.css create mode 100644 src/renderer/components/overlay/Overlay.tsx create mode 100644 src/renderer/components/overlay/account-select/AccountSelectOverlay.tsx create mode 100644 src/renderer/components/overlay/generic-overlay/GenericOverlay.css create mode 100644 src/renderer/components/overlay/generic-overlay/GenericOverlay.tsx create mode 100644 src/renderer/components/overlay/server-select/ServerSelectOverlay.tsx create mode 100644 src/renderer/components/overlay/shared-select/SharedSelect.css create mode 100644 src/renderer/redux/actions/overlayActions.ts create mode 100644 src/renderer/redux/reducers/overlayReducer.ts diff --git a/src/renderer/components/Application.css b/src/renderer/components/Application.css index d10ca79b..454a8aaa 100644 --- a/src/renderer/components/Application.css +++ b/src/renderer/components/Application.css @@ -2,6 +2,9 @@ height: calc(100vh - 22px); background: linear-gradient(to top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0) 100%); } +.appWrapper[overlay] { + filter: blur(3px) contrast(0.9) brightness(1.0); +} .loader-enter { opacity: 0; diff --git a/src/renderer/components/Application.tsx b/src/renderer/components/Application.tsx index 4af53865..561efe60 100644 --- a/src/renderer/components/Application.tsx +++ b/src/renderer/components/Application.tsx @@ -8,14 +8,16 @@ 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 { ViewActionDispatch } from '../redux/actions/viewActions' import { throttle } from 'lodash' import { readdir } from 'fs-extra' import { join } from 'path' +import Overlay from './overlay/Overlay' +import { OverlayPushAction, OverlayActionDispatch } from '../redux/actions/overlayActions' + +import './Application.css' declare const __static: string @@ -27,6 +29,7 @@ function setBackground(id: number) { interface ApplicationProps { currentView: View + overlayQueue: OverlayPushAction[] } interface ApplicationState { @@ -36,13 +39,15 @@ interface ApplicationState { workingView: View } -const mapState = (state: StoreType) => { +const mapState = (state: StoreType): Partial => { return { - currentView: state.currentView + currentView: state.currentView, + overlayQueue: state.overlayQueue } } const mapDispatch = { - setView: (x: View) => setCurrentView(x) + ...ViewActionDispatch, + ...OverlayActionDispatch } class Application extends React.Component { @@ -81,6 +86,10 @@ class Application extends React.Component { + return this.props.overlayQueue.length > 0 + } + private updateWorkingView = throttle(() => { this.setState({ ...this.state, @@ -109,7 +118,32 @@ class Application extends React.Component { - this.props.setView(View.WELCOME) + //this.props.setView(View.WELCOME) + this.props.pushGenericOverlay({ + title: 'Test Title', + description: 'Test Description', + dismissible: true + }) + this.props.pushGenericOverlay({ + title: 'Test Title 2', + description: 'Test Description', + dismissible: true + }) + this.props.pushGenericOverlay({ + title: 'Test Title 3', + description: 'Test Description', + dismissible: true + }) + this.props.pushGenericOverlay({ + title: 'Test Title 4', + description: 'Test Description', + dismissible: true + }) + this.props.pushGenericOverlay({ + title: 'Test Title IMPORTANT', + description: 'Test Description', + dismissible: true + }, true) }, 5000) } const diff = Date.now() - start @@ -132,7 +166,7 @@ class Application extends React.Component -
+
+ + + #upper { position: relative; transition: top 2s ease; - top: 0px; + top: 0; height: 77%; display: flex; } @@ -46,7 +46,7 @@ #landingContainer > #lower > #left { position: relative; transition: top 2s ease; - top: 0px; + top: 0; height: 100%; width: 33%; display: inline-flex; @@ -62,7 +62,7 @@ #landingContainer > #lower > #center { position: relative; transition: top 2s ease; - top: 0px; + top: 0; height: 100%; width: 34%; display: inline-flex; @@ -77,7 +77,7 @@ #landingContainer > #lower > #right { position: relative; transition: top 2s ease; - top: 0px; + top: 0; height: 100%; width: 33%; display: inline-flex; @@ -161,11 +161,11 @@ } #newsArticleTitle:hover, #newsArticleTitle:focus { - text-shadow: 0px 0px 20px white; + text-shadow: 0 0 20px white; } #newsArticleTitle:active { color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7; + text-shadow: 0 0 20px #c7c7c7; } /* News meta container. */ @@ -186,7 +186,7 @@ #newsArticleAuthor { display: inline-block; font-size: 10px; - padding: 0px 5px; + padding: 0 5px; font-weight: bold; border-radius: 2px; } @@ -226,7 +226,7 @@ #newsArticleContainer { width: calc(100% - 25px); height: 100%; - margin: 0px 0px 0px 25px; + margin: 0 0 0 25px; } /* Article content styles. */ @@ -234,7 +234,7 @@ font-size: 12px; overflow-y: scroll; height: 100%; - padding: 0px 15px 0px 15px; + padding: 0 15px 0 15px; } #newsArticleContentScrollable img, #newsArticleContentScrollable iframe { @@ -277,15 +277,15 @@ } .bbCodeSpoilerButton:hover, .bbCodeSpoilerButton:focus { - text-shadow: 0px 0px 20px #ffffff, 0px 0px 20px #ffffff, 0px 0px 20px #ffffff; + text-shadow: 0 0 20px #ffffff, 0 0 20px #ffffff, 0 0 20px #ffffff; } .bbCodeSpoilerButton:active { color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; + text-shadow: 0 0 20px #c7c7c7, 0 0 20px #c7c7c7, 0 0 20px #c7c7c7; } .bbCodeSpoilerText { display: none; - padding: 15px 0px; + padding: 15px 0; border-bottom: 1px solid white; } @@ -312,13 +312,13 @@ -webkit-user-select: none; position: absolute; bottom: 15px; - right: 0px; + right: 0; } /* Navigation status span. */ #newsNavigationStatus { font-size: 12px; - margin: 0px 15px; + margin: 0 15px; } /* Left and right navigation button styles. */ @@ -334,8 +334,8 @@ #newsNavigateLeft:focus #newsNavigationLeftSVG, #newsNavigateRight:hover #newsNavigationRightSVG, #newsNavigateRight:focus #newsNavigationRightSVG { - filter: drop-shadow(0px 0px 2px #fff); - -webkit-filter: drop-shadow(0px 0px 2px #fff); + filter: drop-shadow(0px 0 2px #fff); + -webkit-filter: drop-shadow(0px 0 2px #fff); } #newsNavigateLeft:active #newsNavigationLeftSVG .arrowLine, #newsNavigateRight:active #newsNavigationRightSVG .arrowLine { @@ -343,8 +343,8 @@ } #newsNavigateLeft:active #newsNavigationLeftSVG, #newsNavigateRight:active #newsNavigationRightSVG { - filter: drop-shadow(0px 0px 2px #c7c7c7); - -webkit-filter: drop-shadow(0px 0px 2px #c7c7c7); + filter: drop-shadow(0px 0 2px #c7c7c7); + -webkit-filter: drop-shadow(0px 0 2px #c7c7c7); } #newsNavigateLeft:disabled #newsNavigationLeftSVG .arrowLine, #newsNavigateRight:disabled #newsNavigationRightSVG .arrowLine { @@ -397,11 +397,11 @@ } #newsErrorRetry:focus, #newsErrorRetry:hover { - text-shadow: 0px 0px 20px white; + text-shadow: 0 0 20px white; } #newsErrorRetry:active { color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7; + text-shadow: 0 0 20px #c7c7c7; } /******************************************************************************* @@ -537,7 +537,7 @@ background: rgba(1, 2, 1, 0.5); height: 70px; width: 70px; - box-shadow: 0px 0px 10px 0px rgb(0, 0, 0); + box-shadow: 0 0 10px 0 rgb(0, 0, 0); overflow: hidden; position: relative; background-position: center; @@ -578,7 +578,7 @@ min-width: 135px; font-weight: 900; letter-spacing: 1px; - text-shadow: 0px 0px 20px black; + text-shadow: 0 0 20px black; position: absolute; right: 95px; text-align: right; @@ -615,7 +615,7 @@ height: 1px; width: 14px; background: rgb(255, 255, 255); - margin: 10px 0px; + margin: 10px 0; } /* Social media icon shared styles. */ @@ -642,7 +642,7 @@ .mediaButton { background: none; border: none; - padding: 0px; + padding: 0; display: flex; align-items: center; outline: none; @@ -745,7 +745,7 @@ font-size: 9px; letter-spacing: 1px; font-weight: bold; - text-shadow: 0px 0px 0px #bebcbb; + text-shadow: 0 0 0 #bebcbb; } /* Divider used on the bottom of the landing view. */ @@ -772,7 +772,7 @@ color: #949494; font-size: 8px; font-weight: 900; - text-shadow: 0px 0px 20px #949494; + text-shadow: 0 0 20px #949494; margin-left: 10px; } @@ -809,7 +809,7 @@ bottom: calc(100% + 15px); transform: translateX(-50%); margin-left: 50%; - box-shadow: 0px 0px 20px rgb(0, 0, 0); + box-shadow: 0 0 20px rgb(0, 0, 0); cursor: default; } #mojangStatusTooltip:after { @@ -840,7 +840,7 @@ #mojangStatusNEContainer { display: flex; align-items: center; - margin: 10px 0px; + margin: 10px 0; } /* White bar which surrounds the non essential service title. */ @@ -853,7 +853,7 @@ /* Non essential service title text. */ #mojangStatusNETitle { font-size: 10px; - padding: 0px 3px; + padding: 0 3px; text-align: center; letter-spacing: 1px; } @@ -869,7 +869,7 @@ font-size: 10px; letter-spacing: 1px; line-height: 12px; - padding: 6px 0px; + padding: 6px 0; } /* Displays the status of the mojang service. */ @@ -892,24 +892,24 @@ } #newsButton:hover #newsButtonText, #newsButton:focus #newsButtonText { - text-shadow: 0px 0px 20px #fff, 0px 0px 20px #fff; + text-shadow: 0 0 20px #fff, 0 0 20px #fff; } #newsButton:active { color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; + text-shadow: 0 0 20px #c7c7c7, 0 0 20px #c7c7c7; } #newsButton:hover #newsButtonSVG, #newsButton:focus #newsButtonSVG { - filter: drop-shadow(0px 0px 2px #fff); - -webkit-filter: drop-shadow(0px 0px 2px #fff); + filter: drop-shadow(0px 0 2px #fff); + -webkit-filter: drop-shadow(0px 0 2px #fff); } #newsButton:active #newsButtonSVG .arrowLine { stroke: #c7c7c7; } #newsButton:active #newsButtonSVG { - filter: drop-shadow(0px 0px 2px #c7c7c7); - -webkit-filter: drop-shadow(0px 0px 2px #c7c7c7); + filter: drop-shadow(0px 0 2px #c7c7c7); + -webkit-filter: drop-shadow(0px 0 2px #c7c7c7); } #newsButton:disabled #newsButtonSVG .arrowLine { stroke: rgba(255, 255, 255, 0.75); @@ -938,7 +938,7 @@ color: white; font-weight: 900; letter-spacing: 2px; - text-shadow: 0px 0px 0px #bebcbb; + text-shadow: 0 0 0 #bebcbb; font-size: 11px; line-height: 30px; display: flex; @@ -963,19 +963,19 @@ cursor: pointer; font-weight: 900; letter-spacing: 2px; - text-shadow: 0px 0px 0px #bebcbb; + text-shadow: 0 0 0 #bebcbb; font-size: 20px; - padding: 0px; + padding: 0; transition: 0.25s ease; outline: none; } #launch_button:hover, #launch_button:focus { - text-shadow: 0px 0px 20px #fff, 0px 0px 20px #fff; + text-shadow: 0 0 20px #fff, 0 0 20px #fff; } #launch_button:active { color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; + text-shadow: 0 0 20px #c7c7c7, 0 0 20px #c7c7c7; } #launch_button:disabled { color: #c7c7c7; @@ -999,7 +999,7 @@ #launch_progress_label { font-weight: 900; letter-spacing: 1px; - text-shadow: 0px 0px 0px #bebcbb; + text-shadow: 0 0 0 #bebcbb; font-size: 20px; min-width: 53.21px; max-width: 53.21px; @@ -1020,16 +1020,16 @@ outline: none; cursor: pointer; line-height: 24px; - padding: 0px; + padding: 0; transition: 0.25s ease; } #server_selection_button:hover, #server_selection_button:focus { - text-shadow: 0px 0px 20px #fff, 0px 0px 20px #fff, 0px 0px 20px #fff; + text-shadow: 0 0 20px #fff, 0 0 20px #fff, 0 0 20px #fff; } #server_selection_button:active { color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; + text-shadow: 0 0 20px #c7c7c7, 0 0 20px #c7c7c7, 0 0 20px #c7c7c7; } /* Progress bar styles. */ diff --git a/src/renderer/components/overlay/Overlay.css b/src/renderer/components/overlay/Overlay.css new file mode 100644 index 00000000..e0a2af22 --- /dev/null +++ b/src/renderer/components/overlay/Overlay.css @@ -0,0 +1,12 @@ +/* Overlay container, placed over the main div. */ +#overlayContainer { + position: absolute; + z-index: 500; + top: 22px; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: calc(100% - 22px); + background: rgba(0, 0, 0, 0.50); +} \ No newline at end of file diff --git a/src/renderer/components/overlay/Overlay.tsx b/src/renderer/components/overlay/Overlay.tsx new file mode 100644 index 00000000..e777324a --- /dev/null +++ b/src/renderer/components/overlay/Overlay.tsx @@ -0,0 +1,79 @@ +import * as React from 'react' +import GenericOverlay from './generic-overlay/GenericOverlay' +import ServerSelectOverlay from './server-select/ServerSelectOverlay' +import AccountSelectOverlay from './account-select/AccountSelectOverlay' +import { OverlayContent, PushGenericOverlayAction, PushServerSelectOverlayAction } from '../../redux/actions/overlayActions' +import { StoreType } from '../../redux/store' +import { OverlayPushAction } from '../../redux/actions/overlayActions' +import { connect } from 'react-redux' + +import './Overlay.css' + +interface OverlayProps { + overlayQueue: OverlayPushAction[] +} + +const mapState = (state: StoreType): Partial => { + return { + overlayQueue: state.overlayQueue + } +} + +class Overlay extends React.Component { + + private getGenericOverlay(action: PushGenericOverlayAction): JSX.Element { + return ( + <> + + + ) + } + + private getServerSelectOverlay(action: PushServerSelectOverlayAction): JSX.Element { + return ( + <> + + + ) + } + + private getOverlayContent(): JSX.Element { + if(!this.props.overlayQueue || this.props.overlayQueue.length < 1) { + return (<>) + } + const currentContent = this.props.overlayQueue[0] + switch(currentContent.overlayContent) { + case OverlayContent.GENERIC: + return this.getGenericOverlay(currentContent as PushGenericOverlayAction) + case OverlayContent.SERVER_SELECT: + return this.getServerSelectOverlay(currentContent as PushServerSelectOverlayAction) + case OverlayContent.ACCOUNT_SELECT: + return ( + <> + + + ) + } + } + + render(): JSX.Element { + return <> +
+ {this.getOverlayContent()} +
+ + } + +} + +export default connect(mapState)(Overlay) \ No newline at end of file diff --git a/src/renderer/components/overlay/account-select/AccountSelectOverlay.tsx b/src/renderer/components/overlay/account-select/AccountSelectOverlay.tsx new file mode 100644 index 00000000..f1cf6517 --- /dev/null +++ b/src/renderer/components/overlay/account-select/AccountSelectOverlay.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' + +import '../shared-select/SharedSelect.css' + +export default class AccountSelectOverlay extends React.Component { + + render(): JSX.Element { + return ( + <> +
+ Select an Account +
+
+ {/* Accounts populated here. */} +
+
+
+ +
+ +
+
+
+ + ) + } + +} \ No newline at end of file diff --git a/src/renderer/components/overlay/generic-overlay/GenericOverlay.css b/src/renderer/components/overlay/generic-overlay/GenericOverlay.css new file mode 100644 index 00000000..9829e4ba --- /dev/null +++ b/src/renderer/components/overlay/generic-overlay/GenericOverlay.css @@ -0,0 +1,104 @@ +/* Main overlay content. */ +#overlayContent { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + /*justify-content: space-between;*/ + width: 300px; + /*height: 35%;*/ + box-sizing: border-box; + padding: 15px 0px; + /* background-color: #424242; */ + text-align: center; +} + +/* Main overlay content anchor styles. */ +#overlayContent a, +#overlayDismiss { + color: rgba(202, 202, 202, 0.75); + transition: 0.25s ease; +} +#overlayContent a:hover, +#overlayContent a:focus, +#overlayDismiss:focus { + color: rgba(255, 255, 255, 0.75); +} +#overlayContent a:active, +#overlayDismiss:active { + color: rgba(165, 165, 165, 0.75); +} + +/* Add spacing between overlay content elements. */ +#overlayContent > *:first-child { + margin-top: 0px !important; +} +#overlayContent > *:last-child { + margin-bottom: 0px !important; +} +#overlayContent > * { + margin: 8px 0px; +} + +/* Overlay title styles. */ +#overlayTitle { + font-family: 'Avenir Medium'; + font-size: 20px; + font-weight: bold; + letter-spacing: 1px; + -webkit-user-select: initial; +} + +/* Overlay description styles. */ +#overlayDesc { + font-size: 12px; + font-weight: bold; + -webkit-user-select: initial; +} + +/* Div which contains action buttons. */ +#overlayActionContainer { + display: flex; + flex-direction: column; + justify-content: center; +} + +/* Overlay acknowledge button styles. */ +#overlayAcknowledge { + background: none; + border: 1px solid #ffffff; + color: white; + font-family: 'Avenir Medium'; + font-weight: bold; + border-radius: 2px; + padding: 0px 8.1px; + cursor: pointer; + transition: 0.25s ease; +} +#overlayAcknowledge:hover, +#overlayAcknowledge:focus { + box-shadow: 0px 0px 10px 0px #fff; + outline: none; +} +#overlayAcknowledge:active { + border-color: rgba(255, 255, 255, 0.75); + color: rgba(255, 255, 255, 0.75); +} + +/* Overlay dismiss option styles. */ +#overlayDismiss { + font-weight: bold; + font-size: 10px; + text-decoration: none; + padding-top: 2.5px; + background: none; + border: none; + outline: none; + cursor: pointer; +} +#overlayDismiss:hover { + color: rgba(255, 255, 255, 0.75); +} +#overlayDismiss:active { + color: rgba(165, 165, 165, 0.75); +} \ No newline at end of file diff --git a/src/renderer/components/overlay/generic-overlay/GenericOverlay.tsx b/src/renderer/components/overlay/generic-overlay/GenericOverlay.tsx new file mode 100644 index 00000000..3770b82a --- /dev/null +++ b/src/renderer/components/overlay/generic-overlay/GenericOverlay.tsx @@ -0,0 +1,67 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { OverlayActionDispatch } from '../../../redux/actions/overlayActions' + +import './GenericOverlay.css' + +export interface GenericOverlayProps { + title: string + description: string + acknowledgeText?: string + dismissText?: string + dismissible: boolean + acknowledgeCallback?: (event: React.MouseEvent) => void + dismissCallback?: (event: React.MouseEvent) => void +} + +const mapDispatch = { + ...OverlayActionDispatch +} + +type InternalGenericOverlayProps = GenericOverlayProps & typeof mapDispatch + +class GenericOverlay extends React.Component { + + private getAcknowledgeText = (): string => { + return this.props.acknowledgeText || 'OK' + } + + private getDismissText = (): string => { + return this.props.dismissText || 'Dismiss' + } + + private onAcknowledgeClick = (event: React.MouseEvent): void => { + if(this.props.acknowledgeCallback) { + this.props.acknowledgeCallback(event) + } + this.props.popOverlayContent() + } + + private onDismissClick = (event: React.MouseEvent): void => { + if(this.props.dismissCallback) { + this.props.dismissCallback(event) + } + this.props.popOverlayContent() + } + + render(): JSX.Element { + return <> +
+ {this.props.title} + {this.props.description} +
+ +
+ { this.props.dismissible + ? + : <> + } +
+
+
+ + } + +} + +export default connect(undefined, mapDispatch)(GenericOverlay) \ No newline at end of file diff --git a/src/renderer/components/overlay/server-select/ServerSelectOverlay.tsx b/src/renderer/components/overlay/server-select/ServerSelectOverlay.tsx new file mode 100644 index 00000000..68f54e88 --- /dev/null +++ b/src/renderer/components/overlay/server-select/ServerSelectOverlay.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' + +import '../shared-select/SharedSelect.css' +import { Server } from 'helios-distribution-types' + +export interface ServerSelectOverlayProps { + servers: Server[] +} + +export default class ServerSelectOverlay extends React.Component { + + render(): JSX.Element { + return ( + <> +
+ Available Servers +
+
+ {/* Server listings populated here. */} +
+
+
+ +
+ +
+
+
+ + ) + } + +} \ No newline at end of file diff --git a/src/renderer/components/overlay/shared-select/SharedSelect.css b/src/renderer/components/overlay/shared-select/SharedSelect.css new file mode 100644 index 00000000..0d007913 --- /dev/null +++ b/src/renderer/components/overlay/shared-select/SharedSelect.css @@ -0,0 +1,284 @@ +/* * * +* Overlay View (Server + Account Selection Content) +* * */ + +/* Server selection content container. */ +#serverSelectContent, +#accountSelectContent { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 75%; +} + +/* Server selection header. */ +#serverSelectHeader, +#accountSelectHeader { + font-family: 'Avenir Medium'; + font-size: 20px; + font-weight: bold; + color: #fff; + margin-bottom: 25px; +} + +/* Wrapper div for the list of available servers. */ +#serverSelectList, +#accountSelectList { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + max-height: 65%; + min-height: 40%; +} + +/* Scrollable div which lists the available servers. */ +#serverSelectListScrollable, +#accountSelectListScrollable { + padding: 0px 5px; + overflow-y: scroll; +} +#serverSelectListScrollable::-webkit-scrollbar, +#accountSelectListScrollable::-webkit-scrollbar { + width: 2px; +} +#serverSelectListScrollable::-webkit-scrollbar-track, +#accountSelectListScrollable::-webkit-scrollbar-track { + display: none; +} +#serverSelectListScrollable::-webkit-scrollbar-thumb, +#accountSelectListScrollable::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.50); +} + +/* Content container for a server listing. */ +.serverListing { + border: none; + padding: 0px; + width: 375px; + min-height: 60px; + display: flex; + justify-content: flex-start; + align-items: center; + opacity: 0.6; + transition: 0.25s ease; + cursor: pointer; + position: relative; + background: rgba(131, 131, 131, 0.25); +} +.serverListing[selected] { + cursor: default; + opacity: 1.0; +} +.serverListing:hover, +.serverListing:focus { + outline: none; + opacity: 1.0; +} + +.accountListing { + color: white; + border: 1px solid rgba(126, 126, 126, 0.57); + border-radius: 3px; + padding: 5px 45px; + width: 250px; + display: flex; + justify-content: flex-start; + align-items: center; + opacity: 0.6; + transition: 0.25s ease; + cursor: pointer; + position: relative; + background: rgba(0, 0, 0, 0.25); +} +.accountListing[selected] { + cursor: default; + opacity: 1.0; +} +.accountListing:hover, +.accountListing:focus { + outline: none; + opacity: 1.0; +} + +.accountListingName { + display: flex; + height: 100%; + width: 100%; + padding-left: 10px; +} + +/* Add spacing between server listings. */ +#serverSelectListScrollable > .serverListing:not(:first-child):not(:last-child), +#accountSelectListScrollable > .accountListing:not(:first-child):not(:last-child) { + margin: 5px 0px; +} +#serverSelectListScrollable > .serverListing:first-child, +#accountSelectListScrollable > .accountListing:first-child { + margin-bottom: 5px; +} +#serverSelectListScrollable > .serverListing:last-child, +#accountSelectListScrollable > .accountListing:last-child { + margin-top: 5px; +} + +/* Server listing image. */ +.serverListingImg { + margin: 0px 10px 0px 5px; + border: 1px solid #fff; + height: 50px; + width: 50px; +} + +/* Content container for the server listing's details. */ +.serverListingDetails { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + height: 50px; +} + +/* The name of the server listing. */ +.serverListingName { + font-size: 14px; + font-weight: bold; +} + +/* Description for the server listing. */ +.serverListingDescription { + font-size: 10px; + line-height: 10px; + font-weight: bold; +} + +/* Content container for the server listing's information. */ +.serverListingInfo { + width: 100%; + display: flex; + justify-content: flex-start; +} + +/* The minecraft version of the server listing. */ +.serverListingVersion { + font-size: 10px; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + line-height: 12px; + height: 12px; + border-radius: 2px; + background: rgba(31, 140, 11, 0.8); + padding: 0px 2px; +} + +/* The revision version of the server's manifest. */ +.serverListingRevision { + color: #969696; + font-size: 10px; + line-height: 12px; + padding: 0px 5px; +} + +/* Star which indicates the default (main) server. */ +.serverListingStarWrapper { + display: flex; + align-items: center; + cursor: pointer; + height: 12px; + position: relative; +} +/* Tooltip which displays when hovering over the star. */ +.serverListingStarTooltip { + visibility: hidden; + opacity: 0; + width: 65px; + background-color: rgba(0, 0, 0, 0.40); + text-align: center; + border-radius: 4px; + position: absolute; + z-index: 1; + left: 130%; + font-size: 10px; + transition: visibility 0s linear 0.25s, opacity 0.25s ease; +} +.serverListingStarTooltip::after { + content: " "; + position: absolute; + top: 50%; + right: 100%; /* To the left of the tooltip */ + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent rgba(0, 0, 0, 0.40) transparent transparent; +} +.serverListingStarWrapper:hover .serverListingStarTooltip { + visibility: visible; + opacity: 1; + transition-delay:0s; +} + +/* Content container which contains the server select actions. */ +#serverSelectActions, +#accountSelectActions { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 25px; +} + +/* Server selection confirm button styles. */ +#serverSelectConfirm, +#accountSelectConfirm { + background: none; + border: 1px solid #ffffff; + color: white; + font-family: 'Avenir Medium'; + font-weight: bold; + border-radius: 2px; + padding: 0px 8.1px; + cursor: pointer; + transition: 0.25s ease; + min-height: 20.67px; +} +#serverSelectConfirm:hover, +#serverSelectConfirm:focus, +#accountSelectConfirm:hover, +#accountSelectConfirm:focus { + box-shadow: 0px 0px 10px 0px #fff; + outline: none; +} +#serverSelectConfirm:active, +#accountSelectConfirm:active { + border-color: rgba(255, 255, 255, 0.75); + color: rgba(255, 255, 255, 0.75); +} + +/* Server selection cancel button styles. */ +#serverSelectCancel, +#accountSelectCancel { + font-weight: bold; + font-size: 10px; + text-decoration: none; + padding-top: 2.5px; + color: rgba(202, 202, 202, 0.75); + transition: 0.25s ease; + background: none; + border: none; + outline: none; + cursor: pointer; +} +#serverSelectCancel:hover, +#serverSelectCancel:focus, +#accountSelectCancel:hover, +#accountSelectCancel:focus { + color: rgba(255, 255, 255, 0.75); +} +#serverSelectCancel:active, +#accountSelectCancel:active { + color: rgba(165, 165, 165, 0.75); +} \ No newline at end of file diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index d5e1528b..6f20ddd8 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -15,7 +15,10 @@ document.body.appendChild(mainElement) ReactDOM.render( - + , mainElement diff --git a/src/renderer/redux/actions/overlayActions.ts b/src/renderer/redux/actions/overlayActions.ts new file mode 100644 index 00000000..a4bedb8b --- /dev/null +++ b/src/renderer/redux/actions/overlayActions.ts @@ -0,0 +1,66 @@ +import { Action } from 'redux' +import { GenericOverlayProps } from 'src/renderer/components/overlay/generic-overlay/GenericOverlay' +import { ServerSelectOverlayProps } from 'src/renderer/components/overlay/server-select/ServerSelectOverlay' + +export enum OverlayContent { + ACCOUNT_SELECT, + SERVER_SELECT, + GENERIC +} + +export enum OverlayActionType { + PushContent = 'PUSH_CONTENT', + PopContent = 'POP_CONTENT' +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface OverlayAction extends Action {} + +export interface OverlayPushAction extends OverlayAction { + overlayContent: OverlayContent + timestamp: number + showNext: boolean + payload: T +} + +export interface PushGenericOverlayAction extends OverlayPushAction { + overlayContent: OverlayContent.GENERIC + payload: GenericOverlayProps +} + +export interface PushServerSelectOverlayAction extends OverlayPushAction { + overlayContent: OverlayContent.SERVER_SELECT + payload: ServerSelectOverlayProps +} + +export function pushGenericOverlay(options: GenericOverlayProps, showNext = false): PushGenericOverlayAction { + return { + type: OverlayActionType.PushContent, + overlayContent: OverlayContent.GENERIC, + timestamp: Date.now(), + showNext, + payload: options + } +} + +export function pushServerSelectOverlay(options: ServerSelectOverlayProps, showNext = false): PushServerSelectOverlayAction { + return { + type: OverlayActionType.PushContent, + overlayContent: OverlayContent.SERVER_SELECT, + timestamp: Date.now(), + showNext, + payload: options + } +} + +export function popOverlayContent(): OverlayAction { + return { + type: OverlayActionType.PopContent + } +} + +export const OverlayActionDispatch = { + pushGenericOverlay: (options: GenericOverlayProps, showNext = false): PushGenericOverlayAction => pushGenericOverlay(options, showNext), + pushServerSelectOverlay: (options: ServerSelectOverlayProps, showNext = false): PushServerSelectOverlayAction => pushServerSelectOverlay(options, showNext), + popOverlayContent: (): OverlayAction => popOverlayContent() +} \ No newline at end of file diff --git a/src/renderer/redux/actions/viewActions.ts b/src/renderer/redux/actions/viewActions.ts index 3953e7a8..1558916f 100644 --- a/src/renderer/redux/actions/viewActions.ts +++ b/src/renderer/redux/actions/viewActions.ts @@ -14,4 +14,8 @@ export function setCurrentView(view: View): ChangeViewAction { type: ViewActionType.ChangeView, payload: view } +} + +export const ViewActionDispatch = { + setView: (x: View): ChangeViewAction => setCurrentView(x) } \ No newline at end of file diff --git a/src/renderer/redux/reducers/index.ts b/src/renderer/redux/reducers/index.ts index d5cc9281..29c0f2b7 100644 --- a/src/renderer/redux/reducers/index.ts +++ b/src/renderer/redux/reducers/index.ts @@ -1,8 +1,10 @@ import { combineReducers } from 'redux' import ViewReducer from './viewReducer' import AppReducer from './appReducer' +import OverlayReducer from './overlayReducer' export default combineReducers({ currentView: ViewReducer, + overlayQueue: OverlayReducer, app: AppReducer }) \ No newline at end of file diff --git a/src/renderer/redux/reducers/overlayReducer.ts b/src/renderer/redux/reducers/overlayReducer.ts new file mode 100644 index 00000000..23564430 --- /dev/null +++ b/src/renderer/redux/reducers/overlayReducer.ts @@ -0,0 +1,30 @@ +import { Reducer } from 'redux' +import { OverlayAction, OverlayActionType, OverlayPushAction } from '../actions/overlayActions' + +const defaultOverlayQueue: OverlayPushAction[] = [] + +const OverlayReducer: Reducer[], OverlayAction> = (state = defaultOverlayQueue, action) => { + switch(action.type) { + case OverlayActionType.PushContent: + if((action as OverlayPushAction).showNext && state.length > 0) { + return [ + state[0], + action as OverlayPushAction, + ...state.slice(1) + ] + } else { + return [ + ...state, + action as OverlayPushAction + ] + } + case OverlayActionType.PopContent: + return [ + ...state.slice(1) + ] + + } + return state +} + +export default OverlayReducer \ No newline at end of file diff --git a/src/renderer/redux/reducers/viewReducer.ts b/src/renderer/redux/reducers/viewReducer.ts index 32793e22..f279784f 100644 --- a/src/renderer/redux/reducers/viewReducer.ts +++ b/src/renderer/redux/reducers/viewReducer.ts @@ -2,7 +2,7 @@ import { Reducer } from 'redux' import { View } from '../../meta/Views' import { ChangeViewAction, ViewActionType } from '../actions/viewActions' -const defaultView = View.LOGIN +const defaultView = View.LANDING const ViewReducer: Reducer = (state = defaultView, action) => { switch(action.type) { diff --git a/test/assets/MojangIndexProcessorTest.ts b/test/assets/MojangIndexProcessorTest.ts index 9bef7804..b3f50274 100644 --- a/test/assets/MojangIndexProcessorTest.ts +++ b/test/assets/MojangIndexProcessorTest.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import nock from 'nock' import { URL } from 'url' import { MojangIndexProcessor } from 'common/asset/processor/MojangIndexProcessor' diff --git a/test/mojang/mojangTest.ts b/test/mojang/mojangTest.ts index 8283876c..f7802455 100644 --- a/test/mojang/mojangTest.ts +++ b/test/mojang/mojangTest.ts @@ -1,8 +1,8 @@ -import { Mojang } from "common/mojang/mojang" +import { Mojang } from 'common/mojang/mojang' import { expect } from 'chai' import nock from 'nock' -import { Session } from "common/mojang/model/auth/Session" -import { MojangResponseCode } from "common/mojang/model/internal/Response" +import { Session } from 'common/mojang/model/auth/Session' +import { MojangResponseCode } from 'common/mojang/model/internal/Response' function expectMojangResponse(res: any, responseCode: MojangResponseCode, negate = false) { expect(res).to.not.be.an('error') @@ -29,7 +29,7 @@ describe('Mojang Errors', () => { .get('/check') .reply(500, 'Service temprarily offline.') - const res = await Mojang.status(); + const res = await Mojang.status() expectMojangResponse(res, MojangResponseCode.SUCCESS, true) expect(res.data).to.be.an('array') expect(res.data).to.deep.equal(defStatusHack) @@ -65,7 +65,7 @@ describe('Mojang Status', () => { .get('/check') .reply(200, defStatusHack) - const res = await Mojang.status(); + const res = await Mojang.status() expectMojangResponse(res, MojangResponseCode.SUCCESS) expect(res.data).to.be.an('array') expect(res.data).to.deep.equal(defStatusHack)