2018-08-28 22:50:22 +02:00
|
|
|
<template>
|
2019-10-16 23:27:21 +02:00
|
|
|
<div>
|
|
|
|
<div v-if="isOnline">
|
2019-10-17 21:54:05 +02:00
|
|
|
<!-- 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>
|
2019-10-16 23:27:21 +02:00
|
|
|
<nav class="navbar main-theme is-fixed-top" role="navigation" aria-label="main navigation"
|
|
|
|
v-if="user.authenticated && user.infos.type === authTypes.USER">
|
|
|
|
<div class="navbar-brand">
|
|
|
|
<router-link :to="{name: 'home'}" class="navbar-item logo">
|
|
|
|
<img src="/images/logo-full.svg" alt="Vikunja"/>
|
|
|
|
</router-link>
|
|
|
|
</div>
|
|
|
|
<div class="navbar-end">
|
|
|
|
<div class="user">
|
|
|
|
<img :src="gravatar()" class="avatar" alt=""/>
|
|
|
|
<div class="dropdown is-right is-active">
|
|
|
|
<div class="dropdown-trigger">
|
|
|
|
<button class="button noshadow" @click="userMenuActive = !userMenuActive">
|
|
|
|
<span class="username">{{user.infos.username}}</span>
|
|
|
|
<span class="icon is-small">
|
2019-04-23 21:50:37 +02:00
|
|
|
<icon icon="chevron-down"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<transition name="fade">
|
|
|
|
<div class="dropdown-menu" v-if="userMenuActive">
|
|
|
|
<div class="dropdown-content">
|
|
|
|
<a @click="logout()" class="dropdown-item">
|
|
|
|
Logout
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</transition>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
<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 = false" class="mobilemenu-hide-button" v-if="mobileMenuActive">
|
|
|
|
<icon icon="times"></icon>
|
|
|
|
</a>
|
|
|
|
<div class="app-container">
|
|
|
|
<div class="namespace-container" :class="{'is-active': mobileMenuActive}">
|
|
|
|
<div class="menu top-menu">
|
|
|
|
<ul class="menu-list">
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: 'home'}">
|
2018-12-25 16:03:51 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="calendar"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
Overview
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: 'showTasksInRange', params: {type: 'month'}}">
|
2018-12-25 23:41:55 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon :icon="['far', 'calendar-alt']"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
Next Month
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: 'showTasksInRange', params: {type: 'week'}}">
|
2018-12-25 23:41:55 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="calendar-week"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
Next Week
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: 'listTeams'}">
|
2018-12-25 16:03:51 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="users"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
Teams
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: 'newNamespace'}">
|
2018-12-25 16:03:51 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="layer-group"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
New Namespace
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: 'listLabels'}">
|
2019-03-07 20:48:40 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="tags"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
Labels
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
<aside class="menu namespaces-lists">
|
|
|
|
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
|
|
|
|
<template v-for="n in namespaces">
|
|
|
|
<div :key="n.id">
|
|
|
|
<router-link v-tooltip.right="'Settings'"
|
|
|
|
:to="{name: 'editNamespace', params: {id: n.id} }" class="nsettings"
|
|
|
|
v-if="n.id > 0">
|
2018-12-25 16:03:51 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="cog"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
</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">
|
2018-12-25 16:03:51 +01:00
|
|
|
<span class="icon">
|
|
|
|
<icon icon="plus"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
</router-link>
|
2019-10-19 17:56:47 +02:00
|
|
|
<label class="menu-label" v-tooltip="n.name + ' (' + n.lists.length + ')'" :for="n.id + 'checker'">
|
|
|
|
{{n.name}} ({{n.lists.length}})
|
|
|
|
</label>
|
|
|
|
</div>
|
|
|
|
<input :key="n.id + 'checker'" type="checkbox" checked="checked" :id="n.id + 'checker'" class="checkinput"/>
|
|
|
|
<div class="more-container" :key="n.id + 'child'">
|
|
|
|
<ul class="menu-list can-be-hidden" >
|
|
|
|
<li v-for="l in n.lists" :key="l.id">
|
|
|
|
<router-link :to="{ name: 'showList', params: { id: l.id} }">{{l.title}}
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
<label class="hidden-hint" :for="n.id + 'checker'">
|
|
|
|
Show hidden lists ({{n.lists.length}})...
|
|
|
|
</label>
|
2019-10-16 23:27:21 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</aside>
|
|
|
|
</div>
|
|
|
|
<div class="app-content" :class="{'fullpage-overlay': fullpage}">
|
|
|
|
<a class="mobile-overlay" v-if="mobileMenuActive" @click="mobileMenuActive = false"></a>
|
|
|
|
<transition name="fade">
|
|
|
|
<router-view/>
|
|
|
|
</transition>
|
|
|
|
</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 v-else-if="user.authenticated && user.infos.type === authTypes.LINK_SHARE">
|
|
|
|
<div class="container has-text-centered link-share-view">
|
|
|
|
<div class="column is-10 is-offset-1">
|
|
|
|
<img src="/images/logo-full.svg" alt="Vikunja" class="logo"/>
|
|
|
|
<div class="box has-text-left">
|
|
|
|
<div class="logout">
|
|
|
|
<a @click="logout()" class="button logout">
|
|
|
|
<span>Logout</span>
|
|
|
|
<span class="icon is-small">
|
2019-09-09 19:55:43 +02:00
|
|
|
<icon icon="sign-out-alt"/>
|
|
|
|
</span>
|
2019-10-16 23:27:21 +02:00
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
<router-view/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-else>
|
2019-10-19 16:27:56 +02:00
|
|
|
<div class="container">
|
2019-10-16 23:27:21 +02:00
|
|
|
<div class="column is-4 is-offset-4">
|
|
|
|
<img src="/images/logo-full.svg" alt="Vikunja"/>
|
2019-09-09 19:55:43 +02:00
|
|
|
<router-view/>
|
2019-10-16 23:27:21 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<notifications position="bottom left"/>
|
|
|
|
</div>
|
|
|
|
<div class="app offline" v-else>
|
|
|
|
<div class="offline-message">
|
|
|
|
<h1>You are offline.</h1>
|
|
|
|
<p>Please check your network connection and try again.</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2018-08-28 22:50:22 +02:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2019-03-02 11:25:10 +01:00
|
|
|
import auth from './auth'
|
2018-09-08 21:43:16 +02:00
|
|
|
import message from './message'
|
2019-03-02 11:25:10 +01:00
|
|
|
import router from './router'
|
2019-09-09 19:55:43 +02:00
|
|
|
|
2019-03-02 11:25:10 +01:00
|
|
|
import NamespaceService from './services/namespace'
|
2019-09-09 19:55:43 +02:00
|
|
|
import authTypes from './models/authTypes'
|
2018-09-07 08:42:17 +02:00
|
|
|
|
2019-03-02 11:25:10 +01:00
|
|
|
export default {
|
|
|
|
name: 'app',
|
2018-09-07 08:42:17 +02:00
|
|
|
|
2019-03-02 11:25:10 +01:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
user: auth.user,
|
|
|
|
namespaces: [],
|
2019-03-03 13:50:06 +01:00
|
|
|
namespaceService: NamespaceService,
|
2018-12-25 16:03:51 +01:00
|
|
|
mobileMenuActive: false,
|
|
|
|
fullpage: false,
|
2018-12-25 23:41:55 +01:00
|
|
|
currentDate: new Date(),
|
2019-04-23 21:50:37 +02:00
|
|
|
userMenuActive: false,
|
2019-09-09 19:55:43 +02:00
|
|
|
authTypes: authTypes,
|
2019-10-16 23:27:21 +02:00
|
|
|
isOnline: true,
|
2019-03-02 11:25:10 +01:00
|
|
|
}
|
|
|
|
},
|
2018-11-01 22:34:29 +01:00
|
|
|
beforeMount() {
|
2019-10-16 23:27:21 +02:00
|
|
|
// 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);
|
|
|
|
|
2019-03-02 11:25:10 +01:00
|
|
|
// Password reset
|
2019-10-16 23:27:21 +02:00
|
|
|
if (this.$route.query.userPasswordReset !== undefined) {
|
2018-11-01 22:34:29 +01:00
|
|
|
localStorage.removeItem('passwordResetToken') // Delete an eventually preexisting old token
|
|
|
|
localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset)
|
2019-03-02 11:25:10 +01:00
|
|
|
router.push({name: 'passwordReset'})
|
2018-11-01 22:34:29 +01:00
|
|
|
}
|
2019-03-02 11:25:10 +01:00
|
|
|
// Email verification
|
2019-10-16 23:27:21 +02:00
|
|
|
if (this.$route.query.userEmailConfirm !== undefined) {
|
2019-03-02 11:25:10 +01:00
|
|
|
localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token
|
|
|
|
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
|
|
|
|
router.push({name: 'login'})
|
2018-11-01 23:58:59 +01:00
|
|
|
}
|
2018-11-01 22:34:29 +01:00
|
|
|
},
|
2019-03-02 11:25:10 +01:00
|
|
|
created() {
|
2019-09-09 19:59:58 +02:00
|
|
|
if (auth.user.authenticated && auth.user.infos.type === authTypes.USER && (this.$route.params.name === 'home' || this.namespaces.length === 0)) {
|
2019-03-02 11:25:10 +01:00
|
|
|
this.loadNamespaces()
|
2018-09-08 21:24:10 +02:00
|
|
|
}
|
2019-03-02 11:25:10 +01:00
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
// call the method again if the route changes
|
|
|
|
'$route': 'doStuffAfterRoute'
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
logout() {
|
|
|
|
auth.logout()
|
|
|
|
},
|
2018-09-09 17:23:06 +02:00
|
|
|
gravatar() {
|
2019-03-02 11:25:10 +01:00
|
|
|
return 'https://www.gravatar.com/avatar/' + this.user.infos.avatar + '?s=50'
|
|
|
|
},
|
|
|
|
loadNamespaces() {
|
2019-03-07 20:48:55 +01:00
|
|
|
this.namespaceService = new NamespaceService()
|
2019-03-03 13:50:06 +01:00
|
|
|
this.namespaceService.getAll()
|
2019-03-02 11:25:10 +01:00
|
|
|
.then(r => {
|
|
|
|
this.$set(this, 'namespaces', r)
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
message.error(e, this)
|
|
|
|
})
|
2018-09-09 17:23:06 +02:00
|
|
|
},
|
2019-10-16 23:27:21 +02:00
|
|
|
loadNamespacesIfNeeded(e) {
|
2019-09-09 19:59:58 +02:00
|
|
|
if (auth.user.authenticated && auth.user.infos.type === authTypes.USER && (e.name === 'home' || this.namespaces.length === 0)) {
|
2019-03-02 11:25:10 +01:00
|
|
|
this.loadNamespaces()
|
|
|
|
}
|
2018-09-09 16:17:56 +02:00
|
|
|
},
|
2018-12-25 16:03:51 +01:00
|
|
|
doStuffAfterRoute(e) {
|
|
|
|
this.fullpage = false;
|
|
|
|
this.loadNamespacesIfNeeded(e)
|
|
|
|
this.mobileMenuActive = false
|
|
|
|
},
|
|
|
|
setFullPage() {
|
|
|
|
this.fullpage = true;
|
|
|
|
},
|
2019-03-02 11:25:10 +01:00
|
|
|
},
|
|
|
|
}
|
2018-08-28 22:50:22 +02:00
|
|
|
</script>
|