2020-11-01 17:36:00 +00:00
|
|
|
<template>
|
2022-01-16 17:05:12 +00:00
|
|
|
<aside :class="{'is-active': menuActive}" class="namespace-container">
|
2022-07-07 18:28:39 +00:00
|
|
|
<nav class="menu">
|
2020-11-01 17:36:00 +00:00
|
|
|
<router-link :to="{name: 'home'}" class="logo">
|
2022-01-19 06:57:45 +00:00
|
|
|
<Logo width="164" height="48"/>
|
2020-11-01 17:36:00 +00:00
|
|
|
</router-link>
|
|
|
|
<ul class="menu-list">
|
2022-07-07 18:28:39 +00:00
|
|
|
<li
|
|
|
|
v-for="({title, to, shortcut, icon, }, index) in MAIN_NAV_ITEMS"
|
|
|
|
:key="index"
|
|
|
|
>
|
|
|
|
<router-link :to="to" v-shortcut="shortcut">
|
|
|
|
<icon class="icon" :icon="icon"/>
|
|
|
|
{{ title }}
|
2020-12-23 20:28:32 +00:00
|
|
|
</router-link>
|
|
|
|
</li>
|
2020-11-01 17:36:00 +00:00
|
|
|
</ul>
|
2022-01-16 17:05:12 +00:00
|
|
|
</nav>
|
2020-11-01 17:36:00 +00:00
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
<LoadingIndicator
|
|
|
|
class="namespaces-lists"
|
|
|
|
:isLoading="loading"
|
|
|
|
element="nav"
|
|
|
|
>
|
|
|
|
<NavigationNamespace
|
|
|
|
v-for="(n, nk) in namespaces"
|
|
|
|
:key="n.id"
|
|
|
|
:namespace="n"
|
|
|
|
:title="namespaceTitles[nk]"
|
|
|
|
:showList="listsVisible[n.id] ?? true"
|
|
|
|
/>
|
|
|
|
</LoadingIndicator>
|
|
|
|
<PoweredByLink class="navigation__powered-by-link " />
|
2022-01-16 17:05:12 +00:00
|
|
|
</aside>
|
2020-11-01 17:36:00 +00:00
|
|
|
</template>
|
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
<script setup lang="ts">
|
2022-07-07 18:28:39 +00:00
|
|
|
import {ref, computed, onBeforeMount} from 'vue'
|
2022-02-13 17:11:26 +00:00
|
|
|
import {useStore} from 'vuex'
|
2022-07-07 18:28:39 +00:00
|
|
|
import {useEventListener} from '@vueuse/core'
|
|
|
|
import {useI18n} from 'vue-i18n'
|
2021-11-13 14:16:14 +00:00
|
|
|
|
|
|
|
import Logo from '@/components/home/Logo.vue'
|
2022-07-07 18:28:39 +00:00
|
|
|
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
|
|
|
import NavigationNamespace from './NavigationNamespace.vue'
|
2021-11-13 14:16:14 +00:00
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
import {MENU_ACTIVE} from '@/store/mutation-types'
|
|
|
|
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
|
|
|
import NamespaceModel from '@/models/namespace'
|
2020-11-01 17:36:00 +00:00
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
const {t} = useI18n()
|
|
|
|
|
|
|
|
const MAIN_NAV_ITEMS = [
|
|
|
|
{
|
|
|
|
title: t('navigation.overview'),
|
|
|
|
icon: 'calendar',
|
|
|
|
to: { name: 'home'},
|
|
|
|
shortcut: 'g o',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: t('navigation.upcoming'),
|
|
|
|
icon: ['far', 'calendar-alt'],
|
|
|
|
to: { name: 'tasks.range'},
|
|
|
|
shortcut: 'g u',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: t('namespace.title'),
|
|
|
|
icon: 'layer-group',
|
|
|
|
to: { name: 'namespaces.index'},
|
|
|
|
shortcut: 'g n',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: t('label.title'),
|
|
|
|
icon: 'tags',
|
|
|
|
to: { name: 'labels.index'},
|
|
|
|
shortcut: 'g a',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: t('team.title'),
|
|
|
|
icon: 'users',
|
|
|
|
to: { name: 'teams.index'},
|
|
|
|
shortcut: 'g m',
|
|
|
|
},
|
|
|
|
]
|
2021-10-03 18:48:02 +00:00
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
const store = useStore()
|
2022-07-07 18:28:39 +00:00
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
const menuActive = computed(() => store.state.menuActive)
|
|
|
|
const loading = computed(() => store.state.loading && store.state.loadingModule === 'namespaces')
|
2021-11-13 14:16:14 +00:00
|
|
|
|
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
const namespaces = computed(() => {
|
|
|
|
return (store.state.namespaces.namespaces as NamespaceModel[]).filter(n => !n.isArchived)
|
|
|
|
})
|
|
|
|
const activeLists = computed(() => {
|
|
|
|
return namespaces.value.map(({lists}) => {
|
|
|
|
return lists?.filter(item => {
|
|
|
|
return typeof item !== 'undefined' && !item.isArchived
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2022-04-02 21:09:28 +00:00
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
const namespaceTitles = computed(() => {
|
|
|
|
return namespaces.value.map((namespace) => getNamespaceTitle(namespace))
|
|
|
|
})
|
|
|
|
|
|
|
|
const namespaceListsCount = computed(() => {
|
|
|
|
return namespaces.value.map((_, index) => activeLists.value[index]?.length ?? 0)
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
function resize() {
|
|
|
|
// Hide the menu by default on mobile
|
|
|
|
store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
|
|
|
}
|
2021-07-28 19:56:29 +00:00
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
useEventListener('resize', resize)
|
|
|
|
resize()
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-02-13 17:11:26 +00:00
|
|
|
function toggleLists(namespaceId: number) {
|
|
|
|
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
|
|
|
|
}
|
2021-07-28 19:56:29 +00:00
|
|
|
|
2022-04-02 21:09:28 +00:00
|
|
|
const listsVisible = ref<{ [id: NamespaceModel['id']]: boolean }>({})
|
2022-02-13 17:11:26 +00:00
|
|
|
// FIXME: async action will be unfinished when component mounts
|
|
|
|
onBeforeMount(async () => {
|
|
|
|
const namespaces = await store.dispatch('namespaces/loadNamespaces') as NamespaceModel[]
|
|
|
|
namespaces.forEach(n => {
|
|
|
|
if (typeof listsVisible.value[n.id] === 'undefined') {
|
|
|
|
listsVisible.value[n.id] = true
|
|
|
|
}
|
|
|
|
})
|
2022-02-15 12:07:59 +00:00
|
|
|
})
|
2020-11-01 17:36:00 +00:00
|
|
|
</script>
|
2021-05-19 15:26:05 +00:00
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
<style lang="scss" scoped>
|
|
|
|
$navbar-padding: 2rem;
|
2021-11-22 21:12:54 +00:00
|
|
|
$vikunja-nav-background: var(--site-background);
|
|
|
|
$vikunja-nav-color: var(--grey-700);
|
2021-10-18 12:33:23 +00:00
|
|
|
$vikunja-nav-selected-width: 0.4rem;
|
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
.navigation {
|
2021-10-18 12:33:23 +00:00
|
|
|
position: fixed;
|
|
|
|
top: $navbar-height;
|
2021-11-02 18:20:14 +00:00
|
|
|
bottom: 0;
|
|
|
|
left: 0;
|
2022-07-07 18:28:39 +00:00
|
|
|
transition: transform $transition-duration ease-in;
|
2021-11-02 18:20:14 +00:00
|
|
|
transform: translateX(-100%);
|
2021-10-18 12:33:23 +00:00
|
|
|
overflow-x: auto;
|
|
|
|
width: $navbar-width;
|
|
|
|
|
|
|
|
@media screen and (max-width: $tablet) {
|
|
|
|
top: 0;
|
|
|
|
width: 70vw;
|
2022-04-02 21:09:28 +00:00
|
|
|
z-index: 20;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
&.is-active {
|
2021-11-02 18:20:14 +00:00
|
|
|
transform: translateX(0);
|
|
|
|
transition: transform $transition-duration ease-out;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
2022-07-07 18:28:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.navigation {
|
|
|
|
background: $vikunja-nav-background;
|
|
|
|
color: $vikunja-nav-color;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
|
|
|
|
.menu {
|
|
|
|
|
|
|
|
|
|
|
|
.menu-label,
|
|
|
|
.menu-list span.list-menu-link,
|
|
|
|
.menu-list a {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
cursor: pointer;
|
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
&:hover .favorite {
|
|
|
|
opacity: 1;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
&:hover {
|
|
|
|
background: transparent;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
2022-07-07 18:28:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.list-menu-title {
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
width: 100%;
|
|
|
|
}
|
2021-10-18 12:33:23 +00:00
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
.color-bubble {
|
|
|
|
height: 12px;
|
|
|
|
flex: 0 0 12px;
|
|
|
|
}
|
2021-10-18 12:33:23 +00:00
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
.favorite {
|
|
|
|
margin-left: .25rem;
|
|
|
|
transition: opacity $transition, color $transition;
|
|
|
|
opacity: 0;
|
2021-10-18 12:33:23 +00:00
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
&:hover {
|
|
|
|
color: var(--warning);
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
&.is-favorite {
|
2021-10-18 12:33:23 +00:00
|
|
|
opacity: 1;
|
2022-07-07 18:28:39 +00:00
|
|
|
color: var(--warning);
|
2022-05-06 20:16:54 +00:00
|
|
|
}
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.menu-label {
|
|
|
|
.color-bubble {
|
2022-01-19 06:57:45 +00:00
|
|
|
width: 14px;
|
|
|
|
height: 14px;
|
|
|
|
flex-basis: auto;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.is-archived {
|
|
|
|
min-width: 85px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.menu-list {
|
|
|
|
li {
|
|
|
|
height: 44px;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
|
2021-10-20 12:33:36 +00:00
|
|
|
&:hover {
|
2021-11-22 21:12:54 +00:00
|
|
|
background: var(--white);
|
2021-10-20 12:33:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.dropdown-trigger) {
|
2021-10-18 12:33:23 +00:00
|
|
|
opacity: 0;
|
|
|
|
padding: .5rem;
|
|
|
|
cursor: pointer;
|
|
|
|
transition: $transition;
|
|
|
|
}
|
|
|
|
|
2021-10-20 12:33:36 +00:00
|
|
|
&:hover :deep(.dropdown-trigger) {
|
|
|
|
opacity: 1;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.flip-list-move {
|
|
|
|
transition: transform $transition-duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ghost {
|
2021-11-22 21:12:54 +00:00
|
|
|
background: var(--grey-200);
|
2021-10-18 12:33:23 +00:00
|
|
|
|
|
|
|
* {
|
|
|
|
opacity: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
span.list-menu-link, li > a {
|
|
|
|
padding: 0.75rem .5rem 0.75rem ($navbar-padding * 1.5 - 1.75rem);
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
overflow: hidden;
|
|
|
|
width: 100%;
|
|
|
|
border-left: $vikunja-nav-selected-width solid transparent;
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
height: 1rem;
|
|
|
|
vertical-align: middle;
|
|
|
|
padding-right: 0.5rem;
|
2022-01-19 06:57:45 +00:00
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
&.handle {
|
|
|
|
opacity: 0;
|
|
|
|
transition: opacity $transition;
|
|
|
|
margin-right: .25rem;
|
|
|
|
cursor: grab;
|
|
|
|
}
|
|
|
|
}
|
2022-01-19 06:57:45 +00:00
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
&:hover .icon.handle {
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.router-link-exact-active {
|
2021-11-22 21:12:54 +00:00
|
|
|
color: var(--primary);
|
|
|
|
border-left: $vikunja-nav-selected-width solid var(--primary);
|
2021-10-18 12:33:23 +00:00
|
|
|
|
|
|
|
.icon {
|
2021-11-22 21:12:54 +00:00
|
|
|
color: var(--primary);
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover {
|
2021-11-22 21:12:54 +00:00
|
|
|
border-left: $vikunja-nav-selected-width solid var(--primary);
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-13 14:16:14 +00:00
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
|
|
|
|
&.namespaces-lists {
|
|
|
|
padding-top: math.div($navbar-padding, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
.icon {
|
2021-11-22 21:12:54 +00:00
|
|
|
color: var(--grey-400) !important;
|
2021-10-18 12:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-07 18:28:39 +00:00
|
|
|
.logo {
|
|
|
|
display: block;
|
|
|
|
|
|
|
|
padding-left: 2rem;
|
|
|
|
margin-right: 1rem;
|
|
|
|
|
|
|
|
@media screen and (min-width: $tablet) {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
.top-menu {
|
|
|
|
margin-top: math.div($navbar-padding, 2);
|
|
|
|
|
|
|
|
.menu-list {
|
|
|
|
li {
|
|
|
|
font-weight: 500;
|
|
|
|
font-family: $vikunja-font;
|
|
|
|
}
|
|
|
|
|
|
|
|
span.list-menu-link, li > a {
|
|
|
|
padding-left: 2rem;
|
|
|
|
display: inline-block;
|
2022-01-19 06:57:45 +00:00
|
|
|
|
2021-10-18 12:33:23 +00:00
|
|
|
.icon {
|
|
|
|
padding-bottom: .25rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 15:26:05 +00:00
|
|
|
.list-setting-spacer {
|
2022-05-06 20:07:31 +00:00
|
|
|
width: 2.5rem;
|
2021-05-19 15:26:05 +00:00
|
|
|
flex-shrink: 0;
|
|
|
|
}
|
2022-01-05 12:46:33 +00:00
|
|
|
|
|
|
|
.namespaces-list.loader-container.is-loading {
|
|
|
|
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
|
|
|
|
}
|
2022-05-06 20:05:03 +00:00
|
|
|
|
|
|
|
a.dropdown-item:hover {
|
2022-05-06 20:16:54 +00:00
|
|
|
background: var(--dropdown-item-hover-background-color) !important;
|
2022-05-06 20:05:03 +00:00
|
|
|
}
|
2022-07-07 18:28:39 +00:00
|
|
|
|
|
|
|
.navigation__powered-by-link {
|
|
|
|
margin-top: auto;
|
|
|
|
color: var(--grey-300);
|
|
|
|
text-align: center;
|
|
|
|
display: block;
|
|
|
|
padding-top: 1rem;
|
|
|
|
padding-bottom: 1rem;
|
|
|
|
font-size: .8rem;
|
|
|
|
}
|
2021-05-19 15:26:05 +00:00
|
|
|
</style>
|