import { Controller } from "stimulus"
import { SearchAPI } from "../utils/search_api"
import {DatasetAPI} from "../utils/dataset_api"
import Sortable from 'sortablejs'
export default class extends Controller {
static targets = ['currentPage', 'articleOverlay', 'selectedArticlePanel', 'addArticleButton', 'addCompoundArticleButton', 'compoundArticlePanel']
static values = {currentPage: Number, nbPages: Number, pages: Array, articles: Array, selectedArticles: Array, issueId: String, compoundMode: Boolean}
isDragged = false
viewer = null
selectedCompound = null
connect() {
const selectedParam = (new URL(window.location.href)).searchParams.get('selected')
const selectedCompoundParam = (new URL(window.location.href)).searchParams.get('selected_compound')
if (selectedParam == null) {
this.selectedArticlesValue = []
if (selectedCompoundParam != null) {
const compoundParts = $(`#compound-articles-panel li[data-compound-id="${selectedCompoundParam}"]`).data('parts')
this.selectedCompound = {id: selectedCompoundParam, parts: compoundParts}
$(`#compound-articles-panel li[data-compound-id="${selectedCompoundParam}"]`).addClass("active")
}
this.load_named_entities([this.issueIdValue])
}
else {
this.selectedArticlesValue = [selectedParam]
this.load_named_entities([selectedParam])
}
this.setup_viewer()
this.setup_mention_click()
this.setup_compound()
this.sortable = new Sortable(document.getElementById("compound_list"), {
handle: ".li-handle", // Drag handle selector within list items
draggable: ".cmpnd-item" // Specifies which items inside the element should be draggable
})
}
setup_compound() {
// Compound Mode Activation/Deactivation
$("#compound_switch").prop('checked', false)
$("#compound_switch").on("change", (event) => {
this.compoundModeValue = !this.compoundModeValue
if(this.compoundModeValue) { // if compound mode is being activated
this.unselectArticles()
this.unselect_compound_article()
$("#compound_articles_list li").removeClass("active")
$("#compound_card_content").removeClass("d-none")
}
else { // Compound deactivated
$("#compound_list").html("")
$("#compound_card_content").addClass("d-none")
$('div.article_overlay_compound_selected').removeClass("article_overlay_compound_selected")
this.selectedArticlesValue = []
}
})
// Delete article part when creating a compound article
$("#compound-articles-panel").on("click", ".delete_article_part", (event) => {
const articleId = $(event.target).parents('li').data('id')
$(`#${articleId}`).removeClass("article_overlay_compound_selected")
this.selectedArticlesValue = this.selectedArticlesValue.filter(item => item !== articleId)
$(`li[data-id="${articleId}"]`).remove()
return false
})
// Open the modal to validate and create the compound article
$("#create_compound_button").on("click", (event) => {
const article_parts = new Map(this.selectedArticlesValue.map((artid) => {
const text = this.articlesValue.filter(elt => {return elt.id == artid})[0].all_text
return [artid, text]
}))
if(article_parts.length !== 0) {
SearchAPI.confirm_compond_creation(Object.fromEntries(article_parts), (data) => {
$("#confirm_compound_modal").html(data.modal_content)
let myModal = new bootstrap.Modal(document.getElementById('confirm_compound_modal'), {})
myModal.toggle()
})
}
})
// On the modal, actual creation of the compound article
$("#confirm_compound_modal").on("click", "#create-compound-button", (event) => {
$("#compound_logs").html("")
const title = $("#compound-title").val()
const all_text = this.selectedArticlesValue.map((artid) => {return $(`#${artid}`).data('text') }).join("\n")
event.target.setAttribute('disabled', 'disabled')
event.target.innerHTML = `Loading`
SearchAPI.create_compound(title, all_text, this.issueIdValue, this.selectedArticlesValue, (data) => {
if(data.status === 'ok') {
bootstrap.Modal.getInstance(document.getElementById('confirm_compound_modal')).hide()
$("#compound_articles_list").html(data.html)
$("#compound_switch").prop("checked", false).change()
}
else {
$("#compound_logs").html(data['message'])
event.target.innerHTML = "Create"
event.target.removeAttribute('disabled')
}
})
})
// Delete a previously created compound article
$("#compound_articles_list").on("click", ".delete_compound_article", (event) => {
const parent_li = $(event.target).parents('li')
const compoundId = parent_li.data('compound-id')
if (confirm(`Are you sure you want to delete this compound article ? It will also be deleted from the datasets it belongs to.`)) {
this.unselect_compound_article(compoundId)
SearchAPI.delete_compound_article(compoundId, (data) => {
$("#compound_articles_list").html(data.html)
$("#manage_datasets_content").html(data.datasets)
})
}
return false
})
// Compound article selection
$("#compound_articles_list").on("click", "li", (event) => {
const elt = $(event.target)
if(elt.hasClass("active"))
this.unselect_compound_article(elt.data('compoundId'))
else
this.select_compound_article(elt.data('compoundId'))
return false
})
}
select_compound_article(compoundId) {
const compoundParts = $(`#compound-articles-panel li[data-compound-id="${compoundId}"]`).data('parts')
this.selectedCompound = {id: compoundId, parts: compoundParts}
$("#compound-articles-panel li").removeClass("active")
$(`#compound-articles-panel li[data-compound-id="${compoundId}"]`).addClass("active")
this.unselectArticles()
$(this.addArticleButtonTarget).addClass("d-none")
$(this.addCompoundArticleButtonTarget).removeClass("d-none")
$(".article_overlay_compound_selected").removeClass("article_overlay_compound_selected").addClass("article_overlay")
for(const part_id of this.selectedCompound.parts) {
$(`#${part_id}`).addClass("article_overlay_compound_selected")
}
if (window.history.replaceState) {
let url = new URL(window.location.href)
url.searchParams.set('selected_compound', this.selectedCompound.id)
window.history.replaceState(null, '', url.toString())
}
this.load_named_entities(this.selectedCompound.parts)
this.updateSelectedArticlePanel()
}
unselect_compound_article(compoundId) {
$(this.addCompoundArticleButtonTarget).addClass("d-none")
this.selectedCompound = null
if (compoundId == undefined)
$(`#compound-articles-panel li`).removeClass("active")
else
$(`#compound-articles-panel li[data-compound-id="${compoundId}"]`).removeClass("active")
$(".article_overlay_compound_selected").removeClass("article_overlay_compound_selected").addClass("article_overlay")
if (window.history.replaceState) {
let url = new URL(window.location.href)
url.searchParams.delete('selected_compound')
window.history.replaceState(null, '', url.toString())
}
this.load_named_entities([this.issueIdValue])
this.updateSelectedArticlePanel()
}
setup_mention_click() {
$('#named-entities-panel').on("click", ".entity_mention", (event) => {
let articleId = event.target.dataset['articleId']
// Go to article page and select it
let article = this.articlesValue.filter((obj) => { return obj["id"] == articleId})[0]
let pagenum = article.canvases_parts[0]
pagenum = parseInt(pagenum.substring(pagenum.lastIndexOf('_')+1, pagenum.lastIndexOf("#xywh")))
// this.viewer.goToPage(pagenum)
// this.viewer.viewport.zoomTo(2)
// this.viewer.viewport.panTo(new OpenSeadragon.Point(loc.x+loc.width/2, loc.y+loc.height/2))
})
}
article_clicked(event) {
if(this.isDragged) {
this.isDragged = false
}
else {
const articleId = event.target.getAttribute('id')
// If the article is already selected
if(this.selectedArticlesValue.includes(articleId)) {
if(this.compoundModeValue) {
$(event.target).removeClass("article_overlay_compound_selected")
this.selectedArticlesValue = this.selectedArticlesValue.filter(item => item !== articleId)
$(`li[data-id="${articleId}"]`).remove()
}
else {
this.unselectArticles(articleId)
}
}
else { // If the article is not yet selected
if(this.compoundModeValue) {
$(event.target).addClass("article_overlay_compound_selected")
const arr = this.selectedArticlesValue
arr.push(articleId)
this.selectedArticlesValue = arr
const list_elt = $(`
`)
const text = $(`${$(event.target).data("text").slice(1,-1).substring(0,37)}...
`)
const delete_span = $(``)
const move_span = $(``)
list_elt.append(move_span)
list_elt.append(text)
list_elt.append(delete_span)
$("#compound_list").append(list_elt)
}
else {
this.hide_mask()
$(this.addArticleButtonTarget).removeClass("d-none")
this.selectedCompound = null
$(this.addCompoundArticleButtonTarget).addClass("d-none")
$("#compound_articles_list li").removeClass("active")
$(".article_overlay_compound_selected").removeClass("article_overlay_compound_selected").addClass("article_overlay")
$(".article_overlay_selected").removeClass("article_overlay_selected").addClass("article_overlay")
$(event.target).addClass("article_overlay_selected").removeClass("article_overlay")
this.selectedArticlesValue = [articleId]
this.display_mask($(event.target).data('loc'))
// Change url param for selected article
if (window.history.replaceState) {
let url = new URL(window.location.href)
url.searchParams.delete('selected_compound')
url.searchParams.set('selected', articleId)
window.history.replaceState(null, '', url.toString())
}
$('#named-entities-panel').find(".card-body").html("")
this.load_named_entities([articleId])
this.updateSelectedArticlePanel()
}
}
}
}
unselectArticles(articleId) {
this.hide_mask()
$(this.addArticleButtonTarget).addClass("d-none")
$(".article_overlay_selected").removeClass("article_overlay_selected").addClass("article_overlay")
if (articleId === undefined) {
if (this.selectedArticlesValue.length !== 0) {
$('#named-entities-panel').find(".card-body").html("")
this.load_named_entities([this.issueIdValue])
}
this.selectedArticlesValue = []
}
else {
this.selectedArticlesValue = this.selectedArticlesValue.filter(item => item !== articleId)
$('#named-entities-panel').find(".card-body").html("")
this.load_named_entities([this.issueIdValue])
}
// Change url param for selected article
if (window.history.replaceState) {
let url = new URL(window.location.href)
url.searchParams.delete('selected')
window.history.replaceState(null, '', url.toString())
}
this.updateSelectedArticlePanel()
}
updateSelectedArticlePanel() {
if(this.selectedArticlesValue.length == 0) {
if(this.selectedCompound) {
this.selectedArticlePanelTarget.hidden = false
const text = $.map(this.selectedCompound.parts, (article_id, idx) => {
const art = this.articlesValue.filter(elt => elt.id == article_id)[0]
return art.all_text.replaceAll("\"", "").replaceAll("\\n", "
")
}).join("\n")
$(this.selectedArticlePanelTarget).find('h5')[0].innerHTML = ""
$(this.selectedArticlePanelTarget).find('p')[0].innerHTML = text
}
else {
this.selectedArticlePanelTarget.hidden = true
}
}
else {
this.selectedArticlePanelTarget.hidden = false
const text = $.map(this.selectedArticlesValue, (article_id, idx) => {
return $(`#${article_id}`).data('text').replaceAll("\"", "").replaceAll("\\n", "
")
}).join("\n")
let title
if(this.selectedArticlesValue.length == 1) {
title = this.articlesValue.filter(o => o.id === this.selectedArticlesValue[0])[0].title
if(title == null)
title = this.selectedArticlesValue[0]
}
else
title = "Compound"
$(this.selectedArticlePanelTarget).find('h5')[0].innerHTML = title
$(this.selectedArticlePanelTarget).find('p')[0].innerHTML = text
}
}
load_named_entities(docsIds) {
SearchAPI.load_named_entities(docsIds, (data) => {
$('#named-entities-panel').find(".card-body").html(data)
})
}
selectWorkingDataset(event) {
const datasetID = parseInt($(event.target).find("option:selected").val())
DatasetAPI.setCurrentWorkingDataset(datasetID, (data) => {})
}
addSelectedArticleToWorkingDataset(event) {
DatasetAPI.addSelectedDocumentsToWorkingDataset(this.selectedArticlesValue, (data)=> {
$("#notifications").append(data['notif'])
for(const notif of $('.toast')) {
const notifToast = bootstrap.Toast.getOrCreateInstance(notif)
notifToast.show()
notif.addEventListener('hidden.bs.toast', (event) => {
bootstrap.Toast.getOrCreateInstance(event.target).dispose()
event.target.remove()
})
}
// Find dataset in list and change nb docs
const option = $("#working_dataset_select").find(":selected")
option.html(`${data['title']} (${data['nbissues']+data['nbarticles']} docs)`)
})
}
addSelectedCompoundArticleToWorkingDataset(event) {
DatasetAPI.addSelectedCompoundToWorkingDataset(this.selectedCompound.id, (data) => {
$("#notifications").append(data['notif'])
for(const notif of $('.toast')) {
const notifToast = bootstrap.Toast.getOrCreateInstance(notif)
notifToast.show()
notif.addEventListener('hidden.bs.toast', (event) => {
bootstrap.Toast.getOrCreateInstance(event.target).dispose()
event.target.remove()
})
}
// Find dataset in list and change nb docs
const option = $("#working_dataset_select").find(":selected")
option.html(`${data['title']} (${data['nbissues']+data['nbarticles']+data['nbcompounds']} docs)`)
})
}
addEntireIssueToWorkingDataset(event) {
DatasetAPI.addSelectedDocumentsToWorkingDataset([this.issueIdValue], (data)=> {
$("#notifications").append(data['notif'])
for(const notif of $('.toast')) {
const notifToast = bootstrap.Toast.getOrCreateInstance(notif)
notifToast.show()
notif.addEventListener('hidden.bs.toast', (event) => {
bootstrap.Toast.getOrCreateInstance(event.target).dispose()
event.target.remove()
})
}
// Find dataset in list and change nb docs
const option = $("#working_dataset_select").find(":selected")
option.html(`${data['title']} (${data['nbissues']+data['nbarticles']} docs)`)
})
}
setup_viewer() {
const selectedArticleObject = this.articlesValue.filter((elt)=>{return elt.id == this.selectedArticlesValue[0]})[0]
let initialPage = null
if(selectedArticleObject == undefined) {
if(this.selectedCompound) {
$(this.addCompoundArticleButtonTarget).removeClass("d-none")
$(this.addArticleButtonTarget).addClass("d-none")
const first_article_part = this.articlesValue.filter((elt)=>{return elt.id == this.selectedCompound.parts[0]})[0]
const pagenum = first_article_part.canvases_parts[0]
initialPage = parseInt(pagenum.substring(pagenum.lastIndexOf('_')+1, pagenum.lastIndexOf("#xywh")))-1
}
else {
initialPage = 0
$(this.addArticleButtonTarget).addClass("d-none")
$(this.addCompoundArticleButtonTarget).addClass("d-none")
}
}
else {
$(this.addArticleButtonTarget).removeClass("d-none")
const pagenum = selectedArticleObject.canvases_parts[0]
initialPage = parseInt(pagenum.substring(pagenum.lastIndexOf('_')+1, pagenum.lastIndexOf("#xywh")))-1
}
this.viewer = OpenSeadragon({
id: "openseadragon_view",
prefixUrl: "/static/js/openseadragon/images/feathericons/",
sequenceMode: true,
initialPage: initialPage,
tileSources: this.pagesValue,
showFullPageControl: false
})
// Set the page counter on the osd viewer
this.currentPageValue = this.viewer.currentPage()+1
this.currentPageTarget.innerHTML = this.currentPageValue
// Handler when the current page is changed
this.viewer.addHandler("page", (data) => {
this.currentPageValue = data.page + 1
this.currentPageTarget.innerHTML = this.currentPageValue
if(!this.compoundModeValue) {
$('#named-entities-panel').find(".card-body").html("")
this.load_named_entities([this.issueIdValue])
this.selectedArticlesValue = []
if (window.history.replaceState) {
let url = new URL(window.location.href)
url.searchParams.delete('selected')
window.history.replaceState(null, '', url.toString())
}
}
})
// Handler when a page is open (when landing on page and after a page change)
this.viewer.addHandler("open", (data) => {
for (let article of this.articlesValue) {
let pagenum = article.canvases_parts[0]
pagenum = parseInt(pagenum.substring(pagenum.lastIndexOf('_')+1, pagenum.lastIndexOf("#xywh")))
if (pagenum === this.currentPageValue) {
let bbox = article.bbox
let loc = this.viewer.viewport.imageToViewportRectangle(bbox[0], bbox[1], bbox[2], bbox[3])
let article_class = null
if(this.compoundModeValue) {
if(this.selectedArticlesValue.includes(article.id)) {
article_class = "article_overlay_compound_selected"
}
else {
article_class = "article_overlay"
}
}
else {
if(this.selectedArticlesValue[0] == article.id) {
this.display_mask(loc)
article_class = "article_overlay_selected"
}
else {
if(this.selectedCompound !== null && this.selectedCompound.parts.includes(article.id)) {
article_class = "article_overlay_compound_selected"
}
else {
article_class = "article_overlay"
}
}
}
let elt = $(``)
elt.attr("data-viewer-target", "articleOverlay")
elt.attr("data-action", "click->viewer#article_clicked")
elt.attr("data-loc", JSON.stringify({'x': loc.x, 'y': loc.y, 'width': loc.width, 'height': loc.height}))
elt.attr("data-text", JSON.stringify(article.all_text))
this.viewer.addOverlay({element: elt[0], location: loc})
this.setOSDDragHandler(elt[0])
}
}
this.updateSelectedArticlePanel()
})
}
display_mask(overlay_loc) {
let maskN = $("")
let maskE = $("")
let maskS = $("")
let maskW = $("")
let locN = new OpenSeadragon.Rect(0, 0, overlay_loc.x+overlay_loc.width, overlay_loc.y)
let locE = new OpenSeadragon.Rect(overlay_loc.x+overlay_loc.width, 0, this.viewer.viewport._contentBounds.width-(overlay_loc.x+overlay_loc.width), overlay_loc.y+overlay_loc.height)
let locS = new OpenSeadragon.Rect(overlay_loc.x, overlay_loc.y+overlay_loc.height, this.viewer.viewport._contentBounds.width-overlay_loc.x, this.viewer.viewport._contentBounds.height-(overlay_loc.y+overlay_loc.height))
let locW = new OpenSeadragon.Rect(0, overlay_loc.y, overlay_loc.x, this.viewer.viewport._contentBounds.height-overlay_loc.y)
this.viewer.addOverlay({element: maskN[0], location: locN})
this.viewer.addOverlay({element: maskE[0], location: locE})
this.viewer.addOverlay({element: maskS[0], location: locS})
this.viewer.addOverlay({element: maskW[0], location: locW})
}
hide_mask(){
this.viewer.removeOverlay("mask_north")
this.viewer.removeOverlay("mask_east")
this.viewer.removeOverlay("mask_south")
this.viewer.removeOverlay("mask_west")
}
setOSDDragHandler(element) {
let tracker = new OpenSeadragon.MouseTracker({
clickDistThreshold: 5,
element: element,
dragHandler: (event) => {
this.isDragged = true
let e = event.eventSource.element
$(e).attr('data-noclick', true)
let gestureSettings = null
let canvasDragEventArgs = {
tracker: event.eventSource,
position: event.position,
delta: event.delta,
speed: event.speed,
direction: event.direction,
shift: event.shift,
originalEvent: event.originalEvent,
preventDefaultAction: event.preventDefaultAction
}
if (!canvasDragEventArgs.preventDefaultAction && this.viewer.viewport)
gestureSettings = this.viewer.gestureSettingsByDeviceType(event.pointerType)
if (!this.viewer.panHorizontal)
event.delta.x = 0
if (!this.viewer.panVertical)
event.delta.y = 0
if (this.viewer.viewport.flipped)
event.delta.x = -event.delta.x
if (this.viewer.constrainDuringPan) {
let delta = this.viewer.viewport.deltaPointsFromPixels(event.delta.negate())
this.viewer.viewport.centerSpringX.target.value += delta.x
this.viewer.viewport.centerSpringY.target.value += delta.y
let bounds = this.viewer.viewport.getBounds()
let constrainedBounds = this.viewer.viewport.getConstrainedBounds()
this.viewer.viewport.centerSpringX.target.value -= delta.x
this.viewer.viewport.centerSpringY.target.value -= delta.y
if (bounds.x != constrainedBounds.x)
event.delta.x = 0
if (bounds.y != constrainedBounds.y)
event.delta.y = 0
}
this.viewer.viewport.panBy(this.viewer.viewport.deltaPointsFromPixels(event.delta.negate()), gestureSettings.flickEnabled && !this.viewer.constrainDuringPan)
}
})
}
}