feat: add loading spinner splash screen while loading
This commit is contained in:
parent
14472a45ed
commit
eef2a3d7cc
52
src/App.vue
52
src/App.vue
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="{'is-touch': isTouch}">
|
<div :class="{'is-touch': isTouch}" v-if="ready">
|
||||||
<div :class="{'is-hidden': !online}">
|
<div :class="{'is-hidden': !online}">
|
||||||
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
|
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
|
||||||
<div class="offline" style="height: 0;width: 0;"></div>
|
<div class="offline" style="height: 0;width: 0;"></div>
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
<vikunja-loading v-else/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -36,10 +37,12 @@ import ContentLinkShare from './components/home/contentLinkShare'
|
||||||
import ContentNoAuth from './components/home/contentNoAuth'
|
import ContentNoAuth from './components/home/contentNoAuth'
|
||||||
import {setLanguage} from './i18n'
|
import {setLanguage} from './i18n'
|
||||||
import AccountDeleteService from '@/services/accountDelete'
|
import AccountDeleteService from '@/services/accountDelete'
|
||||||
|
import VikunjaLoading from '@/components/misc/vikunja-loading'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
|
VikunjaLoading,
|
||||||
ContentNoAuth,
|
ContentNoAuth,
|
||||||
ContentLinkShare,
|
ContentLinkShare,
|
||||||
ContentAuth,
|
ContentAuth,
|
||||||
|
@ -54,13 +57,7 @@ export default defineComponent({
|
||||||
this.setupAccountDeletionVerification()
|
this.setupAccountDeletionVerification()
|
||||||
},
|
},
|
||||||
beforeCreate() {
|
beforeCreate() {
|
||||||
// FIXME: async action in beforeCreate, might be not finished when component mounts
|
this.$store.dispatch('loadApp')
|
||||||
this.$store.dispatch('config/update')
|
|
||||||
.then(() => {
|
|
||||||
this.$store.dispatch('auth/checkAuth')
|
|
||||||
})
|
|
||||||
this.$store.dispatch('auth/checkAuth')
|
|
||||||
|
|
||||||
setLanguage()
|
setLanguage()
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -76,6 +73,7 @@ export default defineComponent({
|
||||||
...mapState({
|
...mapState({
|
||||||
online: ONLINE,
|
online: ONLINE,
|
||||||
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
|
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
|
||||||
|
ready: 'vikunjaReady',
|
||||||
}),
|
}),
|
||||||
...mapGetters('auth', [
|
...mapGetters('auth', [
|
||||||
'authUser',
|
'authUser',
|
||||||
|
@ -124,26 +122,26 @@ export default defineComponent({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.offline {
|
.offline {
|
||||||
background: url('@/assets/llama-nightscape.jpg') no-repeat center;
|
background: url('@/assets/llama-nightscape.jpg') no-repeat center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
.offline-message {
|
.offline-message {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
bottom: 5vh;
|
bottom: 5vh;
|
||||||
color: $white;
|
color: $white;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $white;
|
color: $white;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -48,7 +48,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="menu namespaces-lists loader-container" :class="{'is-loading': loading}">
|
<aside class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
|
||||||
<template v-for="(n, nk) in namespaces" :key="n.id" >
|
<template v-for="(n, nk) in namespaces" :key="n.id" >
|
||||||
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
||||||
<span
|
<span
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
>
|
>
|
||||||
<template #item="{element: l}">
|
<template #item="{element: l}">
|
||||||
<li
|
<li
|
||||||
class="loader-container"
|
class="loader-container is-loading-small"
|
||||||
:class="{'is-loading': listUpdating[l.id]}"
|
:class="{'is-loading': listUpdating[l.id]}"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -449,14 +449,6 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
&:hover :deep(.dropdown-trigger) {
|
&:hover :deep(.dropdown-trigger) {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loader-container.is-loading:after {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
top: calc(50% - .75rem);
|
|
||||||
left: calc(50% - .75rem);
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flip-list-move {
|
.flip-list-move {
|
||||||
|
@ -530,14 +522,6 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
padding-top: math.div($navbar-padding, 2);
|
padding-top: math.div($navbar-padding, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loader-container.is-loading:after {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
top: calc(50% - .75rem);
|
|
||||||
left: calc(50% - .75rem);
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $grey-400 !important;
|
color: $grey-400 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<section class="vikunja-loading">
|
||||||
|
<img alt="Vikunja" :src="logoUrl" width="100" height="100"/>
|
||||||
|
<p>
|
||||||
|
<span class="loader-container is-loading-small is-loading"></span>
|
||||||
|
{{ $t('home.vikunjaLoading') }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import logoUrl from '@/assets/logo.svg'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'vikunja-loading',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logoUrl,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vikunja-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-container {
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
&.is-loading:after {
|
||||||
|
border-left-color: $grey-400;
|
||||||
|
border-bottom-color: $grey-400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
|
"vikunjaLoading": "Vikunja is loading…",
|
||||||
"welcomeNight": "Good Night {username}",
|
"welcomeNight": "Good Night {username}",
|
||||||
"welcomeMorning": "Good Morning {username}",
|
"welcomeMorning": "Good Morning {username}",
|
||||||
"welcomeDay": "Hi {username}",
|
"welcomeDay": "Hi {username}",
|
||||||
|
|
|
@ -43,6 +43,7 @@ export const store = createStore({
|
||||||
menuActive: true,
|
menuActive: true,
|
||||||
keyboardShortcutsActive: false,
|
keyboardShortcutsActive: false,
|
||||||
quickActionsActive: false,
|
quickActionsActive: false,
|
||||||
|
vikunjaReady: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
[LOADING](state, loading) {
|
[LOADING](state, loading) {
|
||||||
|
@ -84,6 +85,9 @@ export const store = createStore({
|
||||||
[BACKGROUND](state, background) {
|
[BACKGROUND](state, background) {
|
||||||
state.background = background
|
state.background = background
|
||||||
},
|
},
|
||||||
|
vikunjaReady(state, ready) {
|
||||||
|
state.vikunjaReady = ready
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async [CURRENT_LIST]({state, commit}, currentList) {
|
async [CURRENT_LIST]({state, commit}, currentList) {
|
||||||
|
@ -138,5 +142,10 @@ export const store = createStore({
|
||||||
|
|
||||||
commit(CURRENT_LIST, currentList)
|
commit(CURRENT_LIST, currentList)
|
||||||
},
|
},
|
||||||
|
async loadApp({commit, dispatch}) {
|
||||||
|
await dispatch('config/update')
|
||||||
|
await dispatch('auth/checkAuth')
|
||||||
|
commit('vikunjaReady', true)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
// FIXME: move to loading.vue
|
// FIXME: move to loading.vue
|
||||||
.loader-container.is-loading {
|
.loader-container {
|
||||||
position: relative;
|
&.is-loading {
|
||||||
pointer-events: none;
|
position: relative;
|
||||||
opacity: 0.5;
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@include loader;
|
@include loader;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(50% - 2.5rem);
|
top: calc(50% - 2.5rem);
|
||||||
left: calc(50% - 2.5rem);
|
left: calc(50% - 2.5rem);
|
||||||
width: 5rem;
|
width: 5rem;
|
||||||
height: 5rem;
|
height: 5rem;
|
||||||
border-width: 0.25rem;
|
border-width: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-loading-small.is-loading::after {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
top: calc(50% - .75rem);
|
||||||
|
left: calc(50% - .75rem);
|
||||||
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue