333 lines
9.2 KiB
Vue
333 lines
9.2 KiB
Vue
<script setup>
|
|
import { REACTIONS } from "@/Constants/Reactions.js";
|
|
import { computed, ref } from "vue";
|
|
import { useClient } from "@/plugins/api.js";
|
|
import {useAuthStore} from "@/stores/authStore.js"
|
|
import MustBeLogged from "@/views/MustBeLogged.vue";
|
|
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
|
|
|
const userProfileStore = useUserProfileStore();
|
|
const authStore = useAuthStore()
|
|
|
|
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);
|
|
const touchTimeout = ref(null);
|
|
|
|
const loginModal = ref(false);
|
|
|
|
initializeReactions();
|
|
|
|
async function reactToContent(reaction) {
|
|
if (!authStore.isAuthenticated) {
|
|
loginModal.value = true;
|
|
return;
|
|
}
|
|
|
|
const client = useClient();
|
|
|
|
if (!hasReacted.value) {
|
|
const request = {
|
|
ContentId: contentId.value,
|
|
reaction: reaction,
|
|
userId: userProfileStore.user.id,
|
|
userName: `${userProfileStore.user.firstName} ${userProfileStore.user.lastName}`,
|
|
};
|
|
adjustReactionCount(reaction);
|
|
await client.post("/api/content/reaction/", request);
|
|
|
|
hasReacted.value = true;
|
|
console.log(`Added ${reaction} reaction to content.`);
|
|
} else if (reaction !== currentReaction.value) {
|
|
const requestAdd = {
|
|
ContentId: contentId.value,
|
|
reaction: reaction,
|
|
userId: userProfileStore.user.id,
|
|
userName: `${userProfileStore.user.firstName} ${userProfileStore.user.lastName}`,
|
|
};
|
|
adjustReactionCount(reaction);
|
|
await client.post("/api/content/reaction/", requestAdd);
|
|
|
|
console.log(`Changed reaction to ${reaction} on content.`);
|
|
} else {
|
|
const requestRemove = {
|
|
ContentId: contentId.value,
|
|
userId: userProfileStore.user.id,
|
|
};
|
|
adjustReactionCount(reaction);
|
|
await client.post("/api/content/reaction/remove", requestRemove);
|
|
|
|
hasReacted.value = false;
|
|
console.log("Reaction to content removed.");
|
|
}
|
|
setTimeout(() => {
|
|
menuVisible.value = false;
|
|
}, 500);
|
|
}
|
|
|
|
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 === userProfileStore.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;
|
|
}, 250);
|
|
}
|
|
|
|
function onTouchStart() {
|
|
touchTimeout.value = setTimeout(() => {
|
|
menuVisible.value = true;
|
|
}, 250);
|
|
}
|
|
function onTouchEnd() {
|
|
clearTimeout(touchTimeout.value);
|
|
}
|
|
|
|
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="$el"
|
|
>
|
|
<template v-slot:activator="{ on, attrs }">
|
|
<v-btn
|
|
v-bind="attrs"
|
|
:on="on"
|
|
icon="true"
|
|
variant="plain"
|
|
@touchstart="onTouchStart"
|
|
@touchend="onTouchEnd"
|
|
@mouseup="onMouseUp"
|
|
@mouseover="onMouseOver"
|
|
@mouseleave="onMouseLeave"
|
|
@click="reactToContent(REACTIONS.LIKE)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.LIKE}">mdi-thumb-up-outline</v-icon>
|
|
{{ likeCount }}
|
|
</v-btn>
|
|
</template>
|
|
<v-card
|
|
class="reaction-card"
|
|
@mouseover="keepReactionMenuOpen"
|
|
@mouseleave="hideReactions"
|
|
>
|
|
<v-btn
|
|
variant="plain"
|
|
@click="reactToContent(REACTIONS.DISLIKE)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.DISLIKE}">mdi-thumb-down-outline</v-icon>
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
variant="plain"
|
|
@click="reactToContent(REACTIONS.LOVE)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.LOVE}">mdi-heart-outline</v-icon>
|
|
{{ loveCount }}
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
variant="plain"
|
|
@click="reactToContent(REACTIONS.HAHA)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.HAHA}">mdi-emoticon-excited-outline</v-icon>
|
|
{{ hahaCount }}
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
variant="plain"
|
|
@click="reactToContent(REACTIONS.WOW)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.WOW}">mdi-emoticon-happy-outline</v-icon>
|
|
{{ wowCount }}
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
variant="plain"
|
|
@click="reactToContent(REACTIONS.SAD)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.SAD}">mdi-emoticon-sad-outline</v-icon>
|
|
{{ sadCount }}
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
variant="plain"
|
|
@click="reactToContent(REACTIONS.ANGRY)"
|
|
>
|
|
<v-icon :class="{'active-icon': currentReaction === REACTIONS.ANGRY}">mdi-emoticon-angry-outline</v-icon>
|
|
{{ angryCount }}
|
|
</v-btn>
|
|
</v-card>
|
|
</v-menu>
|
|
<must-be-logged v-model="loginModal" message="Vous devez être connecté pour réagir."></must-be-logged>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.reaction-card {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
padding: 8px;
|
|
margin-top: -35px;
|
|
margin-left: 100px;
|
|
}
|
|
|
|
.active-icon {
|
|
color: blue;
|
|
stroke: blue;
|
|
}
|
|
</style>
|