Worked on several new features:

- ProfilePictures for users are now pulled from the backend
- added a component for chat settings (WIP)
- added a loading screen when the backend is not available
- reworked the "add chat" feature
- started to implement the general send-message functionality
This commit is contained in:
joschuatonn
2024-05-25 13:37:50 +02:00
parent 176abf58b2
commit c8d5bc5734
12 changed files with 486 additions and 86 deletions

View File

@@ -1,46 +1,65 @@
<script setup lang="ts"> <script setup lang="ts">
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
import ProtectedData from './components/ProtectedData.vue'; import ProtectedData from './components/ProtectedData.vue';
import IndexPage from 'pages/IndexPage.vue'; import IndexPage from 'pages/IndexPage.vue';
defineOptions({ import { useUserStore } from './stores/user-store';
import { useQuasar } from 'quasar';
defineOptions({
name: 'App' name: 'App'
}); });
const userStore = useUserStore();
</script> </script>
<template> <template>
<IndexPage></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>
<q-spinner
color="primary"
size="3em"
/>
</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>
<router-view /> <router-view />
</template> </template>
<style> <style>
header { header {
line-height: 1.5; line-height: 1.5;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding-top: 7vh; padding-top: 7vh;
} }
/* CUSTOM SCROLLBAR DESIGN START */ /* CUSTOM SCROLLBAR DESIGN START */
/* width */ /* width */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 5px; width: 5px;
} }
/* Track */ /* Track */
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: white; background: white;
} }
/* Handle */ /* Handle */
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #888; background: #888;
} }
/* Handle on hover */ /* Handle on hover */
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: #555; background: #555;
} }
/* CUSTOM SCROLLBAR DESIGN ENDE */ /* CUSTOM SCROLLBAR DESIGN ENDE */
</style> </style>

View File

@@ -0,0 +1,83 @@
<template>
<q-dialog v-bind:model-value="internalShowDialog" @update:model-value="closeDialog" style="overflow: auto;" full-width>
<q-card style="margin: 3rem 20rem;height: 80vh;padding: 20px;overflow: auto !important;">
<q-card-section class="row items-center q-pb-none">
<div class="text-h4">{{ props.chat!.displayName }}</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<div class="grid-container">
<q-card-section>
<div class="text-h5">Mitglieder</div>
<table>
<tr v-for="user in props.chat!.members">
<td>
<ProfileCardComponent :user="user" :selected="false"></ProfileCardComponent>
</td>
<td v-if="user.id != userStore.user!.id">
<q-btn round flat icon="person_remove" style="color: darkred;" title="Nutzer entfernen"></q-btn>
<q-btn round flat icon="key" title="Nutzer zum Admin machen"></q-btn>
</td>
</tr>
</table>
</q-card-section>
<!-- Namen des Chats ändern -->
<q-card-section>
<div class="text-h5">Namen ändern</div>
<q-input filled v-model="props.chat!.displayName" maxlength="256" counter />
</q-card-section>
<q-card-section>
<div class="text-h5">Aktionen</div>
<q-btn class="deleteButton">Gruppenchat verlassen</q-btn>
<q-btn class="deleteButton">Grupepnchat löschen</q-btn>
</q-card-section>
</div>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import ProfileCardComponent from './ProfileCardComponent.vue';
import { useUserStore } from 'src/stores/user-store';
const showDialog = ref(true);
const userStore = useUserStore();
const emit = defineEmits(['update:showDialog', 'showModal']);
const props = defineProps({
showDialog: Boolean,
chat: Object
});
const internalShowDialog = computed({
get: () => props.showDialog,
set: (value) => emit('update:showDialog', value)
});
const closeDialog = () => {
emit('showModal', true);
}
</script>
<style scoped>
.deleteButton {
color:darkred;
border:2px solid darkred;
border-radius: 6px;
}
.grid-container {
display: grid;
grid-template-columns: auto auto;
padding: 10px;
}
.grid-container > div:nth-child(odd) {
margin-right: 50px;
min-width: 30vw;
}
</style>

View File

@@ -0,0 +1,189 @@
<template>
<q-dialog v-bind:model-value="internalShowDialog" @update:model-value="closeDialog" full-width>
<q-card style="padding: 20px;margin: 0 40rem;height: 80vh;">
<q-card-section class="row items-center q-pb-none">
<div class="text-h4">Chat erstellen</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<div style="display: flex;overflow-x: auto;height: 42px;">
<div v-for="user in usersForChat" style="background: #e0e0e0;border-radius: 10px;display: flex;padding: 3px;margin: 3px;">
<div
style="width: 30px; height: 30px; background-size: cover;background-position: center;border-radius: 100%;"
:style="{'background-image' : 'url('+user.profilePictureUrl+')'}"
></div>
<div class="text-body" style="margin: 3px 10px 0 10px;white-space: nowrap;">{{ user.firstName }} {{ user.lastName }}</div>
<q-btn round flat icon="close" style="font-size: 10px;" @click="handleClickEvent(user)"/>
</div>
</div>
<q-input
filled
v-model="chatName"
placeholder="Name des Chats"
style="margin: 20px 0;"
>
<template v-slot:prepend>
<q-icon name="edit"></q-icon>
</template>
</q-input>
<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: 50%;">
<div v-for="(user, index) in searchResults" class="grid-container">
<div class="wrapper-div">
<q-btn
round
flat
dense
:icon="isUserSelected(user) ? 'remove' : 'add'"
:title="isUserSelected(user) ? 'Entfernen' : 'Hinzufügen'"
style="width:3rem;height:3rem;"
class="button"
@click="handleClickEvent(user)"
/>
</div>
<profile-card-component :user="user" :selected="false"></profile-card-component>
</div>
</div>
<q-btn style="width: 100%;" @click="createChat">Chat erstellen</q-btn>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import ProfileCardComponent from './ProfileCardComponent.vue';
import { useUserStore } from 'src/stores/user-store';
import type { ptpUser } from 'src/models';
import { useQuasar } from 'quasar';
import { useChatStore } from 'src/stores/chat-store';
const showDialog = ref(true);
const userStore = useUserStore();
const searchQuery = ref("");
const chatName = ref("");
const usersForChat = ref([] as ptpUser[]);
const searchResults = ref([] as ptpUser[]);
const $q = useQuasar();
const emit = defineEmits(['update:showDialog', 'showModal']);
const props = defineProps({
showDialog: Boolean,
chat: Object
});
const internalShowDialog = computed({
get: () => props.showDialog,
set: (value) => emit('update:showDialog', value)
});
const closeDialog = () => {
emit('showModal', true);
}
const updateSearchResults = () => {
searchResults.value = userStore.users.filter((u) => u.username.toLocaleLowerCase().includes(searchQuery.value.toLocaleLowerCase()) && u.id != userStore.user?.id);
}
const isUserSelected = (user : ptpUser) => {
return usersForChat.value.indexOf(user) >= 0;
}
const handleClickEvent = (user : ptpUser) => {
if(isUserSelected(user)) {
const index = usersForChat.value.indexOf(user);
usersForChat.value.splice(index, 1);
} else {
usersForChat.value.push(user);
}
}
const createChat = () => {
let errorOccured = false;
if(usersForChat.value.length == 0) {
$q.notify({
message: 'Du hast keine Mitglieder ausgewählt',
position: 'top',
timeout: 2000,
color: "red",
icon: 'warning'
});
errorOccured = true;
}
if(chatName.value == "") {
$q.notify({
message: 'Du hast keinen Namen festgelegt',
position: 'top',
timeout: 2000,
color: "red",
icon: 'warning'
});
errorOccured = true;
}
if(!errorOccured) {
$q.notify({
message: 'Der Chat "'+chatName.value+'" wurde erstellt',
position: 'top',
timeout: 2000,
color: "green",
type: "positive"
});
setupChat(chatName.value);
closeDialog();
chatName.value = "";
usersForChat.value = [] as ptpUser[];
}
}
const chatStore = useChatStore();
const setupChat = async(chatName: string)=>{
await chatStore.createChat(chatName, usersForChat.value.map((user) => user.id));
reloadChats();
}
const reloadChats = async() => {
await chatStore.loadChatsByUser();
};
</script>
<style scoped>
.grid-container {
display: grid;
grid-template-columns: 70px auto;
}
.grid-container > div:nth-child(even) {
min-width: 20rem;
}
.wrapper-div {
height:100%;
position: relative;
}
.button {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
</style>

View File

@@ -4,7 +4,7 @@
:style="{'background-image' : 'url('+props.image+')' }" :style="{'background-image' : 'url('+props.image+')' }"
class="image" class="image"
></div> ></div>
<h6 class="displayName">{{ props.displayName }}</h6> <h6 class="displayName" :title="props.displayName">{{ props.displayName }}</h6>
</div> </div>
</template> </template>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { decodedTextSpanIntersectsWith } from 'typescript';
const props = defineProps({ const props = defineProps({
ownMessage: Boolean, ownMessage: Boolean,
timestamp: String, timestamp: String,
@@ -7,6 +9,9 @@
link: Boolean, link: Boolean,
url: String, url: String,
}) })
const date = new Date(Number(props.timestamp));
const timestampFormatted = date.getDay() + "." + date.getMonth() + "." + date.getFullYear() + ", " + date.getHours() + ":" + date.getMinutes();
</script> </script>
<template> <template>
@@ -16,7 +21,7 @@
<a v-if="props.link" :href="props.url" target="_blank">{{props.url}}</a> <a v-if="props.link" :href="props.url" target="_blank">{{props.url}}</a>
<div style="text-align: right;"> <div style="text-align: right;">
<small style="margin-top: 0;">{{props.timestamp}}</small> <small style="margin-top: 0;">{{timestampFormatted}}</small>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-dialog v-model="internalShowDialog" backdrop-filter="brightness(60%)" full-width> <q-dialog v-bind:model-value="internalShowDialog" @update:model-value="closeDialog" backdrop-filter="brightness(60%)" full-width>
<q-card style="margin: 3rem 20rem;padding: 30px;height: 80vh;overflow-y:scroll" v-if="userStore.userLoaded"> <q-card style="margin: 3rem 20rem;padding: 30px;height: 80vh;overflow-y:scroll" v-if="userStore.userLoaded">
<!--<h1>{{ internalShowDialog }}</h1>--> <!--<h1>{{ internalShowDialog }}</h1>-->
@@ -37,19 +37,25 @@
<h5>Profilbild</h5> <h5>Profilbild</h5>
<p>Du kannst eins der folgenden Porfilbilder aussuchen</p> <p>Du kannst eins der folgenden Porfilbilder aussuchen</p>
<div style="display: flex;"> <div style="display: flex;" v-if="userStore.profilePicturesLoaded">
<div <div
v-for="(imageUrl, index) in profilePictures" v-for="(image, index) in userStore.profilePictures"
:style="{'background-image' : 'url('+imageUrl+')' , 'outline' : imageUrl == userStore.user!.profilePictureUrl ? 'dashed grey' : 'none'}" :style="{'background-image' : 'url('+image.url+')' , 'outline' : image.url == userStore.user!.profilePictureUrl ? 'dashed grey' : 'none'}"
style="color: red;" style="color: red;"
round round
flat flat
outline outline
class="profilePictureSelector" class="profilePictureSelector"
@click="selectProfilePicture(index)" @click="selectProfilePicture(image)"
> >
</div> </div>
</div> </div>
<div v-else>
<q-spinner
color="primary"
size="3em"
/>
</div>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
@@ -73,7 +79,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import {message, ptpTestUser} from 'src/models'; import {profilePicture, message, ptpTestUser} from 'src/models';
import { useUserStore } from 'src/stores/user-store'; import { useUserStore } from 'src/stores/user-store';
import { computed, onMounted, toRefs } from 'vue'; import { computed, onMounted, toRefs } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
@@ -82,7 +88,7 @@
const showingAlert = ref(false); const showingAlert = ref(false);
const $q = useQuasar(); const $q = useQuasar();
const emit = defineEmits(['update:showDialog']); const emit = defineEmits(['update:showDialog', 'showModal']);
const props = defineProps({ const props = defineProps({
showDialog: Boolean showDialog: Boolean
}); });
@@ -92,18 +98,9 @@
set: (value) => emit('update:showDialog', value) set: (value) => emit('update:showDialog', value)
}); });
// Eine Liste der möglichen Bilder soll später aus dem Backend kommen function selectProfilePicture(image : profilePicture) {
let profilePictures : string[] = [
"https://assets.change.org/photos/8/jw/ax/QMjwAxeQAfcpoxs-1600x900-noPad.jpg?1597557421",
"https://media.tenor.com/fErt1YElZ2gAAAAe/shrek-rizz-reverse.png",
"https://i.pinimg.com/736x/8b/08/d0/8b08d0127947da23310a14135b865f61.jpg",
"https://th.bing.com/th/id/R.487fe8708797950ab745a3800c31b7a4?rik=qW3zkDdZfweqmQ&pid=ImgRaw&r=0",
"https://media.discordapp.net/attachments/698162708427833355/1241378744032297041/image.png?ex=6649fb8c&is=6648aa0c&hm=2024b84dc61ec55d1923ff2e517af250d8e63cf599fcc223d014801adfe5b4e0&=&format=webp&quality=lossless&width=416&height=350"
]
function selectProfilePicture(id : number) {
showUnsavedChangesAlert(); showUnsavedChangesAlert();
userStore.user!.profilePictureUrl = profilePictures[id]; userStore.user!.profilePictureUrl = image.url;
} }
function saveProfileChanges() { function saveProfileChanges() {
@@ -116,7 +113,8 @@
}; };
const closeDialog = () => { const closeDialog = () => {
internalShowDialog.value = false; //internalShowDialog.value = false;
emit('showModal', true);
}; };
const showUnsavedChangesAlert = () => { const showUnsavedChangesAlert = () => {

View File

@@ -5,6 +5,8 @@ import App from './App.vue';
import Keycloak from 'keycloak-js'; import Keycloak from 'keycloak-js';
import {useUserStore} from 'stores/user-store'; import {useUserStore} from 'stores/user-store';
import { useQuasar } from 'quasar';
import { useChatStore } from './stores/chat-store';
const app = createApp(App); const app = createApp(App);
const pinia = createPinia(); const pinia = createPinia();
@@ -13,16 +15,41 @@ app.use(pinia);
const userStore = useUserStore(); const userStore = useUserStore();
const chatStore = useChatStore();
const keycloak: Keycloak = await userStore.initKeycloak(); const keycloak: Keycloak = await userStore.initKeycloak();
const $q = useQuasar();
let tries = 1;
// Anfrage ans Backend // Anfrage ans Backend
if (keycloak.subject) { if (keycloak.subject) {
userStore.id = keycloak.subject; userStore.id = keycloak.subject;
await userStore.getPtpUserById(keycloak.subject); await userStore.getPtpUserById(keycloak.subject);
userStore.loadPtpUsersById(keycloak.subject).then(user => { userStore.loadPtpUsersById(keycloak.subject).then(user => {
console.log(JSON.stringify(user)); console.log(JSON.stringify(user));
}) })
const interval = setInterval(() => {
if(userStore.userLoaded) clearInterval(interval);
userStore.getPtpUserById(keycloak.subject!).then(user => {
console.log("New attempt: " + JSON.stringify(user));
console.warn(userStore.userLoaded);
}).catch(error => {
console.error("Ein Netzwerkfehler ist aufgetreten " + tries);
userStore.reconnectTries = tries;
tries++;
});
}, 5000);
chatStore.loadProfilePictures();
userStore.loadProfilePictures();
} else { } else {
console.error("hier ist was schlimmes passiert menno"); console.error("hier ist was schlimmes passiert menno");
} }

View File

@@ -70,3 +70,8 @@ export interface tenorResponseElement {
export interface tenorResponse { export interface tenorResponse {
results: Array<tenorGif>, results: Array<tenorGif>,
} }
export interface profilePicture {
shortName: string;
url: string;
}

View File

@@ -2,7 +2,9 @@
<div style="width:100%;display: flex; align-items: center; justify-content: space-between;height:100vh;position: fixed;"> <div style="width:100%;display: flex; align-items: center; justify-content: space-between;height:100vh;position: fixed;">
<!-- Hier gibt es die ganzen PopUps und so --> <!-- Hier gibt es die ganzen PopUps und so -->
<SettingsPopUp :showDialog="settingsDialog"/> <SettingsPopUp :showDialog="settingsDialog" @showModal="() => settingsDialog = false"/>
<CreateChatComponent :showDialog="createChatDialog" @showModal="() => createChatDialog = false"></CreateChatComponent>
<div style="background: gray; width: 25%;height:100%;"> <div style="background: gray; width: 25%;height:100%;">
<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">
@@ -23,7 +25,7 @@
</template> </template>
</q-input> </q-input>
<div v-if="userStore.usersLoaded && 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 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"
:profilePictureUrl="user.profilePictureUrl" :profilePictureUrl="user.profilePictureUrl"
@@ -41,9 +43,10 @@
</div> </div>
<div style="margin: 20px;" v-if="activeMenu == 'chats'"> <div style="margin: 20px;" v-if="activeMenu == 'chats'">
<q-btn round flat icon="group_add" style="float:right;" @click="createChatDialog = true"></q-btn>
<p>Chats, in denen du Mitglied bist:</p> <p>Chats, in denen du Mitglied bist:</p>
<div v-if="chatStore.chatsLoaded"> <div v-if="chatStore.chatsLoaded" style="padding-top: 10px;">
<div v-for="chat in chatStore.chats" :key="chat.id"> <div v-for="chat in chatStore.chats" :key="chat.id" style="margin-top: 10px;">
<ChatCardComponent <ChatCardComponent
v-if="chat.members.length > 2" v-if="chat.members.length > 2"
@@ -70,7 +73,7 @@
</div> </div>
<br> <!--<br>
<q-input <q-input
v-model="chatName" v-model="chatName"
label="Chat Name" label="Chat Name"
@@ -101,7 +104,7 @@
> >
</q-option-group> </q-option-group>
</div> </div>
<q-btn @click="createChat(chatName)" label="Create Chat"/> <q-btn @click="createChat(chatName)" label="Create Chat"/>-->
</div> </div>
<q-btn <q-btn
@@ -139,9 +142,11 @@
</p> </p>
</div> </div>
<!--<ChatSettingsComponent :chat="selectedChat"></ChatSettingsComponent>--> <q-space />
</div> <q-btn icon="more_vert" round flat @click="chatSettingsDialog = true"></q-btn>
<ChatSettingsComponent :chat="selectedChat" :showDialog="chatSettingsDialog" @showModal="() => chatSettingsDialog = false"></ChatSettingsComponent>
</div>
</div> </div>
@@ -152,7 +157,7 @@
<div v-else> <div v-else>
<p>Du hast schonmal mit dem geschrieben wie schön</p> <p>Du hast schonmal mit dem geschrieben wie schön</p>
<div v-for="(message, index) in messages" :key="index"> <div v-for="(message, index) in (messageStore.messages[selectedChat.id])" :key="index">
<MessageComponent <MessageComponent
v-if="message.gif" v-if="message.gif"
:url="message.content" :url="message.content"
@@ -179,6 +184,33 @@
</div> </div>
</div> </div>
<div v-if="selectedChat.id != null" style="padding: 25px;overflow-y: auto;height: 90%;display: flex;flex-direction: column-reverse;">
<div v-for="(message, index) in (messageStore.messages[selectedChat.id].sort((a, b) => {return (Number(b.timestamp)-Number(a.timestamp))}))" :key="index">
<MessageComponent
v-if="message.gif"
:url="message.content"
:timestamp="message.timestamp"
:own-message="message.isOwn"
gif
></MessageComponent>
<MessageComponent
v-if="!message.gif && message.link"
:url="message.content"
:timestamp="message.timestamp"
:own-message="message.isOwn"
link
></MessageComponent>
<MessageComponent
v-if="!message.gif && !message.link"
:message="message.content"
:timestamp="message.timestamp"
:own-message="message.sender.id == userStore.user!.id"
></MessageComponent>
</div>
</div>
<div class="footer"> <div class="footer">
<q-input filled v-model="message" placeholder="Nachricht eingeben" tabindex="0" @keyup.enter.prevent="sendMessage"> <q-input filled v-model="message" placeholder="Nachricht eingeben" tabindex="0" @keyup.enter.prevent="sendMessage">
@@ -233,6 +265,8 @@
import { useChatStore } from 'stores/chat-store'; import { useChatStore } from 'stores/chat-store';
import SettingsPopUp from 'src/components/SettingsPopUp.vue'; import SettingsPopUp from 'src/components/SettingsPopUp.vue';
import ChatSettingsComponent from 'src/components/ChatSettingsComponent.vue'; import ChatSettingsComponent from 'src/components/ChatSettingsComponent.vue';
import CreateChatComponent from 'src/components/CreateChatComponent.vue';
import { useMessageStore } from 'src/stores/message-store';
defineOptions({ defineOptions({
name: 'IndexPage' name: 'IndexPage'
@@ -249,7 +283,11 @@
const userSearch = ref(''); const userSearch = ref('');
const usersForChat = ref([] as string[]); const usersForChat = ref([] as string[]);
const messageStore = useMessageStore();
const settingsDialog = ref(false); const settingsDialog = ref(false);
const chatSettingsDialog = ref(false);
const createChatDialog = ref(false);
const activeMenu = ref("search"); const activeMenu = ref("search");
@@ -328,12 +366,12 @@
} }
function sendMessage() { function sendMessage() {
//messages.unshift({content: message.value, timestamp: new Date().toTimeString().split(' ')[0].substring(0, 5), isOwn: true, gif: false, link: false}); messageStore.sendMessage(selectedChat.value.id, userStore.user!.id, message.value);
message.value = ""; message.value = "";
} }
function sendGif(url : string) { function sendGif(url : string) {
//messages.unshift({content: url, timestamp: new Date().toTimeString().split(' ')[0].substring(0, 5), isOwn: true, gif: true, link: false}); messageStore.sendMessage(selectedChat.value.id, userStore.user!.id, url);
toggleGifMenu(); toggleGifMenu();
} }
@@ -348,15 +386,6 @@
userStore.getAllPtpUsers(); userStore.getAllPtpUsers();
const createChat = async(chatName: string)=>{
if(chatName == ""){
chatName = "Chat";
}
await chatStore.createChat(chatName, usersForChat.value);
loadChats();
}
const loadChats = async() => { const loadChats = async() => {
await chatStore.loadChatsByUser(); await chatStore.loadChatsByUser();
}; };
@@ -372,6 +401,12 @@
function selectChat(chat : chat) { function selectChat(chat : chat) {
selectedChat.value = chat; selectedChat.value = chat;
selectedUser.value = {} as ptpTestUser; selectedUser.value = {} as ptpTestUser;
if(messageStore.messages[chat.id] == null) {
messageStore.messages[chat.id] = [] as message[];
}
messageStore.loadMessagesByChatID(chat.id);
} }
function hasChatWith(id : string) : boolean { function hasChatWith(id : string) : boolean {

View File

@@ -8,6 +8,8 @@ export const useChatStore = defineStore('chatStore',{
chats: [] as chat[], chats: [] as chat[],
chatsLoaded: false, chatsLoaded: false,
chatExistsReponse: false, chatExistsReponse: false,
profilePictures : [] as string[],
profilePicturesLoaded: false
}), }),
actions: { actions: {
async getChatsByUser(): Promise<chat[]> { async getChatsByUser(): Promise<chat[]> {
@@ -21,6 +23,10 @@ export const useChatStore = defineStore('chatStore',{
this.chatsLoaded = true; this.chatsLoaded = true;
return this.chats; return this.chats;
}, },
async loadProfilePictures(){
this.profilePictures = (await api.get("/users/profilePictures")).data;
this.profilePicturesLoaded = true;
},
async createChat(chatName: string, members: string[]){ async createChat(chatName: string, members: string[]){
const userStore = useUserStore(); const userStore = useUserStore();
if(userStore.id) { if(userStore.id) {
@@ -34,13 +40,6 @@ export const useChatStore = defineStore('chatStore',{
} }
console.log(members); console.log(members);
return (await api.post("/chats/create/" + chatName, members)).data; return (await api.post("/chats/create/" + chatName, members)).data;
},
async loadDoesChatExist(keycloakID : string, otherKeycloakID : string) : Promise<responseModel> {
return (await api.get("/chats/"+keycloakID+"/"+otherKeycloakID)).data;
},
doesChatExist(keycloakID : string, otherKeycloakID : string) {
this.loadDoesChatExist(keycloakID, otherKeycloakID)
.then(m => this.chatExistsReponse = m.success);
} }
} }
}); });

View File

@@ -0,0 +1,28 @@
import { defineStore } from 'pinia';
import { api } from 'src/boot/axios';
import { message, ptpUser, responseModel } from 'src/models';
import Keycloak, { KeycloakInitOptions, KeycloakLoginOptions } from 'keycloak-js';
export const useMessageStore = defineStore('messageStore', {
state: () => ({
messages: [[]] as [message[]],
}),
actions: {
async sendMessage(chatId : number, senderId : string, content : string) {
await api.post("/messages/"+chatId+"/"+senderId+"/"+content).then((result) => {
this.loadMessagesByChatID(chatId);
});
},
loadMessagesByChatID(chatID : number) {
this.getAllMessagesByChatID(chatID).then((messages) => {
this.messages[chatID] = messages;
});
},
async getAllMessagesByChatID(chatID : number) {
return (await api.get("/messages/chat/"+chatID)).data;
},
},
});

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from 'src/boot/axios'; import { api } from 'src/boot/axios';
import { ptpUser, responseModel } from 'src/models'; import { profilePicture, ptpUser, responseModel } from 'src/models';
import Keycloak, { KeycloakInitOptions, KeycloakLoginOptions } from 'keycloak-js'; import Keycloak, { KeycloakInitOptions, KeycloakLoginOptions } from 'keycloak-js';
/** /**
@@ -34,6 +34,9 @@ export const useUserStore = defineStore('userStore', {
usersLoaded: false, usersLoaded: false,
keycloak: null as Keycloak | null, keycloak: null as Keycloak | null,
id: "", id: "",
reconnectTries: 0,
profilePictures: [] as profilePicture[],
profilePicturesLoaded: false
}), }),
actions: { actions: {
async getPtpUserById(id : string) { async getPtpUserById(id : string) {
@@ -48,11 +51,20 @@ export const useUserStore = defineStore('userStore', {
if(this.user!.status == null) { if(this.user!.status == null) {
this.user!.status = "Hey there, I am using ptpChat!"; this.user!.status = "Hey there, I am using ptpChat!";
} }
}).catch(error => {
console.error("Ein Netzwerkfehler ist aufgetreten");
}); });
}, },
async loadPtpUsersById(id : string) : Promise<responseModel> { async loadPtpUsersById(id : string) : Promise<responseModel> {
return (await api.get("/users/login/"+id)).data; return (await api.get("/users/login/"+id)).data;
}, },
async loadProfilePictures() {
(await api.get("/users/profilePictures")).data.forEach((element: string) => {
this.profilePictures.push({ shortName: element.split(" ")[0].replace(":", ""), url: element.split(" ")[1]});
console.log("Hallo ich mache hier was");
});
this.profilePicturesLoaded = true;
},
getAllPtpUsers() { getAllPtpUsers() {
this.loadAllPtpUsers().then((response : ptpUser[]) => { this.loadAllPtpUsers().then((response : ptpUser[]) => {
@@ -67,9 +79,9 @@ export const useUserStore = defineStore('userStore', {
user.status = "Hey there, I am using ptpChat!"; user.status = "Hey there, I am using ptpChat!";
} }
}) })
})
this.usersLoaded = true; this.usersLoaded = true;
})
}, },
async loadAllPtpUsers() { async loadAllPtpUsers() {