Added Reaction component to use and fixed some warning from vue
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://js.stripe.com https://accounts.google.com 'unsafe-eval';">
|
||||
<title>Hutopy</title>
|
||||
</head>
|
||||
|
||||
|
||||
9
src/Constants/Reactions.js
Normal file
9
src/Constants/Reactions.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const REACTIONS = {
|
||||
LIKE: 'Like',
|
||||
DISLIKE: 'Dislike',
|
||||
LOVE: 'Love',
|
||||
HAHA: 'Haha',
|
||||
WOW: 'Wow',
|
||||
SAD: 'Sad',
|
||||
ANGRY: 'Angry'
|
||||
};
|
||||
@@ -64,12 +64,8 @@
|
||||
|
||||
<div class="px-4">
|
||||
<div class="flex justify-around py-2">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon>mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<Reaction :content="content"></Reaction>
|
||||
|
||||
<v-btn
|
||||
:class="{'comment-active': hasMessages}"
|
||||
variant="plain"
|
||||
@@ -152,6 +148,7 @@ import YoutubePlayer from './YoutubePlayer.vue';
|
||||
import ImageViewer from './ImageViewer.vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import Reaction from "@/views/contents/Reaction.vue";
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
|
||||
286
src/views/contents/Reaction.vue
Normal file
286
src/views/contents/Reaction.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<script setup>
|
||||
import { useUserStore } from "@/stores/userStore.js";
|
||||
import { REACTIONS } from "@/Constants/Reactions.js";
|
||||
import { computed, ref } from "vue";
|
||||
import { useClient } from "@/plugins/api.js";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const contentId = computed(() => props.content.id);
|
||||
|
||||
const hasReacted = ref(false);
|
||||
const currentReaction = ref(null);
|
||||
const likeCount = ref(0);
|
||||
const dislikeCount = ref(0);
|
||||
const loveCount = ref(0);
|
||||
const hahaCount = ref(0);
|
||||
const wowCount = ref(0);
|
||||
const sadCount = ref(0);
|
||||
const angryCount = ref(0);
|
||||
|
||||
const menuVisible = ref(false);
|
||||
const holdTimeout = ref(null);
|
||||
const hideTimeout = ref(null);
|
||||
|
||||
initializeReactions();
|
||||
|
||||
async function reactToContent(reaction) {
|
||||
const client = useClient();
|
||||
|
||||
if (!hasReacted.value) {
|
||||
const request = {
|
||||
ContentId: contentId.value,
|
||||
reaction: reaction,
|
||||
userId: userStore.user.id,
|
||||
userName: `${userStore.user.firstName} ${userStore.user.lastName}`,
|
||||
};
|
||||
await client.post("/api/content/reaction/", request);
|
||||
|
||||
adjustReactionCount(reaction, true);
|
||||
hasReacted.value = true;
|
||||
console.log(`Added ${reaction} reaction to content.`);
|
||||
} else if (reaction !== currentReaction.value) {
|
||||
adjustReactionCount(currentReaction.value, false);
|
||||
const requestRemove = {
|
||||
ContentId: contentId.value,
|
||||
userId: userStore.user.id,
|
||||
};
|
||||
await client.post("/api/content/reaction/remove", requestRemove);
|
||||
|
||||
const requestAdd = {
|
||||
ContentId: contentId.value,
|
||||
reaction: reaction,
|
||||
userId: userStore.user.id,
|
||||
userName: `${userStore.user.firstName} ${userStore.user.lastName}`,
|
||||
};
|
||||
await client.post("/api/content/reaction/", requestAdd);
|
||||
|
||||
adjustReactionCount(reaction, true);
|
||||
console.log(`Changed reaction to ${reaction} on content.`);
|
||||
} else {
|
||||
const requestRemove = {
|
||||
ContentId: contentId.value,
|
||||
userId: userStore.user.id,
|
||||
};
|
||||
await client.post("/api/content/reaction/remove", requestRemove);
|
||||
|
||||
adjustReactionCount(reaction, false);
|
||||
hasReacted.value = false;
|
||||
console.log("Reaction to content removed.");
|
||||
}
|
||||
}
|
||||
|
||||
function adjustReactionCount(newReaction) {
|
||||
if (currentReaction.value === newReaction) {
|
||||
switch (newReaction) {
|
||||
case REACTIONS.LIKE:
|
||||
if (likeCount.value > 0) likeCount.value--;
|
||||
break;
|
||||
case REACTIONS.DISLIKE:
|
||||
if (dislikeCount.value > 0) dislikeCount.value--;
|
||||
break;
|
||||
case REACTIONS.LOVE:
|
||||
if (loveCount.value > 0) loveCount.value--;
|
||||
break;
|
||||
case REACTIONS.HAHA:
|
||||
if (hahaCount.value > 0) hahaCount.value--;
|
||||
break;
|
||||
case REACTIONS.WOW:
|
||||
if (wowCount.value > 0) wowCount.value--;
|
||||
break;
|
||||
case REACTIONS.SAD:
|
||||
if (sadCount.value > 0) sadCount.value--;
|
||||
break;
|
||||
case REACTIONS.ANGRY:
|
||||
if (angryCount.value > 0) angryCount.value--;
|
||||
break;
|
||||
}
|
||||
currentReaction.value = null;
|
||||
hasReacted.value = false;
|
||||
} else {
|
||||
if (currentReaction.value) {
|
||||
switch (currentReaction.value) {
|
||||
case REACTIONS.LIKE:
|
||||
if (likeCount.value > 0) likeCount.value--;
|
||||
break;
|
||||
case REACTIONS.DISLIKE:
|
||||
if (dislikeCount.value > 0) dislikeCount.value--;
|
||||
break;
|
||||
case REACTIONS.LOVE:
|
||||
if (loveCount.value > 0) loveCount.value--;
|
||||
break;
|
||||
case REACTIONS.HAHA:
|
||||
if (hahaCount.value > 0) hahaCount.value--;
|
||||
break;
|
||||
case REACTIONS.WOW:
|
||||
if (wowCount.value > 0) wowCount.value--;
|
||||
break;
|
||||
case REACTIONS.SAD:
|
||||
if (sadCount.value > 0) sadCount.value--;
|
||||
break;
|
||||
case REACTIONS.ANGRY:
|
||||
if (angryCount.value > 0) angryCount.value--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (newReaction) {
|
||||
case REACTIONS.LIKE:
|
||||
likeCount.value++;
|
||||
break;
|
||||
case REACTIONS.DISLIKE:
|
||||
dislikeCount.value++;
|
||||
break;
|
||||
case REACTIONS.LOVE:
|
||||
loveCount.value++;
|
||||
break;
|
||||
case REACTIONS.HAHA:
|
||||
hahaCount.value++;
|
||||
break;
|
||||
case REACTIONS.WOW:
|
||||
wowCount.value++;
|
||||
break;
|
||||
case REACTIONS.SAD:
|
||||
sadCount.value++;
|
||||
break;
|
||||
case REACTIONS.ANGRY:
|
||||
angryCount.value++;
|
||||
break;
|
||||
}
|
||||
|
||||
currentReaction.value = newReaction;
|
||||
hasReacted.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function initializeReactions() {
|
||||
const userReaction = props.content.reactions.find((x) => x.userId === userStore.user.id);
|
||||
if (userReaction) {
|
||||
currentReaction.value = userReaction.reaction;
|
||||
hasReacted.value = true;
|
||||
} else {
|
||||
currentReaction.value = null;
|
||||
hasReacted.value = false;
|
||||
}
|
||||
|
||||
likeCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.LIKE).length;
|
||||
dislikeCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.DISLIKE).length;
|
||||
loveCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.LOVE).length;
|
||||
hahaCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.HAHA).length;
|
||||
wowCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.WOW).length;
|
||||
sadCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.SAD).length;
|
||||
angryCount.value = props.content.reactions.filter((x) => x.reaction === REACTIONS.ANGRY).length;
|
||||
}
|
||||
|
||||
function showReactions() {
|
||||
clearTimeout(hideTimeout.value);
|
||||
menuVisible.value = true;
|
||||
}
|
||||
|
||||
function hideReactions() {
|
||||
hideTimeout.value = setTimeout(() => {
|
||||
menuVisible.value = false;
|
||||
}, 1500); // Delay for 0.5 seconds before hiding
|
||||
}
|
||||
|
||||
function onMouseDown() {
|
||||
holdTimeout.value = setTimeout(showReactions, 1500);
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
clearTimeout(holdTimeout.value);
|
||||
hideReactions();
|
||||
}
|
||||
|
||||
function onMouseOver() {
|
||||
if (!isMobileDevice()) {
|
||||
showReactions();
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseLeave() {
|
||||
if (!isMobileDevice()) {
|
||||
hideReactions();
|
||||
}
|
||||
}
|
||||
|
||||
function keepReactionMenuOpen(){
|
||||
clearTimeout(hideTimeout.value);
|
||||
}
|
||||
|
||||
function isMobileDevice() {
|
||||
return window.innerWidth <= 800;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<v-menu
|
||||
class="reaction-card"
|
||||
v-model="menuVisible"
|
||||
offset-y
|
||||
:close-on-content-click="false"
|
||||
transition="scale-transition"
|
||||
attach=".reaction-wrapper"
|
||||
>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn
|
||||
v-bind="attrs"
|
||||
:on="on"
|
||||
variant="plain"
|
||||
@mousedown="onMouseDown"
|
||||
@mouseup="onMouseUp"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseleave="onMouseLeave"
|
||||
@click="reactToContent(REACTIONS.LIKE)"
|
||||
>
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
{{ likeCount }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card
|
||||
class="reaction-card"
|
||||
@mouseover="keepReactionMenuOpen"
|
||||
@mouseleave="hideReactions"
|
||||
>
|
||||
<v-btn variant="plain" icon @click="reactToContent(REACTIONS.LOVE)">
|
||||
<v-icon>mdi-heart-outline</v-icon>
|
||||
{{ loveCount }}
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="reactToContent(REACTIONS.HAHA)">
|
||||
<v-icon>mdi-emoticon-excited-outline</v-icon>
|
||||
{{ hahaCount }}
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="reactToContent(REACTIONS.WOW)">
|
||||
<v-icon>mdi-emoticon-happy-outline</v-icon>
|
||||
{{ wowCount }}
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="reactToContent(REACTIONS.SAD)">
|
||||
<v-icon>mdi-emoticon-sad-outline</v-icon>
|
||||
{{ sadCount }}
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="reactToContent(REACTIONS.ANGRY)">
|
||||
<v-icon>mdi-emoticon-angry-outline</v-icon>
|
||||
{{ angryCount }}
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.reaction-card {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 8px;
|
||||
margin-top: -35px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
</style>
|
||||
@@ -64,12 +64,8 @@
|
||||
|
||||
<div class="px-4">
|
||||
<div class="flex justify-around py-2">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon>mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<Reaction :content="content"></Reaction>
|
||||
|
||||
<v-btn
|
||||
:class="{'comment-active': hasMessages}"
|
||||
variant="plain"
|
||||
@@ -152,6 +148,7 @@ import YoutubePlayer from '../YoutubePlayer.vue';
|
||||
import ImageViewer from '../ImageViewer.vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import Reaction from "@/views/contents/Reaction.vue";
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
|
||||
@@ -64,12 +64,8 @@
|
||||
|
||||
<div class="px-1">
|
||||
<div class="flex justify-around ">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon>mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<Reaction :content="content"></Reaction>
|
||||
|
||||
<v-btn
|
||||
:class="{'comment-active': hasMessages}"
|
||||
variant="plain"
|
||||
@@ -152,6 +148,7 @@ import YoutubePlayer from '../YoutubePlayer.vue';
|
||||
import ImageViewer from '../ImageViewer.vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import Reaction from "@/views/contents/Reaction.vue";
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
|
||||
@@ -49,12 +49,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-around py-2">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon :color="'#313131'">mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon :color="'#000000'">mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<Reaction v-if="data" :content="data"></Reaction>
|
||||
|
||||
<donation-button v-if="data"
|
||||
:color-border="data.colorMenu"
|
||||
@@ -89,6 +84,7 @@ import MessageList from "@/views/messages/MessageList.vue";
|
||||
import DonationButton from "@/views/creators/DonationButton.vue";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useRoute} from 'vue-router';
|
||||
import Reaction from "@/views/contents/Reaction.vue";
|
||||
|
||||
const data = ref(null);
|
||||
const currentImageIndex = ref(0);
|
||||
@@ -119,7 +115,6 @@ const fetchContentData = async (contentId) => {
|
||||
try {
|
||||
const response = await client.get(`/api/contents/${contentId}`);
|
||||
data.value = response.data;
|
||||
console.table(data.value)
|
||||
} catch (error) {
|
||||
console.error(`Error fetching content: ${error}`);
|
||||
}
|
||||
|
||||
@@ -53,12 +53,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-around py-2">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon :color="'#313131'">mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon :color="'#000000'">mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<Reaction v-if="data" :content="data"></Reaction>
|
||||
|
||||
<donation-button v-if="data"
|
||||
:color-border="data.colorMenu"
|
||||
@@ -93,6 +88,7 @@ import MessageList from "@/views/messages/MessageList.vue";
|
||||
import DonationButton from "@/views/creators/DonationButton.vue";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useRoute} from 'vue-router';
|
||||
import Reaction from "@/views/contents/Reaction.vue";
|
||||
|
||||
const data = ref(null);
|
||||
const currentImageIndex = ref(0);
|
||||
@@ -123,7 +119,6 @@ const fetchContentData = async (contentId) => {
|
||||
try {
|
||||
const response = await client.get(`/api/contents/${contentId}`);
|
||||
data.value = response.data;
|
||||
console.table(data.value)
|
||||
} catch (error) {
|
||||
console.error(`Error fetching content: ${error}`);
|
||||
}
|
||||
|
||||
@@ -76,12 +76,12 @@ import {loadStripe} from '@stripe/stripe-js';
|
||||
import {computed, onMounted, ref} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
colorBorder: {type: String, required: true},
|
||||
colorAccent: {type: String, required: true},
|
||||
colorBorder: {required: true},
|
||||
colorAccent: {required: true},
|
||||
creatorId: {type: String, required: true},
|
||||
creatorName: {type: String, required: true},
|
||||
creatorLogo: {type: String, required: true},
|
||||
iconColorClass: {type: String, default: 'text-black'}
|
||||
creatorLogo: {required: true},
|
||||
iconColorClass: {default: 'text-black'}
|
||||
});
|
||||
|
||||
const colorBorder = computed(() => props.colorBorder)
|
||||
|
||||
Reference in New Issue
Block a user