Move top navigation bar to separate component
This commit is contained in:
parent
237a914dee
commit
3877218980
133
src/App.vue
133
src/App.vue
|
@ -3,89 +3,9 @@
|
||||||
<div v-if="online">
|
<div v-if="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>
|
||||||
<nav
|
<top-navigation v-if="userAuthenticated && (userInfo && userInfo.type === authTypes.USER)"/>
|
||||||
: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>
|
|
||||||
<div 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>
|
<icon icon="times"></icon>
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
|
@ -241,7 +161,7 @@
|
||||||
]"
|
]"
|
||||||
class="app-content"
|
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">
|
<transition name="fade">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -315,29 +235,23 @@ import {mapState} from 'vuex'
|
||||||
import authTypes from './models/authTypes'
|
import authTypes from './models/authTypes'
|
||||||
import Rights from './models/rights.json'
|
import Rights from './models/rights.json'
|
||||||
|
|
||||||
import swEvents from './ServiceWorker/events'
|
|
||||||
import Notification from './components/misc/notification'
|
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 KeyboardShortcuts from './components/misc/keyboard-shortcuts'
|
||||||
|
import topNavigation from '@/components/home/topNavigation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
|
topNavigation,
|
||||||
KeyboardShortcuts,
|
KeyboardShortcuts,
|
||||||
Notification,
|
Notification,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menuActive: true,
|
|
||||||
currentDate: new Date(),
|
currentDate: new Date(),
|
||||||
userMenuActive: false,
|
|
||||||
authTypes: authTypes,
|
authTypes: authTypes,
|
||||||
keyboardShortcutsActive: false,
|
keyboardShortcutsActive: false,
|
||||||
|
|
||||||
// Service Worker stuff
|
|
||||||
updateAvailable: false,
|
|
||||||
registration: null,
|
|
||||||
refreshing: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
@ -387,22 +301,9 @@ export default {
|
||||||
this.$router.push({name: 'home'})
|
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
|
// Hide the menu by default on mobile
|
||||||
if (window.innerWidth < 770) {
|
if (window.innerWidth < 770) {
|
||||||
this.menuActive = false
|
this.$store.commit(MENU_ACTIVE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try renewing the token every time vikunja is loaded initially
|
// Try renewing the token every time vikunja is loaded initially
|
||||||
|
@ -432,9 +333,6 @@ export default {
|
||||||
console.log('renewed token')
|
console.log('renewed token')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// This will hide the menu once clicked outside of it
|
|
||||||
this.$nextTick(() => document.addEventListener('click', () => this.userMenuActive = false))
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// call the method again if the route changes
|
// call the method again if the route changes
|
||||||
|
@ -455,6 +353,7 @@ export default {
|
||||||
imprintUrl: state => state.config.legal.imprintUrl,
|
imprintUrl: state => state.config.legal.imprintUrl,
|
||||||
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
|
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
|
||||||
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
|
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
|
||||||
|
menuActive: MENU_ACTIVE,
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
logout() {
|
logout() {
|
||||||
|
@ -477,11 +376,10 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadNamespacesIfNeeded(e)
|
this.loadNamespacesIfNeeded(e)
|
||||||
this.userMenuActive = false
|
|
||||||
|
|
||||||
// If the menu is active on desktop, don't hide it because that would confuse the user
|
// If the menu is active on desktop, don't hide it because that would confuse the user
|
||||||
if (window.innerWidth < 770) {
|
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.
|
// 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, {})
|
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) {
|
toggleFavoriteList(list) {
|
||||||
// The favorites pseudo list is always favorite
|
// The favorites pseudo list is always favorite
|
||||||
// Archived lists cannot be marked favorite
|
// Archived lists cannot be marked favorite
|
||||||
|
|
119
src/components/home/topNavigation.vue
Normal file
119
src/components/home/topNavigation.vue
Normal 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>
|
49
src/components/home/update.vue
Normal file
49
src/components/home/update.vue
Normal 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>
|
|
@ -44,7 +44,7 @@ export default {
|
||||||
params = null,
|
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.
|
// FIXME: This is a bit hacky -> Cleanup.
|
||||||
if (
|
if (
|
||||||
this.$route.name !== 'list.list' &&
|
this.$route.name !== 'list.list' &&
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
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 config from './modules/config'
|
||||||
import auth from './modules/auth'
|
import auth from './modules/auth'
|
||||||
import namespaces from './modules/namespaces'
|
import namespaces from './modules/namespaces'
|
||||||
|
@ -33,6 +33,7 @@ export const store = new Vuex.Store({
|
||||||
currentList: {id: 0},
|
currentList: {id: 0},
|
||||||
background: '',
|
background: '',
|
||||||
hasTasks: false,
|
hasTasks: false,
|
||||||
|
menuActive: true,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
[LOADING](state, loading) {
|
[LOADING](state, loading) {
|
||||||
|
@ -97,5 +98,11 @@ export const store = new Vuex.Store({
|
||||||
[HAS_TASKS](state, hasTasks) {
|
[HAS_TASKS](state, hasTasks) {
|
||||||
state.hasTasks = hasTasks
|
state.hasTasks = hasTasks
|
||||||
},
|
},
|
||||||
|
[MENU_ACTIVE](state, menuActive) {
|
||||||
|
state.menuActive = menuActive
|
||||||
|
},
|
||||||
|
toggleMenu(state) {
|
||||||
|
state.menuActive = !state.menuActive
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
|
@ -4,6 +4,7 @@ export const ONLINE = 'online'
|
||||||
export const IS_FULLPAGE = 'isFullpage'
|
export const IS_FULLPAGE = 'isFullpage'
|
||||||
export const CURRENT_LIST = 'currentList'
|
export const CURRENT_LIST = 'currentList'
|
||||||
export const HAS_TASKS = 'hasTasks'
|
export const HAS_TASKS = 'hasTasks'
|
||||||
|
export const MENU_ACTIVE = 'menuActive'
|
||||||
|
|
||||||
export const CONFIG = 'config'
|
export const CONFIG = 'config'
|
||||||
export const AUTH = 'auth'
|
export const AUTH = 'auth'
|
||||||
|
|
Reference in New Issue
Block a user