Merge pull request #2075 from thecodingmachine/codeAPI

Refactoring Error Screen
This commit is contained in:
David Négrier
2022-04-22 16:52:43 +02:00
committed by GitHub
20 changed files with 651 additions and 184 deletions
+7 -1
View File
@@ -2,6 +2,7 @@
import type { Game } from "../Phaser/Game/Game";
import { chatVisibilityStore } from "../Stores/ChatStore";
import { errorStore } from "../Stores/ErrorStore";
import { errorScreenStore } from "../Stores/ErrorScreenStore";
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
@@ -13,11 +14,16 @@
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
import ErrorDialog from "./UI/ErrorDialog.svelte";
import ErrorScreen from "./UI/ErrorScreen.svelte";
export let game: Game;
</script>
{#if $errorStore.length > 0}
{#if $errorScreenStore !== undefined}
<div>
<ErrorScreen />
</div>
{:else if $errorStore.length > 0}
<div>
<ErrorDialog />
</div>
+144
View File
@@ -0,0 +1,144 @@
<script lang="ts">
import { fly } from "svelte/transition";
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
import { gameManager } from "../../Phaser/Game/GameManager";
import { get } from "svelte/store";
import { onDestroy } from "svelte";
import logoImg from "../images/logo-min-white.png";
let logo = gameManager?.currentStartedRoom?.loginSceneLogo ?? logoImg;
import reload from "../images/reload.png";
let errorScreen = get(errorScreenStore);
function click() {
window.location.reload();
}
let details = errorScreen.details;
let timeVar = errorScreen.timeToRetry ?? 0;
if (errorScreen.type === "retry") {
let interval = setInterval(() => {
if (timeVar <= 1000) click();
timeVar -= 1000;
}, 1000);
onDestroy(() => clearInterval(interval));
}
$: detailsStylized = (details ?? "").replace("{time}", `${timeVar / 1000}`);
</script>
<main class="errorScreen" transition:fly={{ y: -200, duration: 500 }}>
<div style="width: 90%;">
<img src={logo} alt="WorkAdventure" class="logo" />
<div><img src={$errorScreenStore.image} alt="" class="icon" /></div>
{#if $errorScreenStore.type !== "retry"}<h2>{$errorScreenStore.title}</h2>{/if}
<p>{$errorScreenStore.subtitle}</p>
{#if $errorScreenStore.type !== "retry"}<p class="code">Code : {$errorScreenStore.code}</p>{/if}
<p class="details">
{detailsStylized}{#if $errorScreenStore.type === "retry"}<div class="loading" />{/if}
</p>
{#if $errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual}
<button type="button" class="nes-btn is-primary button" on:click={click}>
<img src={reload} alt="" class="reload" />
{$errorScreenStore.buttonTitle}
</button>
{/if}
</div>
</main>
<style lang="scss">
main.errorScreen {
pointer-events: auto;
width: 100%;
background-color: #000000;
color: #ffffff;
text-align: center;
position: absolute;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
height: 100%;
top: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
min-width: 300px;
z-index: 700;
overflow-y: scroll;
padding: 20px 0;
.logo {
width: 50%;
margin-bottom: 50px;
max-height: 25vh;
max-width: 50vw;
}
.icon {
height: 125px;
margin-bottom: 25px;
max-height: 25vh;
max-width: 50vw;
}
h2 {
font-family: "Press Start 2P";
padding: 5px;
font-size: 30px;
}
p {
font-family: "Press Start 2P";
}
p.code {
font-size: 12px;
opacity: 0.6;
user-select: text;
}
p.details {
font-size: 12px;
max-width: 80%;
margin: 0 auto 35px auto;
}
.loading {
display: inline-block;
min-width: 20px;
position: relative;
margin-left: 5px;
}
.loading:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
-webkit-animation: ellipsis steps(4, end) 900ms infinite;
animation: ellipsis steps(4, end) 900ms infinite;
content: "\2026";
width: 0;
font-family: "Press Start 2P";
font-size: 16px;
position: absolute;
left: 0;
top: -19px;
}
@keyframes ellipsis {
to {
width: 1.25em;
}
}
@-webkit-keyframes ellipsis {
to {
width: 1.25em;
}
}
.button {
cursor: pointer;
font-family: "Press Start 2P";
font-size: 14px;
.reload {
margin-top: -4px;
width: 22px;
}
}
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

+12
View File
@@ -25,6 +25,7 @@ import {
TokenExpiredMessage,
WorldConnexionMessage,
ErrorMessage as ErrorMessageTsProto,
ErrorScreenMessage as ErrorScreenMessageTsProto,
UserMovedMessage as UserMovedMessageTsProto,
GroupUpdateMessage as GroupUpdateMessageTsProto,
GroupDeleteMessage as GroupDeleteMessageTsProto,
@@ -47,6 +48,7 @@ import { Subject } from "rxjs";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
import { gameManager } from "../Phaser/Game/GameManager";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
import { errorScreenStore } from "../Stores/ErrorScreenStore";
const manualPingDelay = 20000;
@@ -62,6 +64,9 @@ export class RoomConnection implements RoomConnection {
private readonly _errorMessageStream = new Subject<ErrorMessageTsProto>();
public readonly errorMessageStream = this._errorMessageStream.asObservable();
private readonly _errorScreenMessageStream = new Subject<ErrorScreenMessageTsProto>();
public readonly errorScreenMessageStream = this._errorScreenMessageStream.asObservable();
private readonly _roomJoinedMessageStream = new Subject<{
connection: RoomConnection;
room: RoomJoinedMessageInterface;
@@ -477,6 +482,13 @@ export class RoomConnection implements RoomConnection {
console.error("An error occurred server side: " + message.errorMessage.message);
break;
}
case "errorScreenMessage": {
this._errorScreenMessageStream.next(message.errorScreenMessage);
if (message.errorScreenMessage.code !== "retry") this.closed = true;
console.error("An error occurred server side: " + message.errorScreenMessage.code);
errorScreenStore.setError(message.errorScreenMessage);
break;
}
default: {
// Security check: if we forget a "case", the line below will catch the error at compile-time.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
+10 -24
View File
@@ -1,12 +1,12 @@
import { gameManager } from "../Game/GameManager";
import { Scene } from "phaser";
import { ErrorScene } from "../Reconnecting/ErrorScene";
import { WAError } from "../Reconnecting/WAError";
import { waScaleManager } from "../Services/WaScaleManager";
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
import LL from "../../i18n/i18n-svelte";
import { get } from "svelte/store";
import { localeDetector } from "../../i18n/locales";
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
import { isErrorApiData } from "../../Messages/JsonMessages/ErrorApiData";
import { connectionManager } from "../../Connexion/ConnectionManager";
export const EntrySceneName = "EntryScene";
@@ -47,27 +47,13 @@ export class EntryScene extends Scene {
this.scene.start(nextSceneName);
})
.catch((err) => {
const $LL = get(LL);
if (err.response && err.response.status == 404) {
ErrorScene.showError(
new WAError(
$LL.error.accessLink.title(),
$LL.error.accessLink.subTitle(),
$LL.error.accessLink.details()
),
this.scene
);
} else if (err.response && err.response.status == 403) {
ErrorScene.showError(
new WAError(
$LL.error.connectionRejected.title(),
$LL.error.connectionRejected.subTitle({
error: err.response.data ? ". \n\r \n\r" + `${err.response.data}` : "",
}),
$LL.error.connectionRejected.details()
),
this.scene
);
const errorType = isErrorApiData.safeParse(err?.response?.data);
if (errorType.success) {
if (errorType.data.type === "unauthorized") {
void connectionManager.logout();
} else if (errorType.data.type === "redirect") {
window.location.assign(errorType.data.urlToRedirect);
} else errorScreenStore.setError(err?.response?.data);
} else {
ErrorScene.showError(err, this.scene);
}
@@ -3,7 +3,6 @@ import Image = Phaser.GameObjects.Image;
import Sprite = Phaser.GameObjects.Sprite;
import Text = Phaser.GameObjects.Text;
import ScenePlugin = Phaser.Scenes.ScenePlugin;
import { WAError } from "./WAError";
import Axios from "axios";
export const ErrorSceneName = "ErrorScene";
@@ -88,12 +87,6 @@ export class ErrorScene extends Phaser.Scene {
title: "An error occurred",
subTitle: error,
});
} else if (error instanceof WAError) {
scene.start(ErrorSceneName, {
title: error.title,
subTitle: error.subTitle,
message: error.details,
});
} else if (Axios.isAxiosError(error) && error.response) {
// Axios HTTP error
// client received an error response (5xx, 4xx)
+47 -8
View File
@@ -1,26 +1,65 @@
export class WAError extends Error {
private _type: string;
private _code: string;
private _title: string;
private _subTitle: string;
private _subtitle: string;
private _details: string;
private _timeToRetry: number;
private _canRetryManual: boolean;
private _urlToRedirect: string;
private _buttonTitle: string;
constructor(title: string, subTitle: string, details: string) {
super(title + " - " + subTitle + " - " + details);
constructor(
type: string,
code: string,
title: string,
subtitle: string,
details: string,
timeToRetry: number,
canRetryManual: boolean,
urlToRedirect: string,
buttonTitle: string
) {
super(title + " - " + subtitle + " - " + details);
this._type = type;
this._code = code;
this._title = title;
this._subTitle = subTitle;
this._subtitle = subtitle;
this._details = details;
this._timeToRetry = timeToRetry;
this._canRetryManual = canRetryManual;
this._urlToRedirect = urlToRedirect;
this._buttonTitle = buttonTitle;
// Set the prototype explicitly.
Object.setPrototypeOf(this, WAError.prototype);
}
get type(): string {
return this._type;
}
get code(): string {
return this._code;
}
get title(): string {
return this._title;
}
get subTitle(): string {
return this._subTitle;
get subtitle(): string {
return this._subtitle;
}
get details(): string {
return this._details;
}
get timeToRetry(): number {
return this._timeToRetry;
}
get buttonTitle(): string {
return this._buttonTitle;
}
get urlToRedirect(): string {
return this._urlToRedirect;
}
get canRetryManual(): boolean {
return this._canRetryManual;
}
}
+18
View File
@@ -0,0 +1,18 @@
import { writable } from "svelte/store";
import { ErrorScreenMessage } from "../Messages/ts-proto-generated/protos/messages";
/**
* A store that contains one error of type WAError to be displayed.
*/
function createErrorScreenStore() {
const { subscribe, set } = writable<ErrorScreenMessage>(undefined);
return {
subscribe,
setError: (e: ErrorScreenMessage): void => {
set(e);
},
};
}
export const errorScreenStore = createErrorScreenStore();