Added error handling and handle async component failing

This commit is contained in:
Henry Jameson 2026-06-26 17:43:57 +03:00
commit 670edf3006
9 changed files with 254 additions and 2 deletions

View file

@ -0,0 +1,36 @@
import DialogModal from 'src/components/dialog_modal/dialog_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleXmark } from '@fortawesome/free-solid-svg-icons'
library.add(faCircleXmark)
/**
* This component emits the following events:
* cancelled, emitted when the action should not be performed;
* accepted, emitted when the action should be performed;
*
* The caller should close this dialog after receiving any of the two events.
*/
const ErrorModal = {
components: {
DialogModal,
},
props: {
title: {
type: String,
},
clearText: {
type: String,
},
recoverText: {
type: String,
},
error: {
type: Error,
},
},
emits: ['clear', 'recover']
}
export default ErrorModal

View file

@ -0,0 +1,100 @@
<template>
<DialogModal
v-body-scroll-lock="true"
class="error-modal"
@cancel="onCancel"
>
<template #header>
<span v-text="title ?? $t('general.generic_error')" />
</template>
<div class="content">
<FAIcon
class="error-icon"
icon="circle-xmark"
size="3x"
fixed-width
/>
<div class="text">
<slot>
<p>
<strong><code v-text="error.name" /></strong>
{{ ' - ' }}
<span v-text="error.message" />
</p>
<details open>
<summary>{{ $t('general.generic_error_details') }}</summary>
<code class="stack pre" v-text="error.stack" />
</details>
</slot>
</div>
</div>
<div class="below">
<slot name="below" />
</div>
<template #footer>
<slot name="footerLeft" />
<button
v-if="recoverText"
class="btn button-default"
@click.prevent="$emit('recover')"
v-text="recoverText"
/>
<button
class="btn button-default"
@click.prevent="$emit('clear')"
v-text="clearText ?? $t('general.close')"
/>
</template>
</DialogModal>
</template>
<script src="./error_modal.js"></script>
<style lang="scss">
.error-modal {
.error-icon {
margin-left: 0.75rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.content {
display: flex;
align-items: center;
text-align: left;
justify-content: center;
line-height: 1.5;
p {
margin-top: 0.75em;
margin-bottom: 0.75em;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
.stack {
margin: 0;
overflow-x: auto;
}
.below:not(:empty) {
margin-top: 1em;
}
.text {
max-width: 50ch;
margin-left: 0.5em;
margin-right: 3.5em;
}
}
</style>

View file

@ -0,0 +1,52 @@
import ErrorModal from 'src/components/error_modal/error_modal.vue'
import { useInterfaceStore } from 'src/stores/interface.js'
import { mapState, mapActions } from 'pinia'
const GlobalError = {
components: {
ErrorModal,
},
computed: {
title() {
if (this.globalError == null) return null
return this.globalError.title && this.$t(this.globalError.title)
},
content() {
if (this.globalError == null) return null
if (this.globalError.content) {
return this.$t(this.globalError.content, [this.globalError.error])
} else {
return null
}
},
details() {
if (this.globalError == null) return null
if (this.globalError.error != null) {
return this.globalError.error.toString() + '\n\n' + this.globalError.error.stack
} else {
return this.globalError.details
}
},
recoverText() {
if (this.globalError == null) return null
if (this.globalError.recoverText == null) return null
return this.$t(this.globalError.recoverText)
},
...mapState(useInterfaceStore, ['globalError']),
},
methods: {
clear() {
this.globalError.clear?.()
this.clearGlobalError()
},
recover() {
this.globalError.recover?.()
this.clearGlobalError()
},
...mapActions(useInterfaceStore, ['clearGlobalError']),
},
}
export default GlobalError

View file

@ -0,0 +1,23 @@
<template>
<teleport to="#modal">
<ErrorModal
v-if="globalError"
:error="globalError.error"
:title="title"
:recover-text="recoverText"
@recover="recover"
@clear="clear"
>
<template v-if="content">
<p>{{ content }}</p>
<details v-if="details">
<summary>{{ $t('general.generic_error_details') }}</summary>
<code class="stack pre" v-text="details" />
</details>
</template>
</ErrorModal>
</teleport>
</template>
<script src="./global_error.js"></script>