mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-26 11:24:18 +00:00
337 lines
14 KiB
JavaScript
337 lines
14 KiB
JavaScript
nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
|
|
name = 'Reader';
|
|
|
|
constructor(app) {
|
|
this.app = app;
|
|
|
|
this.data = {};
|
|
|
|
this.elements = {
|
|
container: document.querySelector(`#corpus-analysis-reader-container`),
|
|
corpus: document.querySelector(`#corpus-analysis-reader-corpus`),
|
|
corpusPagination: document.querySelector(`#corpus-analysis-reader-corpus-pagination`),
|
|
error: document.querySelector(`#corpus-analysis-reader-error`),
|
|
progress: document.querySelector(`#corpus-analysis-reader-progress`),
|
|
userInterfaceForm: document.querySelector(`#corpus-analysis-reader-user-interface-form`)
|
|
};
|
|
|
|
this.settings = {
|
|
perPage: parseInt(this.elements.userInterfaceForm['per-page'].value),
|
|
textStyle: parseInt(this.elements.userInterfaceForm['text-style'].value),
|
|
tokenRepresentation: this.elements.userInterfaceForm['token-representation'].value,
|
|
pagination: {
|
|
innerWindow: 5,
|
|
outerWindow: 1
|
|
}
|
|
}
|
|
|
|
this.app.registerExtension(this);
|
|
}
|
|
|
|
async submitForm() {
|
|
this.app.disableActionElements();
|
|
this.elements.error.innerText = '';
|
|
this.elements.error.classList.add('hide');
|
|
this.elements.progress.classList.remove('hide');
|
|
try {
|
|
const paginatedCorpus = await this.data.corpus.o.paginate(1, this.settings.perPage);
|
|
this.data.corpus.p = paginatedCorpus;
|
|
this.renderCorpus();
|
|
this.renderCorpusPagination();
|
|
this.elements.progress.classList.add('hide');
|
|
} catch (error) {
|
|
let errorString = '';
|
|
if ('code' in error) {errorString += `[${error.code}] `;}
|
|
errorString += `${error.constructor.name}`;
|
|
if ('description' in error) {errorString += `: ${error.description}`;}
|
|
this.elements.error.innerText = errorString;
|
|
this.elements.error.classList.remove('hide');
|
|
app.ui.flash(errorString, 'error');
|
|
this.elements.progress.classList.add('hide');
|
|
}
|
|
this.app.enableActionElements();
|
|
}
|
|
|
|
async init() {
|
|
// Init data
|
|
this.data.corpus = this.app.data.corpus;
|
|
// Add event listeners
|
|
this.elements.userInterfaceForm.addEventListener('submit', (event) => {
|
|
event.preventDefault();
|
|
this.submitForm();
|
|
});
|
|
this.elements.userInterfaceForm.addEventListener('change', (event) => {
|
|
if (event.target === this.elements.userInterfaceForm['per-page']) {
|
|
this.settings.perPage = parseInt(this.elements.userInterfaceForm['per-page'].value);
|
|
this.submitForm();
|
|
}
|
|
if (event.target === this.elements.userInterfaceForm['text-style']) {
|
|
this.settings.textStyle = parseInt(this.elements.userInterfaceForm['text-style'].value);
|
|
this.setTextStyle();
|
|
}
|
|
if (event.target === this.elements.userInterfaceForm['token-representation']) {
|
|
this.settings.tokenRepresentation = this.elements.userInterfaceForm['token-representation'].value;
|
|
this.setTokenRepresentation();
|
|
}
|
|
});
|
|
// Load initial data
|
|
await this.submitForm();
|
|
}
|
|
|
|
clearCorpus() {
|
|
// Destroy with .p-attr elements associated Materialize tooltips
|
|
let pAttrElements = this.elements.corpus.querySelectorAll('.p-attr.tooltipped');
|
|
for (let pAttrElement of pAttrElements) {
|
|
M.Tooltip.getInstance(pAttrElement)?.destroy();
|
|
}
|
|
this.elements.corpus.innerHTML = `
|
|
<p class="show-if-only-child">
|
|
<span class="card-title"><i class="left material-icons" style="font-size: inherit;">search</i>Nothing here...</span><br>
|
|
No text available.
|
|
</p>
|
|
`.trim();
|
|
}
|
|
|
|
renderCorpus() {
|
|
this.clearCorpus();
|
|
let item = this.data.corpus.p.items[0];
|
|
this.elements.corpus.innerHTML += `
|
|
<p>${this.cposRange2HTML(item[0], item[item.length - 1])}</p>
|
|
`.trim();
|
|
this.setTextStyle();
|
|
this.setTokenRepresentation();
|
|
}
|
|
|
|
clearCorpusPagination() {
|
|
this.elements.corpusPagination.innerHTML = '';
|
|
this.elements.corpusPagination.classList.add('hide');
|
|
}
|
|
|
|
renderCorpusPagination() {
|
|
this.clearCorpusPagination();
|
|
if (this.data.corpus.p.pages === 0) {return;}
|
|
let pageElement;
|
|
// First page button. Disables first page button if on first page
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="${this.data.corpus.p.page === 1 ? 'disabled' : 'waves-effect'}">
|
|
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.page === 1 ? '' : 'data-target="1"'}>
|
|
<i class="material-icons">first_page</i>
|
|
</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
// Previous page button. Disables previous page button if on first page
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="${this.data.corpus.p.has_prev ? 'waves-effect' : 'disabled'}">
|
|
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.has_prev ? 'data-target="' + this.data.corpus.p.prev_num + '"' : ''}>
|
|
<i class="material-icons">chevron_left</i>
|
|
</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
// First page as number. Hides first page button if on first page
|
|
if (this.data.corpus.p.page > 6) {
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="waves-effect">
|
|
<a class="corpus-analysis-action pagination-trigger" data-target="1">1</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
pageElement = nopaque.Utils.HTMLToElement("<li style='margin-top: 5px;'>…</li>");
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
}
|
|
|
|
// render page buttons (5 before and 5 after current page)
|
|
for (let i = this.data.corpus.p.page - this.settings.pagination.innerWindow; i <= this.data.corpus.p.page; i++) {
|
|
if (i <= 0) {continue;}
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}">
|
|
<a class="corpus-analysis-action pagination-trigger" ${i === this.data.corpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
};
|
|
for (let i = this.data.corpus.p.page +1; i <= this.data.corpus.p.page + this.settings.pagination.innerWindow; i++) {
|
|
if (i > this.data.corpus.p.pages) {break;}
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}">
|
|
<a class="corpus-analysis-action pagination-trigger" ${i === this.data.corpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
};
|
|
// Last page as number. Hides last page button if on last page
|
|
if (this.data.corpus.p.page < this.data.corpus.p.pages - 6) {
|
|
pageElement = nopaque.Utils.HTMLToElement("<li style='margin-top: 5px;'>…</li>");
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="waves-effect">
|
|
<a class="corpus-analysis-action pagination-trigger" data-target="${this.data.corpus.p.pages}">${this.data.corpus.p.pages}</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
}
|
|
// Next page button. Disables next page button if on last page
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="${this.data.corpus.p.has_next ? 'waves-effect' : 'disabled'}">
|
|
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.has_next ? 'data-target="' + this.data.corpus.p.next_num + '"' : ''}>
|
|
<i class="material-icons">chevron_right</i>
|
|
</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
// Last page button. Disables last page button if on last page
|
|
pageElement = nopaque.Utils.HTMLToElement(
|
|
`
|
|
<li class="${this.data.corpus.p.page === this.data.corpus.p.pages ? 'disabled' : 'waves-effect'}">
|
|
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.page === this.data.corpus.p.pages ? '' : 'data-target="' + this.data.corpus.p.pages + '"'}>
|
|
<i class="material-icons">last_page</i>
|
|
</a>
|
|
</li>
|
|
`
|
|
);
|
|
this.elements.corpusPagination.appendChild(pageElement);
|
|
|
|
for (let paginateTriggerElement of this.elements.corpusPagination.querySelectorAll('.pagination-trigger[data-target]')) {
|
|
paginateTriggerElement.addEventListener('click', (event) => {
|
|
event.preventDefault();
|
|
let page = parseInt(paginateTriggerElement.dataset.target);
|
|
this.page(page);
|
|
});
|
|
}
|
|
this.elements.corpusPagination.classList.remove('hide');
|
|
}
|
|
|
|
cposRange2HTML(firstCpos, lastCpos) {
|
|
let html = '';
|
|
for (let cpos = firstCpos; cpos <= lastCpos; cpos++) {
|
|
let prevPAttr = cpos > firstCpos ? this.data.corpus.p.lookups.cpos_lookup[cpos - 1] : null;
|
|
let pAttr = this.data.corpus.p.lookups.cpos_lookup[cpos];
|
|
let nextPAttr = cpos < lastCpos ? this.data.corpus.p.lookups.cpos_lookup[cpos + 1] : null;
|
|
let isEntityStart = 'ent' in pAttr && pAttr.ent !== prevPAttr?.ent;
|
|
let isEntityEnd = 'ent' in pAttr && pAttr.ent !== nextPAttr?.ent;
|
|
// Add a space before pAttr
|
|
if (cpos !== firstCpos || pAttr.simple_pos !== 'PUNCT') {html += ' ';}
|
|
// Add entity start
|
|
if (isEntityStart) {
|
|
html += '<span class="s-attr" data-s-attr="ent">';
|
|
html += `<span class="s-attr" data-cpos="${cpos}" data-id="${pAttr.ent}" data-s-attr="ent_type" data-s-attr-value="${this.data.corpus.p.lookups.ent_lookup[pAttr.ent].type}">`;
|
|
}
|
|
// Add pAttr
|
|
html += `<span class="p-attr" data-cpos="${cpos}"></span>`;
|
|
// Add entity end
|
|
if (isEntityEnd) {
|
|
html += ` <span class="badge black-text hide new white ent-indicator" data-badge-caption="">${this.data.corpus.p.lookups.ent_lookup[pAttr.ent].type}</span>`;
|
|
html += '</span>';
|
|
html += '</span>';
|
|
}
|
|
}
|
|
return html;
|
|
}
|
|
|
|
page(pageNum, callback) {
|
|
if (this.data.corpus.p.page === pageNum && typeof callback === 'function') {
|
|
callback();
|
|
return;
|
|
}
|
|
this.app.disableActionElements();
|
|
window.scrollTo(top);
|
|
this.elements.progress.classList.remove('hide');
|
|
this.data.corpus.o.paginate(pageNum, this.settings.perPage)
|
|
.then(
|
|
(paginatedCorpus) => {
|
|
this.data.corpus.p = paginatedCorpus;
|
|
this.renderCorpus();
|
|
this.renderCorpusPagination();
|
|
this.elements.progress.classList.add('hide');
|
|
this.app.enableActionElements();
|
|
if (typeof callback === 'function') {callback();}
|
|
}
|
|
)
|
|
}
|
|
|
|
setTextStyle() {
|
|
if (this.settings.textStyle >= 0) {
|
|
// Destroy with .p-attr elements associated Materialize tooltips
|
|
for (let pAttrElement of this.elements.corpus.querySelectorAll('.p-attr.tooltipped')) {
|
|
M.Tooltip.getInstance(pAttrElement)?.destroy();
|
|
}
|
|
// Set basic styling on .p-attr elements
|
|
for (let pAttrElement of this.elements.corpus.querySelectorAll('.p-attr')) {
|
|
pAttrElement.setAttribute('class', 'p-attr');
|
|
}
|
|
// Set basic styling on .s-attr[data-s-attr="ent_type"] elements
|
|
for (let entElement of this.elements.corpus.querySelectorAll('.s-attr[data-s-attr="ent_type"]')) {
|
|
entElement.querySelector('.ent-indicator').classList.add('hide');
|
|
// TODO: Check why this is here
|
|
// entElement.removeAttribute('style');
|
|
entElement.setAttribute('class', 's-attr');
|
|
}
|
|
}
|
|
if (this.settings.textStyle >= 1) {
|
|
// Set advanced styling on .s-attr[data-s-attr="ent_type"] elements
|
|
for (let entElement of this.elements.corpus.querySelectorAll('.s-attr[data-s-attr="ent_type"]')) {
|
|
entElement.classList.add('chip', 's-attr-color');
|
|
entElement.querySelector('.ent-indicator').classList.remove('hide');
|
|
}
|
|
}
|
|
if (this.settings.textStyle >= 2) {
|
|
// Set advanced styling on .p-attr elements
|
|
for (let pAttrElement of this.elements.corpus.querySelectorAll('.p-attr')) {
|
|
pAttrElement.classList.add('chip', 'hoverable', 'tooltipped');
|
|
let cpos = pAttrElement.dataset.cpos;
|
|
let pAttr = this.data.corpus.p.lookups.cpos_lookup[cpos];
|
|
let positionalPropertiesHTML = `
|
|
<p class="left-align">
|
|
<b>Positional properties</b><br>
|
|
<span>Token: ${cpos}</span>
|
|
`.trim();
|
|
let structuralPropertiesHTML = `
|
|
<p class="left-align">
|
|
<b>Structural properties</b>
|
|
`.trim();
|
|
for (let [property, propertyValue] of Object.entries(pAttr)) {
|
|
if (['lemma', 'ner', 'pos', 'simple_pos', 'word'].includes(property)) {
|
|
if (propertyValue === 'None') {continue;}
|
|
positionalPropertiesHTML += `<br><i class="material-icons" style="font-size: inherit;">subdirectory_arrow_right</i>${property}: ${propertyValue}`;
|
|
} else {
|
|
structuralPropertiesHTML += `<br><span>${property}: ${propertyValue}</span>`;
|
|
if (!(`${property}_lookup` in this.data.corpus.p.lookups)) {continue;}
|
|
for (let [subproperty, subpropertyValue] of Object.entries(this.data.corpus.p.lookups[`${property}_lookup`][propertyValue])) {
|
|
if (subpropertyValue === 'NULL') {continue;}
|
|
structuralPropertiesHTML += `<br><i class="material-icons" style="font-size: inherit;">subdirectory_arrow_right</i>${subproperty}: ${subpropertyValue}`
|
|
}
|
|
}
|
|
}
|
|
positionalPropertiesHTML += '</p>';
|
|
structuralPropertiesHTML += '</p>';
|
|
M.Tooltip.init(
|
|
pAttrElement,
|
|
{html: positionalPropertiesHTML + structuralPropertiesHTML}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
setTokenRepresentation() {
|
|
for (let pAttrElement of this.elements.corpus.querySelectorAll('.p-attr')) {
|
|
let pAttr = this.data.corpus.p.lookups.cpos_lookup[pAttrElement.dataset.cpos];
|
|
pAttrElement.innerText = pAttr[this.settings.tokenRepresentation];
|
|
}
|
|
}
|
|
}
|