vikunja-frontend/src/App.vue

360 lines
11 KiB
Vue
Raw Normal View History

2018-08-28 22:50:22 +02:00
<template>
2019-11-03 13:44:40 +01:00
<div>
<div v-if="isOnline">
<!-- 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>
<nav class="navbar main-theme is-fixed-top" role="navigation" aria-label="main navigation"
v-if="user.authenticated && (userInfo && userInfo.type === authTypes.USER)">
2019-11-03 13:44:40 +01:00
<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 v-if="updateAvailable" class="update-notification">
<p>There is an update for Vikunja available!</p>
<a @click="refreshApp()" class="button is-primary noshadow">Update Now</a>
</div>
2019-11-03 13:44:40 +01:00
<div class="user">
<img :src="user.infos.getAvatarUrl()" class="avatar" alt=""/>
2019-11-03 13:44:40 +01:00
<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-11-03 13:44:40 +01:00
</button>
</div>
<transition name="fade">
<div class="dropdown-menu" v-if="userMenuActive">
<div class="dropdown-content">
<router-link :to="{name: 'userSettings'}" class="dropdown-item">
Settings
</router-link>
2019-11-03 13:44:40 +01:00
<a @click="logout()" class="dropdown-item">
Logout
</a>
</div>
</div>
</transition>
</div>
</div>
</div>
</nav>
<div v-if="user.authenticated && (userInfo && userInfo.type === authTypes.USER)">
2019-11-03 13:44:40 +01:00
<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">
<router-link :to="{name: 'home'}" class="logo">
<img src="/images/logo-full.svg" alt="Vikunja"/>
</router-link>
2019-11-03 13:44:40 +01:00
<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-11-03 13:44:40 +01:00
Overview
</router-link>
</li>
<li>
<router-link :to="{ name: 'showTasksInRange', params: {type: 'week'}}">
<span class="icon">
<icon icon="calendar-week"/>
</span>
Next Week
2019-11-03 13:44:40 +01:00
</router-link>
</li>
<li>
<router-link :to="{ name: 'showTasksInRange', params: {type: 'month'}}">
<span class="icon">
<icon :icon="['far', 'calendar-alt']"/>
</span>
Next Month
2019-11-03 13:44:40 +01:00
</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-11-03 13:44:40 +01: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-11-03 13:44:40 +01: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-11-03 13:44:40 +01:00
Labels
</router-link>
</li>
</ul>
</div>
<aside class="menu namespaces-lists">
<fancycheckbox v-model="showArchived" @change="loadNamespaces()" class="show-archived-check">
Show Archived
</fancycheckbox>
2019-11-03 13:44:40 +01:00
<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-11-03 13:44:40 +01: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-11-03 13:44:40 +01:00
</router-link>
<label class="menu-label" v-tooltip="n.name + ' (' + n.lists.length + ')'" :for="n.id + 'checker'">
<span class="name">
<span class="color-bubble" v-if="n.hexColor !== ''" :style="{ backgroundColor: n.hexColor }"></span>
{{n.name}} ({{n.lists.length}})
</span>
<span class="is-archived" v-if="n.isArchived">
Archived
</span>
2019-11-03 13:44:40 +01:00
</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: 'list.index', params: { listId: l.id} }">
<span class="name">
<span class="color-bubble" v-if="l.hexColor !== ''" :style="{ backgroundColor: l.hexColor }"></span>
{{l.title}}
</span>
<span class="is-archived" v-if="l.isArchived">
Archived
</span>
2019-11-03 13:44:40 +01:00
</router-link>
</li>
</ul>
<label class="hidden-hint" :for="n.id + 'checker'">
Show hidden lists ({{n.lists.length}})...
</label>
</div>
</template>
</aside>
<a class="menu-bottom-link" target="_blank" href="https://vikunja.io">Powered by Vikunja</a>
2019-11-03 13:44:40 +01:00
</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 && (userInfo && userInfo.type === authTypes.LINK_SHARE)">
2019-11-03 13:44:40 +01:00
<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-11-03 13:44:40 +01:00
</a>
</div>
<router-view/>
</div>
</div>
</div>
</div>
<div v-else>
<div class="container">
<div class="column is-4 is-offset-4">
<img src="/images/logo-full.svg" alt="Vikunja"/>
2019-12-25 17:38:49 +01:00
<div class="message is-info" v-if="motd !== ''">
<div class="message-header">
<p>Info</p>
</div>
<div class="message-body">
{{ motd }}
</div>
</div>
2019-11-03 13:44:40 +01:00
<router-view/>
</div>
</div>
</div>
<notification/>
2019-11-03 13:44:40 +01:00
</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>
import auth from './auth'
import router from './router'
2019-09-09 19:55:43 +02: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
import swEvents from './ServiceWorker/events'
import Notification from './components/global/notification'
import Fancycheckbox from './components/global/fancycheckbox'
export default {
name: 'app',
components: {
Fancycheckbox,
Notification,
},
data() {
return {
user: auth.user,
namespaces: [],
namespaceService: NamespaceService,
2018-12-25 16:03:51 +01:00
mobileMenuActive: false,
fullpage: false,
currentDate: new Date(),
2019-04-23 21:50:37 +02:00
userMenuActive: false,
2019-09-09 19:55:43 +02:00
authTypes: authTypes,
isOnline: true,
2019-12-25 17:38:49 +01:00
motd: '',
showArchived: false,
// Service Worker stuff
updateAvailable: false,
registration: null,
refreshing: false,
}
},
2018-11-01 22:34:29 +01:00
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
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)
router.push({name: 'passwordReset'})
2018-11-01 22:34:29 +01:00
}
// Email verification
if (this.$route.query.userEmailConfirm !== undefined) {
localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
router.push({name: 'login'})
}
2018-11-01 22:34:29 +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)) {
this.loadNamespaces()
}
// Service worker communication
document.addEventListener(swEvents.SW_UPDATED, this.showRefreshUI, { once: true })
navigator.serviceWorker.addEventListener(
'controllerchange', () => {
if (this.refreshing) return;
this.refreshing = true;
window.location.reload();
}
);
2019-12-19 21:50:07 +01:00
2020-04-04 18:29:36 +02:00
// Schedule a token renew every minute
2019-12-19 21:50:07 +01:00
setTimeout(() => {
auth.renewToken()
2020-04-04 18:29:36 +02:00
}, 1000 * 60)
2019-12-25 17:38:49 +01:00
// Set the motd
this.setMotd()
},
watch: {
// call the method again if the route changes
Kanban (#118) Add error message when trying to create an invalid new task in a bucket Prevent creation of new buckets if the bucket title is empty Disable deleting a bucket if it's the last one Disable dragging tasks when they are being updated Fix transition when opening tasks Send the user to list view by default Show loading spinner when updating multiple tasks Add loading spinner when moving tasks Add loading animation when bucket is loading / updating etc Add bucket title edit Fix creating new buckets Add loading animation Add removing buckets Fix creating a new bucket after tasks were moved Fix warning about labels on tasks Fix labels on tasks not updating after retrieval from api Fix property width Add closing and mobile design Make the task detail popup look good Move list views Move task detail view in a popup Add link to tasks Add saving the new task position after it was moved Fix creating new bucket Fix creating a new task Cleanup Disable user selection for task cards Fix drag placeholder Add dragging style to task Add placeholder + change animation duration More cleanup Cleanup / docs Working of dragging and dropping tasks Adjust markup and styling for new library Change kanban library to something that works Add basic calculation of new positions Don't try to create empty tasks Add indicator if a task is done Add moving tasks between buckets Make empty buckets a little smaller Add gimmick for button description Fix color Fix scrolling bucket layout Add creating a new bucket Add hiding the task input field Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/118
2020-04-26 01:11:34 +02:00
'$route': 'doStuffAfterRoute',
},
computed: {
userInfo() {
return auth.getUserInfos()
}
},
methods: {
logout() {
auth.logout()
},
loadNamespaces() {
this.namespaceService = new NamespaceService()
this.namespaceService.getAll({}, {isArchived: this.showArchived})
.then(r => {
this.$set(this, 'namespaces', r)
})
.catch(e => {
this.error(e, this)
})
2018-09-09 17:23:06 +02:00
},
loadNamespacesIfNeeded(e) {
if (auth.user.authenticated && (this.userInfo && this.userInfo.type === authTypes.USER) && (e.name === 'home' || this.namespaces.length === 0)) {
this.loadNamespaces()
}
},
2018-12-25 16:03:51 +01:00
doStuffAfterRoute(e) {
this.fullpage = false;
this.loadNamespacesIfNeeded(e)
this.mobileMenuActive = false
this.userMenuActive = false
2018-12-25 16:03:51 +01:00
},
setFullPage() {
this.fullpage = true;
},
showRefreshUI (e) {
console.log('recieved refresh event', e)
this.registration = e.detail;
this.updateAvailable = true;
},
refreshApp () {
this.updateExists = false;
if (!this.registration || !this.registration.waiting) { return; }
// Notify the service worker to actually do the update
this.registration.waiting.postMessage('skipWaiting');
},
2019-12-25 17:38:49 +01:00
setMotd() {
let cancel = () => {};
// Since the config may not be initialized when we're calling this, we need to retry until it is ready.
if (typeof this.$config === 'undefined') {
cancel = setTimeout(() => {
this.setMotd()
}, 150)
} else {
cancel()
this.motd = this.$config.motd
}
},
},
}
2018-08-28 22:50:22 +02:00
</script>