Improve initial load error handling

This commit is contained in:
James Lyne 2021-05-15 21:09:01 +01:00
parent ce241c93e0
commit b52938aa42
5 changed files with 81 additions and 11 deletions

View File

@ -79,6 +79,14 @@
#splash__error { #splash__error {
margin-top: 2rem; margin-top: 2rem;
transition: opacity 0.5s ease-in; transition: opacity 0.5s ease-in;
display: flex;
flex-direction: column;
text-align: center;
max-width: 60rem;
}
#splash__error-message {
font-family: monospace;
} }
#splash__error[aria-hidden=true] { #splash__error[aria-hidden=true] {
@ -125,7 +133,9 @@
</svg> </svg>
<div id="splash__error" role="alert" aria-hidden="true"> <div id="splash__error" role="alert" aria-hidden="true">
Failed to load server configuration. Retrying<span id="splash__error-attempt"></span>... <span>Failed to load server configuration.</span>
<span id="splash__error-message"></span>
<span id="splash__error-retry">Retrying<span id="splash__error-attempt"></span>...</span>
</div> </div>
</div> </div>

View File

@ -81,6 +81,10 @@
transition: opacity 0.5s ease-in; transition: opacity 0.5s ease-in;
} }
#splash__error-message {
font-family: monospace;
}
#splash__error[aria-hidden=true] { #splash__error[aria-hidden=true] {
opacity: 0; opacity: 0;
} }

View File

@ -29,6 +29,7 @@ import {useStore} from "@/store";
import {ActionTypes} from "@/store/action-types"; import {ActionTypes} from "@/store/action-types";
import {parseUrl} from '@/util'; import {parseUrl} from '@/util';
import {MutationTypes} from "@/store/mutation-types"; import {MutationTypes} from "@/store/mutation-types";
import API from '@/api';
export default defineComponent({ export default defineComponent({
name: 'App', name: 'App',
@ -50,15 +51,24 @@ export default defineComponent({
configAttempts = ref(0), configAttempts = ref(0),
loadConfiguration = () => { loadConfiguration = () => {
store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined).then(() => { let validated = false;
API.validateConfiguration().then(() => {
validated = true;
return store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined).then(() => {
startUpdates(); startUpdates();
window.hideSplash(); window.hideSplash();
});
}).catch(e => { }).catch(e => {
console.error('Failed to load server configuration: ', e); console.error('Failed to load server configuration: ', e);
window.showSplashError(++configAttempts.value); window.showSplashError(e, !validated, ++configAttempts.value);
//Don't retry if config isn't valid
if(validated) {
setTimeout(() => loadConfiguration(), 1000); setTimeout(() => loadConfiguration(), 1000);
}); }
})
}, },
startUpdates = () => { startUpdates = () => {

View File

@ -553,10 +553,40 @@ function buildUpdates(data: Array<any>): DynmapUpdates {
} }
export default { export default {
validateConfiguration(): Promise<void> {
const check = '\nCheck your standalone/config.js file exists and is being loaded correctly.';
if(!window.config || !window.config.url) {
return Promise.reject(`Dynmap's configuration is missing. ${check}`);
}
if(!window.config.url.configuration) {
return Promise.reject(`Dynmap's configuration URL is missing. ${check}`);
}
if(!window.config.url.update) {
return Promise.reject(`Dynmap's update URL is missing. ${check}`);
}
if(!window.config.url.markers) {
return Promise.reject(`Dynmap's markers URL is missing. ${check}`);
}
if(!window.config.url.tiles) {
return Promise.reject(`Dynmap's tiles URL is missing. ${check}`);
}
if(!window.config.url.sendmessage) {
return Promise.reject(`Dynmap's sendmessage URL is missing. ${check}`);
}
return Promise.resolve();
},
getConfiguration(): Promise<DynmapConfigurationResponse> { getConfiguration(): Promise<DynmapConfigurationResponse> {
return fetch(window.config.url.configuration).then(response => { return fetch(window.config.url.configuration).then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error('Network request failed'); throw new Error('Network request failed: ' + response.statusText);
} }
return response.json(); return response.json();

View File

@ -23,7 +23,10 @@ import 'normalize-scss/sass/normalize/_import-now.scss';
import '@/scss/style.scss'; import '@/scss/style.scss';
const splash = document.getElementById('splash'), const splash = document.getElementById('splash'),
splashSpinner = document.getElementById('splash__spinner'),
splashError = document.getElementById('splash__error'), splashError = document.getElementById('splash__error'),
splashErrorMessage = document.getElementById('splash__error-message'),
splashRetry = document.getElementById('splash__error-retry'),
splashAttempt = document.getElementById('splash__error-attempt'), splashAttempt = document.getElementById('splash__error-attempt'),
svgs = import.meta.globEager('/assets/icons/*.svg'); svgs = import.meta.globEager('/assets/icons/*.svg');
@ -35,14 +38,27 @@ window.hideSplash = function() {
}); });
}; };
window.showSplashError = function(attempts: number) { window.showSplashError = function(message: string, fatal: boolean, attempts: number) {
if(splashError) { if(splashError) {
splashError.setAttribute('aria-hidden', 'false'); splashError.setAttribute('aria-hidden', 'false');
} }
if(splashAttempt) { if(splashErrorMessage) {
splashErrorMessage.innerText = message || 'Unknown error';
}
if(splashSpinner && fatal) {
splashSpinner.style.visibility = 'hidden';
}
if(splashAttempt && splashRetry) {
if(fatal) {
splashAttempt.hidden = splashRetry.hidden = true;
} else if(attempts) {
splashAttempt.hidden = splashRetry.hidden = false;
splashAttempt.textContent = attempts.toString(); splashAttempt.textContent = attempts.toString();
} }
}
}; };
if(splash) { if(splash) {