567 lines
14 KiB
Vue
567 lines
14 KiB
Vue
<template>
|
||
<nav>
|
||
<ul>
|
||
<!-- Hauptmenü -->
|
||
<li
|
||
v-for="(item, key) in menu"
|
||
:key="key"
|
||
class="mainmenuitem"
|
||
@click="handleItem(item, $event)"
|
||
>
|
||
<span
|
||
v-if="item.icon"
|
||
:style="`background-image:url('/images/icons/${item.icon}')`"
|
||
class="menu-icon"
|
||
> </span>
|
||
<span>{{ $t(`navigation.${key}`) }}</span>
|
||
|
||
<!-- Untermenü Ebene 1 -->
|
||
<ul v-if="hasTopLevelSubmenu(item)" class="submenu1">
|
||
<li
|
||
v-for="(subitem, subkey) in item.children"
|
||
:key="subkey"
|
||
@click="handleItem(subitem, $event)"
|
||
>
|
||
<span
|
||
v-if="subitem.icon"
|
||
:style="`background-image:url('/images/icons/${subitem.icon}')`"
|
||
class="submenu-icon"
|
||
> </span>
|
||
<span>{{ subitem?.label || $t(`navigation.m-${key}.${subkey}`) }}</span>
|
||
<span
|
||
v-if="hasSecondLevelSubmenu(subitem, subkey)"
|
||
class="subsubmenu"
|
||
>▶</span>
|
||
|
||
<!-- Forum‑Unterliste -->
|
||
<ul
|
||
v-if="subkey === 'forum' && forumList.length"
|
||
class="submenu2"
|
||
>
|
||
<li
|
||
v-for="forum in forumList"
|
||
:key="forum.id"
|
||
@click="handleItem({ action: 'openForum', params: forum.id }, $event)"
|
||
>
|
||
{{ forum.name }}
|
||
</li>
|
||
</ul>
|
||
|
||
<!-- Vokabeltrainer-Unterliste (Sprachen) -->
|
||
<ul
|
||
v-else-if="subkey === 'vocabtrainer' && vocabLanguagesList.length"
|
||
class="submenu2"
|
||
>
|
||
<li
|
||
@click="handleItem({ path: '/socialnetwork/vocab/new' }, $event)"
|
||
>
|
||
{{ $t('navigation.m-sprachenlernen.m-vocabtrainer.newLanguage') }}
|
||
</li>
|
||
<li
|
||
v-for="lang in vocabLanguagesList"
|
||
:key="lang.id"
|
||
@click="handleItem({ path: `/socialnetwork/vocab/${lang.id}` }, $event)"
|
||
>
|
||
{{ lang.name }}
|
||
</li>
|
||
</ul>
|
||
|
||
<!-- Weiteres Untermenü Ebene 2 -->
|
||
<ul
|
||
v-else-if="subitem.children"
|
||
class="submenu2"
|
||
>
|
||
<li
|
||
v-for="(subsubitem, subsubkey) in subitem.children"
|
||
:key="subsubkey"
|
||
@click="handleItem(subsubitem, $event)"
|
||
>
|
||
<span
|
||
v-if="subsubitem.icon"
|
||
:style="`background-image:url('/images/icons/${subsubitem.icon}')`"
|
||
class="submenu-icon"
|
||
> </span>
|
||
<span>{{ subsubitem?.label || $t(`navigation.m-${key}.m-${subkey}.${subsubkey}`) }}</span>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
|
||
<!-- Eingeloggte Freunde -->
|
||
<li
|
||
v-if="item.showLoggedinFriends === 1 && friendsList.length"
|
||
v-for="friend in friendsList"
|
||
:key="friend.id"
|
||
@click="handleItem({ action: 'openChat', params: friend.id }, $event)"
|
||
>
|
||
{{ friend.username }}
|
||
<ul class="submenu2">
|
||
<li
|
||
@click="handleItem({ action: 'openChat', params: friend.id }, $event)"
|
||
>
|
||
{{ $t('navigation.m-friends.chat') }}
|
||
</li>
|
||
<li
|
||
@click="handleItem({ action: 'openProfile', params: friend.id }, $event)"
|
||
>
|
||
{{ $t('navigation.m-friends.profile') }}
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="right-block">
|
||
<span @click="accessMailbox" class="mailbox"></span>
|
||
<span class="logoutblock">
|
||
<span class="username">{{ user.username }}</span>
|
||
<span @click="logout" class="menuitem">
|
||
{{ $t('navigation.logout') }}
|
||
</span>
|
||
</span>
|
||
</div>
|
||
</nav>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapGetters, mapActions } from 'vuex';
|
||
import { createApp } from 'vue';
|
||
import apiClient from '@/utils/axios.js';
|
||
|
||
import RandomChatDialog from '../dialogues/chat/RandomChatDialog.vue';
|
||
import MultiChatDialog from '../dialogues/chat/MultiChatDialog.vue';
|
||
|
||
// Wichtig: die zentrale Instanzen importieren
|
||
import store from '@/store';
|
||
import router from '@/router';
|
||
import i18n from '@/i18n';
|
||
|
||
export default {
|
||
name: 'AppNavigation',
|
||
components: {
|
||
RandomChatDialog,
|
||
MultiChatDialog
|
||
},
|
||
data() {
|
||
return {
|
||
forumList: [],
|
||
friendsList: [],
|
||
vocabLanguagesList: []
|
||
};
|
||
},
|
||
computed: {
|
||
...mapGetters(['menu', 'user', 'menuNeedsUpdate', 'socket'])
|
||
},
|
||
watch: {
|
||
menuNeedsUpdate(newVal) {
|
||
if (newVal) this.loadMenu();
|
||
},
|
||
socket(newSocket) {
|
||
if (newSocket) {
|
||
newSocket.on('forumschanged', this.fetchForums);
|
||
newSocket.on('friendloginchanged', this.fetchFriends);
|
||
newSocket.on('reloadmenu', this.loadMenu);
|
||
}
|
||
}
|
||
},
|
||
created() {
|
||
if (this.user?.id) {
|
||
this.loadMenu();
|
||
this.fetchForums();
|
||
this.fetchFriends();
|
||
this.fetchVocabLanguages();
|
||
}
|
||
},
|
||
beforeUnmount() {
|
||
const sock = this.socket;
|
||
if (sock) {
|
||
sock.off('forumschanged');
|
||
sock.off('friendloginchanged');
|
||
sock.off('reloadmenu');
|
||
}
|
||
},
|
||
methods: {
|
||
...mapActions(['loadMenu', 'logout']),
|
||
|
||
hasChildren(item) {
|
||
if (!item?.children) {
|
||
return false;
|
||
}
|
||
|
||
if (Array.isArray(item.children)) {
|
||
return item.children.length > 0;
|
||
}
|
||
|
||
return Object.keys(item.children).length > 0;
|
||
},
|
||
|
||
hasTopLevelSubmenu(item) {
|
||
return this.hasChildren(item) || (item?.showLoggedinFriends === 1 && this.friendsList.length > 0);
|
||
},
|
||
|
||
hasSecondLevelSubmenu(subitem, subkey) {
|
||
if (subkey === 'forum') {
|
||
return this.forumList.length > 0;
|
||
}
|
||
|
||
if (subkey === 'vocabtrainer') {
|
||
return this.vocabLanguagesList.length > 0;
|
||
}
|
||
|
||
return this.hasChildren(subitem);
|
||
},
|
||
|
||
openMultiChat() {
|
||
// Räume können später dynamisch geladen werden, hier als Platzhalter ein Beispiel:
|
||
const exampleRooms = [
|
||
{ id: 1, title: 'Allgemein' },
|
||
{ id: 2, title: 'Rollenspiel' }
|
||
];
|
||
const ref = this.$root.$refs.multiChatDialog;
|
||
if (ref && typeof ref.open === 'function') {
|
||
ref.open(exampleRooms);
|
||
} else if (ref?.$refs?.dialog && typeof ref.$refs.dialog.open === 'function') {
|
||
ref.$refs.dialog.open();
|
||
} else {
|
||
console.error('MultiChatDialog nicht bereit oder ohne open()');
|
||
}
|
||
},
|
||
|
||
async fetchForums() {
|
||
try {
|
||
const res = await apiClient.get('/api/forum');
|
||
this.forumList = res.data;
|
||
} catch (err) {
|
||
console.error('Error fetching forums:', err);
|
||
}
|
||
},
|
||
|
||
async fetchFriends() {
|
||
try {
|
||
const res = await apiClient.get('/api/socialnetwork/friends/loggedin');
|
||
this.friendsList = res.data;
|
||
} catch (err) {
|
||
console.error('Error fetching friends:', err);
|
||
}
|
||
},
|
||
|
||
async fetchVocabLanguages() {
|
||
try {
|
||
const res = await apiClient.get('/api/vocab/languages');
|
||
this.vocabLanguagesList = res.data?.languages || [];
|
||
} catch (err) {
|
||
console.error('Error fetching vocab languages:', err);
|
||
this.vocabLanguagesList = [];
|
||
}
|
||
},
|
||
|
||
openForum(forumId) {
|
||
this.$router.push({ name: 'Forum', params: { id: forumId } });
|
||
},
|
||
|
||
openProfile(userId) {
|
||
this.$root.$refs.userProfileDialog.userId = userId;
|
||
this.$root.$refs.userProfileDialog.open();
|
||
},
|
||
|
||
openChat(userId) {
|
||
console.log('openChat:', userId);
|
||
// Datei erstellen und ans body anhängen
|
||
const container = document.createElement('div');
|
||
document.body.appendChild(container);
|
||
},
|
||
|
||
/**
|
||
* Einheitliche Klick‑Logik:
|
||
* 1) Nur aufklappen, wenn noch Untermenüs existieren
|
||
* 2) Bei `view`: Dialog/Window öffnen
|
||
* 3) Bei `action`: custom action aufrufen
|
||
* 4) Sonst: normale Router-Navigation
|
||
*/
|
||
handleItem(item, event) {
|
||
event.stopPropagation();
|
||
|
||
// 1) nur aufklappen, wenn es echte Untermenüs gibt (nicht bei leerem children wie bei Startseite)
|
||
if (this.hasChildren(item)) return;
|
||
|
||
// 2) view → Dialog/Window
|
||
if (item.view) {
|
||
const dialogRef = this.$root.$refs[item.class];
|
||
if (!dialogRef) {
|
||
console.error(`Dialog-Ref '${item.class}' nicht gefunden! Bitte prüfe Ref und Menü-Konfiguration.`);
|
||
return;
|
||
}
|
||
// Robust öffnen: erst open(), sonst auf inneres DialogWidget zurückgreifen
|
||
if (typeof dialogRef.open === 'function') {
|
||
dialogRef.open();
|
||
} else if (dialogRef.$refs?.dialog && typeof dialogRef.$refs.dialog.open === 'function') {
|
||
dialogRef.$refs.dialog.open();
|
||
} else {
|
||
console.error(`Dialog '${item.class}' gefunden, aber keine open()-Methode verfügbar.`);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 3) custom action (openForum, openChat, ...)
|
||
if (item.action && typeof this[item.action] === 'function') {
|
||
return this[item.action](item.params, event);
|
||
}
|
||
|
||
// 4) Standard‑Navigation
|
||
if (item.path) {
|
||
this.$router.push(item.path);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '../assets/styles.scss';
|
||
|
||
nav,
|
||
nav > ul {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 0;
|
||
margin: 0;
|
||
cursor: pointer;
|
||
flex-direction: row;
|
||
z-index: 999;
|
||
}
|
||
|
||
nav {
|
||
max-width: var(--shell-max-width);
|
||
margin: 0 auto;
|
||
align-items: stretch;
|
||
gap: 10px;
|
||
padding: 6px 12px;
|
||
border-radius: 0;
|
||
background: var(--color-primary-orange-light);
|
||
border-top: 1px solid rgba(93, 64, 55, 0.08);
|
||
border-bottom: 1px solid rgba(93, 64, 55, 0.12);
|
||
box-shadow: none;
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
nav > ul {
|
||
flex: 1;
|
||
align-items: center;
|
||
gap: 6px;
|
||
background: transparent;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
ul {
|
||
list-style-type: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
nav > ul > li {
|
||
display: flex;
|
||
align-items: center;
|
||
min-height: 36px;
|
||
padding: 0 12px;
|
||
line-height: 1;
|
||
border-radius: 999px;
|
||
transition: background-color 0.25s, color 0.25s, transform 0.2s;
|
||
}
|
||
|
||
nav > ul > li:hover {
|
||
background-color: rgba(248, 162, 43, 0.16);
|
||
white-space: nowrap;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
nav > ul > li:hover > span {
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
nav > ul > li:hover > ul {
|
||
display: inline-block;
|
||
}
|
||
|
||
a {
|
||
text-decoration: none;
|
||
}
|
||
|
||
.right-block {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding-left: 8px;
|
||
}
|
||
|
||
.logoutblock {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 2px;
|
||
}
|
||
|
||
.menuitem {
|
||
cursor: pointer;
|
||
color: var(--color-primary);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.mailbox {
|
||
background-image: url('@/assets/images/icons/message24.png');
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
width: 40px;
|
||
height: 40px;
|
||
text-align: left;
|
||
border-radius: 999px;
|
||
background-color: rgba(120, 195, 138, 0.12);
|
||
border: 1px solid rgba(93, 64, 55, 0.1);
|
||
}
|
||
|
||
.mainmenuitem {
|
||
position: relative;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.submenu1 {
|
||
position: absolute;
|
||
border: 1px solid rgba(93, 64, 55, 0.12);
|
||
background: rgba(255, 252, 247, 0.98);
|
||
left: 0;
|
||
top: calc(100% + 10px);
|
||
min-width: 220px;
|
||
padding: 8px;
|
||
border-radius: 18px;
|
||
box-shadow: 0 10px 18px rgba(93, 64, 55, 0.12);
|
||
max-height: 0;
|
||
overflow: visible;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: max-height 0.25s ease-in-out,
|
||
opacity 0.05s ease-in-out,
|
||
visibility 0s 0.05s;
|
||
}
|
||
|
||
.mainmenuitem:hover .submenu1 {
|
||
max-height: 500px;
|
||
opacity: 1;
|
||
visibility: visible;
|
||
transition: max-height 0.25s ease-in-out,
|
||
opacity 0.05s ease-in-out,
|
||
visibility 0s;
|
||
}
|
||
|
||
.submenu1 > li {
|
||
padding: 0.75em 0.9em;
|
||
line-height: 1em;
|
||
color: var(--color-text-secondary);
|
||
position: relative;
|
||
border-radius: 14px;
|
||
}
|
||
|
||
.submenu1 > li:hover {
|
||
color: var(--color-text-primary);
|
||
background-color: rgba(248, 162, 43, 0.12);
|
||
}
|
||
|
||
.menu-icon,
|
||
.submenu-icon {
|
||
display: inline-block;
|
||
background-repeat: no-repeat;
|
||
line-height: 1em;
|
||
}
|
||
|
||
.menu-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.submenu-icon {
|
||
width: 1.2em;
|
||
height: 1em;
|
||
margin-right: 3px;
|
||
background-size: 1.2em 1.2em;
|
||
}
|
||
|
||
.submenu2 {
|
||
position: absolute;
|
||
background: rgba(255, 252, 247, 0.98);
|
||
left: calc(100% + 8px);
|
||
top: 0;
|
||
min-width: 220px;
|
||
padding: 8px;
|
||
border-radius: 18px;
|
||
border: 1px solid rgba(71, 52, 35, 0.12);
|
||
box-shadow: 0 10px 18px rgba(93, 64, 55, 0.12);
|
||
max-height: 0;
|
||
overflow: hidden;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: max-height 0.25s ease-in-out,
|
||
opacity 0.05s ease-in-out,
|
||
visibility 0s 0.05s;
|
||
}
|
||
|
||
.submenu1 > li:hover .submenu2 {
|
||
max-height: 500px;
|
||
opacity: 1;
|
||
visibility: visible;
|
||
transition: max-height 0.25s ease-in-out,
|
||
opacity 0.05s ease-in-out,
|
||
visibility 0s;
|
||
}
|
||
|
||
.submenu2 > li {
|
||
padding: 0.75em 0.9em;
|
||
line-height: 1em;
|
||
color: var(--color-text-secondary);
|
||
border-radius: 14px;
|
||
}
|
||
|
||
.submenu2 > li:hover {
|
||
color: var(--color-text-primary);
|
||
background-color: rgba(120, 195, 138, 0.14);
|
||
}
|
||
|
||
.subsubmenu {
|
||
float: right;
|
||
font-size: 8pt;
|
||
margin-right: -4px;
|
||
}
|
||
|
||
.username {
|
||
font-weight: 800;
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
nav {
|
||
margin: 0;
|
||
flex-direction: column;
|
||
padding: 8px 10px;
|
||
}
|
||
|
||
nav > ul,
|
||
.right-block {
|
||
width: 100%;
|
||
}
|
||
|
||
.right-block {
|
||
justify-content: space-between;
|
||
padding-left: 0;
|
||
}
|
||
|
||
.logoutblock {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.submenu1,
|
||
.submenu2 {
|
||
position: static;
|
||
min-width: 100%;
|
||
margin-top: 8px;
|
||
}
|
||
}
|
||
</style>
|