Add export options to subcorpora

This commit is contained in:
Patrick Jentsch
2023-01-19 14:59:09 +01:00
parent 8b8df68781
commit 14820643ed
4 changed files with 218 additions and 68 deletions

View File

@ -401,6 +401,25 @@ class CQiSubcorpus {
});
}
partial_export(matchIdList, context=50) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
match_id_list: matchIdList,
context: context
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.partial_export', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
fdst_1(cutoff, field, attribute) {
return new Promise((resolve, reject) => {
const args = {

View File

@ -47,6 +47,8 @@ class CorpusAnalysisConcordance {
this.data.corpus.o.query(subcorpusName, query)
.then(cQiStatus => {
subcorpus.q = query;
subcorpus.selectedItems = new Set();
if (subcorpusName !== 'Last') {this.data.subcorpora.Last = subcorpus;}
return this.data.corpus.o.subcorpora.get(subcorpusName);
})
.then(cQiSubcorpus => {
@ -56,8 +58,6 @@ class CorpusAnalysisConcordance {
.then(
paginatedSubcorpus => {
subcorpus.p = paginatedSubcorpus;
subcorpus.selectedItems = {};
if (subcorpusName !== 'Last') {this.data.subcorpora.Last = subcorpus;}
this.data.subcorpora[subcorpusName] = subcorpus;
this.settings.selectedSubcorpus = subcorpusName;
this.renderSubcorpusList();
@ -154,48 +154,140 @@ class CorpusAnalysisConcordance {
renderSubcorpusActions() {
this.clearSubcorpusActions();
this.elements.subcorpusActions.innerHTML += `
<a class="btn-floating btn-small tooltipped waves-effect waves-light corpus-analysis-action download-subcorpus-selection-trigger" data-tooltip="Download subcorpus selection">
<i class="material-icons">playlist_add_check</i>
<a class="btn-floating btn-small tooltipped waves-effect waves-light corpus-analysis-action subcorpus-export-trigger" data-tooltip="Export subcorpus">
<i class="material-icons">export</i>
</a>
<a class="btn-floating btn-small tooltipped waves-effect waves-light corpus-analysis-action download-subcorpus-trigger" data-tooltip="Download subcorpus">
<i class="material-icons">file_download</i>
</a>
<a class="btn-floating btn-small red tooltipped waves-effect waves-light corpus-analysis-action delete-subcorpus-trigger" data-tooltip="Delete subcorpus">
<a class="btn-floating btn-small red tooltipped waves-effect waves-light corpus-analysis-action subcorpus-delete-trigger" data-tooltip="Delete subcorpus">
<i class="material-icons">delete</i>
</a>
`.trim();
M.Tooltip.init(this.elements.subcorpusActions.querySelectorAll('.tooltipped'));
this.elements.subcorpusActions.querySelector('.download-subcorpus-trigger').addEventListener('click', event => {
event.preventDefault();
app.flash('This feature is currently not available', 'error');
});
this.elements.subcorpusActions.querySelector('.download-subcorpus-selection-trigger').addEventListener('click', event => {
this.elements.subcorpusActions.querySelector('.subcorpus-export-trigger').addEventListener('click', event => {
event.preventDefault();
let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
if (JSON.stringify(subcorpus.selectedItems) === '{}') {app.flash('No items selected', 'error'); return;}
let csvContent = 'sep=,\r\n';
csvContent += '"#Match","Text title","Left context","KWIC","Right context"\r\n';
for (let selectedItem of Object.values(subcorpus.selectedItems)) {
csvContent += `"${selectedItem.num}",`;
csvContent += `"${selectedItem.textTitle.replace('"', '""')}",`;
csvContent += `"${selectedItem.leftContext.replace('"', '""')}",`;
csvContent += `"${selectedItem.kwic.replace('"', '""')}",`;
csvContent += `"${selectedItem.rightContext.replace('"', '""')}"\r\n`;
}
// Create a blob
let blob = new Blob([csvContent], {type: 'text/csv;charset=utf-8;'});
let url = URL.createObjectURL(blob);
// Create a link to download it
let pom = document.createElement('a');
pom.href = url;
pom.setAttribute('download', 'export.csv');
pom.click();
// console.log(csvContent);
// let encodedUri = encodeURI(csvContent);
// window.open(encodedUri);
let modalElementId = Utils.generateElementId('export-subcorpus-modal-');
let exportFormatSelectElementId = Utils.generateElementId('export-format-select-');
let exportSelectedMatchesOnlyCheckboxElementId = Utils.generateElementId('export-selected-matches-only-checkbox-');
let exportFileNameInputElementId = Utils.generateElementId('export-file-name-input-');
let modalElement = Utils.HTMLToElement(
`
<div class="modal" id="${modalElementId}">
<div class="modal-content">
<h4>Export subcorpus "${subcorpus.o.name}"</h4>
<br>
<div class="row">
<div class="input-field col s3">
<select id="${exportFormatSelectElementId}">
<option value="csv" selected>CSV</option>
<option value="json">JSON</option>
</select>
<label>Export format</label>
</div>
<div class="input-field col s9">
<input id="${exportFileNameInputElementId}" type="text" class="validate" value="export">
<label class="active" for="${exportFileNameInputElementId}">Export filename without filename extension (.csv/.json/...)</label>
</div>
<p class="col s12">
<label>
<input id="${exportSelectedMatchesOnlyCheckboxElementId}" type="checkbox" ${subcorpus.selectedItems.size === 0 ? '' : 'checked'}>
<span>Export selected matches only</span>
</label>
</p>
</div>
</div>
<div class="modal-footer">
<a class="btn-flat modal-close waves-effect waves-light">Cancel</a>
<a class="action-button btn modal-close waves-effect waves-light" data-action="export">Export</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let exportFormatSelectElement = modalElement.querySelector(`#${exportFormatSelectElementId}`);
let exportFormatSelect = M.FormSelect.init(exportFormatSelectElement);
let exportSelectedMatchesOnlyCheckboxElement = modalElement.querySelector(`#${exportSelectedMatchesOnlyCheckboxElementId}`);
let exportFileNameInputElement = modalElement.querySelector(`#${exportFileNameInputElementId}`);
let exportButton = modalElement.querySelector('.action-button[data-action="export"]');
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
exportFormatSelect.destroy();
modal.destroy();
modalElement.remove();
}
}
);
exportButton.addEventListener('click', event => {
event.preventDefault();
this.app.disableActionElements();
this.elements.progress.classList.remove('hide');
let exportFormat = exportFormatSelectElement.value;
let exportFileName = exportFileNameInputElement.value;
let exportFileNameExtension = exportFormat === 'csv' ? 'csv' : 'json';
let exportFileNameWithExtension = `${exportFileName}.${exportFileNameExtension}`;
let exportSelectedMatchesOnly = exportSelectedMatchesOnlyCheckboxElement.checked;
let promise;
if (exportSelectedMatchesOnly) {
if (subcorpus.selectedItems.size === 0) {
this.elements.progress.classList.add('hide');
this.app.enableActionElements();
app.flash('No matches selected', 'error');
return;
}
promise = subcorpus.o.partial_export([...subcorpus.selectedItems], 50);
} else {
promise = subcorpus.o.export(50);
}
promise.then(
data => {
let blob;
if (exportFormat === 'csv') {
let csvContent = 'sep=,\r\n';
csvContent += '"#Match","Text title","Left context","KWIC","Right context"';
for (let match of data.matches) {
csvContent += '\r\n';
csvContent += `"${match.num}"`;
csvContent += ',';
let textIds = new Set();
for (let cpos = match.c[0]; cpos <= match.c[1]; cpos++) {
textIds.add(data.cpos_lookup[cpos].text);
}
csvContent += '"' + [...textIds].map(x => data.text_lookup[x].title.replace('"', '""')).join(', ') + '"';
csvContent += ',';
if (match.lc !== null) {
let lc_cpos_list = [];
for (let cpos = match.lc[0]; cpos <= match.lc[1]; cpos++) {lc_cpos_list.push(cpos);}
csvContent += '"' + lc_cpos_list.map(x => data.cpos_lookup[x].word.replace('"', '""')).join(' ') + '"';
}
csvContent += ',';
let c_cpos_list = [];
for (let cpos = match.c[0]; cpos <= match.c[1]; cpos++) {c_cpos_list.push(cpos);}
csvContent += '"' + c_cpos_list.map(x => data.cpos_lookup[x].word.replace('"', '""')).join(' ') + '"';
csvContent += ',';
let rc_cpos_list = [];
for (let cpos = match.rc[0]; cpos <= match.rc[1]; cpos++) {rc_cpos_list.push(cpos);}
if (match.rc !== null) {
csvContent += '"' + rc_cpos_list.map(x => data.cpos_lookup[x].word.replace('"', '""')).join(' ') + '"';
}
}
blob = new Blob([csvContent], {type: 'text/csv;charset=utf-8;'});
} else {
blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json;charset=utf-8;'});
}
let url = URL.createObjectURL(blob);
let pom = document.createElement('a');
pom.href = url;
pom.setAttribute('download', exportFileNameWithExtension);
pom.click();
this.elements.progress.classList.add('hide');
this.app.enableActionElements();
});
});
modal.open();
});
this.elements.subcorpusActions.querySelector('.delete-subcorpus-trigger').addEventListener('click', event => {
this.elements.subcorpusActions.querySelector('.subcorpus-delete-trigger').addEventListener('click', event => {
event.preventDefault();
let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
subcorpus.o.drop().then(
@ -296,18 +388,12 @@ class CorpusAnalysisConcordance {
event.preventDefault();
let itemElement = selectTriggerElement.closest('.item');
let itemId = parseInt(itemElement.dataset.id);
if (itemId in subcorpus.selectedItems) {
delete subcorpus.selectedItems[itemId];
if (subcorpus.selectedItems.has(itemId)) {
subcorpus.selectedItems.delete(itemId);
selectTriggerElement.classList.remove('green');
selectTriggerElement.querySelector('i').textContent = 'add';
} else {
subcorpus.selectedItems[itemId] = {
num: itemId,
textTitle: itemElement.querySelector('.text-title').textContent,
leftContext: [...itemElement.querySelectorAll('.left-context .p-attr')].map(x => x.textContent).join(' '),
kwic: [...itemElement.querySelectorAll('.kwic .p-attr')].map(x => x.textContent).join(' '),
rightContext: [...itemElement.querySelectorAll('.right-context .p-attr')].map(x => x.textContent).join(' ')
};
subcorpus.selectedItems.add(itemId);
selectTriggerElement.classList.add('green');
selectTriggerElement.querySelector('i').textContent = 'check';
}