Files
yourpart3/frontend/src/components/AppNavigation.vue
Torsten Schulz (local) 19ee6ba0a1 Add password reset localization and chat configuration
- Implemented German and English localization for password reset functionality.
- Added WebSocket URL resolution logic in chat services to support various environments and configurations.
- Created centralized chat configuration for event keys and payload mappings.
- Developed RoomsView component for admin chat room management, including create, edit, and delete functionalities.
2025-08-18 07:44:56 +02:00

430 lines
10 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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"
>&nbsp;</span>
<span>{{ $t(`navigation.${key}`) }}</span>
<!-- Untermenü Ebene 1 -->
<ul v-if="item.children" 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"
>&nbsp;</span>
<span>{{ $t(`navigation.m-${key}.${subkey}`) }}</span>
<span
v-if="subkey === 'forum' || subitem.children"
class="subsubmenu"
>&#x25B6;</span>
<!-- ForumUnterliste -->
<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>
<!-- 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"
>&nbsp;</span>
<span>{{ $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: []
};
},
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();
}
},
beforeUnmount() {
const sock = this.socket;
if (sock) {
sock.off('forumschanged');
sock.off('friendloginchanged');
sock.off('reloadmenu');
}
},
methods: {
...mapActions(['loadMenu', 'logout']),
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);
}
},
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 KlickLogik:
* 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
if (item.children) 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) StandardNavigation
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;
background-color: #f9a22c;
color: #000;
padding: 0;
margin: 0;
cursor: pointer;
flex-direction: row;
z-index: 999;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
nav > ul > li {
padding: 0 1em;
line-height: 2.5em;
transition: background-color 0.25s;
}
nav > ul > li:hover {
background-color: #d37c06;
white-space: nowrap;
}
nav > ul > li:hover > span {
color: #000;
}
nav > ul > li:hover > ul {
display: inline-block;
}
a {
text-decoration: none;
}
.right-block {
display: flex;
gap: 10px;
}
.logoutblock {
display: flex;
flex-direction: column;
}
.menuitem {
cursor: pointer;
color: #7e471b;
}
.mailbox {
background-image: url('@/assets/images/icons/message24.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
padding-left: 24px;
text-align: left;
}
.mainmenuitem {
position: relative;
}
.submenu1 {
position: absolute;
border: 1px solid #7e471b;
background-color: #f9a22c;
left: 0;
top: 2.5em;
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.5em;
line-height: 1em;
color: #7e471b;
position: relative;
}
.submenu1 > li:hover {
color: #000;
background-color: #d37c06;
}
.menu-icon,
.submenu-icon {
display: inline-block;
background-repeat: no-repeat;
line-height: 1em;
}
.menu-icon {
width: 24px;
height: 24px;
margin-right: 3px;
}
.submenu-icon {
width: 1.2em;
height: 1em;
margin-right: 3px;
background-size: 1.2em 1.2em;
}
.submenu2 {
position: absolute;
background-color: #f9a22c;
left: 100%;
top: 0;
border: 1px solid #7e471b;
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.5em;
line-height: 1em;
color: #7e471b;
}
.submenu2 > li:hover {
color: #000;
background-color: #d37c06;
}
.subsubmenu {
float: right;
font-size: 8pt;
margin-right: -4px;
}
</style>