designtopack/src/components/comments/Comments.vue
2025-02-11 18:45:23 +01:00

332 lines
9.2 KiB
Vue

<template>
<aside id="comments-container" aria-labelledby="comments-label">
<h2 id="comments-label" class="sr-only">Commentaires</h2>
<div
class="comments | flow"
:class="{ empty: !comments || comments.length === 0 }"
>
<template v-if="comments">
<template v-if="!openedComment">
<Comment
v-for="(comment, commentIndex) in sortedComments"
:comment="comment"
:commentIndex="comments.length - commentIndex"
:key="comment.id"
@click="openComment(comment)"
@update:file="changeFile"
@close:comment="closeComment"
/>
</template>
<template v-else>
<button
class="btn | justify-start w-full | bg-white-10 text-white | px-8"
data-icon="chevron-single-left"
@click="
openedComment = null;
isAddOpen = false;
"
>
<span>Retour à la liste</span>
</button>
<Comment
:comment="openedComment"
data-opened="true"
@update:file="changeFile"
@close:comment="closeComment"
/>
<div v-if="sortedReplies.length > 0" class="replies | flow">
<Comment
v-for="(reply, commentIndex) in sortedReplies"
:comment="reply"
:commentIndex="sortedReplies.length - commentIndex"
:key="reply.id"
@update:file="changeFile"
@close:comment="closeComment"
/>
</div>
</template>
</template>
</div>
<button
v-if="!openedComment && !isAddOpen"
id="create-comment"
class="btn btn--white-20 | w-full"
@click="toggleCommentPositionMode(true)"
>
Ajouter un commentaire
</button>
<button
v-else-if="openedComment && !isAddOpen"
id="reply-comment"
class="btn btn--white-20 | justify-start w-full | text-white-50"
@click="isAddOpen = true"
>
Répondre
</button>
<!-- TODO: afficher #new-comment une fois le bouton Ajouter un commentaire cliqué -->
<div
v-if="waitingForCommentPosition"
id="new-comment"
class="bg-primary | text-sm text-white | rounded-lg | p-12"
>
<p class="flex justify-start | mb-12" data-icon="comment">
<strong>Nouveau commentaire</strong>
</p>
<p>
Dans la zone du contenu, cliquez vous souhaitez positionner le
commentaire
</p>
</div>
<form
v-if="isAddOpen"
action=""
method="post"
class="flow | p-12 | rounded-xl"
@submit="handleSubmit"
>
<label class="sr-only" for="comment">Votre commentaire</label>
<textarea
name="comment"
id="comment"
placeholder="Ajouter un commentaire…"
rows="5"
class="text-sm | rounded-lg bg-black p-12"
v-model="draftComment.text"
></textarea>
<footer class="flex">
<input type="submit" class="btn btn--tranparent" />
<button class="btn btn--white-10" @click="isAddOpen = false">
Annuler
</button>
</footer>
</form>
</aside>
</template>
<script setup>
import dayjs from 'dayjs';
import 'dayjs/locale/fr';
import uniqid from 'uniqid';
import { watch, ref, computed } from 'vue';
import { useUserStore } from '../../stores/user';
import { usePageStore } from '../../stores/page';
import { useApiStore } from '../../stores/api';
import { useDialogStore } from '../../stores/dialog';
import Comment from './Comment.vue';
import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router';
dayjs.locale('fr');
const { user } = useUserStore();
const { page } = usePageStore();
const dialog = useDialogStore();
const { comments, openedFile, openedComment, draftComment, activeTracks } =
storeToRefs(useDialogStore());
const api = useApiStore();
const isAddOpen = ref(false);
const route = useRoute();
const waitingForCommentPosition = ref(false);
const sortedComments = computed(() => {
return [...comments.value].reverse();
});
const sortedReplies = ref(null);
watch(openedComment, (newVal) => {
sortedReplies.value = newVal ? newVal.replies.slice().reverse() : null;
if (!newVal) {
unhighlightAllComments();
}
});
watch(isAddOpen, (newVal) => {
if (newVal) {
setTimeout(() => {
document.querySelector('textarea#comment').focus();
}, 100);
}
});
const viewContainer =
openedFile.value?.type === 'document'
? document.querySelector('.vpv-pages-inner-container')
: document.querySelector('.track');
window.addEventListener('keydown', (event) => {
if (
isAddOpen.value &&
event.key === 'Enter' &&
(event.metaKey || event.ctrlKey)
) {
handleSubmit();
}
});
// Functions
function unhighlightAllComments() {
document.querySelectorAll('.comment-marker.big').forEach((commentNode) => {
commentNode.classList.remove('big');
commentNode.classList.remove('active');
});
}
function handleSubmit(event = null) {
if (event) {
event.preventDefault();
}
const date = dayjs().format();
const newComment = {
fileName: openedFile.value ? openedFile.value.name : false,
userUuid: user.uuid,
text: draftComment.value.text,
date,
position: {
pageIndex: openedComment.value
? openedComment.value.position.pageIndex
: draftComment.value.position.pageIndex,
x: openedComment.value ? false : draftComment.value.position.x,
y: openedComment.value ? false : draftComment.value.position.y,
},
id: uniqid(),
};
if (openedComment.value) {
replyComment(newComment);
} else {
addComment(newComment);
}
}
async function replyComment(newComment) {
newComment.parentId = openedComment.value.id;
const matchFileParentUri = openedFile.value.url.match(
/projects\/.*?(?=\/[^/]+\/[^/]+$)/
);
newComment.fileParentUri = matchFileParentUri ? matchFileParentUri[0] : null;
const newFile = await api.replyComment(newComment);
resetDraftComment();
isAddOpen.value = false;
dialog.updateFile(newFile);
openedComment.value = newFile.comments.find(
(item) => item.id === openedComment.value.id
);
}
async function addComment(newComment) {
const matchFileParentUri = openedFile.value.url.match(
/projects\/.*?(?=\/[^/]+\/[^/]+$)/
);
newComment.fileParentUri = matchFileParentUri ? matchFileParentUri[0] : null;
const newFile = await api.addComment(newComment);
resetDraftComment();
isAddOpen.value = false;
if (!newFile) return;
dialog.updateFile(newFile);
if (activeTracks.value) {
activeTracks.value = activeTracks.value.map((track) => {
if (track.files) {
track.files = track.files.map((file) =>
file.uuid === newFile.uuid ? newFile : file
);
}
return track;
});
}
}
function resetDraftComment() {
draftComment.value.text = '';
draftComment.value.position = null;
draftComment.value.padeIndex = null;
}
function closeComment() {
openedComment.value = null;
}
function toggleCommentPositionMode(enable) {
if (enable) {
waitingForCommentPosition.value = true;
viewContainer.classList.add('waiting-comment');
viewContainer.addEventListener('click', handleCommentPositionClick);
} else {
waitingForCommentPosition.value = false;
viewContainer.classList.remove('waiting-comment');
viewContainer.removeEventListener('click', handleCommentPositionClick);
}
}
function handleCommentPositionClick(event) {
if (openedFile.value.type === 'document') {
prepareDraftCommentInPdf(event);
} else {
prepareDraftCommentInImage(event);
}
isAddOpen.value = true;
toggleCommentPositionMode(false);
}
function prepareDraftCommentInPdf(event) {
const pageContainer = event.target.closest('.page-inner-container');
if (!pageContainer || !viewContainer) return;
const pageLabel = pageContainer
.closest('.vpv-page-inner-container')
.getAttribute('aria-label');
const pageIndex = pageLabel.charAt(pageLabel.length - 1);
const viewRect = viewContainer.getBoundingClientRect();
const pageRect = pageContainer.getBoundingClientRect();
const pageScroll = viewRect.top - pageRect.top;
const mouseTop = event.clientY;
const y = mouseTop - viewRect.top + pageScroll;
const x = event.clientX - pageRect.left;
const relativeX = (x / pageRect.width) * 100;
const relativeY = (y / pageRect.height) * 100;
console.log(pageIndex);
draftComment.value.position = {
x: relativeX,
y: relativeY,
pageIndex: parseInt(pageIndex),
};
}
function prepareDraftCommentInImage(event) {
if (!viewContainer) return;
const imageRect = viewContainer.getBoundingClientRect();
const mouseTop = event.clientY;
const mouseLeft = event.clientX;
const relativeX = ((mouseLeft - imageRect.left) / imageRect.width) * 100;
const relativeY = ((mouseTop - imageRect.top) / imageRect.height) * 100;
draftComment.value.position = {
x: relativeX,
y: relativeY,
};
}
function openComment(comment) {
if (comment.isEditMode) return;
openedComment.value = comment;
if (activeTracks.value?.length === 1) {
showCorrespondingView();
}
}
function showCorrespondingView() {
openedFile.value = activeTracks.value[0].files.find(
(file) => file.uuid === openedComment.value.location.file.uuid
);
}
</script>