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:
21
src/App.vue
21
src/App.vue
@@ -2,13 +2,31 @@
|
|||||||
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';
|
||||||
|
import { useUserStore } from './stores/user-store';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
defineOptions({
|
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>
|
||||||
|
|
||||||
@@ -43,4 +61,5 @@ header {
|
|||||||
background: #555;
|
background: #555;
|
||||||
}
|
}
|
||||||
/* CUSTOM SCROLLBAR DESIGN ENDE */
|
/* CUSTOM SCROLLBAR DESIGN ENDE */
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
83
src/components/ChatSettingsComponent.vue
Normal file
83
src/components/ChatSettingsComponent.vue
Normal 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>
|
||||||
189
src/components/CreateChatComponent.vue
Normal file
189
src/components/CreateChatComponent.vue
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
27
src/main.ts
27
src/main.ts
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
28
src/stores/message-store.ts
Normal file
28
src/stores/message-store.ts
Normal 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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user