rework Waiting/Error display and Overlay

This commit is contained in:
axolotle 2021-02-15 15:37:34 +01:00
parent 957ca17b84
commit 9c85c42b98
5 changed files with 211 additions and 119 deletions

View file

@ -27,7 +27,7 @@
class="error-btn ml-auto py-0" class="error-btn ml-auto py-0"
variant="danger" variant="danger"
> >
<small>View error</small> <small v-t="'api_error.view_error'" />
</b-button> </b-button>
<!-- TIME DISPLAY --> <!-- TIME DISPLAY -->

View file

@ -1,38 +1,90 @@
<template> <template>
<div class="error mt-4 mb-5" v-if="error"> <!-- This card receives style from `ViewLockOverlay` if used inside it -->
<h2>{{ $t('api_errors_titles.' + error.name) }} :/</h2> <div>
<b-card-body>
<b-card-title v-t="'api_errors_titles.' + error.name" />
<em v-t="'api_error.sorry'" /> <em v-t="'api_error.sorry'" />
<div class="alert alert-info mt-4"> <div class="alert alert-info my-3">
<span v-html="$t('api_error.help')" /> <span v-html="$t('api_error.help')" />
<br>{{ $t('api_error.info') }} <br>{{ $t('api_error.info') }}
</div> </div>
<h5 v-t="'error'" /> <!-- FIXME USE DD DL DT -->
<pre><code>"{{ error.code }}" {{ error.status }}</code></pre> <p class="m-0">
<strong v-t="'error'" />: <code>"{{ error.code }}" {{ error.status }}</code>
</p>
<p>
<strong v-t="'action'" />: <code>"{{ error.method }}" {{ error.path }}</code>
</p>
<h5 v-t="'action'" /> <p>
<pre><code>"{{ error.method }}" {{ error.path }}</code></pre> <strong v-t="'api_error.error_message'" /> <span v-html="error.message" />
</p>
<h5>Message</h5>
<p v-html="error.message" />
<template v-if="error.traceback"> <template v-if="error.traceback">
<h5 v-t="'traceback'" /> <p>
<pre><code class="text-dark">{{ error.traceback }}</code></pre> <strong v-t="'traceback'" />
</p>
<pre><code>{{ error.traceback }}</code></pre>
</template> </template>
<template v-if="hasMessages">
<p class="my-2">
<strong v-t="'api_error.server_said'" />
</p>
<message-list-group :messages="action.messages" bordered />
</template>
</b-card-body>
<b-card-footer footer-bg-variant="danger">
<!-- TODO add copy error ? -->
<b-button
variant="light" size="sm"
v-t="'words.dismiss'" @click="dismiss"
/>
</b-card-footer>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import MessageListGroup from '@/components/MessageListGroup'
export default { export default {
name: 'ErrorPage', name: 'ErrorPage',
computed: mapGetters(['error']) components: {
MessageListGroup
},
// FIXME add redirect if they're no error (if reload or route entered by hand) props: {
action: { type: Object, required: true }
},
computed: {
...mapGetters(['error']),
hasMessages () {
return this.action && this.action.messages.length > 0
}
},
methods: {
dismiss () {
if (this.error && this.error.method === 'GET') {
history.back()
}
this.$store.dispatch('DELETE_ERROR')
}
}
} }
</script> </script>
<style lang="scss" scoped>
code, pre code {
color: $black;
}
</style>

View file

@ -1,38 +1,18 @@
<template> <template>
<b-overlay <b-overlay
variant="white" rounded="sm" opacity="0.5" variant="white" opacity="0.75"
no-center no-center
:show="show" :show="waiting || error !== null"
> >
<slot name="default" /> <slot name="default" />
<template v-slot:overlay> <template v-slot:overlay>
<b-card no-body> <b-card no-body class="card-overlay" v-if="lastAction">
<div v-if="error === null" class="mt-3 px-3"> <b-card-header header-bg-variant="white">
<div class="custom-spinner" :class="spinner" /> <query-header :action="lastAction" status-size="lg" />
</div> </b-card-header>
<b-card-body v-if="error"> <component :is="error ? 'ErrorDisplay' : 'WaitingDisplay'" :action="lastAction" />
<error-display />
</b-card-body>
<b-card-body v-else class="pb-4">
<b-card-title class="text-center m-0" v-t="'api_waiting'" />
<!-- PROGRESS BAR -->
<b-progress
v-if="progress" class="mt-4"
:max="progress.max" height=".5rem"
>
<b-progress-bar variant="success" :value="progress.values[0]" />
<b-progress-bar variant="warning" :value="progress.values[1]" animated />
<b-progress-bar variant="secondary" :value="progress.values[2]" striped />
</b-progress>
</b-card-body>
<b-card-footer v-if="error" class="justify-content-end">
<b-button variant="primary" v-t="'ok'" @click="$store.dispatch('DELETE_ERROR')" />
</b-card-footer>
</b-card> </b-card>
</template> </template>
</b-overlay> </b-overlay>
@ -40,102 +20,52 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { ErrorDisplay } from '@/views/_partials' import { ErrorDisplay, WaitingDisplay } from '@/views/_partials'
import QueryHeader from '@/components/QueryHeader'
export default { export default {
name: 'ViewLockOverlay', name: 'ViewLockOverlay',
computed: { computed: mapGetters(['waiting', 'error', 'lastAction']),
...mapGetters(['waiting', 'lastAction', 'error', 'spinner']),
show () {
return this.waiting || this.error !== null
},
progress () {
if (!this.lastAction) return null
const progress = this.lastAction.progress
if (!progress) return null
return {
values: progress, max: progress.reduce((sum, value) => (sum + value), 0)
}
}
},
components: { components: {
ErrorDisplay ErrorDisplay,
WaitingDisplay,
QueryHeader
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.card { // Style for `ErrorDisplay` and `WaitingDisplay`'s cards
.card-overlay {
position: sticky; position: sticky;
top: 5vh; top: 10vh;
margin: 0 5%; margin: 0 5%;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
margin: 0 10%; margin: 0 10%;
} }
@include media-breakpoint-up(lg) { @include media-breakpoint-up(lg) {
margin: 0 20%; margin: 0 15%;
} }
}
.card-body { ::v-deep {
padding-bottom: 2rem; .card-body {
max-height: 50vh; padding: 1.5rem;
max-height: 60vh;
overflow-y: auto; overflow-y: auto;
} }
.progress { .card-footer {
margin-top: 2rem; padding: .5rem .75rem;
} display: flex;
justify-content: flex-end;
.custom-spinner {
animation: 4s linear infinite;
background-repeat: no-repeat;
&.pacman {
height: 24px;
width: 24px;
background-image: url('../../assets/spinners/pacman.gif');
animation-name: back-and-forth-pacman;
@keyframes back-and-forth-pacman {
0%, 100% { transform: scale(1); margin-left: 0; }
49% { transform: scale(1); margin-left: calc(100% - 24px);}
50% { transform: scale(-1); margin-left: calc(100% - 24px);}
99% { transform: scale(-1); margin-left: 0;}
} }
} }
&.magikarp { .card-header {
height: 32px; padding: .5rem .75rem;
width: 32px;
background-image: url('../../assets/spinners/magikarp.gif');
animation-name: back-and-forth-magikarp;
@keyframes back-and-forth-magikarp {
0%, 100% { transform: scale(1, 1); margin-left: 0; }
49% { transform: scale(1, 1); margin-left: calc(100% - 32px);}
50% { transform: scale(-1, 1); margin-left: calc(100% - 32px);}
99% { transform: scale(-1, 1); margin-left: 0;}
}
}
&.nyancat {
height: 40px;
width: 100px;
background-image: url('../../assets/spinners/nyancat.gif');
animation-name: back-and-forth-nyancat;
@keyframes back-and-forth-nyancat {
0%, 100% { transform: scale(1, 1); margin-left: 0; }
49% { transform: scale(1, 1); margin-left: calc(100% - 100px);}
50% { transform: scale(-1, 1); margin-left: calc(100% - 100px);}
99% { transform: scale(-1, 1); margin-left: 0;}
}
} }
} }
</style> </style>

View file

@ -0,0 +1,107 @@
<template>
<!-- This card receives style from `ViewLockOverlay` if used inside it -->
<b-card-body>
<b-card-title class="text-center mt-4" v-t="hasMessages ? 'api.processing' : 'api_waiting'" />
<!-- PROGRESS BAR -->
<b-progress
v-if="progress" class="mt-4"
:max="progress.max" height=".5rem"
>
<b-progress-bar variant="success" :value="progress.values[0]" />
<b-progress-bar variant="warning" :value="progress.values[1]" animated />
<b-progress-bar variant="secondary" :value="progress.values[2]" striped />
</b-progress>
<!-- OR SPINNER -->
<div v-else class="custom-spinner my-4" :class="spinner" />
<message-list-group
v-if="hasMessages" :messages="action.messages"
bordered fixed-height auto-scroll
/>
</b-card-body>
</template>
<script>
import { mapGetters } from 'vuex'
import MessageListGroup from '@/components/MessageListGroup'
export default {
name: 'WaitingDisplay',
components: {
MessageListGroup
},
props: {
action: { type: Object, required: true }
},
computed: {
...mapGetters(['spinner']),
hasMessages () {
return this.action && this.action.messages.length > 0
},
progress () {
const progress = this.action.progress
if (!progress) return null
return {
values: progress, max: progress.reduce((sum, value) => (sum + value), 0)
}
}
}
}
</script>
<style lang="scss" scoped>
.custom-spinner {
animation: 4s linear infinite;
background-repeat: no-repeat;
&.pacman {
height: 24px;
width: 24px;
background-image: url('../../assets/spinners/pacman.gif');
animation-name: back-and-forth-pacman;
@keyframes back-and-forth-pacman {
0%, 100% { transform: scale(1); margin-left: 0; }
49% { transform: scale(1); margin-left: calc(100% - 24px);}
50% { transform: scale(-1); margin-left: calc(100% - 24px);}
99% { transform: scale(-1); margin-left: 0;}
}
}
&.magikarp {
height: 32px;
width: 32px;
background-image: url('../../assets/spinners/magikarp.gif');
animation-name: back-and-forth-magikarp;
@keyframes back-and-forth-magikarp {
0%, 100% { transform: scale(1, 1); margin-left: 0; }
49% { transform: scale(1, 1); margin-left: calc(100% - 32px);}
50% { transform: scale(-1, 1); margin-left: calc(100% - 32px);}
99% { transform: scale(-1, 1); margin-left: 0;}
}
}
&.nyancat {
height: 40px;
width: 100px;
background-image: url('../../assets/spinners/nyancat.gif');
animation-name: back-and-forth-nyancat;
@keyframes back-and-forth-nyancat {
0%, 100% { transform: scale(1, 1); margin-left: 0; }
49% { transform: scale(1, 1); margin-left: calc(100% - 100px);}
50% { transform: scale(-1, 1); margin-left: calc(100% - 100px);}
99% { transform: scale(-1, 1); margin-left: 0;}
}
}
}
</style>

View file

@ -1,5 +1,8 @@
export { default as ErrorDisplay } from './ErrorDisplay' export { default as ErrorDisplay } from './ErrorDisplay'
export { default as WaitingDisplay } from './WaitingDisplay'
export { default as HistoryConsole } from './HistoryConsole' export { default as HistoryConsole } from './HistoryConsole'
export { default as ViewLockOverlay } from './ViewLockOverlay' export { default as ViewLockOverlay } from './ViewLockOverlay'
export { default as DomainForm } from './DomainForm' export { default as DomainForm } from './DomainForm'
export { default as PasswordForm } from './PasswordForm' export { default as PasswordForm } from './PasswordForm'