The user now has the ability to load more gifs | added a menu to the main chat-page to choose between the search tab and the chat list

This commit is contained in:
joschuatonn
2024-05-09 17:42:53 +02:00
parent 31240e9032
commit 50dea58b5a
5 changed files with 238 additions and 80 deletions

View File

@@ -4,14 +4,17 @@
timestamp: String,
message: String,
gif: Boolean,
link: Boolean,
url: String,
})
</script>
<template>
<div class="message" :class="{'sent' : props.ownMessage, 'received' : !props.ownMessage}">
<p v-if="!props.gif" style="margin-bottom: 0;">{{props.message}}</p>
<p v-if="!props.gif && !props.link" style="margin-bottom: 0;">{{props.message}}</p>
<img v-if="props.gif" :src="props.url" alt="GIF" style="width: 100%;border-radius: 10px;" />
<a v-if="props.link" :href="props.url" target="_blank">{{props.url}}</a>
<div style="text-align: right;">
<small style="margin-top: 0;">{{props.timestamp}}</small>
</div>
@@ -27,6 +30,11 @@
box-shadow: 1px 1px 5px 0 rgba(0,0,0,0.15);
}
.message p {
max-width: 100%;
overflow-wrap: break-word;
}
.sent {
background: #729EA1;
margin-left: auto;

View File

@@ -11,6 +11,14 @@ export interface responseModel {
response: any;
}
export interface message {
content: string;
timestamp: string;
isOwn: boolean;
gif: boolean;
link: boolean;
}
export interface tenorGif {
url: string,
duration: number,

View File

@@ -1,41 +1,73 @@
<template>
<div style="width:100%;display: flex; align-items: center; justify-content: space-between;height:100vh;">
<div style="background: gray; width: 30%;height:100%;padding: 20px;">
<div>
<div style="width:100%;display: flex; align-items: center; justify-content: space-between;height:100vh;position: fixed;">
<div style="background: gray; width: 25%;height:100%;">
<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'}">
<q-icon name="search"></q-icon>
</div>
<div title="Meine Chats anzeigen" @click="activeMenu = 'chats'" :class="{menuItemActive : activeMenu === 'chats', menuItemInactive : activeMenu !== 'chats'}">
<q-icon name="chat"></q-icon>
</div>
</div>
<div style="margin: 20px;" v-if="activeMenu == 'search'">
<q-input filled v-model="searchQuery" placeholder="Nach Nutzern suchen">
<template v-slot:prepend>
<q-icon name="search"></q-icon>
</template>
</q-input>
<p>{{searchQuery}}</p>
<ProfileCardComponent displayName="Max Mustermann" username="maxmustermann" profilePictureUrl="https://th.bing.com/th/id/OIP.ab5MRsPm1MXfNDVSMW6cjQHaHa?rs=1&pid=ImgDetMain" status="Hey there!"></ProfileCardComponent>
<div v-if="userStore.usersLoaded && searchQuery.length > 0" style="margin-top: 20px;">
<ProfileCardComponent v-for="user in userStore.users.filter(u => u.username.includes(searchQuery) || searchQuery == '*')" :displayName="user.firstName + ' ' + user.lastName" :username="user.username" profilePictureUrl="https://th.bing.com/th/id/OIP.ab5MRsPm1MXfNDVSMW6cjQHaHa?rs=1&pid=ImgDetMain" status="Hey there!"></ProfileCardComponent>
<div v-if="userStore.users.filter(u => u.username.includes(searchQuery) || searchQuery == '*').length == 0" style="text-align: center;">
<!--<q-icon name="error"></q-icon>
<p>Wir konnten keine Nutzer mit diesem Nutzernamen finden</p>-->
<img src="https://media.tenor.com/KOZLvzU0o4kAAAAd/no-results.gif" style="border-radius: 1rem;" alt="No search results found">
</div>
</div>
<div style="width: 70%;height: 100%;background: #DFD0BF;">
<p v-else>Gib einen Nutzername ein, um die Suche zu beginnen</p>
</div>
<div style="margin: 20px;" v-if="activeMenu == 'chats'">
<p>Hier werden dann die Chats angezeigt</p>
</div>
</div>
<div style="width: 75%;height: 100%;background: #DFD0BF;">
<div id="messageContainer" style="padding: 25px;overflow-y: auto;height: 90%;display: flex;flex-direction: column-reverse;">
<MessageComponent timestamp="13:12" url="https://media.tenor.com/mtiOW6O-k8YAAAAd/shrek-shrek-rizz.gif" gif></MessageComponent>
<MessageComponent message="Halo" timestamp="13:03" own-message></MessageComponent>
<MessageComponent message="Hallo na" timestamp="13:04"></MessageComponent>
<MessageComponent message="Blubber" timestamp="13:06"></MessageComponent>
<MessageComponent message="Testtt" timestamp="13:03" own-message></MessageComponent>
<MessageComponent message="Hola" timestamp="13:03" own-message></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent><MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<MessageComponent message="Mal schauen was hier passiert, wenn man viel reinschreibt hahahahahahhahahaha" timestamp="13:06"></MessageComponent>
<div v-for="(message, index) in messages" :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.isOwn"
></MessageComponent>
</div>
</div>
<div style="width: 70%;height:auto; background: gray;position: absolute;bottom:0;padding: 10px;">
<div class="footer">
<q-input filled v-model="message" placeholder="Nachricht eingeben" tabindex="0" @keyup.enter.prevent="sendMessage">
<template v-slot:after>
@@ -46,7 +78,9 @@
<q-btn round dense flat icon="send" id="sendMessageBtn" @click="sendMessage"></q-btn>
</template>
</q-input>
</div>
<!-- GIF SUCHE -->
<div style="background: white;padding: 10px;border-radius: 10px;width: 20rem;height: 30rem;overflow-y:auto;position: absolute;right:20px;bottom:5rem;" v-if="showGifMenu" id="gifSearchMenu">
<q-input v-model="gifSearch" @update:model-value="inputChanged" dense placeholder="Nach GIF suchen" id="gifSearchBar">
<template v-slot:append>
@@ -63,11 +97,14 @@
</div>
</div>
</div>
</div>
<div style="text-align: center;" v-if="loadedGifsAmount < 50">
<q-btn round dense flat @click="loadMoreGifs" title="Mehr anzeigen">+</q-btn>
</div>
</div>
<!-- GIF SUCHE ENDE -->
</div>
</div>
</template>
@@ -76,6 +113,8 @@ import { ref } from 'vue';
import MessageComponent from 'components/MessageComponent.vue';
import { useTenorStore } from 'stores/tenor-store';
import ProfileCardComponent from 'components/ProfileCardComponent.vue';
import {message} from 'src/models';
import {useUserStore} from 'stores/user-store';
defineOptions({
name: 'IndexPage'
@@ -85,20 +124,69 @@ const searchQuery = ref("")
const message = ref("");
const gifSearch = ref("");
const activeMenu = ref("search");
let loadedGifsAmount = 10;
let messages : message[] = [
{content: "Halllo", timestamp: "13:38", isOwn: true, gif: false, link: false},
{content: "https://www.google.com/", timestamp: "13:20", isOwn: false, gif: false, link: false},
{content: "Halllo", timestamp: "13:15", isOwn: false, gif: false, link: false},
{content: "Halllo", timestamp: "13:14", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:13", isOwn: true, gif: false, link: false},
{content: "https://media.tenor.com/mtiOW6O-k8YAAAAd/shrek-shrek-rizz.gif", timestamp: "13:11", isOwn: false, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Halllo", timestamp: "13:10", isOwn: true, gif: false, link: false},
{content: "Hallo", timestamp: "13:10", isOwn: true, gif: false, link: false}
];
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(false);
const changed = ref(true);
function inputChanged() {
changed.value = true;
}
const tenorStore = useTenorStore();
const userStore = useUserStore();
function getGifs() {
let query = gifSearch.value != "" ? gifSearch.value : "shrek";
if(changed.value) {
tenorStore.getGifsBySearchTerm(gifSearch.value);
tenorStore.getGifsBySearchTerm(query, loadedGifsAmount);
changed.value = false;
}
}
@@ -107,23 +195,33 @@ function toggleGifMenu() {
showGifMenu.value = !showGifMenu.value;
if(showGifMenu.value) {
// Somehow focus the input field
document.getElementById("gifSearchBar")!.focus();
//document.getElementById("gifSearchBar")!.focus();
}
}
function sendMessage() {
alert(message.value);
messages.unshift({content: message.value, timestamp: new Date().toTimeString().split(' ')[0].substring(0, 5), isOwn: true, gif: false, link: false});
message.value = "";
}
function sendGif(url : string) {
alert(url);
messages.unshift({content: url, timestamp: new Date().toTimeString().split(' ')[0].substring(0, 5), isOwn: true, gif: true, link: false});
toggleGifMenu();
}
tenorStore.getGifsBySearchTerm("shrek");
function loadMoreGifs() {
loadedGifsAmount += 10;
console.log(loadedGifsAmount);
changed.value = true;
}
setInterval(getGifs, 500);
userStore.getAllPtpUsers();
</script>
@@ -146,4 +244,37 @@ setInterval(getGifs, 500);
transition: .3s;
filter: brightness(0.5);
}
.footer {
width: 75%;
height:auto;
background: gray;
position: absolute;
bottom:0;
padding: 10px;
}
.navigation {
font-size: 2rem;
}
.navigation > div {
width: 50%;
text-align: center;
cursor: pointer;
transition: .3s;
}
.navigation > div:hover {
background: #555555;
transition: .3s;
}
.menuItemActive {
border-bottom: 2px solid black;
}
.menuItemInactive {
border-bottom: 2px solid transparent;
}
</style>

View File

@@ -12,15 +12,15 @@ export const useTenorStore = defineStore('tenorStore', {
},
actions: {
getGifsBySearchTerm(searchTerm : string) {
this.loadGifsBySearchTerm(searchTerm).then((response : any) => {
getGifsBySearchTerm(searchTerm : string, amount : number) {
this.loadGifsBySearchTerm(searchTerm, amount).then((response : any) => {
this.gifs = response.data.results;
this.gifsLoaded = true;
console.log(this.gifsLoaded + " / from store");
});
},
async loadGifsBySearchTerm(searchTerm : string) : Promise<any> {
return await api.get("https://tenor.googleapis.com/v2/search?q="+searchTerm+"&key=AIzaSyBXsUPCjnmjXwcdqMLsyhrH1zm_6VYH_fY&client_key=praxistransferProjekt&limit=10");
async loadGifsBySearchTerm(searchTerm : string, amount : number) : Promise<any> {
return await api.get("https://tenor.googleapis.com/v2/search?q="+searchTerm+"&key=AIzaSyBXsUPCjnmjXwcdqMLsyhrH1zm_6VYH_fY&client_key=praxistransferProjekt&limit="+amount);
}
},
});

View File

@@ -7,6 +7,8 @@ export const useUserStore = defineStore('userStore', {
state: () => ({
user: {"keycloakID":null,"firstName":null,"lastName":null,"username":null},
userLoaded: false,
users: [] as ptpUser[],
usersLoaded: false,
}),
getters: {
@@ -22,5 +24,14 @@ export const useUserStore = defineStore('userStore', {
async loadPtpUsersByKeycloakID(keycloakID : string) : Promise<responseModel> {
return (await api.get("/users/login/"+keycloakID)).data;
},
getAllPtpUsers() {
this.loadAllPtpUsers().then((response : ptpUser[]) => {
this.users = response;
this.usersLoaded = true;
})
},
async loadAllPtpUsers() {
return (await api.get("/users/user")).data;
}
},
});