Refactor main navigation for clearer separation of concerns
Split up the MainNavigationBar component into a separate HeaderBar which decides which header to display, a separate MobileMenuButton that handles the menu and its toggling, and also move some computed attributes to a composable.
This commit is contained in:
parent
d4a941ab27
commit
ebdd175082
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="flex min-h-full flex-col">
|
||||
<MainNavigationBar v-if="!route.meta.hideChrome" class="flex-none" />
|
||||
<HeaderBar v-if="!route.meta.hideChrome" class="flex-none" />
|
||||
<CloseButton v-if="route.meta.showCloseButton" class="flex-none" />
|
||||
<RouterView v-slot="{ Component }" class="flex-auto">
|
||||
<Transition mode="out-in" name="app">
|
||||
|
|
@ -15,13 +15,14 @@
|
|||
import log from "loglevel";
|
||||
|
||||
import AppFooter from "@/components/AppFooter.vue";
|
||||
import MainNavigationBar from "@/components/header/MainNavigationBar.vue";
|
||||
// import MainNavigationBar from "@/components/header/MainNavigationBar.vue";
|
||||
import { graphqlClient } from "@/graphql/client";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { provideClient } from "@urql/vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import CloseButton from "./components/header/CloseButton.vue";
|
||||
import HeaderBar from "./components/header/HeaderBar.vue";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
const { t } = useTranslation();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hidden flex-shrink-0 items-center lg:flex">
|
||||
<div class="flex items-center">
|
||||
<router-link to="/" class="flex">
|
||||
<it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" />
|
||||
</router-link>
|
||||
<router-link to="/" class="flex">
|
||||
<div class="ml-1 border-l border-white pl-3 pr-10 text-2xl text-white">
|
||||
{{ t("general.title") }}
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import MainNavigationBar from "./MainNavigationBar.vue";
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||
<template v-else>
|
||||
<MainNavigationBar />
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
- nav
|
||||
- default
|
||||
- backnav
|
||||
- cockpitnav
|
||||
- course selector
|
||||
- subnav
|
||||
|
||||
- mediathek
|
||||
- calendar
|
||||
- notifications
|
||||
- profile
|
||||
- links
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import AccountMenu from "@/components/header/AccountMenu.vue";
|
||||
import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
||||
import MobileMenu from "@/components/header/MobileMenu.vue";
|
||||
import NotificationPopover from "@/components/notifications/NotificationPopover.vue";
|
||||
import NotificationPopoverContent from "@/components/notifications/NotificationPopoverContent.vue";
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
|
|
@ -9,6 +7,7 @@ import { getLoginURL } from "@/router/utils";
|
|||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useNotificationsStore } from "@/stores/notifications";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useNavigationAttributes } from "@/utils/navigation";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import {
|
||||
getCockpitUrl,
|
||||
|
|
@ -22,6 +21,8 @@ import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
|||
import { useTranslation } from "i18next-vue";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import DefaultNavigation from "./DefaultNavigation.vue";
|
||||
import MobileMenuButton from "./MobileMenuButton.vue";
|
||||
|
||||
log.debug("MainNavigationBar created");
|
||||
|
||||
|
|
@ -33,11 +34,21 @@ const {
|
|||
inCockpit,
|
||||
inCompetenceProfile,
|
||||
inLearningMentor,
|
||||
inCourse,
|
||||
inLearningPath,
|
||||
inMediaLibrary,
|
||||
inAppointments,
|
||||
} = useRouteLookups();
|
||||
const {
|
||||
hasCompetenceNaviMenu,
|
||||
hasLearningPathMenu,
|
||||
hasMediaLibraryMenu,
|
||||
hasCockpitMenu,
|
||||
hasPreviewMenu,
|
||||
hasAppointmentsMenu,
|
||||
hasNotificationsMenu,
|
||||
hasLearningMentor,
|
||||
hasSessionTitle,
|
||||
} = useNavigationAttributes();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const state = reactive({
|
||||
|
|
@ -69,100 +80,14 @@ onMounted(() => {
|
|||
log.debug("MainNavigationBar mounted");
|
||||
});
|
||||
|
||||
const hasLearningPathMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("learning-path") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasCompetenceNaviMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("competence-navi") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasMediaLibraryMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("media-library") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasCockpitMenu = computed(
|
||||
() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("expert-cockpit")
|
||||
) && inCourse()
|
||||
);
|
||||
|
||||
const hasPreviewMenu = computed(
|
||||
() =>
|
||||
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("preview")) &&
|
||||
inCourse()
|
||||
);
|
||||
|
||||
const hasAppointmentsMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
||||
userStore.loggedIn &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasNotificationsMenu = computed(() => {
|
||||
return userStore.loggedIn;
|
||||
});
|
||||
|
||||
const hasLearningMentor = computed(() => {
|
||||
if (!inCourse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const courseSession = courseSessionsStore.currentCourseSession;
|
||||
return courseSession.actions.includes("learning-mentor");
|
||||
});
|
||||
|
||||
const mentorTabTitle = computed(() =>
|
||||
courseSessionsStore.currentCourseSession?.course.configuration.is_uk
|
||||
? "a.Praxisbildner"
|
||||
: "a.Lernbegleitung"
|
||||
);
|
||||
|
||||
const hasSessionTitle = computed(() => {
|
||||
return courseSessionsStore.currentCourseSession?.title && inCourse();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||
<div v-else>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
v-if="userStore.loggedIn"
|
||||
:show="state.showMobileNavigationMenu"
|
||||
:course-session="courseSessionsStore.currentCourseSession"
|
||||
:has-media-library-menu="hasMediaLibraryMenu"
|
||||
:has-cockpit-menu="hasCockpitMenu"
|
||||
:has-preview-menu="hasPreviewMenu"
|
||||
:has-learning-path-menu="hasLearningPathMenu"
|
||||
:has-competence-navi-menu="hasCompetenceNaviMenu"
|
||||
:has-learning-mentor="hasLearningMentor"
|
||||
:has-notifications-menu="hasNotificationsMenu"
|
||||
:has-appointments-menu="hasAppointmentsMenu"
|
||||
:media-url="
|
||||
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
||||
"
|
||||
:user="userStore"
|
||||
@closemodal="state.showMobileNavigationMenu = false"
|
||||
@logout="userStore.handleLogout()"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport to="body">
|
||||
<ItFullScreenModal
|
||||
v-if="userStore.loggedIn"
|
||||
|
|
@ -172,41 +97,12 @@ const hasSessionTitle = computed(() => {
|
|||
<AccountMenu @close="state.showMobileProfileMenu = false" />
|
||||
</ItFullScreenModal>
|
||||
</Teleport>
|
||||
<Transition name="nav">
|
||||
<nav class="bg-blue-900 text-white">
|
||||
<div class="mx-auto px-4 lg:px-8">
|
||||
<div class="relative flex h-16 justify-between">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center lg:hidden">
|
||||
<!-- Mobile menu button -->
|
||||
<div
|
||||
data-cy="navigation-mobile-menu-button"
|
||||
class="flex"
|
||||
@click="state.showMobileNavigationMenu = true"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="h-8 w-8 text-white hover:text-sky-500 focus:text-sky-500 focus:outline-none"
|
||||
>
|
||||
<it-icon-menu class="h-8 w-8" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MobileMenuButton />
|
||||
<div class="flex flex-1 items-stretch justify-start">
|
||||
<div class="hidden flex-shrink-0 items-center lg:flex">
|
||||
<div class="flex items-center">
|
||||
<router-link to="/" class="flex">
|
||||
<it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" />
|
||||
</router-link>
|
||||
<router-link to="/" class="flex">
|
||||
<div
|
||||
class="ml-1 border-l border-white pl-3 pr-10 text-2xl text-white"
|
||||
>
|
||||
{{ t("general.title") }}
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<DefaultNavigation />
|
||||
|
||||
<!-- Satisfy the type checker; these menu items are
|
||||
only relevant if there is a current course session -->
|
||||
|
|
@ -216,9 +112,7 @@ const hasSessionTitle = computed(() => {
|
|||
v-if="hasCockpitMenu"
|
||||
data-cy="navigation-cockpit-link"
|
||||
:to="
|
||||
getCockpitUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
getCockpitUrl(courseSessionsStore.currentCourseSession.course.slug)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCockpit() }"
|
||||
|
|
@ -291,9 +185,7 @@ const hasSessionTitle = computed(() => {
|
|||
<router-link
|
||||
v-if="hasMediaLibraryMenu"
|
||||
:to="
|
||||
getMediaCenterUrl(
|
||||
courseSessionsStore.currentCourseSession?.course.slug
|
||||
)
|
||||
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course.slug)
|
||||
"
|
||||
data-cy="medialibrary-link"
|
||||
class="nav-item-no-mobile"
|
||||
|
|
@ -372,17 +264,13 @@ const hasSessionTitle = computed(() => {
|
|||
</Popover>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a class="" :href="getLoginURL({ lang: userStore.language })">
|
||||
Login
|
||||
</a>
|
||||
<a class="" :href="getLoginURL({ lang: userStore.language })">Login</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useNavigationAttributes } from "@/utils/navigation";
|
||||
import { getMediaCenterUrl } from "@/utils/utils";
|
||||
import { ref } from "vue";
|
||||
import MobileMenu from "./MobileMenu.vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
const {
|
||||
hasCompetenceNaviMenu,
|
||||
hasLearningPathMenu,
|
||||
hasMediaLibraryMenu,
|
||||
hasCockpitMenu,
|
||||
hasPreviewMenu,
|
||||
hasAppointmentsMenu,
|
||||
hasNotificationsMenu,
|
||||
hasLearningMentor,
|
||||
} = useNavigationAttributes();
|
||||
|
||||
const showMenu = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
v-if="userStore.loggedIn"
|
||||
:show="showMenu"
|
||||
:course-session="courseSessionsStore.currentCourseSession"
|
||||
:has-media-library-menu="hasMediaLibraryMenu"
|
||||
:has-cockpit-menu="hasCockpitMenu"
|
||||
:has-preview-menu="hasPreviewMenu"
|
||||
:has-learning-path-menu="hasLearningPathMenu"
|
||||
:has-competence-navi-menu="hasCompetenceNaviMenu"
|
||||
:has-learning-mentor="hasLearningMentor"
|
||||
:has-notifications-menu="hasNotificationsMenu"
|
||||
:has-appointments-menu="hasAppointmentsMenu"
|
||||
:media-url="
|
||||
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
||||
"
|
||||
:user="userStore"
|
||||
@closemodal="showMenu = false"
|
||||
@logout="userStore.handleLogout()"
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="absolute inset-y-0 left-0 flex items-center lg:hidden">
|
||||
<!-- Mobile menu button -->
|
||||
<div data-cy="navigation-mobile-menu-button" class="flex" @click="showMenu = true">
|
||||
<button
|
||||
type="button"
|
||||
class="h-8 w-8 text-white hover:text-sky-500 focus:text-sky-500 focus:outline-none"
|
||||
>
|
||||
<it-icon-menu class="h-8 w-8" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { computed } from "vue";
|
||||
import { useRouteLookups } from "./route";
|
||||
|
||||
export function useNavigationAttributes() {
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const userStore = useUserStore();
|
||||
const { inCourse } = useRouteLookups();
|
||||
|
||||
const hasCompetenceNaviMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("competence-navi") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
const hasLearningPathMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("learning-path") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
const hasMediaLibraryMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("media-library") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
const hasCockpitMenu = computed(
|
||||
() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("expert-cockpit")
|
||||
) && inCourse()
|
||||
);
|
||||
const hasPreviewMenu = computed(
|
||||
() =>
|
||||
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("preview")) &&
|
||||
inCourse()
|
||||
);
|
||||
|
||||
const hasAppointmentsMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
||||
userStore.loggedIn &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasNotificationsMenu = computed(() => {
|
||||
return userStore.loggedIn;
|
||||
});
|
||||
const hasLearningMentor = computed(() => {
|
||||
if (!inCourse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const courseSession = courseSessionsStore.currentCourseSession;
|
||||
return courseSession.actions.includes("learning-mentor");
|
||||
});
|
||||
const hasSessionTitle = computed(() => {
|
||||
return courseSessionsStore.currentCourseSession?.title && inCourse();
|
||||
});
|
||||
|
||||
return {
|
||||
hasCompetenceNaviMenu,
|
||||
hasLearningPathMenu,
|
||||
hasMediaLibraryMenu,
|
||||
hasCockpitMenu,
|
||||
hasPreviewMenu,
|
||||
hasAppointmentsMenu,
|
||||
hasNotificationsMenu,
|
||||
hasLearningMentor,
|
||||
hasSessionTitle,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue