FIrst version carto debat

This commit is contained in:
Henintsoa 2021-11-16 13:17:46 +03:00
parent bb0168d25d
commit 77f9c7a6a3
11 changed files with 713 additions and 593 deletions

View file

@ -21,11 +21,11 @@ const router = new VueRouter({
{path: '/votemajoritaire', component: vueLoader('views/votemajoritaire')}, {path: '/votemajoritaire', component: vueLoader('views/votemajoritaire')},
{path: '/createVoteMajoritaire', component: vueLoader('views/createVoteMajoritaire')}, {path: '/createVoteMajoritaire', component: vueLoader('views/createVoteMajoritaire')},
{path: '/votemajoritairedetails', component: vueLoader('views/votemajoritairedetails')}, {path: '/votemajoritairedetails', component: vueLoader('views/votemajoritairedetails')},
{path: '/carto', component: vueLoader('views/cartoTest')},
{path: '/creation-debats-sur-cartes', component: vueLoader('views/createDebateSurCarte')}, {path: '/creation-debats-sur-cartes', component: vueLoader('views/createDebateSurCarte')},
{path: '/documents-debats-sur-cartes', component: vueLoader('views/documentsDebatesSurCarte')}, {path: '/documents-debats-sur-cartes', component: vueLoader('views/documentsDebatesSurCarte')},
{path: '/documents-debats-sur-cartes/:id', component: vueLoader('views/documentDebatesSurCarte')}, {path: '/documents-debats-sur-cartes/:id', component: vueLoader('views/documentDebatesSurCarte')},
{path: '/debats-sur-cartes', component: vueLoader('views/debatesSurCarte')}, {path: '/debats-sur-cartes', component: vueLoader('views/debatesSurCarte')},
{path: '/debats-sur-cartes/:id', component: vueLoader('views/debateSurCarte')},
{path: '*', component: vueLoader('views/404')}, {path: '*', component: vueLoader('views/404')},
] ]
}); });

View file

@ -120,11 +120,13 @@ function Entity(obj = {}) {
function Category(obj = {}) { function Category(obj = {}) {
Entity.call(this, obj); Entity.call(this, obj);
this.type = DebateType.BASIC;
} }
Category.prototype.attrs = { Category.prototype.attrs = {
id: Number, id: Number,
name: String, name: String,
picture: String, picture: String,
type: String,
debatesCount: Number, debatesCount: Number,
lastCommentDate: Date, lastCommentDate: Date,
}; };
@ -134,10 +136,15 @@ Category.prototype.oneToMany = {
Category.prototype.debates = function () { Category.prototype.debates = function () {
return this.documents.map((document) => document.debates).flat(); return this.documents.map((document) => document.debates).flat();
}; };
Category.prototype.is = function (type) {
return DebateType._value[this.type] == DebateType._value[type];
}
function Document(obj = {}) { function Document(obj = {}) {
Entity.call(this, obj); Entity.call(this, obj);
this.type = DebateType.BASIC; this.type = DebateType.BASIC;
this.meshLine = 1;
this.meshColumn = 1;
} }
Document.prototype.attrs = { Document.prototype.attrs = {
id: Number, id: Number,

View file

@ -102,7 +102,7 @@ const messages = {
guests: "Invité⋅e⋅s", guests: "Invité⋅e⋅s",
debates: "Débats", debates: "Débats",
new_debate: "Nouveau débat", new_debate: "Nouveau débat",
new_map_debate: "Nouveau débat sur caarte", new_map_debate: "Nouveau débat sur carte",
invite_in_debate: "Inviter au débat", invite_in_debate: "Inviter au débat",
SUPERADMIN: "Super administrateur⋅rice", SUPERADMIN: "Super administrateur⋅rice",
SUPERADMIN: "Root", SUPERADMIN: "Root",

View file

@ -1,569 +0,0 @@
<template>
<base-layout id="debate">
<template v-slot:title>
<h1>Debat sur carte</h1>
</template>
<!-- ************************************************* -->
<div id="mainContainer">
<div id="documentContainer">
<div class="scroll-area" @scroll="hidePopup(); updateSpaghettis();" ref="documentContainer">
<div class="wrap">
<documented v-bind:value="$t('documentation.debate_document')">
<div id="mapContainer">
<img
id="mapImg"
src="assets/img/mapimg.png"
alt="map image"
/>
<table id="myTable">
<tr
v-for="(_, indexOfLine) in nbOfLines"
:key="indexOfLine"
>
<td
v-for="(_, indexOfColumn) in nbOfColumns"
:key="indexOfColumn"
@click="select(indexOfLine, indexOfColumn)"
:class="{
selected: arrayData[indexOfLine][indexOfColumn],
}"
></td>
</tr>
</table>
</div>
</documented>
</div>
</div>
</div>
<div id="commentsContainer" v-bind:class="{ loading: mountedChildren < debate.commentsCount }"/>
<div
class="scroll-area"
@scroll="
hidePopup();
updateSpaghettis();
"
ref="commentsContainer"
>
<comment-widget
v-for="comment in filteredComments"
v-show="displayableComments[comment.id]"
v-bind:displayable-comments="displayableComments"
v-bind:key="comment.id"
v-bind:comment="comment"
v-bind:search="search"
v-bind:hide-on-scroll="true"
@tag-edition="editTags($event)"
@selection-end="selectionHandler($event)"
@focus-me="selectComment($event); scrollToComment($event)"
@select-me="selectComment($event)"
@highlight-me="highlight($event)"
@mounted="mountChild($event)"
@report="reportComment($event)"
@collapse="collapseComment($event)">
</comment-widget>
</div>
</div>
</div>
<div
v-show="selectedRange"
class="selection_popup z-depth-2"
ref="arguePopup"
v-bind:style="'top: ' + popup.y + 'px; left: ' + popup.x + 'px;'"
@click="createComment()"
@mousedown.stop=""
@mouseup.stop=""
>
{{ $t("argue").toLowerCase() }}
</div>
<template v-slot:addons>
<comment-modal
id="commentModal"
ref="commentModal"
v-bind:comment="newComment">
</comment-modal>
<tags-modal ref="tagModal">
</tags-modal>
</template>
</base-layout>
</template>
<style scoped>
table,
td,
th {
border: 1px solid black;
}
table {
border-collapse: collapse;
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
#mapContainer {
position: relative;
}
#mapImg {
width: 100%;
height: 100%;
}
.selected {
background-color: rgb(0 0 0 / 26%);
}
</style>
<script>
module.exports = {
data() {
return {
nbOfLines: 5,
nbOfColumns: 9,
arrayData: false,
// ********************************
debate: false,
newComment: false,
sortByPosition: false,
popup: { x: -9999, y: -9999 },
displayableComments: {},
selectedRange: false,
search: "",
mountedChildren: 0,
leftDisplay: "document",
scrapsMagicNumber: 2,
spaghettiOver: "",
spaghettiCountDown: 0,
scraps: [],
themes: [],
themeInput: "",
themeHelper: false,
// ****************************************
};
},
created() {
this.initDataArray();
this.fetchData();
},
computed: {
filteredComments() {
return this.debate.comments ? this.debate.comments.filter((comment) => !comment.parent) : []
},
documentAsDom() {
let div = document.createElement("div");
div.innerHTML = this.debate.document.content;
return div;
},
spaghettiData() {
if (this.leftDisplay === "theme") {
return this.themes;
} else {
return this.scraps;
}
},
},
watch: {
'$route'() {
this.fetchData();
if (this.$route.query.comment) {
this.scrollToComment(this.$route.query.comment);
}
},
debate() {
this.displayableComments = {};
//This is to make all the properties reactive
this.debate.comments.forEach((comment) => Vue.set(this.displayableComments, comment.id, true));
},
leftDisplay(val) {
if (val === 'scraps') {
this.fetchScraps();
}
if (val !== 'document') {
this.updateSpaghettis();
}
},
// search(val) {
// if (this.leftDisplay === 'scraps' || this.leftDisplay === 'theme') {
// this.updateSpaghettis();
// }
// this.updateDisplayableComments();
// },
sortByPosition(val) {
if (val) {
this.debate.deepSortComments((a, b) => a.compareBoundaryPoints(Range.START_TO_START, b));
} else {
this.debate.deepSortComments((a, b) => (a.created > b.created) ? 1 : -1);
}
if (this.leftDisplay !== 'document') {
// Sort by disply position
this.$nextTick(() => {
this.spaghettiData.forEach((s) => {
s.comments.sort((a, b) => {
let divA = document.getElementById("comment_" + a.id);
let divB = document.getElementById("comment_" + b.id);
return divA.offsetTop - divB.offsetTop
});
})
});
this.updateSpaghettis();
}
}
},
methods: {
/******************* */
fetchData() {
ArenService.Debates.get({
id: 3,
onSuccess: (debate) => {
this.debate = debate;
},
onError: () => {
this.$router.push("/404");
}
});
},
fetchScraps() {
ArenService.Debates.getScraps({
id: this.debate.id,
onSuccess: (result) => {
if (result.length > 0) {
let arrLength = result.map(r => r.comments.length).sort();
let minLength = arrLength[arrLength.length - this.scrapsMagicNumber];
result = result.filter(s => s.comments.length > 1 && s.comments.length >= minLength);
result.forEach((s, i) => {
s.id = i;
s.comments = s.comments.map(cid => ArenService.Store.get(cid, Comment));
let range = new Range();
range.setPathStart(this.documentAsDom, s.startContainer, s.startOffset);
range.setPathEnd(this.documentAsDom, s.endContainer, s.endOffset);
s.selected = false;
s.html = range.getHtml();
s.spaghettis = [];
s.comments.sort((a, b) => {
let divA = document.getElementById("comment_" + a.id);
let divB = document.getElementById("comment_" + b.id);
return divA.offsetTop - divB.offsetTop
});
});
this.scraps = result;
this.updateSpaghettis();
}
}
});
},
fetchTheme() {
if (this.themes.findIndex((t) => t.html === this.themeInput) === -1) {
ArenService.Debates.getTheme({
id: this.debate.id,
query: {theme: this.themeInput},
onSuccess: (result) => {
if (result.comments.length > 0) {
result.comments = result.comments.map(cid => ArenService.Store.get(cid, Comment)).filter((c) => c !== -1);
result.html = result.theme;
result.spaghettis = [];
result.selected = false;
result.comments.sort((a, b) => {
let divA = document.getElementById("comment_" + a.id);
let divB = document.getElementById("comment_" + b.id);
return divA.offsetTop - divB.offsetTop
});
this.themes.push(result);
this.updateSpaghettis();
this.themeInput = "";
} else {
this.themeHelper = this.$t('helper.theme_not_found');
setTimeout(() => this.themeHelper = false, 3500);
}
}
});
} else {
this.themeHelper = this.$t('helper.theme_already_set');
setTimeout(() => this.themeHelper = false, 3500);
}
},
updateDisplayableComments() {
let selectedScrapComments = false;
this.debate.comments.forEach((comment) => this.displayableComments[comment.id] = !selectedScrapComments || selectedScrapComments.includes(comment));
},
mountChild(comment) {
this.mountedChildren++;
console.table("Mounted Child", this.mountedChildren);
// If there is a commentId in the url, scroll to its position
if (this.$route.query.comment && this.mountedChildren >= this.debate.commentsCount) {
this.$nextTick(() => {
this.scrollToComment(this.$route.query.comment);
});
}
// When a new comment is created, scrool to its position
if (this.newComment && comment.owner.id === this.$root.user.id) {
this.$nextTick(() => {
this.scrollToComment(comment);
this.newComment = false;
});
}
},
hidePopup() {
this.selectedRange = false;
},
clearSelection( ) {
if (this.$route.query.comment) {
this.$router.replace({'query': null});
}
clearSelection( );
},
editTags(comment) {
this.$refs.tagModal.comment = comment;
let copy = [...comment.proposedTags];
this.$refs.tagModal.open((returnComment) => {
if (returnComment) {
ArenService.Comments.edit({
data: returnComment,
loading: false
});
} else {
comment.proposedTags = copy;
}
});
},
mailleSelectionHandler(comment,text){
if (this.newComment === false) {
this.newComment = new Comment( );
}
if (!this.$refs.commentModal.isOpen()) {
this.newComment.debate = this.debate;
this.newComment.hypostases = [];
this.newComment.tags = [];
this.newComment.parent = comment;
this.newComment.selection = text;
this.newComment.startContainer = 0;
this.newComment.endContainer = 0;
this.newComment.startOffset = 0;
this.newComment.endOffset = 0;
} else {
this.hidePopup();
}
this.createComment();
},
selectionHandler(comment) {
let selection = getSelection( );
let position = selection.anchorNode.compareDocumentPosition(selection.focusNode);
let backward = false;
if (!position && selection.anchorOffset > selection.focusOffset ||
position === Node.DOCUMENT_POSITION_PRECEDING)
backward = true;
if (!selection.isCollapsed) {
this.selectedRange = selection.getRangeAt(0);
this.selectedRange.affineToWord();
if (this.newComment === false) {
this.newComment = new Comment( );
}
if (!this.$refs.commentModal.isOpen()) {
this.newComment.debate = this.debate;
this.newComment.hypostases = [];
this.newComment.tags = [];
this.newComment.parent = comment;
this.newComment.selection = this.selectedRange.getHtml();
let container = this.getCommentContainer(this.newComment);
this.newComment.startContainer = container.getChildPathTo(this.selectedRange.startContainer);
this.newComment.endContainer = container.getChildPathTo(this.selectedRange.endContainer);
this.newComment.startOffset = this.selectedRange.startOffset;
this.newComment.endOffset = this.selectedRange.endOffset;
this.popupPositionFromRange(backward);
} else {
this.hidePopup();
}
}
},
popupPositionFromRange(backward) {
if (this.selectedRange) {
backward = backward === undefined ? this.$refs.arguePopup.classList.contains('top') : backward;
let top = mainContainer.offsetTop + 28;
let bottom = mainContainer.offsetHeight - window.scrollY + top - 58;
let rects = this.selectedRange.getClientRects( );
let start = rects[0];
let end = rects[rects.length - 1];
if (backward && start.y > top || (end.y + end.height) > bottom) {
this.popup.x = start.x + window.scrollX;
this.popup.y = start.y + window.scrollY;
this.$refs.arguePopup.classList.remove('bottom');
this.$refs.arguePopup.classList.add('top');
} else {
this.popup.x = end.x + end.width + window.scrollX;
this.popup.y = end.y + end.height + window.scrollY;
this.$refs.arguePopup.classList.remove('top');
this.$refs.arguePopup.classList.add('bottom');
}
}
},
getCommentContainer(comment) {
return comment.parent
? document.querySelector("#comment_" + comment.parent.id + " .argumentation")
: this.$refs.documentDisplay;
},
createComment( ) {
if (!this.$root.user.is('USER')) {
this.$confirm({
title: this.$t('not_connected'),
message: this.$t('helper.not_connected'),
isInfo: true
});
} else {
this.$refs.commentModal.open((returnValue) => {
if (returnValue) {
ArenService.Debates.addComment({
id: this.newComment.debate.id,
data: this.newComment,
loading: false
});
}
});
this.hidePopup( );
// For the selected text to stay
this.highlight(this.newComment);
}
},
selectComment(comment) {
let range = new Range( );
let container = comment.parent
? document.querySelector("#comment_" + comment.parent.id + " .argumentation")
: this.$refs.documentDisplay;
range.setPathStart(container, comment.startContainer, comment.startOffset);
range.setPathEnd(container, comment.endContainer, comment.endOffset);
clearSelection( );
getSelection( ).addRange(range);
return range;
},
highlight(comment) {
if (!comment.parent) {
let range = this.selectComment(comment);
if (!inScrollView(this.$refs.documentContainer, range)) {
scrollCenter(this.$refs.documentContainer, range);
}
} else {
// Select after scroll is done because of lazy loading
this.scrollToComment(comment.parent, false, () => {
this.selectComment(comment);
});
}
},
scrollToComment(comment, focus = true, callback = () => {}) {
let commentDiv = document.getElementById("comment_" + (comment.id ? comment.id : comment));
let previousScroll = this.$refs.commentsContainer.scrollTop;
if (focus) {
commentDiv.querySelector(".focuser").focus( );
}
this.$refs.commentsContainer.scrollTop = previousScroll;
if (!inScrollView(this.$refs.commentsContainer, commentDiv)) {
scrollCenter(this.$refs.commentsContainer, commentDiv, callback);
} else {
callback();
}
},
scrollToScrap(scrapId, callback) {
let scrapDiv = document.getElementById("scrap_" + scrapId);
if (!inScrollView(this.$refs.documentContainer, scrapDiv)) {
scrollCenter(this.$refs.documentContainer, scrapDiv, callback);
} else {
callback();
}
},
spaghetti(scrap, scrapIndex, comment, commentIndex) {
let scrapId = "scrap_" + scrapIndex;
let commentId = "comment_" + comment.id;
let scrapDiv = document.getElementById(scrapId);
let commentDiv = document.getElementById(commentId);
// Check if a parent is collapsed, if so remove spaghettis
let answersDiv = commentDiv.closest(".answers-container");
while (answersDiv && commentDiv) {
let commentParentDiv = answersDiv.previousElementSibling;
if (commentDiv.offsetTop - commentParentDiv.offsetTop < (commentParentDiv.offsetHeight / 2)) {
commentDiv = false;
} else {
answersDiv = answersDiv.parentElement.closest(".answers-container");
}
}
// Only if the div exists and is visible
if (commentDiv && commentDiv.offsetParent !== null) {
let offsetStart = this.$refs.documentContainer.scrollTop;
let offsetEnd = this.$refs.commentsContainer.scrollTop;
let heightStart = Math.min(scrapDiv.offsetHeight * 0.5, scrap.comments.length * 2);
let deltaY = heightStart / scrap.comments.length;
return d3.linkHorizontal().x(d => d.x).y(d => d.y)({
source: {
x: (scrapDiv.offsetLeft + scrapDiv.offsetWidth),
y: (scrapDiv.offsetTop + commentIndex * deltaY + (scrapDiv.offsetHeight - heightStart) / 2) - offsetStart
},
target: {
x: commentDiv.offsetLeft,
y: (commentDiv.offsetTop + commentDiv.offsetHeight / 2) - offsetEnd
}
});
}
return "";
},
updateSpaghettis() {
if (this.leftDisplay !== 'document') {
this.$nextTick(() => {
this.spaghettiData.forEach((s, scrapIndex) => {
s.spaghettis = s.comments.map((c, comIndex) => this.spaghetti(s, scrapIndex, c, comIndex));
s.color = d3.interpolateRainbow(scrapIndex / this.spaghettiData.length);
});
});
}
},
clickSpaghetti() {
let arrIds = this.spaghettiOver.split('_');
this.spaghettiCountDown = 2;
this.scrollToScrap(arrIds[1], () => this.spaghettiCountDown--);
this.scrollToComment(arrIds[2], true, () => this.spaghettiCountDown--);
},
collapseComment(duration) {
let start = Date.now();
duration = duration + 500;
let interval = setInterval(() => {
this.updateSpaghettis();
if (Date.now() - start >= duration) {
clearInterval(interval);
}
}, 1000 / 60);
},
reportComment(comment) {
this.$confirm({
title: this.$t('report'),
message: this.$t('helper.report_details')
});
},
/********************* */
initDataArray() {
let arr = [];
let col = Array(this.nbOfColumns).fill(false);
for (let i = 0; i < this.nbOfLines; i++) {
arr.push(col);
}
this.arrayData = arr;
},
select(l, c) {
let row = this.arrayData[l].slice();
row[c] = true;
this.arrayData.splice(l, 1, row);
this.mailleSelectionHandler(null,`Maille ligne ${l+1}, colonne ${c+1}`);
}
},
components: {
"tags-modal": vueLoader("components/modals/tagsModal"),
"comment-modal": vueLoader("components/modals/commentModal"),
"comment-widget": vueLoader("components/widgets/comment"),
"bullets-container": vueLoader("components/widgets/bulletsContainer"),
},
};
</script>

View file

@ -196,7 +196,7 @@
data( ) { data( ) {
return { return {
debate: new Debate( ), debate: new Debate( ),
categories: ArenService.Store.Category, categories: ArenService.Store.Category.filter(cat=>cat.is('CARTO')),
institution: new Institution( ), institution: new Institution( ),
search: "", search: "",
step: 0, step: 0,

View file

@ -0,0 +1,651 @@
<template>
<base-layout id="debate">
<template v-slot:title>
<h1>Debat sur carte</h1>
</template>
<!-- ************************************************* -->
<div id="mainContainer">
<div id="documentContainer">
<div
class="scroll-area"
@scroll="
hidePopup();
updateSpaghettis();
"
ref="documentContainer"
>
<div class="wrap">
<documented v-bind:value="$t('documentation.debate_document')">
<div id="mapContainer">
<img
id="mapImg"
:src="debate.document.mapLink"
alt="map image"
/>
<table id="myTable" v-if="debate">
<tr
v-for="(_, indexOfLine) in debate.document.meshLine"
:key="indexOfLine"
>
<td
v-for="(_, indexOfColumn) in debate.document.meshColumn"
:key="indexOfColumn"
@click="select(indexOfLine, indexOfColumn)"
:class="{
selected: arrayData[indexOfLine][indexOfColumn],
}"
></td>
</tr>
</table>
</div>
</documented>
</div>
</div>
</div>
<div
id="commentsContainer"
v-bind:class="{ loading: mountedChildren < debate.commentsCount }"
>
<div
class="scroll-area"
@scroll="
hidePopup();
updateSpaghettis();
"
ref="commentsContainer"
>
<comment-widget
v-for="comment in filteredComments"
v-show="displayableComments[comment.id]"
v-bind:displayable-comments="displayableComments"
v-bind:key="comment.id"
v-bind:comment="comment"
v-bind:search="search"
v-bind:hide-on-scroll="true"
@tag-edition="editTags($event)"
@selection-end="selectionHandler($event)"
@focus-me="
selectComment($event);
scrollToComment($event);
"
@select-me="selectComment($event)"
@highlight-me="highlight($event)"
@mounted="mountChild($event)"
@report="reportComment($event)"
@collapse="collapseComment($event)"
>
</comment-widget>
</div>
</div>
</div>
<div
v-show="selectedRange"
class="selection_popup z-depth-2"
ref="arguePopup"
v-bind:style="'top: ' + popup.y + 'px; left: ' + popup.x + 'px;'"
@click="createComment()"
@mousedown.stop=""
@mouseup.stop=""
>
{{ $t("argue").toLowerCase() }}
</div>
<template v-slot:addons>
<comment-modal
id="commentModal"
ref="commentModal"
v-bind:comment="newComment"
>
</comment-modal>
<tags-modal ref="tagModal"> </tags-modal>
</template>
</base-layout>
</template>
<style scoped>
table,
td,
th {
border: 1px solid black;
}
table {
border-collapse: collapse;
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
#mapContainer {
position: relative;
}
#mapImg {
width: 100%;
height: 100%;
}
.selected {
background-color: rgb(0 0 0 / 26%);
}
</style>
<script>
module.exports = {
data() {
return {
//nbOfLines: 5,
//nbOfColumns: 9,
arrayData: false,
// ********************************
debate: false,
newComment: false,
sortByPosition: false,
popup: { x: -9999, y: -9999 },
displayableComments: {},
selectedRange: false,
search: "",
mountedChildren: 0,
leftDisplay: "document",
scrapsMagicNumber: 2,
spaghettiOver: "",
spaghettiCountDown: 0,
scraps: [],
themes: [],
themeInput: "",
themeHelper: false,
// ****************************************
};
},
created() {
this.fetchData();
ArenService.CommentListener.listen({
id: this.$route.params.id,
onMessage: (comment) => {
Vue.set(this.displayableComments, comment.id, true);
},
});
},
computed: {
filteredComments() {
return this.debate.comments
? this.debate.comments.filter((comment) => !comment.parent)
: [];
},
documentAsDom() {
let div = document.createElement("div");
div.innerHTML = this.debate.document.content;
return div;
},
spaghettiData() {
if (this.leftDisplay === "theme") {
return this.themes;
} else {
return this.scraps;
}
},
},
watch: {
$route() {
this.fetchData();
if (this.$route.query.comment) {
this.scrollToComment(this.$route.query.comment);
}
},
debate() {
this.displayableComments = {};
//This is to make all the properties reactive
this.debate.comments.forEach((comment) =>
Vue.set(this.displayableComments, comment.id, true)
);
},
leftDisplay(val) {
if (val === "scraps") {
this.fetchScraps();
}
if (val !== "document") {
this.updateSpaghettis();
}
},
// search(val) {
// if (this.leftDisplay === 'scraps' || this.leftDisplay === 'theme') {
// this.updateSpaghettis();
// }
// this.updateDisplayableComments();
// },
sortByPosition(val) {
if (val) {
this.debate.deepSortComments((a, b) =>
a.compareBoundaryPoints(Range.START_TO_START, b)
);
} else {
this.debate.deepSortComments((a, b) =>
a.created > b.created ? 1 : -1
);
}
if (this.leftDisplay !== "document") {
// Sort by disply position
this.$nextTick(() => {
this.spaghettiData.forEach((s) => {
s.comments.sort((a, b) => {
let divA = document.getElementById("comment_" + a.id);
let divB = document.getElementById("comment_" + b.id);
return divA.offsetTop - divB.offsetTop;
});
});
});
this.updateSpaghettis();
}
},
},
methods: {
/******************* */
fetchData() {
ArenService.Debates.get({
id: this.$route.params.id,
onSuccess: (debate) => {
this.debate = debate;
let arr = [];
let col = Array(debate.document.meshColumn).fill(false);
for (let i = 0; i < debate.document.meshLine; i++) {
arr.push(col);
}
this.arrayData = arr;
},
onError: () => {
this.$router.push("/404");
},
});
},
fetchScraps() {
ArenService.Debates.getScraps({
id: this.debate.id,
onSuccess: (result) => {
if (result.length > 0) {
let arrLength = result.map((r) => r.comments.length).sort();
let minLength =
arrLength[arrLength.length - this.scrapsMagicNumber];
result = result.filter(
(s) => s.comments.length > 1 && s.comments.length >= minLength
);
result.forEach((s, i) => {
s.id = i;
s.comments = s.comments.map((cid) =>
ArenService.Store.get(cid, Comment)
);
let range = new Range();
range.setPathStart(
this.documentAsDom,
s.startContainer,
s.startOffset
);
range.setPathEnd(this.documentAsDom, s.endContainer, s.endOffset);
s.selected = false;
s.html = range.getHtml();
s.spaghettis = [];
s.comments.sort((a, b) => {
let divA = document.getElementById("comment_" + a.id);
let divB = document.getElementById("comment_" + b.id);
return divA.offsetTop - divB.offsetTop;
});
});
this.scraps = result;
this.updateSpaghettis();
}
},
});
},
fetchTheme() {
if (this.themes.findIndex((t) => t.html === this.themeInput) === -1) {
ArenService.Debates.getTheme({
id: this.debate.id,
query: { theme: this.themeInput },
onSuccess: (result) => {
if (result.comments.length > 0) {
result.comments = result.comments
.map((cid) => ArenService.Store.get(cid, Comment))
.filter((c) => c !== -1);
result.html = result.theme;
result.spaghettis = [];
result.selected = false;
result.comments.sort((a, b) => {
let divA = document.getElementById("comment_" + a.id);
let divB = document.getElementById("comment_" + b.id);
return divA.offsetTop - divB.offsetTop;
});
this.themes.push(result);
this.updateSpaghettis();
this.themeInput = "";
} else {
this.themeHelper = this.$t("helper.theme_not_found");
setTimeout(() => (this.themeHelper = false), 3500);
}
},
});
} else {
this.themeHelper = this.$t("helper.theme_already_set");
setTimeout(() => (this.themeHelper = false), 3500);
}
},
updateDisplayableComments() {
let selectedScrapComments = false;
this.debate.comments.forEach(
(comment) =>
(this.displayableComments[comment.id] =
!selectedScrapComments || selectedScrapComments.includes(comment))
);
},
mountChild(comment) {
this.mountedChildren++;
console.table("Mounted Child", this.mountedChildren);
// If there is a commentId in the url, scroll to its position
if (
this.$route.query.comment &&
this.mountedChildren >= this.debate.commentsCount
) {
this.$nextTick(() => {
this.scrollToComment(this.$route.query.comment);
});
}
// When a new comment is created, scrool to its position
if (this.newComment && comment.owner.id === this.$root.user.id) {
this.$nextTick(() => {
this.scrollToComment(comment);
this.newComment = false;
});
}
},
hidePopup() {
this.selectedRange = false;
},
clearSelection() {
if (this.$route.query.comment) {
this.$router.replace({ query: null });
}
clearSelection();
},
editTags(comment) {
this.$refs.tagModal.comment = comment;
let copy = [...comment.proposedTags];
this.$refs.tagModal.open((returnComment) => {
if (returnComment) {
ArenService.Comments.edit({
data: returnComment,
loading: false,
});
} else {
comment.proposedTags = copy;
}
});
},
mailleSelectionHandler(comment, text) {
if (this.newComment === false) {
this.newComment = new Comment();
}
if (!this.$refs.commentModal.isOpen()) {
this.newComment.debate = this.debate;
this.newComment.hypostases = [];
this.newComment.tags = [];
this.newComment.parent = comment;
this.newComment.selection = text;
this.newComment.startContainer = 0;
this.newComment.endContainer = 0;
this.newComment.startOffset = 0;
this.newComment.endOffset = 0;
} else {
this.hidePopup();
}
this.createComment();
},
selectionHandler(comment) {
let selection = getSelection();
let position = selection.anchorNode.compareDocumentPosition(
selection.focusNode
);
let backward = false;
if (
(!position && selection.anchorOffset > selection.focusOffset) ||
position === Node.DOCUMENT_POSITION_PRECEDING
)
backward = true;
if (!selection.isCollapsed) {
this.selectedRange = selection.getRangeAt(0);
this.selectedRange.affineToWord();
if (this.newComment === false) {
this.newComment = new Comment();
}
if (!this.$refs.commentModal.isOpen()) {
this.newComment.debate = this.debate;
this.newComment.hypostases = [];
this.newComment.tags = [];
this.newComment.parent = comment;
this.newComment.selection = this.selectedRange.getHtml();
let container = this.getCommentContainer(this.newComment);
this.newComment.startContainer = container.getChildPathTo(
this.selectedRange.startContainer
);
this.newComment.endContainer = container.getChildPathTo(
this.selectedRange.endContainer
);
this.newComment.startOffset = this.selectedRange.startOffset;
this.newComment.endOffset = this.selectedRange.endOffset;
this.popupPositionFromRange(backward);
} else {
this.hidePopup();
}
}
},
popupPositionFromRange(backward) {
if (this.selectedRange) {
backward =
backward === undefined
? this.$refs.arguePopup.classList.contains("top")
: backward;
let top = mainContainer.offsetTop + 28;
let bottom = mainContainer.offsetHeight - window.scrollY + top - 58;
let rects = this.selectedRange.getClientRects();
let start = rects[0];
let end = rects[rects.length - 1];
if ((backward && start.y > top) || end.y + end.height > bottom) {
this.popup.x = start.x + window.scrollX;
this.popup.y = start.y + window.scrollY;
this.$refs.arguePopup.classList.remove("bottom");
this.$refs.arguePopup.classList.add("top");
} else {
this.popup.x = end.x + end.width + window.scrollX;
this.popup.y = end.y + end.height + window.scrollY;
this.$refs.arguePopup.classList.remove("top");
this.$refs.arguePopup.classList.add("bottom");
}
}
},
getCommentContainer(comment) {
return comment.parent
? document.querySelector(
"#comment_" + comment.parent.id + " .argumentation"
)
: this.$refs.documentDisplay;
},
createComment() {
if (!this.$root.user.is("USER")) {
this.$confirm({
title: this.$t("not_connected"),
message: this.$t("helper.not_connected"),
isInfo: true,
});
} else {
this.$refs.commentModal.open((returnValue) => {
if (returnValue) {
ArenService.Debates.addComment({
id: this.newComment.debate.id,
data: this.newComment,
loading: false,
});
}
});
this.hidePopup();
// For the selected text to stay
this.highlight(this.newComment);
}
},
selectComment(comment) {
let range = new Range();
let container = comment.parent
? document.querySelector(
"#comment_" + comment.parent.id + " .argumentation"
)
: this.$refs.documentDisplay;
range.setPathStart(
container,
comment.startContainer,
comment.startOffset
);
range.setPathEnd(container, comment.endContainer, comment.endOffset);
clearSelection();
getSelection().addRange(range);
return range;
},
highlight(comment) {
if (!comment.parent) {
let range = this.selectComment(comment);
if (!inScrollView(this.$refs.documentContainer, range)) {
scrollCenter(this.$refs.documentContainer, range);
}
} else {
// Select after scroll is done because of lazy loading
this.scrollToComment(comment.parent, false, () => {
this.selectComment(comment);
});
}
},
scrollToComment(comment, focus = true, callback = () => {}) {
let commentDiv = document.getElementById(
"comment_" + (comment.id ? comment.id : comment)
);
let previousScroll = this.$refs.commentsContainer.scrollTop;
if (focus) {
commentDiv.querySelector(".focuser").focus();
}
this.$refs.commentsContainer.scrollTop = previousScroll;
if (!inScrollView(this.$refs.commentsContainer, commentDiv)) {
scrollCenter(this.$refs.commentsContainer, commentDiv, callback);
} else {
callback();
}
},
scrollToScrap(scrapId, callback) {
let scrapDiv = document.getElementById("scrap_" + scrapId);
if (!inScrollView(this.$refs.documentContainer, scrapDiv)) {
scrollCenter(this.$refs.documentContainer, scrapDiv, callback);
} else {
callback();
}
},
spaghetti(scrap, scrapIndex, comment, commentIndex) {
let scrapId = "scrap_" + scrapIndex;
let commentId = "comment_" + comment.id;
let scrapDiv = document.getElementById(scrapId);
let commentDiv = document.getElementById(commentId);
// Check if a parent is collapsed, if so remove spaghettis
let answersDiv = commentDiv.closest(".answers-container");
while (answersDiv && commentDiv) {
let commentParentDiv = answersDiv.previousElementSibling;
if (
commentDiv.offsetTop - commentParentDiv.offsetTop <
commentParentDiv.offsetHeight / 2
) {
commentDiv = false;
} else {
answersDiv = answersDiv.parentElement.closest(".answers-container");
}
}
// Only if the div exists and is visible
if (commentDiv && commentDiv.offsetParent !== null) {
let offsetStart = this.$refs.documentContainer.scrollTop;
let offsetEnd = this.$refs.commentsContainer.scrollTop;
let heightStart = Math.min(
scrapDiv.offsetHeight * 0.5,
scrap.comments.length * 2
);
let deltaY = heightStart / scrap.comments.length;
return d3
.linkHorizontal()
.x((d) => d.x)
.y((d) => d.y)({
source: {
x: scrapDiv.offsetLeft + scrapDiv.offsetWidth,
y:
scrapDiv.offsetTop +
commentIndex * deltaY +
(scrapDiv.offsetHeight - heightStart) / 2 -
offsetStart,
},
target: {
x: commentDiv.offsetLeft,
y: commentDiv.offsetTop + commentDiv.offsetHeight / 2 - offsetEnd,
},
});
}
return "";
},
updateSpaghettis() {
if (this.leftDisplay !== "document") {
this.$nextTick(() => {
this.spaghettiData.forEach((s, scrapIndex) => {
s.spaghettis = s.comments.map((c, comIndex) =>
this.spaghetti(s, scrapIndex, c, comIndex)
);
s.color = d3.interpolateRainbow(
scrapIndex / this.spaghettiData.length
);
});
});
}
},
clickSpaghetti() {
let arrIds = this.spaghettiOver.split("_");
this.spaghettiCountDown = 2;
this.scrollToScrap(arrIds[1], () => this.spaghettiCountDown--);
this.scrollToComment(arrIds[2], true, () => this.spaghettiCountDown--);
},
collapseComment(duration) {
let start = Date.now();
duration = duration + 500;
let interval = setInterval(() => {
this.updateSpaghettis();
if (Date.now() - start >= duration) {
clearInterval(interval);
}
}, 1000 / 60);
},
reportComment(comment) {
this.$confirm({
title: this.$t("report"),
message: this.$t("helper.report_details"),
});
},
/********************* */
select(l, c) {
let row = this.arrayData[l].slice();
row[c] = true;
this.arrayData.splice(l, 1, row);
this.mailleSelectionHandler(
null,
`Maille ligne ${l + 1}, colonne ${c + 1}`
);
},
},
components: {
"tags-modal": vueLoader("components/modals/tagsModal"),
"comment-modal": vueLoader("components/modals/commentModal"),
"comment-widget": vueLoader("components/widgets/comment"),
"bullets-container": vueLoader("components/widgets/bulletsContainer"),
},
};
</script>

View file

@ -60,7 +60,9 @@
query: categoryId ? {category: categoryId, overview: true} : {overview: true}, query: categoryId ? {category: categoryId, overview: true} : {overview: true},
onSuccess: debates => debates.forEach(d => { onSuccess: debates => debates.forEach(d => {
if (!this.categories.includes(d.document.category)) { if (!this.categories.includes(d.document.category)) {
this.categories.push(d.document.category) if(d.document.category.is("CARTO")){
this.categories.push(d.document.category);
}
} }
}) })
}); });

View file

@ -26,6 +26,7 @@
<template v-slot:right> <template v-slot:right>
<span <span
:disabled="document.mapLink.length<=0"
v-if="editionMode" v-if="editionMode"
class="waves-effect waves-light btn" class="waves-effect waves-light btn"
@click="saveDocument()" @click="saveDocument()"
@ -45,13 +46,12 @@
class="waves-effect waves-light btn" class="waves-effect waves-light btn"
@click=" @click="
fetchData(); fetchData();
editionMode = true; editionMode = true;"
"
> >
{{ $t("modify") }} {{ $t("modify") }}
</span> </span>
</template> </template>
<div id="cartoDetails"> <div id="cartoDetails" v-if="editionMode">
<div> <div>
<label for="loadCarto" class="btn">Charger la carte</label> <label for="loadCarto" class="btn">Charger la carte</label>
<input <input
@ -64,21 +64,21 @@
<div class="cartoInformation-bloc"> <div class="cartoInformation-bloc">
<div class="mailleInfo"> <div class="mailleInfo">
<label>Nombre de ligne </label> <label>Nombre de ligne </label>
<input type="text" maxlength="2" v-model.number="nbOfLines" /> <input type="text" maxlength="2" v-model.number="document.meshLine" />
</div> </div>
<div class="mailleInfo"> <div class="mailleInfo">
<label>Nombre de colonne </label> <label>Nombre de colonne </label>
<input type="text" maxlength="2" v-model.number="nbOfColumns" /> <input type="text" maxlength="2" v-model.number="document.meshColumn" />
</div> </div>
</div> </div>
</div> </div>
<div id="mapContainer" v-if="imgUrl"> <div id="mapContainer" v-if="imgUrl">
<img id="mapImg" :src="imgUrl" alt="map image" /> <img id="mapImg" :src="imgUrl" alt="map image" />
<table> <table>
<tr v-for="(_, indexOfLine) in nbOfLines" :key="indexOfLine"> <tr v-for="(_, indexOfLine) in document.meshLine" :key="indexOfLine">
<td <td
v-for="(_, indexOfColumn) in nbOfColumns" v-for="(_, indexOfColumn) in document.meshColumn"
:key="indexOfColumn" :key="indexOfColumn"
></td> ></td>
</tr> </tr>
@ -142,8 +142,8 @@ module.exports = {
editionMode: false, editionMode: false,
selectedCarto: null, selectedCarto: null,
imgUrl: null, imgUrl: null,
nbOfLines: 0, // nbOfLines: 0,
nbOfColumns: 0, // nbOfColumns: 0,
}; };
}, },
created() { created() {
@ -163,12 +163,16 @@ module.exports = {
onSuccess: (document) => { onSuccess: (document) => {
this.document = document; this.document = document;
this.copyDocument(this.document, this.unmodifiedDocument); this.copyDocument(this.document, this.unmodifiedDocument);
this.imgUrl = document.mapLink;
}, },
}); });
} else { } else {
this.document = new Document(); this.document = new Document();
this.document.type = "CARTO";
this.document.mapLink="";
this.document.category = new Category(); this.document.category = new Category();
this.document.category.id = this.$route.query.category; this.document.category.id = this.$route.query.category;
this.document.category.type = "CARTO";
this.editionMode = true; this.editionMode = true;
} }
}, },
@ -181,6 +185,7 @@ module.exports = {
}, },
saveDocument() { saveDocument() {
this.editionMode = false; this.editionMode = false;
//this.document.mapLink = this.getBase64Image();
ArenService.Documents.createOrUpdate({ ArenService.Documents.createOrUpdate({
data: this.document, data: this.document,
onSuccess: (document) => { onSuccess: (document) => {
@ -199,7 +204,28 @@ module.exports = {
const file = event.target.files[0]; const file = event.target.files[0];
this.selectedCarto = file; this.selectedCarto = file;
this.imgUrl = URL.createObjectURL(file); this.imgUrl = URL.createObjectURL(file);
this.document.mapLink = this.getBase64Image();
}, },
getBase64Image() {
// Create an empty canvas element
let img = document.getElementById("mapImg");
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
//let dt = dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
console.log(dataURL);
return dataURL;
}
}, },
components: { components: {
"wysiwyg-editor": vueLoader("components/widgets/wysiwygEditor"), "wysiwyg-editor": vueLoader("components/widgets/wysiwygEditor"),

View file

@ -65,7 +65,7 @@
module.exports = { module.exports = {
data( ) { data( ) {
return { return {
categories: ArenService.Store.Category categories: ArenService.Store.Category.filter(cat=>!cat.is('CARTO'))
}; };
}, },
created( ) { created( ) {

View file

@ -65,7 +65,7 @@
module.exports = { module.exports = {
data( ) { data( ) {
return { return {
categories: ArenService.Store.Category categories: ArenService.Store.Category.filter(cat=>cat.is('CARTO'))
}; };
}, },
created( ) { created( ) {
@ -84,14 +84,17 @@
name: document.name + " - copie", name: document.name + " - copie",
author: document.author, author: document.author,
content: document.content, content: document.content,
category: {id: document.category.id} category: {id: document.category.id, type: "CARTO"}
}); });
ArenService.Documents.duplicate({ ArenService.Documents.duplicate({
id: document.id id: document.id
}); });
}, },
createOrUpdateCategory(category = {}) { createOrUpdateCategory(category = {}) {
this.$refs.categoryModal.category = new Category(category); let catg = new Category(category);
catg.type = "CARTO"
this.$refs.categoryModal.category = catg;
console.log(this.$refs.categoryModal.category);
this.$refs.categoryModal.open((updatedCat) => { this.$refs.categoryModal.open((updatedCat) => {
if (updatedCat) { if (updatedCat) {
ArenService.Categories.createOrUpdate({ ArenService.Categories.createOrUpdate({

View file

@ -111,14 +111,14 @@
<li v-if="user.is('USER')"> <li v-if="user.is('USER')">
<router-link to="/votemajoritaire" v-bind:class="{ active: $route.path === '/votemajoritaire' || $route.path === '/createVoteMajoritaire' || $route.path === '/votemajoritairedetails'}">Votes majoritaires</router-link> <router-link to="/votemajoritaire" v-bind:class="{ active: $route.path === '/votemajoritaire' || $route.path === '/createVoteMajoritaire' || $route.path === '/votemajoritairedetails'}">Votes majoritaires</router-link>
</li> </li>
<li> <li v-if="user.is('USER')">
<router-link to="/creation-debats-sur-cartes" v-bind:class="{ active: $route.path === '/creation-debats-sur-cartes' }">Ouvrir un débat sur carte</router-link> <router-link to="/creation-debats-sur-cartes" v-bind:class="{ active: $route.path === '/creation-debats-sur-cartes' }">Ouvrir dc</router-link>
</li> </li>
<li> <li v-if="user.is('USER')">
<router-link to="/documents-debats-sur-cartes" v-bind:class="{ active: $route.path === '/documents-debats-sur-cartes' }">Mes documents de débat sur carte</router-link> <router-link to="/documents-debats-sur-cartes" v-bind:class="{ active: $route.path === '/documents-debats-sur-cartes' }">Mes documents dc</router-link>
</li> </li>
<li> <li v-if="user.is('USER')">
<router-link to="/debats-sur-cartes" v-bind:class="{ active: $route.path === '/debats-sur-cartes' }">Mes débats sur carte</router-link> <router-link to="/debats-sur-cartes" v-bind:class="{ active: $route.path === '/debats-sur-cartes' }">Débats sur carte</router-link>
</li> </li>
</ul> </ul>
<ul class="right"> <ul class="right">