Skip to content

Commit

Permalink
Copy Paste internationalization semantics. Preserve internationalizat…
Browse files Browse the repository at this point in the history
…ion in column, leave blank otherwise
  • Loading branch information
kennethbruskiewicz committed Dec 5, 2023
1 parent 511c82e commit e6665e0
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 47 deletions.
154 changes: 110 additions & 44 deletions lib/DataHarmonizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import $ from 'jquery';
import i18next from 'i18next';
import { utils as XlsxUtils, read as xlsxRead } from 'xlsx/xlsx.mjs';
import { renderContent, urlToClickableAnchor } from './utils/content';

import { MULTIVALUED_DELIMITER } from './utils/fields';
import { wait, isValidHeaderRow } from '@/lib/utils/general';
import { readFileAsync, updateSheetRange } from '@/lib/utils/files';
import {
Expand Down Expand Up @@ -415,7 +415,7 @@ class DataHarmonizer {
console.log(
`TEMPLATE ERROR: Check the regular expression syntax for "${new_field.title}".`
);
console.log(err);
console.error(err);
// Allow anything until regex fixed.
new_field.pattern = new RegExp(/.*/);
}
Expand Down Expand Up @@ -464,7 +464,7 @@ class DataHarmonizer {
this.loadSpreadsheetData(contentBuffer);
}
} catch (err) {
console.log(err);
console.error(err);
}
}

Expand All @@ -487,9 +487,6 @@ class DataHarmonizer {
*/
createHot() {

this.clipboardCache = '';
this.sheetclip = new SheetClip();

this.invalid_cells = {};
if (this.hot) {
this.hot.destroy(); // handles already existing data
Expand All @@ -504,32 +501,40 @@ class DataHarmonizer {

const self = this;
if (fields.length) {
const hot_settings = {
data: [], // Enables true reset
nestedHeaders: this.getNestedHeaders(),
columns: this.getColumns(),
colHeaders: true,
rowHeaders: true,
copyPaste: true,
manualColumnResize: true,
//colWidths: [100], //Just fixes first column width
afterCopy: function (changes) {

this.clipboardCache = '';
this.clipboardCoordCache = {
'CopyPaste.copy': {},
'CopyPaste.cut': {},
'CopyPaste.paste': {},
'Action:CopyPaste': ''
}; // { startCol, startRow, endCol, endRow }
this.sheetclip = new SheetClip();

const hot_copy_paste_settings = {
afterCopy: function (changes, coords) {
self.clipboardCache = self.sheetclip.stringify(changes);
self.clipboardCoordCache['CopyPaste.copy'] = coords[0];
self.clipboardCoordCache['Action:CopyPaste'] = 'copy';
},
afterCut: function (changes) {
afterCut: function (changes, coords) {

self.clipboardCache = self.sheetclip.stringify(changes);
},
afterPaste: function (changes, ...rest) {
const { startRow, startCol, endRow, endCol } = rest;
self.clipboardCoordCache['CopyPaste.cut'] = coords[0];
self.clipboardCoordCache['Action:CopyPaste'] = 'cut';

// TODO: copy over internationalization data
// self.hot.getCellMeta(startRow, startCol).getAttribute('data-i18n')
// self.hot.getCellMeta(startRow, startCol).getAttribute('data-i18n-options')
// self.hot.getCell(endRow, endCol)//.setAttribute('data-i18n', null);
// self.hot.getCell(endRow, endCol)//.setAttribute('data-i18n-options', null);
// clear internationalization data
const TD_source = self.hot.getCell(coords[0].startRow, coords[0].startCol);
if (TD_source.getAttribute('data-i18n').includes('multiselect')) {
TD_source.setAttribute('data-i18n-options', JSON.stringify({ value: [] }));
};

},
afterPaste: function (changes, coords) {
// we want to be sure that our cache is up to date, even if someone pastes data from another source than our tables.
self.clipboardCache = self.sheetclip.stringify(changes);
self.clipboardCoordCache['CopyPaste.paste'] = coords[0];
self.clipboardCoordCache['Action:CopyPaste'] = 'paste';
},
contextMenu: [
'copy',
Expand All @@ -555,7 +560,19 @@ class DataHarmonizer {
'remove_row',
'row_above',
'row_below',
],
]
};

const hot_settings = {
...hot_copy_paste_settings,
data: [], // Enables true reset
nestedHeaders: this.getNestedHeaders(),
columns: this.getColumns(),
colHeaders: true,
rowHeaders: true,
copyPaste: true,
manualColumnResize: true,
//colWidths: [100], //Just fixes first column width
minRows: 100,
minSpareRows: 100,
width: '100%',
Expand Down Expand Up @@ -645,6 +662,7 @@ class DataHarmonizer {
}
},
afterRenderer: (TD, row, col) => {

if (Object.prototype.hasOwnProperty.call(self.invalid_cells, row)) {
if (
Object.prototype.hasOwnProperty.call(self.invalid_cells[row], col)
Expand All @@ -660,28 +678,71 @@ class DataHarmonizer {
this.enableMultiSelection();

} else {
console.log(
console.warn(
'This template had no sections and fields: ' + this.template_name
);
}

// this.hot.addHook('beforeChange', function (changes, source) {

// })

// hooks, since passing them as callbacks would result in recurisve dependencies
this.hot.addHook(
'afterChange',
function (changes, source) {
if (source !== 'loadData') {

if (source === 'CopyPaste.paste') {

const previousAction = `CopyPaste.${self.clipboardCoordCache['Action:CopyPaste']}`;

// take the value from the cut/copy cell
// NOTE: assuming it doesn't change in between pastes!
// shouldn't matter... since under this logic pastes are idempotent upto the next cut/copy
const { startRow, startCol } = self.clipboardCoordCache[previousAction];
const TD_source = self.hot.getCell(startRow, startCol);

for (let i = 0; i < changes.length; i++) {
const [endRow, prop] = changes[i];
const endCol = self.hot.propToCol(prop);
if (startRow !== endRow || startCol !== endCol) {
let TD_target = self.hot.getCell(endRow, endCol);

// shared column-type should allow transfer of data options, else don't bother (will cause problems)
// the existence of the multiselect formatter means the paste can't be a naive one for data-i18n (which would otherwise just be the innerText of the cell/the label)
// for safety let's equivocate them if they share a column and formatter (so guaranteed to be the same type and schema)
// TODO: ISSUE: more semantic way to do this, like with classes?
if (startCol === endCol && TD_source.getAttribute('data-i18n').includes('multiselect')) {
TD_target.setAttribute('data-i18n', 'multiselect.label;[append]multiselect.arrow');
TD_target.setAttribute('data-i18n-options', JSON.stringify({
value: self.clipboardCache.split(MULTIVALUED_DELIMITER)
}));
}

}
}

}


if (source !== 'loadData' && source !== 'CopyPaste.paste') {
// Not triggered by the initial load
for (let i = 0; i < changes.length; i++) {
let [row, prop] = changes[i];

const [row, prop] = changes[i];
const col = self.hot.propToCol(prop);
const td = self.hot.getCell(row, col);

const TD = self.hot.getCell(row, col);
// Assuming the new value is a translation key:
const localizedText = i18next.t($(td).attr('data-i18n'));
const localizedText = i18next.t($(TD).attr('data-i18n'));
// // Set the localized text to the cell
td.innerText = localizedText;
// $(td).localize();
TD.innerText = localizedText;

self.hot.render();

}
}

}.bind(self)
);
}
Expand Down Expand Up @@ -1304,6 +1365,7 @@ class DataHarmonizer {

ret.push(col);
}

return ret;
}

Expand All @@ -1318,27 +1380,28 @@ class DataHarmonizer {
*/
enableMultiSelection() {
const fields = this.getFields();
const that = this;

this.hot.updateSettings({
afterBeginEditing: function (row, col) {

that.hot.getCell(row, col).setAttribute('data-i18n', 'multiselect.label;[append]multiselect.arrow');
if (that.hot.getCell(row, col).getAttribute('data-i18n-options') == null) {
that.hot.getCell(row, col).setAttribute('data-i18n-options', JSON.stringify({ value: [] }));
} else {
that.hot.getCell(row, col).setAttribute('data-i18n-options', JSON.stringify({ value: $('#field-description-text .multiselect').val() }));
}
const self = this;

if (fields[col].flatVocabulary && fields[col].multivalued === true) {
const self = this;

this.getCell(row, col).setAttribute('data-i18n', 'multiselect.label;[append]multiselect.arrow');
if (this.getCell(row, col).getAttribute('data-i18n-options') == null) {
this.getCell(row, col).setAttribute('data-i18n-options', JSON.stringify({ value: [] }));
} else {
this.getCell(row, col).setAttribute('data-i18n-options', JSON.stringify({ value: $('#field-description-text .multiselect').val() }));
}

const value = this.getDataAtCell(row, col);
const selections = parseMultivaluedValue(value);
const formattedValue = formatMultivaluedValue(selections);
// Cleanup of empty values that can occur with leading/trailing or double ";"
if (value !== formattedValue) {
this.setDataAtCell(row, col, formattedValue, 'thisChange');
}

let content = '';
if (fields[col].flatVocabulary) {
fields[col].flatVocabulary.forEach(function (field) {
Expand Down Expand Up @@ -1381,16 +1444,19 @@ class DataHarmonizer {
});

// add localization information to cell
that.hot.getCell(row, col).setAttribute('data-i18n', 'multiselect.label;[append]multiselect.arrow');
that.hot.getCell(row, col)
self.getCell(row, col)
.setAttribute('data-i18n', 'multiselect.label;[append]multiselect.arrow');
self.getCell(row, col)
.setAttribute('data-i18n-options', JSON.stringify({
value: $('#field-description-text .multiselect').val()
}));

self.setDataAtCell(row, col, newValCsv, 'thisChange');

});
// Saves users a click:
$('#field-description-text .multiselect')[0].selectize.focus();

}
},
});
Expand Down
2 changes: 1 addition & 1 deletion lib/editors/KeyValueEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const keyValueListRenderer = function (
}

// Use the ID as the translation key for the data-i18n attribute
item ? TD.setAttribute('data-i18n', item._id) : undefined ;
item ? TD.setAttribute('data-i18n', item._id) : undefined;

Handsontable.renderers
.getRenderer('autocomplete')
Expand Down
3 changes: 1 addition & 2 deletions web/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ document.addEventListener('DOMContentLoaded', function () {
// Takes `lang` as argument (unused)
initI18n((lang) => {
console.log(lang);
//dh.hot.render();
console.log('localizing in render')
// dh.hot.render();
$(document).localize();
});

Expand Down

0 comments on commit e6665e0

Please sign in to comment.