Added handling if the user is offline (#35)

This commit is contained in:
konrad 2019-10-16 21:27:21 +00:00 committed by Gitea
parent 2a7871cf96
commit 3211e1e8ea
6 changed files with 1167 additions and 127 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -1,156 +1,174 @@
<template> <template>
<div id="app"> <div>
<nav class="navbar main-theme is-fixed-top" role="navigation" aria-label="main navigation" v-if="user.authenticated && user.infos.type === authTypes.USER"> <div v-if="isOnline">
<div class="navbar-brand"> <nav class="navbar main-theme is-fixed-top" role="navigation" aria-label="main navigation"
<router-link :to="{name: 'home'}" class="navbar-item logo"> v-if="user.authenticated && user.infos.type === authTypes.USER">
<img src="/images/logo-full.svg" alt="Vikunja"/> <div class="navbar-brand">
</router-link> <router-link :to="{name: 'home'}" class="navbar-item logo">
</div> <img src="/images/logo-full.svg" alt="Vikunja"/>
<div class="navbar-end"> </router-link>
<div class="user"> </div>
<img :src="gravatar()" class="avatar" alt=""/> <div class="navbar-end">
<div class="dropdown is-right is-active"> <div class="user">
<div class="dropdown-trigger"> <img :src="gravatar()" class="avatar" alt=""/>
<button class="button noshadow" @click="userMenuActive = !userMenuActive"> <div class="dropdown is-right is-active">
<span class="username">{{user.infos.username}}</span> <div class="dropdown-trigger">
<span class="icon is-small"> <button class="button noshadow" @click="userMenuActive = !userMenuActive">
<span class="username">{{user.infos.username}}</span>
<span class="icon is-small">
<icon icon="chevron-down"/> <icon icon="chevron-down"/>
</span> </span>
</button> </button>
</div> </div>
<transition name="fade"> <transition name="fade">
<div class="dropdown-menu" v-if="userMenuActive"> <div class="dropdown-menu" v-if="userMenuActive">
<div class="dropdown-content"> <div class="dropdown-content">
<a @click="logout()" class="dropdown-item"> <a @click="logout()" class="dropdown-item">
Logout Logout
</a> </a>
</div> </div>
</div> </div>
</transition> </transition>
</div> </div>
</div> </div>
</div> </div>
</nav> </nav>
<div v-if="user.authenticated && user.infos.type === authTypes.USER"> <div v-if="user.authenticated && user.infos.type === authTypes.USER">
<a @click="mobileMenuActive = true" class="mobilemenu-show-button" v-if="!mobileMenuActive"><icon icon="bars"></icon></a> <a @click="mobileMenuActive = true" class="mobilemenu-show-button" v-if="!mobileMenuActive">
<a @click="mobileMenuActive = false" class="mobilemenu-hide-button" v-if="mobileMenuActive"><icon icon="times"></icon></a> <icon icon="bars"></icon>
<div class="app-container"> </a>
<div class="namespace-container" :class="{'is-active': mobileMenuActive}"> <a @click="mobileMenuActive = false" class="mobilemenu-hide-button" v-if="mobileMenuActive">
<div class="menu top-menu"> <icon icon="times"></icon>
<ul class="menu-list"> </a>
<li> <div class="app-container">
<router-link :to="{ name: 'home'}"> <div class="namespace-container" :class="{'is-active': mobileMenuActive}">
<div class="menu top-menu">
<ul class="menu-list">
<li>
<router-link :to="{ name: 'home'}">
<span class="icon"> <span class="icon">
<icon icon="calendar"/> <icon icon="calendar"/>
</span> </span>
Overview Overview
</router-link> </router-link>
</li> </li>
<li> <li>
<router-link :to="{ name: 'showTasksInRange', params: {type: 'month'}}"> <router-link :to="{ name: 'showTasksInRange', params: {type: 'month'}}">
<span class="icon"> <span class="icon">
<icon :icon="['far', 'calendar-alt']"/> <icon :icon="['far', 'calendar-alt']"/>
</span> </span>
Next Month Next Month
</router-link> </router-link>
</li> </li>
<li> <li>
<router-link :to="{ name: 'showTasksInRange', params: {type: 'week'}}"> <router-link :to="{ name: 'showTasksInRange', params: {type: 'week'}}">
<span class="icon"> <span class="icon">
<icon icon="calendar-week"/> <icon icon="calendar-week"/>
</span> </span>
Next Week Next Week
</router-link> </router-link>
</li> </li>
<li> <li>
<router-link :to="{ name: 'listTeams'}"> <router-link :to="{ name: 'listTeams'}">
<span class="icon"> <span class="icon">
<icon icon="users"/> <icon icon="users"/>
</span> </span>
Teams Teams
</router-link> </router-link>
</li> </li>
<li> <li>
<router-link :to="{ name: 'newNamespace'}"> <router-link :to="{ name: 'newNamespace'}">
<span class="icon"> <span class="icon">
<icon icon="layer-group"/> <icon icon="layer-group"/>
</span> </span>
New Namespace New Namespace
</router-link> </router-link>
</li> </li>
<li> <li>
<router-link :to="{ name: 'listLabels'}"> <router-link :to="{ name: 'listLabels'}">
<span class="icon"> <span class="icon">
<icon icon="tags"/> <icon icon="tags"/>
</span> </span>
Labels Labels
</router-link> </router-link>
</li> </li>
</ul> </ul>
</div> </div>
<aside class="menu namespaces-lists"> <aside class="menu namespaces-lists">
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div> <div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
<template v-for="n in namespaces"> <template v-for="n in namespaces">
<div :key="n.id"> <div :key="n.id">
<router-link v-tooltip.right="'Settings'" :to="{name: 'editNamespace', params: {id: n.id} }" class="nsettings" v-if="n.id > 0"> <router-link v-tooltip.right="'Settings'"
:to="{name: 'editNamespace', params: {id: n.id} }" class="nsettings"
v-if="n.id > 0">
<span class="icon"> <span class="icon">
<icon icon="cog"/> <icon icon="cog"/>
</span> </span>
</router-link> </router-link>
<router-link v-tooltip="'Add a new list in the ' + n.name + ' namespace'" :to="{ name: 'newList', params: { id: n.id} }" class="nsettings" :key="n.id + 'newList'" v-if="n.id > 0"> <router-link v-tooltip="'Add a new list in the ' + n.name + ' namespace'"
:to="{ name: 'newList', params: { id: n.id} }" class="nsettings"
:key="n.id + 'newList'" v-if="n.id > 0">
<span class="icon"> <span class="icon">
<icon icon="plus"/> <icon icon="plus"/>
</span> </span>
</router-link> </router-link>
<div class="menu-label"> <div class="menu-label">
{{n.name}} {{n.name}}
</div> </div>
</div> </div>
<ul class="menu-list" :key="n.id + 'child'"> <ul class="menu-list" :key="n.id + 'child'">
<li v-for="l in n.lists" :key="l.id"> <li v-for="l in n.lists" :key="l.id">
<router-link :to="{ name: 'showList', params: { id: l.id} }">{{l.title}}</router-link> <router-link :to="{ name: 'showList', params: { id: l.id} }">{{l.title}}
</li> </router-link>
</ul> </li>
</template> </ul>
</aside> </template>
</div> </aside>
<div class="app-content" :class="{'fullpage-overlay': fullpage}"> </div>
<a class="mobile-overlay" v-if="mobileMenuActive" @click="mobileMenuActive = false"></a> <div class="app-content" :class="{'fullpage-overlay': fullpage}">
<transition name="fade"> <a class="mobile-overlay" v-if="mobileMenuActive" @click="mobileMenuActive = false"></a>
<router-view/> <transition name="fade">
</transition> <router-view/>
</div> </transition>
</div> </div>
</div> </div>
<!-- FIXME: This will only be triggered when the root component is already loaded before doing link share auth. Will "fix" itself once we use vuex. --> </div>
<div v-else-if="user.authenticated && user.infos.type === authTypes.LINK_SHARE"> <!-- FIXME: This will only be triggered when the root component is already loaded before doing link share auth. Will "fix" itself once we use vuex. -->
<div class="container has-text-centered link-share-view"> <div v-else-if="user.authenticated && user.infos.type === authTypes.LINK_SHARE">
<div class="column is-10 is-offset-1"> <div class="container has-text-centered link-share-view">
<img src="/images/logo-full.svg" alt="Vikunja" class="logo"/> <div class="column is-10 is-offset-1">
<div class="box has-text-left"> <img src="/images/logo-full.svg" alt="Vikunja" class="logo"/>
<div class="logout"> <div class="box has-text-left">
<a @click="logout()" class="button logout"> <div class="logout">
<span>Logout</span> <a @click="logout()" class="button logout">
<span class="icon is-small"> <span>Logout</span>
<span class="icon is-small">
<icon icon="sign-out-alt"/> <icon icon="sign-out-alt"/>
</span> </span>
</a> </a>
</div> </div>
<router-view/>
</div>
</div>
</div>
</div>
<div v-else>
<div class="container has-text-centered">
<div class="column is-4 is-offset-4">
<img src="/images/logo-full.svg" alt="Vikunja"/>
<router-view/> <router-view/>
</div> </div>
</div> </div>
</div> </div>
</div> <notifications position="bottom left"/>
<div v-else> </div>
<div class="container has-text-centered"> <div class="app offline" v-else>
<div class="column is-4 is-offset-4"> <div class="offline-message">
<img src="/images/logo-full.svg" alt="Vikunja"/> <h1>You are offline.</h1>
<router-view/> <p>Please check your network connection and try again.</p>
</div> </div>
</div> </div>
</div> </div>
<notifications position="bottom left" />
</div>
</template> </template>
<script> <script>
@ -174,17 +192,23 @@
currentDate: new Date(), currentDate: new Date(),
userMenuActive: false, userMenuActive: false,
authTypes: authTypes, authTypes: authTypes,
isOnline: true,
} }
}, },
beforeMount() { beforeMount() {
// Check if the user is offline, show a message then
this.isOnline = navigator.onLine
window.addEventListener('online', () => this.isOnline = navigator.onLine);
window.addEventListener('offline', () => this.isOnline = navigator.onLine);
// Password reset // Password reset
if(this.$route.query.userPasswordReset !== undefined) { if (this.$route.query.userPasswordReset !== undefined) {
localStorage.removeItem('passwordResetToken') // Delete an eventually preexisting old token localStorage.removeItem('passwordResetToken') // Delete an eventually preexisting old token
localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset) localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset)
router.push({name: 'passwordReset'}) router.push({name: 'passwordReset'})
} }
// Email verification // Email verification
if(this.$route.query.userEmailConfirm !== undefined) { if (this.$route.query.userEmailConfirm !== undefined) {
localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm) localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
router.push({name: 'login'}) router.push({name: 'login'})
@ -216,7 +240,7 @@
message.error(e, this) message.error(e, this)
}) })
}, },
loadNamespacesIfNeeded(e){ loadNamespacesIfNeeded(e) {
if (auth.user.authenticated && auth.user.infos.type === authTypes.USER && (e.name === 'home' || this.namespaces.length === 0)) { if (auth.user.authenticated && auth.user.infos.type === authTypes.USER && (e.name === 'home' || this.namespaces.length === 0)) {
this.loadNamespaces() this.loadNamespaces()
} }

View File

@ -1,4 +1,21 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
/* eslint-disable no-undef */
console.log('Hello from the service worker'); // Cache assets
workbox.routing.registerRoute(
// This regexp matches all files in precache-manifest
new RegExp('.+\\.(css|json|js|eot|svg|ttf|woff|woff2|png|html|txt)$'),
new workbox.strategies.StaleWhileRevalidate()
);
// Always send api reqeusts through the network
workbox.routing.registerRoute(
new RegExp('(\\/)?api\\/v1\\/.*$'),
new workbox.strategies.NetworkOnly()
);
// Cache everything else
workbox.routing.registerRoute(
new RegExp('.*'),
new workbox.strategies.StaleWhileRevalidate()
);

View File

@ -314,4 +314,29 @@ h1,h2,h3,h4,h5,h6{
.logout { .logout {
text-align: right; text-align: right;
} }
}
.offline {
background: url('../public/images/llama-nightscape.png') no-repeat center;
-webkit-background-size: cover;
background-size: cover;
height: 100vh;
.offline-message {
text-align: center;
position: absolute;
width: 100vw;
bottom: 5vh;
color: $white;
padding: 0 1em;
h1 {
font-weight: bold;
font-size: 1.5em;
text-align: center;
color: $white;
font-weight: 700 !important;
font-size: 1.5em;
}
}
} }

View File

@ -156,4 +156,8 @@
## Fancy stuff ## Fancy stuff
* [ ] PWA * [ ] PWA
* [x] Basics
* [ ] Should only show a small notification somewhere when offline
* [ ] Cache even api requests, but show a notification on lists "content may be outdated"
* [ ] Need a strategy to show all of this if the page is offline
* [ ] Vuex * [ ] Vuex