fixed some bugs so that the Präsentation kein komplettes Desaster wird

This commit is contained in:
joschuatonn
2024-06-25 08:52:05 +02:00
parent f229e3bfbb
commit b38e610d61
13 changed files with 440 additions and 284 deletions

View File

@@ -16,7 +16,7 @@ import { useQuasar } from 'quasar';
<template> <template>
<IndexPage v-if="userStore.userLoaded"></IndexPage> <IndexPage v-if="userStore.userLoaded"></IndexPage>
<div style="display: flex; align-items: center;justify-content: center;padding-top: 45vh;flex-direction: column;" v-else> <div style="display: flex; align-items: center;justify-content: center;padding-top: 45vh;flex-direction: column;width: 100vw; height: 100vh;background: white;" v-else>
<div> <div>
<q-spinner <q-spinner
color="primary" color="primary"
@@ -24,7 +24,8 @@ import { useQuasar } from 'quasar';
/> />
</div> </div>
<div class="text-h6" v-if="userStore.reconnectTries < 2">ptpChat wird geladen...</div> <div class="text-h6" v-if="userStore.reconnectTries < 2">ptpChat wird geladen...</div>
<div class="text-h6" v-else>Verbindung wird wiederhergestellt (Versuch {{ userStore.reconnectTries }})</div> <div class="text-h6" v-else-if="userStore.reconnectTries < 20">Verbindung wird wiederhergestellt (Versuch {{ userStore.reconnectTries }})</div>
<div class="text-h6" v-else>Also langsam wirds peinlich</div>
</div> </div>
<router-view /> <router-view />
@@ -62,4 +63,16 @@ import { useQuasar } from 'quasar';
} }
/* CUSTOM SCROLLBAR DESIGN ENDE */ /* CUSTOM SCROLLBAR DESIGN ENDE */
:root {
--defaultGridBorderRadius: 1rem;
--defaultElementBackground: rgb(197, 197, 197);
}
body {
background: rgb(50, 50, 50);
height: 100vh;
width: 100vw;
overflow: hidden !important;
}
</style> </style>

View File

@@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps({ const props = defineProps({
chat: Object, chat: {
type: Object,
required: true
},
selected: Boolean, selected: Boolean,
lastMessage: String, lastMessage: String,
}) })
@@ -9,42 +12,52 @@
</script> </script>
<template> <template>
<div class="profileCard" :class="selected ? 'selected' : ''"> <div class="profileCard" :class="{ selected: props.selected }">
<div class="profilePicture" :style="{'background-image' : 'url('+props.chat!.profilepictureAsString+')' }"></div> <div class="profilePicture" :style="{ backgroundImage: 'url(' + props.chat.profilepicture + ')' }"></div>
<div style="margin-left:10px;"> <div class="profileInfo">
<p style="margin-bottom: 0;line-height: 25px;vertical-align: middle;height: 25px;display: table-cell;"> <p class="displayName">{{ props.chat.displayName }}</p>
<span style="font-weight: bold;font-size: 1.2rem;">{{props.chat!.displayName}}</span> <p class="lastMessage">{{ props.lastMessage }}</p>
</p>
<p style="margin: 0;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;width:20rem;">{{ props.lastMessage }}</p>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.profileCard {
display: flex;
border-radius: 10px;
padding: 10px;
transition: 0.3s;
cursor: pointer;
}
.profileCard:hover,
.selected {
background: rgba(0, 0, 0, 0.2);
}
.profilePicture { .profilePicture {
width: 50px; width: 50px;
height: 50px; height: 50px;
border-radius: 50%; border-radius: 50%;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
} }
.profileCard { .profileInfo {
display: flex; margin-left: 10px;
border-radius: 10px;
padding: 10px;
transition: .3s;
cursor: pointer;
} }
.profileCard:hover { .displayName {
transition: .3s; font-weight: bold;
background: rgba(0,0,0,0.2); font-size: 1.2rem;
margin-bottom: 0;
} }
.selected { .lastMessage {
background: rgba(0,0,0,0.2); margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 20rem;
} }
</style> </style>

View File

@@ -1,38 +1,35 @@
<template> <template>
<q-dialog v-bind:model-value="internalShowDialog" @update:model-value="closeDialog" style="overflow: auto;" full-width> <q-dialog v-bind:model-value="internalShowDialog" @update:model-value="closeDialog" style="overflow-x: hidden;" full-width>
<q-card style="margin: 3rem 20rem;height: 80vh;padding: 20px;overflow: auto !important;"> <q-card style="margin: 3rem 15rem;height: 80vh;padding: 20px;overflow: auto !important;">
<q-card-section class="row items-center q-pb-none"> <q-card-section class="row items-center q-pb-none">
<div class="text-h4">{{ chat!.displayName }}</div> <div class="text-h4">{{ props.chat!.displayName }}</div>
<q-space /> <q-space />
<q-btn icon="close" flat round dense v-close-popup /> <q-btn icon="close" flat round dense v-close-popup />
</q-card-section> </q-card-section>
<div class="grid-container" v-if="chat?.members"> <div class="grid-container" v-if="chat?.members">
<q-card-section> <q-card-section style="grid-row: span 2;">
<div class="text-h5">Mitglieder ({{ chat!.members.length }})</div> <div class="text-h5">Mitglieder ({{ props.chat!.members.length }})</div>
<table> <div style="height:15rem;overflow-y: auto;">
<tr v-for="user in chat.members"> <table>
<td> <tr v-for="user in props.chat!.members">
<ProfileCardComponent :user="user" :selected="false"></ProfileCardComponent> <td>
</td> <ProfileCardComponent :user="user" :selected="false" :showAdminTag="isUserAdmin(user.id)"></ProfileCardComponent>
<td v-if="user.id != userStore.user!.id && currentUserIsAdmin"> </td>
<q-btn round flat icon="person_remove" style="color: darkred;" title="Nutzer entfernen" @click="removeUser(user.id)"></q-btn> <td v-if="user.id != userStore.user!.id && currentUserIsAdmin">
<q-btn round flat icon="key" title="Nutzer zum Admin machen"></q-btn> <q-btn round flat icon="person_remove" style="color: darkred;" title="Nutzer entfernen" @click="removeUser(user.id)"></q-btn>
</td> <q-btn round flat icon="arrow_downward" title="Adminrechte entziehen" @click="removeAdmin(user.id)" v-if="isUserAdmin(user.id)"></q-btn>
</tr> <q-btn round flat icon="arrow_upward" title="Nutzer zum Admin machen" @click="addAdmin(user.id)" v-if="!isUserAdmin(user.id)"></q-btn>
</table> </td>
</tr>
</table>
</div>
</q-card-section> </q-card-section>
<!-- Namen des Chats ändern --> <!-- Namen des Chats ändern -->
<q-card-section v-if="currentUserIsAdmin"> <q-card-section v-if="currentUserIsAdmin">
<div class="text-h5">Namen ändern</div> <div class="text-h5">Namen ändern</div>
<q-input filled v-model="chat.displayName" maxlength="256" counter @update:model-value="showUnsavedChangesAlert"/> <q-input filled v-model="props.chat!.displayName" maxlength="256" counter @update:model-value="showUnsavedChangesAlert" style="width: 30rem"/>
</q-card-section>
<q-card-section>
<div class="text-h5">Aktionen</div>
<q-btn class="deleteButton" @click="removeUser(userStore.user!.id)">Gruppenchat verlassen</q-btn>
<q-btn class="deleteButton" v-if="currentUserIsAdmin">Gruppenchat löschen</q-btn>
</q-card-section> </q-card-section>
<q-card-section v-if="currentUserIsAdmin"> <q-card-section v-if="currentUserIsAdmin">
@@ -41,7 +38,8 @@
<div style="display: flex;" v-if="chatStore.profilePicturesLoaded"> <div style="display: flex;" v-if="chatStore.profilePicturesLoaded">
<div <div
v-for="(image, index) in chatStore.profilePictures" v-for="(image, index) in chatStore.profilePictures"
:style="{'background-image' : 'url('+image.url+')' , 'outline' : image.url == chat.profilepictureAsString ? 'dashed grey' : 'none'}" :key="index"
:style="{'background-image' : 'url('+image.url+')' , 'outline' : image.url == chat.profilepicture ? 'dashed grey' : 'none'}"
style="color: red;" style="color: red;"
round round
flat flat
@@ -58,6 +56,46 @@
/> />
</div> </div>
</q-card-section> </q-card-section>
<q-card-section>
<div class="text-h5">Mitglieder hinzufügen</div>
<q-input
filled
v-model="searchQuery"
placeholder="Nach Nutzern suchen"
@update:model-value="updateSearchResults"
>
<template v-slot:prepend>
<q-icon name="search"></q-icon>
</template>
</q-input>
<div style="height: 12rem; overflow-y: auto;">
<div v-for="(user, index) in searchResults" class="grid-container-search">
<div class="wrapper-div">
<q-btn
v-if="!props.chat!.members.find((u : ptpUser) => user.id == u.id)"
round
flat
dense
icon="add"
title="Hinzufügen"
style="width:3rem;height:3rem;"
class="button"
@click="handleClickEvent(user)"
/>
</div>
<ProfileCardComponent :user="user" :selected="false"></ProfileCardComponent>
</div>
</div>
</q-card-section>
<q-card-section>
<div class="text-h5">Aktionen</div>
<q-btn class="deleteButton" @click="removeUser(userStore.user!.id)">Gruppenchat verlassen</q-btn>
<q-btn class="deleteButton" v-if="currentUserIsAdmin" @click="deleteChat">Gruppenchat löschen</q-btn>
</q-card-section>
</div> </div>
</q-card> </q-card>
</q-dialog> </q-dialog>
@@ -71,18 +109,25 @@
import type { chat } from 'src/models'; import type { chat } from 'src/models';
import { useChatStore } from 'src/stores/chat-store'; import { useChatStore } from 'src/stores/chat-store';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { sendNotification } from 'src/utils'; import { sendNotification, getFilteredUserList } from 'src/utils';
const userStore = useUserStore(); const userStore = useUserStore();
const chatStore = useChatStore(); const chatStore = useChatStore();
const showingAlert = ref(false); const showingAlert = ref(false);
const searchQuery = ref("");
const searchResults = ref([] as ptpUser[]);
const emit = defineEmits(['update:showDialog', 'showModal']); const emit = defineEmits(['update:showDialog', 'showModal']);
const props = defineProps({ const props = defineProps({
showDialog: Boolean, showDialog: Boolean,
chatID: Number, chatID: Number,
leaveChat: Function chat: Object,
leaveChat: {
type: Function,
required: true
}
}); });
const internalShowDialog = computed({ const internalShowDialog = computed({
@@ -95,37 +140,86 @@
} }
const selectProfilePicture = (image: profilePicture) => { const selectProfilePicture = (image: profilePicture) => {
chat.value!.profilepictureAsString = image.url; chat.value!.profilepicture = image.url;
//props.chat!.profilepictureAsString = image.url; //props.chat!.profilepictureAsString = image.url;
showUnsavedChangesAlert(); showUnsavedChangesAlert();
} }
const $q = useQuasar(); const $q = useQuasar();
const chat = ref(chatStore.chats.find((chat) => chat.id == props.chatID)); const chat = ref(chatStore.chats.find((chat) => chat.id == props.chat!.id));
const currentUserIsAdmin = computed(() => { const currentUserIsAdmin = computed(() => {
return chat.value!.adminIds.includes(userStore.user!.id); return props.chat!.adminIds.includes(userStore.user!.id);
}); });
const updateSearchResults = () => {
searchResults.value = getFilteredUserList(searchQuery.value, userStore.users, userStore.user!.id);
}
const handleClickEvent = (user : ptpUser) => {
chatStore.addUserToChat(user.id, props.chatID!).then((response : responseModel) => {
chatStore.loadChatsByUser().then(() => {
sendNotification(response.message, response.success);
props.chat!.members.unshift(user);
});
});
}
const removeUser = (keycloakID : string) => { const removeUser = (keycloakID : string) => {
chatStore.removeUserFromChat(keycloakID, props.chatID!).then((response : responseModel) => { chatStore.removeUserFromChat(keycloakID, props.chatID!).then((response : responseModel) => {
chatStore.loadChatsByUser().then(() => { chatStore.loadChatsByUser().then(() => {
if(response.success) { if(response.success) {
if(userStore.user!.id == keycloakID) { if(userStore.user!.id == keycloakID) {
sendNotification("Du hast den Chat verlassen", true); sendNotification("Du hast den Chat verlassen", true);
props.leaveChat!(); props.leaveChat();
closeDialog(); closeDialog();
} else { } else {
sendNotification(response.message, response.success); sendNotification(response.message, response.success);
} }
chat.value = chatStore.chats.find((chat) => chat.id == props.chatID); //props.chat!.members.shift();
const index = props.chat!.members.indexOf(props.chat!.members.find((user : ptpUser) => user.id == keycloakID));
if (index > -1) {
props.chat!.members.splice(index, 1);
}
//chat.value = chatStore.chats.find((chat) => chat.id == props.chatID);
} }
}); });
}); });
} }
const deleteChat = () => {
chatStore.deleteChat(props.chatID!).then((response : responseModel) => {
sendNotification(response.message, response.success);
props.leaveChat();
closeDialog();
});
}
const addAdmin = (keycloakID : string) => {
chatStore.addAdmin(props.chatID!, keycloakID).then((response : responseModel) => {
chatStore.loadChatsByUser().then(() => {
sendNotification(response.message, response.success);
chat.value = chatStore.chats.find((chat) => chat.id == props.chatID);
});
});
}
const removeAdmin = (keycloakID : string) => {
chatStore.removeAdmin(props.chatID!, keycloakID).then((response : responseModel) => {
chatStore.loadChatsByUser().then(() => {
sendNotification(response.message, response.success);
chat.value = chatStore.chats.find((chat) => chat.id == props.chatID);
});
});
}
const isUserAdmin = (keycloakID : string) => {
return chat.value!.adminIds.includes(keycloakID);
}
const saveChatChanges = () => { const saveChatChanges = () => {
chatStore.updateChat(chat.value as chat).then((response : responseModel) => { chatStore.updateChat(chat.value as chat).then((response : responseModel) => {
showingAlert.value = false; showingAlert.value = false;
@@ -142,13 +236,19 @@
color: "green", color: "green",
icon: 'announcement', icon: 'announcement',
actions: [ actions: [
{ label: 'Änderungen speichern', color: 'white', handler: () => { saveChatChanges() } }, { label: 'Speichern', color: 'white', handler: () => { saveChatChanges() } },
{ label: 'Verwerfen', color: 'white', handler: () => { discardChanges() } },
] ]
}); });
showingAlert.value = true; showingAlert.value = true;
} }
} }
const discardChanges = () => {
showingAlert.value = false;
closeDialog();
}
</script> </script>
<style scoped> <style scoped>
@@ -184,4 +284,25 @@
transition: .3s; transition: .3s;
filter: brightness(0.6); filter: brightness(0.6);
} }
/* MIGHT BE MOVED LATER */
.grid-container-search {
display: grid;
grid-template-columns: 70px auto;
}
.grid-container-search > div:nth-child(even) {
min-width: 20rem;
}
.wrapper-div {
height:100%;
position: relative;
}
.button {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
</style> </style>

View File

@@ -18,6 +18,7 @@
</div> </div>
</div> </div>
<q-input <q-input
filled filled
v-model="chatName" v-model="chatName"
@@ -70,10 +71,9 @@
import ProfileCardComponent from './ProfileCardComponent.vue'; import ProfileCardComponent from './ProfileCardComponent.vue';
import { useUserStore } from 'src/stores/user-store'; import { useUserStore } from 'src/stores/user-store';
import type { ptpUser, responseModel } from 'src/models'; import type { ptpUser, responseModel } from 'src/models';
import {sendNotification} from 'src/utils'; import {getFilteredUserList, sendNotification} from 'src/utils';
import { useChatStore } from 'src/stores/chat-store'; import { useChatStore } from 'src/stores/chat-store';
const showDialog = ref(true);
const userStore = useUserStore(); const userStore = useUserStore();
const searchQuery = ref(""); const searchQuery = ref("");
const chatName = ref(""); const chatName = ref("");
@@ -83,8 +83,7 @@
const emit = defineEmits(['update:showDialog', 'showModal']); const emit = defineEmits(['update:showDialog', 'showModal']);
const props = defineProps({ const props = defineProps({
showDialog: Boolean, showDialog: Boolean
chat: Object
}); });
const internalShowDialog = computed({ const internalShowDialog = computed({
@@ -97,7 +96,7 @@
} }
const updateSearchResults = () => { const updateSearchResults = () => {
searchResults.value = userStore.users.filter((u) => u.username.toLocaleLowerCase().includes(searchQuery.value.toLocaleLowerCase()) && u.id != userStore.user?.id); searchResults.value = getFilteredUserList(searchQuery.value, userStore.users, userStore.user!.id);
} }
const isUserSelected = (user : ptpUser) => { const isUserSelected = (user : ptpUser) => {

View File

@@ -3,14 +3,12 @@
const props = defineProps({ const props = defineProps({
ownMessage: Boolean, ownMessage: Boolean,
timestamp: String,
message: String,
sender: Object,
gif: Boolean,
link: Boolean,
url: String,
showSender: Boolean, showSender: Boolean,
nametagColor: String, nametagColor: String,
message: {
type: Object,
required: true
}
}) })
const options: Intl.DateTimeFormatOptions = { const options: Intl.DateTimeFormatOptions = {
@@ -21,7 +19,7 @@
minute: '2-digit', minute: '2-digit',
}; };
const date = new Date(Number(props.timestamp)); const date = new Date(Number(props.message.timestamp));
let timestampFormatted = date.toLocaleString('de-DE', options); let timestampFormatted = date.toLocaleString('de-DE', options);
const daysOffset = getDaysOffset(date, new Date()); const daysOffset = getDaysOffset(date, new Date());
@@ -34,12 +32,12 @@
<template> <template>
<div class="message" :class="{'sent' : props.ownMessage, 'received' : !props.ownMessage}"> <div class="message" :class="{'sent' : props.ownMessage, 'received' : !props.ownMessage}">
<div v-if="props.showSender && !props.ownMessage" style="display: flex; justify-content: space-between;"> <div v-if="props.showSender && !props.ownMessage" style="display: flex; justify-content: space-between;">
<div class="text-body" style="font-weight: bold;" :style="{'color' : props.nametagColor }">{{ props.sender!.firstName }} {{ props.sender!.lastName }}</div> <div class="text-body" style="font-weight: bold;" :style="{'color' : props.nametagColor }">{{ props.message!.sender.firstName }} {{ props.message!.sender.lastName }}</div>
<div class="text-body" style="color: #505050"><small>@{{ props.sender!.username }}</small></div> <div class="text-body" style="color: #505050"><small>@{{ props.message!.sender.username }}</small></div>
</div> </div>
<p v-if="!props.gif && !props.link" style="margin-bottom: 0;">{{props.message}}</p> <p v-if="!props.message.gif && !props.message.link" style="margin-bottom: 0;">{{props.message.content}}</p>
<img v-if="props.gif" :src="props.url" alt="GIF" style="width: 100%;border-radius: 10px;" /> <img v-if="props.message.gif" :src="props.message.content" alt="GIF" style="width: 100%;border-radius: 10px;" />
<a v-if="props.link" :href="props.url" target="_blank">{{props.url}}</a> <a v-if="props.message.link" :href="props.message.content" target="_blank">{{props.message.content}}</a>
<div style="text-align: right;"> <div style="text-align: right;">
<small style="margin-top: 0;color: #505050;">{{timestampFormatted}}</small> <small style="margin-top: 0;color: #505050;">{{timestampFormatted}}</small>

View File

@@ -9,7 +9,8 @@
user: Object, user: Object,
selected: Boolean, selected: Boolean,
slim: Boolean, slim: Boolean,
lastMessage: String lastMessage: String,
showAdminTag: Boolean
}) })
</script> </script>
@@ -21,6 +22,7 @@
<p style="margin-bottom: 0;line-height: 25px;vertical-align: middle;height: 25px;display: table-cell;"> <p style="margin-bottom: 0;line-height: 25px;vertical-align: middle;height: 25px;display: table-cell;">
<span style="font-weight: bold;font-size: 1.2rem;">{{props.user!.firstName}} {{ props.user!.lastName }}</span> <span style="font-weight: bold;font-size: 1.2rem;">{{props.user!.firstName}} {{ props.user!.lastName }}</span>
<span v-if="!props.slim">@{{props.user!.username}}</span> <span v-if="!props.slim">@{{props.user!.username}}</span>
<span v-if="props.showAdminTag" style="color: darkred;"> [Administrator]</span>
</p> </p>
<p v-if="!props.slim" style="margin: 0;">{{ props.user!.status }}</p> <p v-if="!props.slim" style="margin: 0;">{{ props.user!.status }}</p>

View File

@@ -6,7 +6,7 @@
<q-card style="padding: 20px;"> <q-card style="padding: 20px;">
<q-card-section> <q-card-section>
<div class="text-h4">Account löschen</div> <div class="text-h4">Account löschen</div>
<div class="text-body">Bist du sicher, dass du deinen Account löschen möchtest?</div> <div class="text-body">Bist du sicher, dass du deinen Account löschen möchtest? (Dieses Feature ist momentan noch nicht aktiv)</div>
</q-card-section> </q-card-section>
<q-card-actions align="right"> <q-card-actions align="right">
<q-btn label="Abbrechen" class="greyButton" style="width: 48%;" @click="showDeleteAccountPopup = false" /> <q-btn label="Abbrechen" class="greyButton" style="width: 48%;" @click="showDeleteAccountPopup = false" />
@@ -147,7 +147,6 @@
} }
const closeDialog = () => { const closeDialog = () => {
//internalShowDialog.value = false;
emit('showModal', true); emit('showModal', true);
}; };
@@ -160,13 +159,19 @@
color: "green", color: "green",
icon: 'announcement', icon: 'announcement',
actions: [ actions: [
{ label: 'Änderungen speichern', color: 'white', handler: () => { saveProfileChanges() } }, { label: 'Speichern', color: 'white', handler: () => { saveProfileChanges() } },
{ label: 'Verwerfen', color: 'white', handler: () => { discardChanges() } },
] ]
}); });
showingAlert.value = true; showingAlert.value = true;
} }
} }
const discardChanges = () => {
showingAlert.value = false;
closeDialog();
}
</script> </script>
<style scoped> <style scoped>

View File

@@ -10,6 +10,7 @@ import { useNotificationStore } from './stores/notification-store';
const app = createApp(App); const app = createApp(App);
const pinia = createPinia(); const pinia = createPinia();
app.use(pinia); app.use(pinia);
@@ -17,51 +18,35 @@ app.use(pinia);
const userStore = useUserStore(); const userStore = useUserStore();
const chatStore = useChatStore(); const chatStore = useChatStore();
let keycloak:Keycloak; let keycloak:Keycloak;
userStore.initKeycloak() userStore.initKeycloak().then(r=> {
.then(r=> keycloak=r;
{
keycloak=r
let tries = 1;
// Anfrage ans Backend if (keycloak.subject) {
userStore.id = keycloak.subject;
userStore.getPtpUserById(keycloak.subject).then(() =>
userStore.loadPtpUsersById(keycloak.subject||"").then(user => {
console.log(JSON.stringify(user));
})
);
if (keycloak.subject) { const interval = setInterval(() => {
if(userStore.userLoaded) {
chatStore.loadProfilePictures();
userStore.loadProfilePictures();
clearInterval(interval);
}
userStore.id = keycloak.subject; userStore.getPtpUserById(keycloak.subject!);
userStore.getPtpUserById(keycloak.subject).then(() => }, 5000);
userStore.loadPtpUsersById(keycloak.subject||"").then(user => {
console.log(JSON.stringify(user));
}));
Notification.requestPermission().then((result) => {
useNotificationStore().saveDecision(result === "granted");
});
const interval = setInterval(() => {
if(userStore.userLoaded) clearInterval(interval); } else {
console.error("hier ist was schlimmes passiert menno");
userStore.getPtpUserById(keycloak.subject!).then(user => { }
console.log("New attempt: " + JSON.stringify(user)); }).catch(e=>console.error(e));
console.warn(userStore.userLoaded);
}).catch(() => {
console.error("Ein Netzwerkfehler ist aufgetreten " + tries);
userStore.reconnectTries = tries;
tries++;
});
}, 5000);
chatStore.loadProfilePictures();
userStore.loadProfilePictures();
Notification.requestPermission().then((result) => {
useNotificationStore().saveDecision(result === "granted");
});
} else {
console.error("hier ist was schlimmes passiert menno");
}
}).catch(e=>console.error(e));
window.onfocus = function() { window.onfocus = function() {

View File

@@ -21,7 +21,6 @@ export interface chat{
messages: message[]; messages: message[];
displayName: string; displayName: string;
profilepicture: string; profilepicture: string;
profilepictureAsString: string;
groupChat: boolean; groupChat: boolean;
adminIds: string[]; adminIds: string[];
} }

View File

@@ -1,23 +1,22 @@
<template> <template>
<div style="width:100%;display: flex; align-items: center; justify-content: space-between;height:100vh;position: fixed;"> <div class="container">
<!-- Hier gibt es die ganzen PopUps und so --> <!-- Hier gibt es die ganzen PopUps und so -->
<SettingsPopUp :showDialog="settingsDialog" @showModal="() => settingsDialog = false"/> <SettingsPopUp :showDialog="settingsDialog" @showModal="() => settingsDialog = false"/>
<CreateChatComponent :showDialog="createChatDialog" @showModal="() => createChatDialog = false" :leaveChat="resetSelectedChat"></CreateChatComponent> <CreateChatComponent :showDialog="createChatDialog" @showModal="() => createChatDialog = false"></CreateChatComponent>
<div style="background: gray; width: 25%;height:100%;border-right: 1px solid black;"> <div class="menu">
<div style="display: flex; align-items: center; justify-content: space-between;" class="navigation"> <div style="display: flex; align-items: center; justify-content: space-between;" class="navigation">
<div title="Nach Nutzern suchen" @click="activeMenu = 'search'" :class="{menuItemActive : activeMenu === 'search', menuItemInactive : activeMenu !== 'search'}"> <div title="Nach Nutzern suchen" @click="selectTab('search')" :class="{menuItemActive : activeMenu === 'search', menuItemInactive : activeMenu !== 'search'}">
<q-icon name="search"></q-icon> <q-icon name="search"></q-icon>
</div> </div>
<div title="Meine Chats anzeigen" @click="activeMenu = 'chats'" :class="{menuItemActive : activeMenu === 'chats', menuItemInactive : activeMenu !== 'chats'}"> <div title="Meine Chats anzeigen" @click="selectTab('chats')" :class="{menuItemActive : activeMenu === 'chats', menuItemInactive : activeMenu !== 'chats'}">
<q-icon name="chat"></q-icon> <q-icon name="chat"></q-icon>
</div> </div>
</div> </div>
<div style="margin: 20px;" v-if="activeMenu == 'search'"> <div style="margin: 20px;" v-if="activeMenu == 'search'">
<q-input filled v-model="searchQuery" placeholder="Nach Nutzern suchen"> <q-input filled v-model="searchQuery" placeholder="Nach Nutzern suchen">
<template v-slot:prepend> <template v-slot:prepend>
@@ -27,14 +26,13 @@
<div v-if="userStore.usersLoaded && userStore.userLoaded && searchQuery.length > 0 && searchQuery.replace(/\s/g, '').length" style="margin-top: 20px;"> <div v-if="userStore.usersLoaded && userStore.userLoaded && searchQuery.length > 0 && searchQuery.replace(/\s/g, '').length" style="margin-top: 20px;">
<ProfileCardComponent <ProfileCardComponent
v-for="user in userStore.users.filter(u => (u.username.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || (u.firstName + ' ' + u.lastName).toLocaleLowerCase().includes(searchQuery.trimStart().toLocaleLowerCase()) || searchQuery == '*') && u.id != userStore.id && searchQuery.replace(/\s/g, '').length)" :displayName="user.firstName + ' ' + user.lastName" :username="user.username" v-for="user in getFilteredUserList(searchQuery, userStore.users, userStore.user!.id)"
:profilePictureUrl="user.profilepicture"
:status="user.status"
:user="user" :user="user"
:selected="selectedUser.id == user.id" :selected="selectedUser.id == user.id"
@click="selectUser(user as ptpUser)" @click="selectUser(user as ptpUser)"
></ProfileCardComponent> ></ProfileCardComponent>
<div v-if="userStore.users.filter(u => u.username.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || (u.firstName + ' ' + u.lastName).toLocaleLowerCase().includes(searchQuery.trimStart().toLocaleLowerCase()) || searchQuery == '*').length == 0 || !searchQuery.replace(/\s/g, '').length" style="text-align: center;">
<div v-if="getFilteredUserList(searchQuery, userStore.users, userStore.user!.id).length == 0" style="text-align: center;">
<img src="https://media.tenor.com/KOZLvzU0o4kAAAAd/no-results.gif" style="border-radius: 1rem;" alt="No search results found"> <img src="https://media.tenor.com/KOZLvzU0o4kAAAAd/no-results.gif" style="border-radius: 1rem;" alt="No search results found">
</div> </div>
</div> </div>
@@ -57,21 +55,13 @@
></ChatCardComponent> ></ChatCardComponent>
<ProfileCardComponent <ProfileCardComponent
v-else-if="chat.members.length == 2" v-else
:user="userStore.users.find((user) => user.id === chat.members.find((member) => member.id !== userStore.user!.id)?.id)" :user="userStore.users.find((user) => user.id === chat.members.find((member) => member.id !== userStore.user!.id)?.id)"
:selected="chat.id == selectedChat.id" :selected="chat.id == selectedChat.id"
@click="selectChat(chat as chat)" @click="selectChat(chat as chat)"
slim slim
:lastMessage="getChatPreviewMessage(chat.id)" :lastMessage="getChatPreviewMessage(chat.id)"
></ProfileCardComponent> ></ProfileCardComponent>
<ChatCardComponent
v-else
:selected="chat.id == selectedChat.id"
:chat="chat"
@click="selectChat(chat as chat)"
:lastMessage="getChatPreviewMessage(chat.id)"
></ChatCardComponent>
</div> </div>
</div> </div>
</div> </div>
@@ -81,16 +71,13 @@
flat flat
v-if="userStore.userLoaded" v-if="userStore.userLoaded"
:style="{'background-image' : 'url('+userStore.user!.profilepicture+')' }" :style="{'background-image' : 'url('+userStore.user!.profilepicture+')' }"
style="background-size: cover;background-position: center;position: fixed; left:1rem; bottom: 1rem;" style="background-size: cover;background-position: center;position: fixed; left:36px; bottom: 30px;"
title="Profil und Einstellungen" title="Profil und Einstellungen"
@click="settingsDialog = true" @click="settingsDialog = true"
> ></q-btn>
</q-btn>
</div> </div>
<!-- RECHTE SPALTE --> <!-- RECHTE SPALTE -->
<div style="width: 75%;height: 100%;background: #DFD0BF;">
<div class="header" v-if="selectedUser.id != null || selectedChat.id != null" style="padding: 10px;"> <div class="header" v-if="selectedUser.id != null || selectedChat.id != null" style="padding: 10px;">
<HeaderInfoContainerComponent <HeaderInfoContainerComponent
v-if="!selectedChat.groupChat" v-if="!selectedChat.groupChat"
@@ -101,7 +88,7 @@
<div v-else style="display: flex;"> <div v-else style="display: flex;">
<HeaderInfoContainerComponent <HeaderInfoContainerComponent
:displayName="selectedChat.displayName" :displayName="selectedChat.displayName"
:image="selectedChat.profilepictureAsString" :image="selectedChat.profilepicture"
></HeaderInfoContainerComponent> ></HeaderInfoContainerComponent>
<div> <div>
@@ -115,7 +102,7 @@
<q-space /> <q-space />
<q-btn icon="more_vert" round flat @click="chatSettingsDialog = true"></q-btn> <q-btn icon="more_vert" round flat @click="chatSettingsDialog = true"></q-btn>
<ChatSettingsComponent :chat="selectedChat" :chatID="selectedChat.id" :showDialog="chatSettingsDialog" @showModal="() => chatSettingsDialog = false"></ChatSettingsComponent> <ChatSettingsComponent :chat="selectedChat" :chatID="selectedChat.id" :showDialog="chatSettingsDialog" @showModal="() => chatSettingsDialog = false" :leaveChat="resetSelectedChat"></ChatSettingsComponent>
</div> </div>
</div> </div>
@@ -124,40 +111,19 @@
<div class="text-h6">Wähle einen Chat aus</div> <div class="text-h6">Wähle einen Chat aus</div>
</div> </div>
<div v-if="selectedChat.id != null" class="main" style="overflow-y: auto;display: flex;flex-direction: column-reverse;">
<div v-if="selectedUser.id != null && selectedChat.members.length == 2" id="messageContainer" style="padding: 25px;overflow-y: auto;height: 100%;display: flex;flex-direction: column-reverse;"> <div v-if="selectedChat.id == -1" style="text-align: center;">
<div v-if="!hasChatWith(selectedUser.id)" style="width: 100%; text-align: center;font-size: 1.2rem;"> <div class="text-h5" style="font-weight: normal;">Das ist der Beginn deines Chats mit <b>{{ selectedChat.displayName }}</b></div>
<p>Das ist der Beginn deines Chats mit <strong>{{selectedUser.firstName}} {{selectedUser.lastName}}</strong></p>
</div>
<div v-else>
<p>Du hast schonmal mit dem geschrieben wie schön</p>
</div>
</div>
<div v-if="selectedChat.id != null" style="padding: 75px 25px 0 25px;overflow-y: auto;height: 90%;display: flex;flex-direction: column-reverse;">
<div v-for="(message, index) in (messageStore.messages[selectedChat.id])" :key="index">
<MessageComponent
v-if="message.gif || message.link"
:url="message.content"
:timestamp="message.timestamp"
:own-message="message.sender.id == userStore.user!.id"
:link="message.link"
:gif="message.gif"
:showSender="selectedChat.members.length > 2 && (index == messageStore.messages[selectedChat.id].length - 1 || messageStore.messages[selectedChat.id][index+1].sender!.id != message.sender.id)"
></MessageComponent>
<MessageComponent
v-if="!message.gif && !message.link"
:message="message.content"
:nametagColor="chatStore.chats.find((chat) => chat.id == selectedChat.id)!.members.find((member) => member.id === message.sender.id)?.nametagColor!"
:timestamp="message.timestamp"
:own-message="message.sender.id == userStore.user!.id"
:sender="message.sender"
:showSender="selectedChat.members.length > 2 && (index == messageStore.messages[selectedChat.id].length - 1 || messageStore.messages[selectedChat.id][index+1].sender!.id != message.sender.id)"
></MessageComponent>
</div> </div>
<MessageComponent
v-for="(message, index) in (messageStore.messages[selectedChat.id])"
:key="index"
:message="message"
:nametagColor="!chatStore.userColors[message.sender.username] ? 'black' : chatStore.userColors[message.sender.username]"
:own-message="message.sender.id == userStore.user!.id"
:showSender="selectedChat.groupChat && (index == messageStore.messages[selectedChat.id].length - 1 || messageStore.messages[selectedChat.id][index+1].sender!.id != message.sender.id)"
></MessageComponent>
</div> </div>
<div class="footer"> <div class="footer">
@@ -196,13 +162,11 @@
</div> </div>
</div> </div>
<!-- GIF SUCHE ENDE --> <!-- GIF SUCHE ENDE -->
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue'; import { ref } from 'vue';
import MessageComponent from 'components/MessageComponent.vue'; import MessageComponent from 'components/MessageComponent.vue';
import { useTenorStore } from 'stores/tenor-store'; import { useTenorStore } from 'stores/tenor-store';
import ProfileCardComponent from 'components/ProfileCardComponent.vue'; import ProfileCardComponent from 'components/ProfileCardComponent.vue';
@@ -216,8 +180,8 @@
import ChatSettingsComponent from 'src/components/ChatSettingsComponent.vue'; import ChatSettingsComponent from 'src/components/ChatSettingsComponent.vue';
import CreateChatComponent from 'src/components/CreateChatComponent.vue'; import CreateChatComponent from 'src/components/CreateChatComponent.vue';
import { useMessageStore } from 'src/stores/message-store'; import { useMessageStore } from 'src/stores/message-store';
import { useNotificationStore } from 'src/stores/notification-store'; import { sendNotification, getFilteredUserList } from 'src/utils';
import { sendNotification } from 'src/utils'; import { Notify } from 'quasar';
defineOptions({ defineOptions({
name: 'IndexPage' name: 'IndexPage'
@@ -226,14 +190,10 @@
const tenorStore = useTenorStore(); const tenorStore = useTenorStore();
const userStore = useUserStore(); const userStore = useUserStore();
const chatStore = useChatStore(); const chatStore = useChatStore();
const notificationStore = useNotificationStore();
const searchQuery = ref("") const searchQuery = ref("")
const message = ref(""); const message = ref("");
const gifSearch = ref(""); const gifSearch = ref("");
const chatName = ref('');
const userSearch = ref('');
const usersForChat = ref([] as string[]);
const messageStore = useMessageStore(); const messageStore = useMessageStore();
@@ -241,13 +201,23 @@
const chatSettingsDialog = ref(false); const chatSettingsDialog = ref(false);
const createChatDialog = ref(false); const createChatDialog = ref(false);
const showGifMenu = ref(false);
const activeMenu = ref("chats"); // Speichert, ob der Suchbegriff im GIF-Menü sich geändert hat
// Wenn nicht, wird keine neue Anfrage an Tenor geschickt
const gifSearchQueryChanged = ref(true);
const activeMenu = ref("search");
const selectedUser = ref({} as ptpUser);
const selectedChat = ref({} as chat);
let loadedGifsAmount = 10; let loadedGifsAmount = 10;
let messages : message[] = [ const selectTab = (tab : string) => {
]; activeMenu.value = tab;
getMessages();
}
const getChatPreviewMessage = (chatId : number) => { const getChatPreviewMessage = (chatId : number) => {
const messageArray : message[] = messageStore.messages[chatId]; const messageArray : message[] = messageStore.messages[chatId];
@@ -259,7 +229,7 @@
let messageContent = newestMessage.content; let messageContent = newestMessage.content;
let userDescriptor = newestMessage.sender.firstName; let userDescriptor = newestMessage.sender.firstName;
if(isTenorGifUrl(newestMessage.content)) { if(newestMessage.gif) {
messageContent = "GIF"; messageContent = "GIF";
} }
@@ -274,46 +244,16 @@
} }
} }
function isUrl(url : string) {
try {
new URL(url);
return true;
} catch (err) {
return false;
}
}
function isTenorGifUrl(url : string) {
return url.startsWith("https://media.tenor.com/") && url.endsWith(".gif");
}
messages.forEach((msg) => {
if(isTenorGifUrl(msg.content)) {
msg.gif = true;
} else if(isUrl(msg.content)) {
msg.link = true;
}
})
const selectedGifURL = ref("");
const showGifMenu = ref(false);
const changed = ref(true);
function inputChanged() { function inputChanged() {
changed.value = true; gifSearchQueryChanged.value = true;
} }
function getGifs() { function getGifs() {
let query = gifSearch.value != "" ? gifSearch.value : "the office"; let query = gifSearch.value != "" ? gifSearch.value : "the office";
if(changed.value) { if(gifSearchQueryChanged.value) {
tenorStore.getGifsBySearchTerm(query, loadedGifsAmount); tenorStore.getGifsBySearchTerm(query, loadedGifsAmount);
changed.value = false; gifSearchQueryChanged.value = false;
} }
} }
@@ -354,7 +294,7 @@
function loadMoreGifs() { function loadMoreGifs() {
loadedGifsAmount += 10; loadedGifsAmount += 10;
console.log(loadedGifsAmount); console.log(loadedGifsAmount);
changed.value = true; gifSearchQueryChanged.value = true;
} }
function getMessages() { function getMessages() {
@@ -368,10 +308,30 @@
await chatStore.loadChatsByUser(); await chatStore.loadChatsByUser();
}; };
const updateChatList = () => {
chatStore.loadChatsByUser().then(() => {
// Hier wird geprüft, ob man immer noch Mitglied des momentan ausgewählten Chats ist
if(chatStore.chats.find((chat) => chat.id == selectedChat.value.id) == null && selectedChat.value.id && selectedChat.value.groupChat) {
Notify.create({
message: "Der Chat '"+selectedChat.value.displayName+"' existiert nicht mehr, oder du bist kein Teil mehr davon",
position: 'top',
color: "red",
icon: "warning",
actions: [
{ icon: 'close', color: 'white', round: true, handler: () => { /* ... */ } }
]
});
chatSettingsDialog.value = false;
selectedChat.value = {} as chat;
}
});
}
setInterval(getMessages, 5000); setInterval(getMessages, 5000);
setInterval(chatStore.loadChatsByUser, 5000); setInterval(updateChatList, 5000);
setInterval(getGifs, 500); setInterval(getGifs, 500);
userStore.getAllPtpUsers(); userStore.getAllPtpUsers();
@@ -380,29 +340,40 @@
await chatStore.loadChatsByUser(); await chatStore.loadChatsByUser();
}; };
const selectedUser = ref({} as ptpUser);
const selectedChat = ref({} as chat);
const resetSelectedChat = () => { const resetSelectedChat = () => {
//console.log("HAHHSHHDSD");
selectedChat.value = {} as chat selectedChat.value = {} as chat
} }
function selectUser(user : ptpUser) { function selectUser(user : ptpUser) {
selectedUser.value = user; // Hier wird zuerst geprüft, ob es bereits einen Chat mit dem Nutzer gibt
//selectedChat.value = {} as chat; // Wenn ja, wird dieser Chat ausgewählt
// CHECKEN, OB ES SCHON EINEN CHAT GIBT // Wenn nein, wird ein Dummy-Objekt erstellt
const exampleChat: chat = {
id: -1, let chatExists = false;
members: [user, userStore.user!],
messages: [], chatStore.chats.forEach((chat) => {
displayName: user.firstName + " " + user.lastName, if(!chat.groupChat && chat.members.find((member) => member.id == user.id)) {
profilepicture: user.profilepicture, selectedChat.value = chat;
profilepictureAsString: user.profilepicture, getMessages();
groupChat: false, chatExists = true;
adminIds: [] }
}; });
selectedChat.value = exampleChat;
if(!chatExists) {
selectedUser.value = user;
const exampleChat: chat = {
id: -1,
members: [user, userStore.user!],
messages: [],
displayName: user.firstName + " " + user.lastName,
profilepicture: user.profilepicture,
groupChat: false,
adminIds: []
};
selectedChat.value = exampleChat;
}
} }
function selectChat(chat : chat) { function selectChat(chat : chat) {
@@ -416,25 +387,11 @@
messageStore.loadMessagesByChatID(chat.id); messageStore.loadMessagesByChatID(chat.id);
} }
function hasChatWith(id : string) : boolean {
// Hier wird geprüft, ob es ein Chat mit einem bestimmten anderen Nutzer existiert
const chatExists = chatStore.chats.find((chat) => chat.members.length === 2 && chat.members.find((member) => member.id === id));
return Boolean(chatExists);
}
// Init methods // Init methods
loadChats(); loadChats();
const user = ref();
onMounted(() => {
user.value = userStore.user;
})
</script> </script>
<style scoped> <style scoped>
@@ -457,24 +414,6 @@
filter: brightness(0.5); filter: brightness(0.5);
} }
.footer {
width: 75%;
height:auto;
background: gray;
position: absolute;
bottom:0;
padding: 10px;
}
.header {
width: 75%;
height: auto;
background: gray;
position: fixed;
right: 0;
top: 0;
}
.navigation { .navigation {
font-size: 2rem; font-size: 2rem;
} }
@@ -503,4 +442,55 @@
font-weight: bold; font-weight: bold;
margin-bottom: 0; margin-bottom: 0;
} }
</style>
/* CSS GRID THINGS */
.container {
display: grid;
color: #111111;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"menu header"
"menu main"
"menu footer";
height: 97vh;
background: #FFF5E1;
margin: 1.5vh 20px;
border-radius: var(--defaultGridBorderRadius);
}
.menu {
grid-area: menu;
background-color: var(--defaultElementBackground);
color: black;
border-radius: 1rem 0 0 1rem;
width: 30rem;
}
.header {
grid-area: header;
background-color: var(--defaultElementBackground);
color: black;
padding: 10px;
border-top-right-radius: 1rem;
margin-bottom: 0;
}
.main {
grid-area: main;
background-color: transparent;
padding: 20px;
overflow-y: auto;
border-radius: var(--defaultGridBorderRadius);
margin-bottom: 0;
}
.footer {
grid-area: footer;
background-color: var(--defaultElementBackground);
color: black;
padding: 10px;
border-bottom-right-radius: 1rem;
}
</style>

View File

@@ -5,13 +5,18 @@ import { useUserStore } from 'stores/user-store';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { getRandomHexColor } from 'src/utils'; import { getRandomHexColor } from 'src/utils';
interface StringKeyValue {
[key: string]: string;
}
export const useChatStore = defineStore('chatStore',{ export const useChatStore = defineStore('chatStore',{
state:() => ({ state:() => ({
chats: [] as chat[], chats: [] as chat[],
chatsLoaded: false, chatsLoaded: false,
chatExistsReponse: false, chatExistsReponse: false,
profilePictures : [] as profilePicture[], profilePictures : [] as profilePicture[],
profilePicturesLoaded: false profilePicturesLoaded: false,
userColors: {} as StringKeyValue
}), }),
actions: { actions: {
async getChatsByUser(): Promise<chat[]> { async getChatsByUser(): Promise<chat[]> {
@@ -25,9 +30,11 @@ export const useChatStore = defineStore('chatStore',{
this.chats.forEach((c) => { this.chats.forEach((c) => {
c.members.forEach((member) => { c.members.forEach((member) => {
member.nametagColor = getRandomHexColor(); if(!this.userColors[member.username]){
this.userColors[member.username] = getRandomHexColor();
}
}) })
}) });
this.chatsLoaded = true; this.chatsLoaded = true;
return this.chats; return this.chats;
@@ -57,17 +64,35 @@ export const useChatStore = defineStore('chatStore',{
const updatedChat : chat = {...chat}; const updatedChat : chat = {...chat};
console.log("UpdatedChatUrl: " + updatedChat.profilepicture);
this.profilePictures.forEach((element: profilePicture) => { this.profilePictures.forEach((element: profilePicture) => {
if(element.url === updatedChat.profilepicture){ if(element.url === updatedChat.profilepicture){
updatedChat.profilepicture = element.shortName; updatedChat.profilepicture = element.shortName;
} }
}); });
return (await api.post("/chats/update", chat)).data; return (await api.post("/chats/update", updatedChat)).data;
},
async addAdmin(chatID: number, keycloakID: string){
return (await api.post("/chats/addAdmin/"+chatID+"/"+keycloakID)).data;
},
async removeAdmin(chatID: number, keycloakID: string){
return (await api.post("/chats/removeAdmin/"+chatID+"/"+keycloakID)).data;
}, },
async removeUserFromChat(keycloakID : string, chatID : number) { async removeUserFromChat(keycloakID : string, chatID : number) {
return (await api.delete("/chats/removeUser/"+chatID+"/"+keycloakID)).data; return (await api.delete("/chats/removeUser/"+chatID+"/"+keycloakID)).data;
},
async addUserToChat(keycloakID : string, chatID : number) {
return (await api.post("/chats/addUser/"+chatID+"/"+keycloakID)).data;
},
async deleteChat(chatID : number) {
return (await api.delete("/chats/"+chatID)).data;
} }
} }
}); });

View File

@@ -44,7 +44,7 @@ export const useUserStore = defineStore('userStore', {
this.user = response.response; this.user = response.response;
this.userLoaded = true; this.userLoaded = true;
}).catch(error => { }).catch(error => {
console.error("Ein Netzwerkfehler ist aufgetreten"); this.reconnectTries++;
}); });
}, },
async loadPtpUsersById(id : string) : Promise<responseModel> { async loadPtpUsersById(id : string) : Promise<responseModel> {
@@ -87,7 +87,6 @@ export const useUserStore = defineStore('userStore', {
}, },
async initKeycloak() : Promise<Keycloak>{ async initKeycloak() : Promise<Keycloak>{
try { try {
await keycloak.init(initOptions).then( (auth: boolean): void => { await keycloak.init(initOptions).then( (auth: boolean): void => {
console.log("Authenticated: " + auth); console.log("Authenticated: " + auth);
@@ -98,11 +97,13 @@ export const useUserStore = defineStore('userStore', {
this.keycloak = keycloak; this.keycloak = keycloak;
return keycloak; return keycloak;
}, },
async login() { async login() {
if(!this.keycloak){ if(!this.keycloak){
await this.initKeycloak(); await this.initKeycloak();
}else await this.keycloak?.login( {prompt: 'login'} as KeycloakLoginOptions); }else await this.keycloak?.login( {prompt: 'login'} as KeycloakLoginOptions);
}, },
async logout() { async logout() {
window.location.href= 'http://localhost:' + keycloakPort + '/realms/' + realm + '/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost:' + frontendPort + '&client_id=' + clientId; window.location.href= 'http://localhost:' + keycloakPort + '/realms/' + realm + '/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost:' + frontendPort + '&client_id=' + clientId;
}, },

View File

@@ -1,4 +1,5 @@
import { Notify } from "quasar"; import { Notify } from "quasar";
import { ptpUser } from "./models";
export const sendNotification = (message : string, successful : Boolean) => { export const sendNotification = (message : string, successful : Boolean) => {
Notify.create({ Notify.create({
@@ -53,4 +54,8 @@ export const getRandomHexColor = () => {
const saturation = 100; // Full saturation const saturation = 100; // Full saturation
const lightness = 30; // Adjust this value to set the desired lightness (0-100) const lightness = 30; // Adjust this value to set the desired lightness (0-100)
return hslToHex(hue, saturation, lightness); return hslToHex(hue, saturation, lightness);
}
export const getFilteredUserList = (searchQuery : string, userList : ptpUser[], ownID : string) => {
return userList.filter(u => (u.username.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase()) || (u.firstName + ' ' + u.lastName).toLocaleLowerCase().includes(searchQuery.trimStart().toLocaleLowerCase()) || searchQuery == '*') && u.id != ownID && searchQuery.replace(/\s/g, '').length);
} }