Move top navigation bar to separate component

This commit is contained in:
kolaente 2020-11-01 16:49:24 +01:00
parent 237a914dee
commit 3877218980
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
6 changed files with 187 additions and 126 deletions

View File

@ -3,89 +3,9 @@
<div v-if="online">
<!-- 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="{'has-background': background}"
aria-label="main navigation"
class="navbar main-theme is-fixed-top"
role="navigation"
v-if="userAuthenticated && (userInfo && userInfo.type === authTypes.USER)">
<div class="navbar-brand">
<router-link :to="{name: 'home'}" class="navbar-item logo">
<img alt="Vikunja" src="/images/logo-full-pride.svg" v-if="(new Date()).getMonth() === 5"/>
<img alt="Vikunja" src="/images/logo-full.svg" v-else/>
</router-link>
<a
@click="menuActive = !menuActive"
class="menu-show-button"
@shortkey="() => menuActive = !menuActive"
v-shortkey="['ctrl', 'e']"
>
<icon icon="bars"></icon>
</a>
</div>
<a
@click="menuActive = true"
class="menu-show-button"
>
<icon icon="bars"></icon>
</a>
<div class="list-title" v-if="currentList.id">
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? 'Loading...' : currentList.title }}
</h1>
<router-link
:to="{ name: 'list.edit', params: { id: currentList.id } }"
class="icon"
v-if="canWriteCurrentList">
<icon icon="cog" size="2x"/>
</router-link>
</div>
<div class="navbar-end">
<div class="update-notification" v-if="updateAvailable">
<p>There is an update for Vikunja available!</p>
<a @click="refreshApp()" class="button is-primary noshadow">Update Now</a>
</div>
<div class="user">
<img :src="userAvatar" alt="" class="avatar"/>
<div class="dropdown is-right is-active">
<div class="dropdown-trigger">
<button @click.stop="userMenuActive = !userMenuActive" class="button noshadow">
<span class="username">{{ userInfo.username }}</span>
<span class="icon is-small">
<icon icon="chevron-down"/>
</span>
</button>
</div>
<transition name="fade">
<div class="dropdown-menu" v-if="userMenuActive">
<div class="dropdown-content">
<router-link :to="{name: 'user.settings'}" class="dropdown-item">
Settings
</router-link>
<a :href="imprintUrl" class="dropdown-item" target="_blank" v-if="imprintUrl">Imprint</a>
<a
:href="privacyPolicyUrl"
class="dropdown-item"
target="_blank"
v-if="privacyPolicyUrl">
Privacy policy
</a>
<a @click="keyboardShortcutsActive = true" class="dropdown-item">Keyboard
Shortcuts</a>
<a @click="logout()" class="dropdown-item">
Logout
</a>
</div>
</div>
</transition>
</div>
</div>
</div>
</nav>
<top-navigation v-if="userAuthenticated && (userInfo && userInfo.type === authTypes.USER)"/>
<div v-if="userAuthenticated && (userInfo && userInfo.type === authTypes.USER)">
<a @click="menuActive = false" class="menu-hide-button" v-if="menuActive">
<a @click="$store.commit('menuActive', false)" class="menu-hide-button" v-if="menuActive">
<icon icon="times"></icon>
</a>
<div
@ -241,7 +161,7 @@
]"
class="app-content"
>
<a @click="menuActive = false" class="mobile-overlay" v-if="menuActive"></a>
<a @click="$store.commit('menuActive', false)" class="mobile-overlay" v-if="menuActive"></a>
<transition name="fade">
<router-view/>
</transition>
@ -315,29 +235,23 @@ import {mapState} from 'vuex'
import authTypes from './models/authTypes'
import Rights from './models/rights.json'
import swEvents from './ServiceWorker/events'
import Notification from './components/misc/notification'
import {CURRENT_LIST, IS_FULLPAGE, ONLINE} from './store/mutation-types'
import {CURRENT_LIST, IS_FULLPAGE, MENU_ACTIVE, ONLINE} from './store/mutation-types'
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
import topNavigation from '@/components/home/topNavigation'
export default {
name: 'app',
components: {
topNavigation,
KeyboardShortcuts,
Notification,
},
data() {
return {
menuActive: true,
currentDate: new Date(),
userMenuActive: false,
authTypes: authTypes,
keyboardShortcutsActive: false,
// Service Worker stuff
updateAvailable: false,
registration: null,
refreshing: false,
}
},
beforeMount() {
@ -387,22 +301,9 @@ export default {
this.$router.push({name: 'home'})
}
// Service worker communication
document.addEventListener(swEvents.SW_UPDATED, this.showRefreshUI, {once: true})
if (navigator && navigator.serviceWorker) {
navigator.serviceWorker.addEventListener(
'controllerchange', () => {
if (this.refreshing) return
this.refreshing = true
window.location.reload()
},
)
}
// Hide the menu by default on mobile
if (window.innerWidth < 770) {
this.menuActive = false
this.$store.commit(MENU_ACTIVE, false)
}
// Try renewing the token every time vikunja is loaded initially
@ -432,9 +333,6 @@ export default {
console.log('renewed token')
}
})
// This will hide the menu once clicked outside of it
this.$nextTick(() => document.addEventListener('click', () => this.userMenuActive = false))
},
watch: {
// call the method again if the route changes
@ -455,6 +353,7 @@ export default {
imprintUrl: state => state.config.legal.imprintUrl,
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
menuActive: MENU_ACTIVE,
}),
methods: {
logout() {
@ -477,11 +376,10 @@ export default {
}
this.loadNamespacesIfNeeded(e)
this.userMenuActive = false
// If the menu is active on desktop, don't hide it because that would confuse the user
if (window.innerWidth < 770) {
this.menuActive = false
this.$store.commit(MENU_ACTIVE, false)
}
// Reset the current list highlight in menu if the current list is not list related.
@ -500,19 +398,6 @@ export default {
this.$store.commit(CURRENT_LIST, {})
}
},
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')
},
toggleFavoriteList(list) {
// The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite

View File

@ -0,0 +1,119 @@
<template>
<nav
:class="{'has-background': background}"
aria-label="main navigation"
class="navbar main-theme is-fixed-top"
role="navigation"
>
<div class="navbar-brand">
<router-link :to="{name: 'home'}" class="navbar-item logo">
<img alt="Vikunja" src="/images/logo-full-pride.svg" v-if="(new Date()).getMonth() === 5"/>
<img alt="Vikunja" src="/images/logo-full.svg" v-else/>
</router-link>
<a
@click="$store.commit('toggleMenu')"
class="menu-show-button"
@shortkey="() => $store.commit('toggleMenu')"
v-shortkey="['ctrl', 'e']"
>
<icon icon="bars"></icon>
</a>
</div>
<a
@click="$store.commit('toggleMenu')"
class="menu-show-button"
>
<icon icon="bars"></icon>
</a>
<div class="list-title" v-if="currentList.id">
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? 'Loading...' : currentList.title }}
</h1>
<router-link
:to="{ name: 'list.edit', params: { id: currentList.id } }"
class="icon"
v-if="canWriteCurrentList">
<icon icon="cog" size="2x"/>
</router-link>
</div>
<div class="navbar-end">
<update/>
<div class="user">
<img :src="userAvatar" alt="" class="avatar"/>
<div class="dropdown is-right is-active">
<div class="dropdown-trigger">
<button @click.stop="userMenuActive = !userMenuActive" class="button noshadow">
<span class="username">{{ userInfo.username }}</span>
<span class="icon is-small">
<icon icon="chevron-down"/>
</span>
</button>
</div>
<transition name="fade">
<div class="dropdown-menu" v-if="userMenuActive">
<div class="dropdown-content">
<router-link :to="{name: 'user.settings'}" class="dropdown-item">
Settings
</router-link>
<a
:href="imprintUrl"
class="dropdown-item"
target="_blank"
v-if="imprintUrl">
Imprint
</a>
<a
:href="privacyPolicyUrl"
class="dropdown-item"
target="_blank"
v-if="privacyPolicyUrl">
Privacy policy
</a>
<a @click="keyboardShortcutsActive = true" class="dropdown-item">Keyboard
Shortcuts</a>
<a @click="logout()" class="dropdown-item">
Logout
</a>
</div>
</div>
</transition>
</div>
</div>
</div>
</nav>
</template>
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST} from '@/store/mutation-types'
import Rights from '@/models/rights.json'
import Update from '@/components/home/update'
export default {
name: 'topNavigation',
data() {
return {
userMenuActive: false,
}
},
components: {
Update,
},
created() {
// This will hide the menu once clicked outside of it
this.$nextTick(() => document.addEventListener('click', () => this.userMenuActive = false))
},
computed: mapState({
userInfo: state => state.auth.info,
userAvatar: state => state.auth.avatarUrl,
userAuthenticated: state => state.auth.authenticated,
currentList: CURRENT_LIST,
background: 'background',
imprintUrl: state => state.config.legal.imprintUrl,
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
}),
}
</script>

View File

@ -0,0 +1,49 @@
<template>
<div class="update-notification" v-if="updateAvailable">
<p>There is an update for Vikunja available!</p>
<a @click="refreshApp()" class="button is-primary noshadow">Update Now</a>
</div>
</template>
<script>
import swEvents from '@/ServiceWorker/events.json'
export default {
name: 'update',
data() {
return {
updateAvailable: false,
registration: null,
refreshing: false,
}
},
created() {
document.addEventListener(swEvents.SW_UPDATED, this.showRefreshUI, {once: true})
if (navigator && navigator.serviceWorker) {
navigator.serviceWorker.addEventListener(
'controllerchange', () => {
if (this.refreshing) return
this.refreshing = true
window.location.reload()
},
)
}
},
methods: {
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')
},
},
}
</script>

View File

@ -44,7 +44,7 @@ export default {
params = null,
) {
// Because this function is triggered every time on navigation, we're putting a condition here to only load it when we actually want to show tasks
// Because this function is triggered every time on topNavigation, we're putting a condition here to only load it when we actually want to show tasks
// FIXME: This is a bit hacky -> Cleanup.
if (
this.$route.name !== 'list.list' &&

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import Vuex from 'vuex'
import {CURRENT_LIST, ERROR_MESSAGE, HAS_TASKS, IS_FULLPAGE, LOADING, ONLINE} from './mutation-types'
import {CURRENT_LIST, ERROR_MESSAGE, HAS_TASKS, IS_FULLPAGE, LOADING, MENU_ACTIVE, ONLINE} from './mutation-types'
import config from './modules/config'
import auth from './modules/auth'
import namespaces from './modules/namespaces'
@ -33,6 +33,7 @@ export const store = new Vuex.Store({
currentList: {id: 0},
background: '',
hasTasks: false,
menuActive: true,
},
mutations: {
[LOADING](state, loading) {
@ -97,5 +98,11 @@ export const store = new Vuex.Store({
[HAS_TASKS](state, hasTasks) {
state.hasTasks = hasTasks
},
[MENU_ACTIVE](state, menuActive) {
state.menuActive = menuActive
},
toggleMenu(state) {
state.menuActive = !state.menuActive
}
},
})

View File

@ -4,6 +4,7 @@ export const ONLINE = 'online'
export const IS_FULLPAGE = 'isFullpage'
export const CURRENT_LIST = 'currentList'
export const HAS_TASKS = 'hasTasks'
export const MENU_ACTIVE = 'menuActive'
export const CONFIG = 'config'
export const AUTH = 'auth'