backend-plus 2.7.2 → 2.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/for-client/my-tables.js +282 -71
- package/for-client/my-things.js +25 -0
- package/lib/backend-plus.d.ts +6 -0
- package/lib/backend-plus.js +121 -56
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -247,6 +247,10 @@ client-setup | front-end options
|
|
|
247
247
|
.menu | whether it uses the integrated menus
|
|
248
248
|
.title | screen title
|
|
249
249
|
.lang | frontend language (and locale), for now "es" or "en"
|
|
250
|
+
.tab-plus | "only": the `.tab` files in `install` are read with [tab-plus](https://npmjs.org/package/tab-plus) and the `.txt "\|"` option of the grid export uses tab-plus (the first line of a `.tab` can be pasted to keep its sparse columns)
|
|
251
|
+
.tab-plus-table-options | per table, options for the `.tab` exported by the grid with `tab-plus: only`: a string is the first line of a `.tab` (it is shown in the "options" box, where it can be edited); an object `{fields, columnDefs}` is used if the box is left empty. At startup it is completed with the first line of the `.tab` of each table not defined there
|
|
252
|
+
.downloads.method | how the grid export saves the file: `SaveFilePicker` (default) uses the "save as" dialog if the browser has `showSaveFilePicker` (otherwise "confirm" and the "download" link); `legacy` always uses "confirm" and the "download" link
|
|
253
|
+
.downloads.close-retries | with `SaveFilePicker`, how many times it retries (0 to 5, default 0) if replacing the file fails when closing it, waiting 200 ms and doubling on each retry
|
|
250
254
|
db | database options
|
|
251
255
|
.motor | for now only 'postgresql'
|
|
252
256
|
.database |
|
package/for-client/my-tables.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
/*global myOwn, my, xlsxNowBrowser, Pikaday */
|
|
3
|
+
/*global myOwn, my, xlsxNow, xlsxNowBrowser, Pikaday */
|
|
4
4
|
/*global miniMenuPromise, dialogPromise, alertPromise, confirmPromise, promptPromise, simpleFormPromise */
|
|
5
5
|
/*global Blob, document, CustomEvent, URL */
|
|
6
6
|
|
|
@@ -50,6 +50,7 @@ myOwn.i18n.messages.en=changing(myOwn.i18n.messages.en, {
|
|
|
50
50
|
allTWillDelete: "(ALL {$t} records will be deleted)",
|
|
51
51
|
anotherUserChangedTheRow: "The data was changed in the database",
|
|
52
52
|
confirmDeleteAll: "Do you want to delete these records?",
|
|
53
|
+
confirmExport: "confirm",
|
|
53
54
|
deleteAllRecords: "delete all records",
|
|
54
55
|
deleteRecord: "delete record",
|
|
55
56
|
details: "details",
|
|
@@ -94,6 +95,8 @@ myOwn.i18n.messages.en=changing(myOwn.i18n.messages.en, {
|
|
|
94
95
|
orientationToggle: "toggle orientation (vertical vs horizontal)",
|
|
95
96
|
prepare: "prepare",
|
|
96
97
|
preparingForExport: "preparing for export",
|
|
98
|
+
saveToDiskError: "The file could not be saved",
|
|
99
|
+
saveToDiskEmptied: "The file could not be saved and it was left empty (the browser empties it when it is chosen)",
|
|
97
100
|
recordsReimaining: "{$r} records remains in the table",
|
|
98
101
|
refresh: "refresh - retrive data from database",
|
|
99
102
|
replaceNewLineWithSpace: 'replace new lines with spaces',
|
|
@@ -104,6 +107,9 @@ myOwn.i18n.messages.en=changing(myOwn.i18n.messages.en, {
|
|
|
104
107
|
skippedColumns:'skipped columns',
|
|
105
108
|
skipUnknownFieldsAtImport:'skip unknown fields at import',
|
|
106
109
|
table: "table",
|
|
110
|
+
tabPlusHeader: "first line of a .tab to keep its column order and its sparse columns (optional)",
|
|
111
|
+
tabPlusOptions: "options",
|
|
112
|
+
tabPlusHeaderUnknownFields: "Nothing was exported. These columns of the pasted line are not among the exported ones: ",
|
|
107
113
|
thereAreNot: "no",
|
|
108
114
|
uploadFile: "upload file $1",
|
|
109
115
|
verticalEdit: "vertical edit",
|
|
@@ -120,6 +126,7 @@ myOwn.i18n.messages.es=changing(myOwn.i18n.messages.es, {
|
|
|
120
126
|
allTWillDelete: "(se borrarán todos los registros: {$t} registros)",
|
|
121
127
|
anotherUserChangedTheRow: "Hubo un cambio en la base de datos para este registro",
|
|
122
128
|
confirmDeleteAll: "¿Desea borrar estos registros?",
|
|
129
|
+
confirmExport: "confirmar",
|
|
123
130
|
deleteAllRecords: "borrar todos los registros",
|
|
124
131
|
deleteRecord: "borrar este registro",
|
|
125
132
|
details: "detalles",
|
|
@@ -164,6 +171,8 @@ myOwn.i18n.messages.es=changing(myOwn.i18n.messages.es, {
|
|
|
164
171
|
orientationToggle: "cambiar la orientación de la tabla (por fila o por columna)",
|
|
165
172
|
prepare: "preparar",
|
|
166
173
|
preparingForExport: "preparando para exportar",
|
|
174
|
+
saveToDiskError: "No se pudo guardar el archivo",
|
|
175
|
+
saveToDiskEmptied: "No se pudo guardar el archivo y quedó vacío (el navegador lo vacía al elegirlo)",
|
|
167
176
|
recordsReimaining: "quedan {$r} registros en la tabla",
|
|
168
177
|
refresh: "refrescar la grilla desde la base de datos",
|
|
169
178
|
replaceNewLineWithSpace: 'replazar saltos de línea por espacios',
|
|
@@ -174,6 +183,9 @@ myOwn.i18n.messages.es=changing(myOwn.i18n.messages.es, {
|
|
|
174
183
|
skippedColumns:'Columnas salteadas',
|
|
175
184
|
skipUnknownFieldsAtImport:'saltear columnas que no existan',
|
|
176
185
|
table: "tabla",
|
|
186
|
+
tabPlusHeader: "primera línea de un .tab para respetar su orden de columnas y sus columnas esparsas (opcional)",
|
|
187
|
+
tabPlusOptions: "opciones",
|
|
188
|
+
tabPlusHeaderUnknownFields: "No se exportó nada. Estas columnas de la línea pegada no están entre las exportadas: ",
|
|
177
189
|
thereAreNot: "no hay",
|
|
178
190
|
uploadFile: "subir el archivo $1",
|
|
179
191
|
verticalEdit: "edición en forma de ficha",
|
|
@@ -1550,8 +1562,17 @@ myOwn.INCLUDE_LOOKUP_COLUMNS_IN_TXT_EXPORT=true;
|
|
|
1550
1562
|
|
|
1551
1563
|
myOwn.dialogDownload = function dialogDownload(grid){
|
|
1552
1564
|
return dialogPromise(function(dialogWindow, closeWindow){
|
|
1553
|
-
var
|
|
1565
|
+
var downloadsConfig = my.config.config?.downloads || {};
|
|
1566
|
+
var canSaveToDisk = downloadsConfig.method != 'legacy' && !!window.showSaveFilePicker;
|
|
1567
|
+
var actionElement=html.button({class:'export-a'},canSaveToDisk ? my.messages.download : my.messages.confirmExport).create();
|
|
1554
1568
|
var downloadElement=html.a({class:'export-a'},my.messages.download).create();
|
|
1569
|
+
var useTabPlus = my.config.config?.['tab-plus'] == 'only';
|
|
1570
|
+
var tabPlusTableOptions = (my.config.config?.['tab-plus-table-options'] || {})[grid.def.name];
|
|
1571
|
+
var tabPlusHeader=html.textarea({rows:3, style:'width:100%; box-sizing:border-box'}).create();
|
|
1572
|
+
if(typeof tabPlusTableOptions == 'string'){
|
|
1573
|
+
tabPlusHeader.value = tabPlusTableOptions;
|
|
1574
|
+
}
|
|
1575
|
+
var tabPlusHeaderDiv=html.div([html.div({title:my.messages.tabPlusHeader}, my.messages.tabPlusOptions), tabPlusHeader]).create();
|
|
1555
1576
|
var input={
|
|
1556
1577
|
xlsx : html.input({class:'export-radio',type:'radio', value:'xlsx', name:'format', checked:true}).create(),
|
|
1557
1578
|
txt : html.input({class:'export-radio',type:'radio', value:'txt' , name:'format'}).create(),
|
|
@@ -1572,7 +1593,8 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1572
1593
|
html.div([html.label([input.readOnly , html.span(my.messages.readOnly)])]),
|
|
1573
1594
|
html.div([html.label([input.hiddens , html.span(my.messages.hiddens)])]),
|
|
1574
1595
|
html.br(),
|
|
1575
|
-
|
|
1596
|
+
tabPlusHeaderDiv,
|
|
1597
|
+
html.div([actionElement]),
|
|
1576
1598
|
]),
|
|
1577
1599
|
html.div({class:'state-preparing'}, [
|
|
1578
1600
|
html.div(my.messages.preparingForExport),
|
|
@@ -1587,55 +1609,193 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1587
1609
|
html.div('.'),
|
|
1588
1610
|
]).create();
|
|
1589
1611
|
dialogWindow.appendChild(mainDiv);
|
|
1590
|
-
var fieldsDef2Export=[];
|
|
1591
|
-
var extraColumns={}
|
|
1592
1612
|
var lastColumnExported=-1;
|
|
1593
|
-
var
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
var
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1613
|
+
var showTabPlusHeader=function(){
|
|
1614
|
+
tabPlusHeaderDiv.style.display = useTabPlus && input.txt.checked ? '' : 'none';
|
|
1615
|
+
}
|
|
1616
|
+
var fileUrl=null;
|
|
1617
|
+
var exporting=false;
|
|
1618
|
+
// cuenta los cambios de opciones: lo que se terminó de armar con opciones viejas no se ofrece
|
|
1619
|
+
var optionsVersion=0;
|
|
1620
|
+
// Al cambiar una opción, lo que estaba listo para descargar ya no corresponde: se vuelve a "confirmar"
|
|
1621
|
+
var optionsChanged=function(){
|
|
1622
|
+
optionsVersion++;
|
|
1623
|
+
showTabPlusHeader();
|
|
1624
|
+
if(fileUrl!=null){
|
|
1625
|
+
URL.revokeObjectURL(fileUrl);
|
|
1626
|
+
fileUrl=null;
|
|
1627
|
+
}
|
|
1628
|
+
downloadElement.removeAttribute("href");
|
|
1629
|
+
downloadElement.removeAttribute("download");
|
|
1630
|
+
if(!exporting){
|
|
1631
|
+
mainDiv.setAttribute("current-state", "chossing");
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
likeAr(input).forEach(function(element){ element.onchange=optionsChanged; });
|
|
1635
|
+
tabPlusHeader.oninput=optionsChanged;
|
|
1636
|
+
showTabPlusHeader();
|
|
1637
|
+
// El texto se arma entero. En dos tiempos: al llamarla valida las opciones (instantáneo, lanza el error si
|
|
1638
|
+
// no sirven) y devuelve {dotExtension, generate}; generate() arma el texto.
|
|
1639
|
+
var textExport=function(fieldsDef2Export){
|
|
1640
|
+
if(input.csv.checked){
|
|
1641
|
+
return {dotExtension:'.csv', generate:function(){
|
|
1642
|
+
return txtExport(fieldsDef2Export, ',', function(txt){
|
|
1643
|
+
return /[\n,"]/.test(txt)?'"'+txt.replace(/"/g,'""')+'"':txt
|
|
1644
|
+
});
|
|
1645
|
+
}};
|
|
1646
|
+
}else if(useTabPlus){
|
|
1647
|
+
var header=tabPlusExportHeader(fieldsDef2Export);
|
|
1648
|
+
return {dotExtension:'.tab', generate:function(){
|
|
1649
|
+
return tabPlusExport(fieldsDef2Export, header);
|
|
1650
|
+
}};
|
|
1651
|
+
}else{
|
|
1652
|
+
var trans={
|
|
1653
|
+
'|':'\\x7C',
|
|
1654
|
+
'\\':'\\\\',
|
|
1655
|
+
'\r':'\\r',
|
|
1656
|
+
'\n':'\\n',
|
|
1657
|
+
}
|
|
1658
|
+
return {dotExtension:'.tab', generate:function(){
|
|
1659
|
+
return txtExport(fieldsDef2Export, '|', function(txt){
|
|
1660
|
+
return txt.replace(/[|\\\r\n]/g,function(char){
|
|
1661
|
+
return trans[char];
|
|
1662
|
+
})
|
|
1663
|
+
});
|
|
1664
|
+
}};
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
var saveToDisk=function(fieldsDef2Export){
|
|
1668
|
+
var step='showSaveFilePicker';
|
|
1669
|
+
var setStep=function(newStep){ step=newStep; };
|
|
1670
|
+
var file;
|
|
1671
|
+
if(input.xlsx.checked){
|
|
1672
|
+
var options=excelOptions(fieldsDef2Export);
|
|
1673
|
+
file={dotExtension:'.xlsx', write:function(writable){
|
|
1674
|
+
var currentOptions=options||excelOptions(fieldsDef2Export);
|
|
1675
|
+
options=null;
|
|
1676
|
+
setStep('write');
|
|
1677
|
+
return xlsxNow.createXlsxStream(currentOptions).pipeTo(writable, {preventClose:true, preventAbort:true});
|
|
1678
|
+
}};
|
|
1679
|
+
}else{
|
|
1680
|
+
var textFile=textExport(fieldsDef2Export);
|
|
1681
|
+
var text=null;
|
|
1682
|
+
file={dotExtension:textFile.dotExtension, write:function(writable){
|
|
1683
|
+
if(text==null){
|
|
1684
|
+
setStep('generate');
|
|
1685
|
+
text=textFile.generate();
|
|
1686
|
+
}
|
|
1687
|
+
setStep('write');
|
|
1688
|
+
return writable.write(text);
|
|
1689
|
+
}};
|
|
1690
|
+
}
|
|
1691
|
+
var writeOnce=function(handle){
|
|
1692
|
+
setStep('createWritable');
|
|
1693
|
+
return handle.createWritable().then(function(writable){
|
|
1694
|
+
return Promise.resolve().then(function(){
|
|
1695
|
+
return file.write(writable);
|
|
1696
|
+
}).then(function(){
|
|
1697
|
+
setStep('close');
|
|
1698
|
+
return writable.close();
|
|
1699
|
+
}, function(err){
|
|
1700
|
+
return writable.abort().then(function(){
|
|
1701
|
+
throw err;
|
|
1702
|
+
}, function(abortErr){
|
|
1703
|
+
console.error(abortErr);
|
|
1704
|
+
throw err;
|
|
1705
|
+
});
|
|
1706
|
+
});
|
|
1604
1707
|
});
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
var trans={
|
|
1617
|
-
'|':'\\x7C',
|
|
1618
|
-
'\\':'\\\\',
|
|
1619
|
-
'\r':'\\r',
|
|
1620
|
-
'\n':'\\n',
|
|
1621
|
-
}
|
|
1622
|
-
replacer=function(txt){
|
|
1623
|
-
return txt.replace(/[|\\\r\n]/g,function(char){
|
|
1624
|
-
return trans[char];
|
|
1625
|
-
})
|
|
1626
|
-
}
|
|
1627
|
-
dotExtension='.tab';
|
|
1708
|
+
}
|
|
1709
|
+
var closeRetries=downloadsConfig['close-retries'] || 0;
|
|
1710
|
+
var writeWithRetries=function(handle, attempt){
|
|
1711
|
+
return writeOnce(handle).catch(function(err){
|
|
1712
|
+
if(step==='close' && err.name==='InvalidStateError' && attempt<closeRetries){
|
|
1713
|
+
console.error('saveToDisk retry', attempt+1, err);
|
|
1714
|
+
return new Promise(function(resolve){
|
|
1715
|
+
setTimeout(resolve, 200 * Math.pow(2, attempt));
|
|
1716
|
+
}).then(function(){
|
|
1717
|
+
return writeWithRetries(handle, attempt+1);
|
|
1718
|
+
});
|
|
1628
1719
|
}
|
|
1629
|
-
|
|
1720
|
+
throw err;
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
var mimeTypes={'.xlsx':'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.csv':'text/csv', '.tab':'text/plain'};
|
|
1724
|
+
var accept={};
|
|
1725
|
+
accept[mimeTypes[file.dotExtension]]=[file.dotExtension];
|
|
1726
|
+
return window.showSaveFilePicker({
|
|
1727
|
+
suggestedName: grid.def.name+file.dotExtension,
|
|
1728
|
+
types: [{description: file.dotExtension, accept: accept}]
|
|
1729
|
+
}).then(function(handle){
|
|
1730
|
+
return writeWithRetries(handle, 0).then(function(){
|
|
1731
|
+
return 'saved';
|
|
1732
|
+
});
|
|
1733
|
+
}, function(err){
|
|
1734
|
+
if(err.name==='AbortError'){
|
|
1735
|
+
return 'cancelled';
|
|
1630
1736
|
}
|
|
1631
|
-
|
|
1737
|
+
throw err;
|
|
1738
|
+
}).catch(function(err){
|
|
1739
|
+
console.error('saveToDisk', step, err);
|
|
1740
|
+
// showSaveFilePicker vacía el archivo elegido antes de devolverlo: si falla después, quedó vacío
|
|
1741
|
+
var message=step==='showSaveFilePicker' ? my.messages.saveToDiskError : my.messages.saveToDiskEmptied;
|
|
1742
|
+
throw new Error(message+' ('+step+') '+err.name+': '+err.message);
|
|
1743
|
+
});
|
|
1632
1744
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1745
|
+
var prepareDownload=function(fieldsDef2Export){
|
|
1746
|
+
var version=optionsVersion;
|
|
1747
|
+
if(fileUrl!=null){
|
|
1748
|
+
URL.revokeObjectURL(fileUrl);
|
|
1749
|
+
fileUrl=null;
|
|
1750
|
+
}
|
|
1751
|
+
return Promise.resolve().then(function(){
|
|
1752
|
+
if(input.xlsx.checked){
|
|
1753
|
+
return xlsxNowBrowser.createXlsxBlob(excelOptions(fieldsDef2Export)).then(function(blob){
|
|
1754
|
+
return {blob:blob, dotExtension:'.xlsx'};
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
var textFile=textExport(fieldsDef2Export);
|
|
1758
|
+
return {blob:new Blob([textFile.generate()], {type: 'text/plain'}), dotExtension:textFile.dotExtension};
|
|
1759
|
+
}).then(function(file){
|
|
1760
|
+
if(version!=optionsVersion){
|
|
1761
|
+
return 'chossing';
|
|
1762
|
+
}
|
|
1763
|
+
fileUrl = URL.createObjectURL(file.blob);
|
|
1764
|
+
downloadElement.href=fileUrl;
|
|
1765
|
+
downloadElement.setAttribute("download", grid.def.name+file.dotExtension);
|
|
1766
|
+
return 'ready';
|
|
1767
|
+
});
|
|
1637
1768
|
}
|
|
1638
|
-
|
|
1769
|
+
actionElement.onclick=function(){
|
|
1770
|
+
if(exporting){
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
var fieldsDef2Export=grid.def.fields.filter(function(fieldDef){
|
|
1774
|
+
return (fieldDef.inTable!==false || input.fromOtherTables.checked)
|
|
1775
|
+
&& fieldDef.visible
|
|
1776
|
+
&& (fieldDef.allow.update || input.readOnly.checked || fieldDef.isPk)
|
|
1777
|
+
&& (!grid.view.hiddenColumns.includes(fieldDef.name) || input.hiddens.checked);
|
|
1778
|
+
});
|
|
1779
|
+
var exportPromise;
|
|
1780
|
+
try{
|
|
1781
|
+
exportPromise = canSaveToDisk ? saveToDisk(fieldsDef2Export).then(function(){ return 'chossing'; })
|
|
1782
|
+
: prepareDownload(fieldsDef2Export);
|
|
1783
|
+
}catch(err){
|
|
1784
|
+
alertPromise(err.message);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
exporting=true;
|
|
1788
|
+
mainDiv.setAttribute("current-state", "preparing");
|
|
1789
|
+
exportPromise.then(function(state){
|
|
1790
|
+
exporting=false;
|
|
1791
|
+
mainDiv.setAttribute("current-state", state);
|
|
1792
|
+
}, function(err){
|
|
1793
|
+
exporting=false;
|
|
1794
|
+
mainDiv.setAttribute("current-state", "chossing");
|
|
1795
|
+
alertPromise(err.message);
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
var txtExport = function(fieldsDef2Export, separator, replacer){
|
|
1639
1799
|
var data=[];
|
|
1640
1800
|
var titles = fieldsDef2Export.map(function(fieldDef){
|
|
1641
1801
|
if(fieldDef.inTable===false && !input.fromOtherTables.checked) return '';
|
|
@@ -1650,11 +1810,66 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1650
1810
|
return value!=null?typeStore.typerFrom(fieldDef).toPlainString(value):'';
|
|
1651
1811
|
}));
|
|
1652
1812
|
});
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1813
|
+
return data.map(function(line){return line.map(replacer).join(separator)}).join('\r\n');
|
|
1814
|
+
}
|
|
1815
|
+
var tabPlusExportHeader = function(fieldsDef2Export){
|
|
1816
|
+
var tabPlus = require('tab-plus');
|
|
1817
|
+
var exportedNames = fieldsDef2Export.map(function(fieldDef){ return fieldDef.name; });
|
|
1818
|
+
// si se pegó un archivo entero solo interesa su primera línea
|
|
1819
|
+
var headerLine = tabPlusHeader.value.split(/\r?\n/).find(function(line){ return line.trim()!=''; });
|
|
1820
|
+
var header = null;
|
|
1821
|
+
if(headerLine!=null){
|
|
1822
|
+
header = tabPlus.parseTab(headerLine, {columnDefs: myOwn.tabPlusColumnDefs(fieldsDef2Export)});
|
|
1823
|
+
}else if(tabPlusTableOptions!=null && typeof tabPlusTableOptions == 'object'){
|
|
1824
|
+
header = tabPlusTableOptions;
|
|
1825
|
+
}
|
|
1826
|
+
if(header!=null){
|
|
1827
|
+
var unknownNames = header.fields.filter(function(name){ return !exportedNames.includes(name); });
|
|
1828
|
+
if(unknownNames.length){
|
|
1829
|
+
throw new Error(my.messages.tabPlusHeaderUnknownFields + unknownNames.join(', '));
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return header;
|
|
1833
|
+
}
|
|
1834
|
+
var tabPlusExport = function(fieldsDef2Export, header){
|
|
1835
|
+
var tabPlus = require('tab-plus');
|
|
1836
|
+
var autoTabPlus = require('auto-tab-plus');
|
|
1837
|
+
var exportedNames = fieldsDef2Export.map(function(fieldDef){ return fieldDef.name; });
|
|
1838
|
+
var rows = grid.depotsToDisplay.map(function(depot){
|
|
1839
|
+
var row = {};
|
|
1840
|
+
fieldsDef2Export.forEach(function(fieldDef){
|
|
1841
|
+
var value=depot.row[fieldDef.name];
|
|
1842
|
+
row[fieldDef.name] = value!=null?typeStore.typerFrom(fieldDef).toPlainString(value):null;
|
|
1843
|
+
});
|
|
1844
|
+
return row;
|
|
1845
|
+
});
|
|
1846
|
+
var tab;
|
|
1847
|
+
if(header==null){
|
|
1848
|
+
tab = {fields:exportedNames, rows:rows};
|
|
1849
|
+
}else{
|
|
1850
|
+
var newNames = exportedNames.filter(function(name){ return !header.fields.includes(name); });
|
|
1851
|
+
var fields = header.fields.concat(newNames);
|
|
1852
|
+
var decided = autoTabPlus.decideSparseColumns(
|
|
1853
|
+
fields,
|
|
1854
|
+
rows.map(function(row){ return fields.map(function(name){ return row[name]; }); }),
|
|
1855
|
+
header.columnDefs,
|
|
1856
|
+
header.columnDefs ? {under: autoTabPlus.defaultThreshold, sparse: newNames}
|
|
1857
|
+
: {under: autoTabPlus.defaultThreshold, fixed: header.fields, auto: newNames}
|
|
1858
|
+
);
|
|
1859
|
+
tab = {fields:decided.fields, columnDefs:decided.columnDefs, rows:rows};
|
|
1860
|
+
}
|
|
1861
|
+
var fieldDefByName = {};
|
|
1862
|
+
fieldsDef2Export.forEach(function(fieldDef){ fieldDefByName[fieldDef.name] = fieldDef; });
|
|
1863
|
+
var columnDefs = {};
|
|
1864
|
+
tab.fields.forEach(function(name){
|
|
1865
|
+
var sparse = tab.columnDefs?.[name]?.sparseDefault !== undefined;
|
|
1866
|
+
var emptyField = myOwn.tabPlusGenerateEmptyField(fieldDefByName[name], sparse);
|
|
1867
|
+
columnDefs[name] = {emptyField: emptyField};
|
|
1868
|
+
if(tab.columnDefs){
|
|
1869
|
+
tab.columnDefs[name] = {...tab.columnDefs[name], emptyField: emptyField};
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
return tabPlus.generateTab(tab, {eol:'\r\n', columnDefs: columnDefs});
|
|
1658
1873
|
}
|
|
1659
1874
|
var XLSX_STYLES={
|
|
1660
1875
|
header : {bold:true},
|
|
@@ -1668,7 +1883,7 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1668
1883
|
// Las columnas que salen de exportJsonFieldAsColumns aparecen recién al
|
|
1669
1884
|
// recorrer las filas, pero el encabezado se escribe antes que ellas: con
|
|
1670
1885
|
// escritura secuencial hay que conocerlas de entrada, y esta pasada las junta.
|
|
1671
|
-
var collectExtraColumns = function collectExtraColumns(depots, fieldDefs, firstColumn){
|
|
1886
|
+
var collectExtraColumns = function collectExtraColumns(depots, fieldDefs, firstColumn, extraColumns){
|
|
1672
1887
|
var lastColumn=firstColumn-1;
|
|
1673
1888
|
if(grid.def.exportJsonFieldAsColumns){
|
|
1674
1889
|
depots.forEach(function(depot){
|
|
@@ -1700,9 +1915,11 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1700
1915
|
// Las filas de una tabla, como las toma xlsx-now: la primera es el
|
|
1701
1916
|
// encabezado y cada una de las demás es un array donde la posición es la
|
|
1702
1917
|
// columna. `leftColumn` corre la tabla entera hacia la derecha.
|
|
1703
|
-
|
|
1918
|
+
// Es un generador para que xlsx-now pida cada fila recién cuando la va a escribir.
|
|
1919
|
+
var tableRowsXLS = function* tableRowsXLS(depots, fieldDefs, leftColumn){
|
|
1704
1920
|
leftColumn=leftColumn||0;
|
|
1705
|
-
var
|
|
1921
|
+
var extraColumns={};
|
|
1922
|
+
var lastColumn=collectExtraColumns(depots, fieldDefs, fieldDefs.length, extraColumns);
|
|
1706
1923
|
lastColumnExported = Math.max(lastColumnExported, lastColumn);
|
|
1707
1924
|
var pad=new Array(leftColumn);
|
|
1708
1925
|
var headerRow=pad.concat(fieldDefs.map(function(field){
|
|
@@ -1711,8 +1928,8 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1711
1928
|
likeAr(extraColumns).forEach(function(pos, name){
|
|
1712
1929
|
headerRow[pos+leftColumn]={v:name, s:'extraHeader'};
|
|
1713
1930
|
});
|
|
1714
|
-
|
|
1715
|
-
|
|
1931
|
+
yield headerRow;
|
|
1932
|
+
for(var depot of depots){
|
|
1716
1933
|
var row=pad.concat([]);
|
|
1717
1934
|
var addCell = function addCell(value, fieldDef, iColumn){
|
|
1718
1935
|
if(value!=null){
|
|
@@ -1733,11 +1950,10 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1733
1950
|
addCell(value, fieldDef, iColumn);
|
|
1734
1951
|
}
|
|
1735
1952
|
});
|
|
1736
|
-
|
|
1737
|
-
}
|
|
1738
|
-
return rows;
|
|
1953
|
+
yield row;
|
|
1954
|
+
}
|
|
1739
1955
|
}
|
|
1740
|
-
var
|
|
1956
|
+
var excelOptions = function(fieldsDef2Export){
|
|
1741
1957
|
var sheet1name=grid.def.name.length>27?grid.def.name.slice(0,27)+'...':grid.def.name;
|
|
1742
1958
|
var sheet2name=grid.def.name!=="metadata"?"metadata":"meta-data";
|
|
1743
1959
|
var dataSheet = {
|
|
@@ -1745,11 +1961,15 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1745
1961
|
freezeRows: my.config.config?.['unfreeze-excel-rows'] ? 0 : 1,
|
|
1746
1962
|
freezeColumns: my.config.config?.['unfreeze-excel-columns'] ? 0 : (grid.def.primaryKey?.length ?? 0),
|
|
1747
1963
|
autoWidthMax: 40,
|
|
1964
|
+
autoWidthMin: 5,
|
|
1965
|
+
autoWidthRows: 16384,
|
|
1748
1966
|
rows: tableRowsXLS(grid.depotsToDisplay, fieldsDef2Export)
|
|
1749
1967
|
};
|
|
1750
1968
|
var metadataSheet = {
|
|
1751
1969
|
name:sheet2name,
|
|
1752
1970
|
autoWidthMax: 40,
|
|
1971
|
+
autoWidthMin: 5,
|
|
1972
|
+
autoWidthRows: 16384,
|
|
1753
1973
|
rows: [
|
|
1754
1974
|
[],
|
|
1755
1975
|
[{v:'table',s:'header'}, grid.def.name],
|
|
@@ -1767,22 +1987,13 @@ myOwn.dialogDownload = function dialogDownload(grid){
|
|
|
1767
1987
|
}).map(function(fieldDef){
|
|
1768
1988
|
return {row:fieldDef};
|
|
1769
1989
|
});
|
|
1770
|
-
extraColumns={};
|
|
1771
1990
|
metadataSheet.rows.push(...(tableRowsXLS(fieldPropertiesDepot,fieldPropertiesDefs,1)));
|
|
1772
1991
|
}
|
|
1773
1992
|
}
|
|
1774
|
-
|
|
1993
|
+
return {
|
|
1775
1994
|
sheets: [dataSheet, metadataSheet],
|
|
1776
1995
|
styles:XLSX_STYLES
|
|
1777
|
-
}
|
|
1778
|
-
mainDiv.setAttribute("current-state", "ready");
|
|
1779
|
-
var url = URL.createObjectURL(blob);
|
|
1780
|
-
downloadElement.href=url;
|
|
1781
|
-
downloadElement.setAttribute("download", grid.def.name+".xlsx");
|
|
1782
|
-
}).catch(function(err){
|
|
1783
|
-
mainDiv.setAttribute("current-state", "chossing");
|
|
1784
|
-
alertPromise(err.message);
|
|
1785
|
-
});
|
|
1996
|
+
};
|
|
1786
1997
|
};
|
|
1787
1998
|
});
|
|
1788
1999
|
};
|
package/for-client/my-things.js
CHANGED
|
@@ -919,6 +919,31 @@ myOwn.registerPostInput = function registerPostInput(postInputName, postInputFun
|
|
|
919
919
|
TypeStore.type.text.postInputs[postInputName]=postInputFunction;
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
+
// Cómo se lee en un .tab (tab-plus) un campo sin contenido: vale '' si el campo acepta '' y no acepta null,
|
|
923
|
+
// y null en cualquier otro caso. La usan quienes leen un .tab (el instalador y el diálogo de exportar al
|
|
924
|
+
// leer la línea de opciones), por eso no se escribe en el archivo.
|
|
925
|
+
myOwn.tabPlusEmptyField = function tabPlusEmptyField(fieldDef){
|
|
926
|
+
return fieldDef.allowEmptyText && ('nullable' in fieldDef) && !fieldDef.nullable ? 'string' : 'null';
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
var tabPlusSparseEmptyField = Symbol('tab-plus sparse empty field');
|
|
930
|
+
|
|
931
|
+
myOwn.tabPlusGenerateEmptyField = function tabPlusGenerateEmptyField(fieldDef, sparse){
|
|
932
|
+
if(sparse){
|
|
933
|
+
return tabPlusSparseEmptyField;
|
|
934
|
+
}
|
|
935
|
+
return fieldDef.typeName=='text' && fieldDef.nullable!==false && fieldDef.allowEmptyText ? 'null' : 'both';
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// el emptyField de cada campo, en el formato de options.columnDefs de tab-plus
|
|
939
|
+
myOwn.tabPlusColumnDefs = function tabPlusColumnDefs(fieldDefs){
|
|
940
|
+
var columnDefs = {};
|
|
941
|
+
fieldDefs.forEach(function(fieldDef){
|
|
942
|
+
columnDefs[fieldDef.name] = {emptyField: myOwn.tabPlusEmptyField(fieldDef)};
|
|
943
|
+
});
|
|
944
|
+
return columnDefs;
|
|
945
|
+
}
|
|
946
|
+
|
|
922
947
|
return myOwn;
|
|
923
948
|
|
|
924
949
|
});
|
package/lib/backend-plus.d.ts
CHANGED
|
@@ -511,6 +511,12 @@ export interface AppConfigClientSetup // front-end config
|
|
|
511
511
|
"user-scalable"?:string
|
|
512
512
|
"unfreeze-excel-rows"?:boolean
|
|
513
513
|
"unfreeze-excel-columns"?:boolean
|
|
514
|
+
"tab-plus"?:"only"|"compare" // "only": the installer reads the .tab with tab-plus and the grid exports "|" with tab-plus
|
|
515
|
+
downloads?:{
|
|
516
|
+
method?:"legacy"|"SaveFilePicker"
|
|
517
|
+
"close-retries"?:0|1|2|3|4|5
|
|
518
|
+
}
|
|
519
|
+
"tab-plus-table-options"?:Record<string, string|{fields:string[], columnDefs?:Record<string, {position:number, sparseDefault?:string|null}>}>
|
|
514
520
|
}
|
|
515
521
|
export interface AppConfig {
|
|
516
522
|
package: {
|
package/lib/backend-plus.js
CHANGED
|
@@ -30,6 +30,7 @@ var stackTrace = require('stack-trace');
|
|
|
30
30
|
var locatePath = require('@upgraded/locate-path');
|
|
31
31
|
var jsYaml = require('js-yaml');
|
|
32
32
|
var tabPlus = require('tab-plus');
|
|
33
|
+
var readline = require('readline');
|
|
33
34
|
var nodemailer = require('nodemailer');
|
|
34
35
|
var os = require('os');
|
|
35
36
|
const cors = require('cors');
|
|
@@ -822,6 +823,15 @@ AppBackend.prototype.start = function start(opts){
|
|
|
822
823
|
be.config = config;
|
|
823
824
|
be.clientSetup = {config: be.config["client-setup"]};
|
|
824
825
|
be.clientSetup.config.prefix = be.clientSetup.config.prefix || be.config.server["base-url"].substr(1)+':';
|
|
826
|
+
var downloadsConfig = be.config["client-setup"].downloads;
|
|
827
|
+
if(downloadsConfig != null){
|
|
828
|
+
if(downloadsConfig.method != null && !['legacy','SaveFilePicker'].includes(downloadsConfig.method)){
|
|
829
|
+
throw new Error('client-setup.downloads.method must be legacy or SaveFilePicker');
|
|
830
|
+
}
|
|
831
|
+
if(downloadsConfig['close-retries'] != null && ![0,1,2,3,4,5].includes(downloadsConfig['close-retries'])){
|
|
832
|
+
throw new Error('client-setup.downloads.close-retries must be an integer from 0 to 5');
|
|
833
|
+
}
|
|
834
|
+
}
|
|
825
835
|
if(config.nodb){
|
|
826
836
|
console.log("server without database");
|
|
827
837
|
}else{
|
|
@@ -1385,6 +1395,10 @@ AppBackend.prototype.start = function start(opts){
|
|
|
1385
1395
|
});
|
|
1386
1396
|
}).then(function(){
|
|
1387
1397
|
return be.postConfig();
|
|
1398
|
+
}).then(function(){
|
|
1399
|
+
if(be.config["client-setup"]["tab-plus"] == "only"){
|
|
1400
|
+
return be.collectTabPlusTableOptions();
|
|
1401
|
+
}
|
|
1388
1402
|
}).then(function(){
|
|
1389
1403
|
if(be.config["client-setup"].menu===true){
|
|
1390
1404
|
var contextForDump = be.getContextForDump();
|
|
@@ -2341,6 +2355,8 @@ AppBackend.prototype.clientIncludes = function clientIncludes(req, opts) {
|
|
|
2341
2355
|
{ type: 'js', module: 'xlsx-now', modPath: '../../umd', file: 'xlsx-now.umd.js' },
|
|
2342
2356
|
{ type: 'js', module: 'xlsx-now', modPath: '../../umd', file: 'xlsx-now-browser.umd.js' },
|
|
2343
2357
|
{ type: 'js', module: 'require-bro' },
|
|
2358
|
+
{ type: 'js', module: 'tab-plus' },
|
|
2359
|
+
{ type: 'js', module: 'tab-plus', file: 'auto-tab-plus.js' },
|
|
2344
2360
|
{ type: 'js', module: 'js-yaml', modPath: 'browser', file: 'js-yaml.umd.min.js' },
|
|
2345
2361
|
{ type: 'js', module: 'cast-error' },
|
|
2346
2362
|
{ type: 'js', module: 'lazy-some' },
|
|
@@ -3018,6 +3034,61 @@ AppBackend.prototype.dumpDbTableFields = function dumpDbTableFields(tableDef, op
|
|
|
3018
3034
|
return fields
|
|
3019
3035
|
}
|
|
3020
3036
|
|
|
3037
|
+
AppBackend.prototype.tableDataFileCandidates = function tableDataFileCandidates(tableName){
|
|
3038
|
+
var be = this;
|
|
3039
|
+
var buscarEn=[Path.join('local-install',tableName+'.tab')].concat(
|
|
3040
|
+
be.appStack.map(function(stackNode){
|
|
3041
|
+
return Path.join(stackNode.path,'../install',tableName+'.tab').replace(regexpDistReplacer,'$1$2');
|
|
3042
|
+
})
|
|
3043
|
+
);
|
|
3044
|
+
if(be.config.install['table-data-dir'] instanceof Array){
|
|
3045
|
+
buscarEn=be.config.install['table-data-dir'].map(path=>Path.join(path,tableName+'.tab')).concat(buscarEn);
|
|
3046
|
+
}else if(be.config.install['table-data-dir']){
|
|
3047
|
+
buscarEn=[].concat(Path.join(be.config.install['table-data-dir'],tableName+'.tab')).concat(buscarEn);
|
|
3048
|
+
}
|
|
3049
|
+
return buscarEn;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
async function readTabHeaderLine(fileName){
|
|
3053
|
+
var stream = fs.createReadStream(fileName, {encoding:'utf8'});
|
|
3054
|
+
var lines = readline.createInterface({input:stream, crlfDelay:Infinity});
|
|
3055
|
+
try{
|
|
3056
|
+
var first = true;
|
|
3057
|
+
for await (var line of lines){
|
|
3058
|
+
if(first){
|
|
3059
|
+
if(/[-[]/.test(line[0])) return null;
|
|
3060
|
+
line = line.replace(/^/,'');
|
|
3061
|
+
first = false;
|
|
3062
|
+
}
|
|
3063
|
+
if(!(/^[-| ]*$/.test(line))) return line;
|
|
3064
|
+
}
|
|
3065
|
+
return null;
|
|
3066
|
+
}finally{
|
|
3067
|
+
lines.close();
|
|
3068
|
+
// readline no cierra su input: si se deja de leer antes del final el archivo queda abierto
|
|
3069
|
+
stream.destroy();
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
// para el diálogo de exportar: completa client-setup.tab-plus-table-options con la primera línea del .tab de
|
|
3074
|
+
// cada tabla que no la tenga definida
|
|
3075
|
+
AppBackend.prototype.collectTabPlusTableOptions = async function collectTabPlusTableOptions(){
|
|
3076
|
+
var be = this;
|
|
3077
|
+
var clientConfig = be.config["client-setup"];
|
|
3078
|
+
var tableOptions = clientConfig["tab-plus-table-options"] || {};
|
|
3079
|
+
var collected = {};
|
|
3080
|
+
await Promise.all(likeAr(be.tableStructures).map(async function(_tableDef, tableName){
|
|
3081
|
+
if(Object.prototype.hasOwnProperty.call(tableOptions, tableName)) return;
|
|
3082
|
+
var theTableFileName = await locatePath(be.tableDataFileCandidates(tableName));
|
|
3083
|
+
if(theTableFileName==undefined) return;
|
|
3084
|
+
var headerLine = await readTabHeaderLine(theTableFileName);
|
|
3085
|
+
if(headerLine!=null){
|
|
3086
|
+
collected[tableName] = headerLine;
|
|
3087
|
+
}
|
|
3088
|
+
}).array());
|
|
3089
|
+
clientConfig["tab-plus-table-options"] = {...collected, ...tableOptions};
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3021
3092
|
AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(partialTableStructures, opts = {}){
|
|
3022
3093
|
var complete = opts.complete;
|
|
3023
3094
|
var be = this;
|
|
@@ -3360,19 +3431,14 @@ $body$;
|
|
|
3360
3431
|
'-- END DATA FROM DUMP'
|
|
3361
3432
|
]
|
|
3362
3433
|
} else {
|
|
3434
|
+
if(be.config?.internals?.["tab-plus"] !== undefined){
|
|
3435
|
+
throw new Error('the config internals.tab-plus was moved to client-setup.tab-plus');
|
|
3436
|
+
}
|
|
3437
|
+
var tabPlusMode = be.config["client-setup"]["tab-plus"];
|
|
3363
3438
|
await Promise.all(likeAr(partialTableStructures).map(function(tableDef, tableName, _, i){
|
|
3364
3439
|
if(tableDef?.sql?.isTable === false) return;
|
|
3365
3440
|
// TODO: buscar
|
|
3366
|
-
var buscarEn=
|
|
3367
|
-
be.appStack.map(function(stackNode){
|
|
3368
|
-
return Path.join(stackNode.path,'../install',tableName+'.tab').replace(regexpDistReplacer,'$1$2');
|
|
3369
|
-
})
|
|
3370
|
-
);
|
|
3371
|
-
if(be.config.install['table-data-dir'] instanceof Array){
|
|
3372
|
-
buscarEn=be.config.install['table-data-dir'].map(path=>Path.join(path,tableName+'.tab')).concat(buscarEn);
|
|
3373
|
-
}else if(be.config.install['table-data-dir']){
|
|
3374
|
-
buscarEn=[].concat(Path.join(be.config.install['table-data-dir'],tableName+'.tab')).concat(buscarEn);
|
|
3375
|
-
}
|
|
3441
|
+
var buscarEn=be.tableDataFileCandidates(tableName);
|
|
3376
3442
|
if(!i) console.log('buscar en', buscarEn)
|
|
3377
3443
|
return locatePath(buscarEn).then(function(theTableFileName){
|
|
3378
3444
|
if(theTableFileName==undefined){
|
|
@@ -3424,43 +3490,43 @@ $body$;
|
|
|
3424
3490
|
.map(line => splitRawRowIntoRow(line))
|
|
3425
3491
|
.filter(line => line.length>1 || line.length==1 && line[0].trim() );
|
|
3426
3492
|
if(lines.length>1){
|
|
3427
|
-
if(
|
|
3428
|
-
lines[0][0]
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
var parsedTabPlus = tabPlus.parseTab(content, {
|
|
3493
|
+
if(tabPlusMode != "only"){
|
|
3494
|
+
if(lines[0][0].startsWith('\ufeff')){
|
|
3495
|
+
lines[0][0]=lines[0][0].replace('\ufeff','')
|
|
3496
|
+
}
|
|
3497
|
+
rows=lines.slice(1);
|
|
3498
|
+
var filteredFieldDef = lines[0].filter(filterField);
|
|
3499
|
+
if(tablesWithStrictSequence[tableName]){
|
|
3500
|
+
var dataString="COPY "+db.quoteIdent(tableName)+" ("+
|
|
3501
|
+
filteredFieldDef.map(db.quoteIdent).join(', ')+
|
|
3502
|
+
') FROM stdin;\n'+
|
|
3503
|
+
rows.map(function(line){
|
|
3504
|
+
return line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
|
|
3505
|
+
var def = tableDef.field[filteredFieldDef[i]];
|
|
3506
|
+
return value==='' ? (
|
|
3507
|
+
def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
|
|
3508
|
+
): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
|
|
3509
|
+
}).join('\t')+'\n';
|
|
3510
|
+
}).join('')+'\\.\n';
|
|
3511
|
+
}else{
|
|
3512
|
+
var dataString="insert into "+db.quoteIdent(tableName)+" ("+
|
|
3513
|
+
filteredFieldDef.map(db.quoteIdent).join(', ')+
|
|
3514
|
+
') values\n'+
|
|
3515
|
+
rows.map(function(line){
|
|
3516
|
+
return "("+line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
|
|
3517
|
+
var def = tableDef.field[filteredFieldDef[i]];
|
|
3518
|
+
if(def == null) {
|
|
3519
|
+
throw Error("no se encuentra la columna "+filteredFieldDef[i]+" en "+tableName);
|
|
3520
|
+
}
|
|
3521
|
+
return value==='' ? (
|
|
3522
|
+
def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
|
|
3523
|
+
) : db.quoteNullable(value);
|
|
3524
|
+
}).join(', ')+")";
|
|
3525
|
+
}).join(',\n')+';\n';
|
|
3526
|
+
}
|
|
3527
|
+
dataText.push(dataString);
|
|
3528
|
+
} else if(tabPlusMode){
|
|
3529
|
+
var parsedTabPlus = tabPlus.parseTab(content, {columnDefs: myOwn.tabPlusColumnDefs(tableDef.fields)});
|
|
3464
3530
|
var tabPlusFields = parsedTabPlus.fields;
|
|
3465
3531
|
var tabPlusRows = parsedTabPlus.rows;
|
|
3466
3532
|
/* a partir de acá: solo construcción de SQL a partir de {fields,rows}, sin volver a tocar el parseo */
|
|
@@ -3471,11 +3537,9 @@ $body$;
|
|
|
3471
3537
|
filteredFieldDefTabPlus.map(db.quoteIdent).join(', ')+
|
|
3472
3538
|
') FROM stdin;\n'+
|
|
3473
3539
|
tabPlusRows.map(function(line){
|
|
3474
|
-
return line.filter(function(_,i){ return filterField(tabPlusFields[i]);}).map(function(value
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
|
|
3478
|
-
): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
|
|
3540
|
+
return line.filter(function(_,i){ return filterField(tabPlusFields[i]);}).map(function(value){
|
|
3541
|
+
return value===null ? '\\N'
|
|
3542
|
+
: value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
|
|
3479
3543
|
}).join('\t')+'\n';
|
|
3480
3544
|
}).join('')+'\\.\n';
|
|
3481
3545
|
}else{
|
|
@@ -3488,13 +3552,11 @@ $body$;
|
|
|
3488
3552
|
if(def == null) {
|
|
3489
3553
|
throw Error("no se encuentra la columna "+filteredFieldDefTabPlus[i]+" en "+tableName);
|
|
3490
3554
|
}
|
|
3491
|
-
return value
|
|
3492
|
-
def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
|
|
3493
|
-
) : db.quoteNullable(value);
|
|
3555
|
+
return db.quoteNullable(value);
|
|
3494
3556
|
}).join(', ')+")";
|
|
3495
3557
|
}).join(',\n')+';\n';
|
|
3496
3558
|
}
|
|
3497
|
-
if(dataStringTabPlus !== dataString){
|
|
3559
|
+
if(tabPlusMode == "compare" && dataStringTabPlus !== dataString){
|
|
3498
3560
|
var oldSqlPath = path.replace(/\.tab$/,'')+'-old-local.sql';
|
|
3499
3561
|
var newSqlPath = path.replace(/\.tab$/,'')+'-new-local.sql';
|
|
3500
3562
|
fs.writeFileSync(oldSqlPath, dataString, {encoding:'UTF8'});
|
|
@@ -3505,6 +3567,9 @@ $body$;
|
|
|
3505
3567
|
console.log('TRY_TAB_PLUS: el parseo con tab-plus difiere del parseo anterior para '+tableName+' ('+path+'). Ver '+oldSqlPath+' y '+newSqlPath);
|
|
3506
3568
|
}
|
|
3507
3569
|
}
|
|
3570
|
+
if (tabPlusMode == "only") {
|
|
3571
|
+
dataText.push(dataStringTabPlus);
|
|
3572
|
+
}
|
|
3508
3573
|
}
|
|
3509
3574
|
}
|
|
3510
3575
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-plus",
|
|
3
3
|
"description": "Backend for the anti Pareto rule",
|
|
4
|
-
"version": "2.7.
|
|
4
|
+
"version": "2.7.4",
|
|
5
5
|
"author": "Codenautas <codenautas@googlegroups.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "codenautas/backend-plus",
|
|
@@ -72,11 +72,11 @@
|
|
|
72
72
|
"simple-git": "^3.36.0",
|
|
73
73
|
"sql-tools": "^0.1.7",
|
|
74
74
|
"stack-trace": "^1.0.0",
|
|
75
|
-
"tab-plus": "^0.
|
|
75
|
+
"tab-plus": "^0.2.0",
|
|
76
76
|
"type-store": "^0.6.1",
|
|
77
77
|
"typed-controls": "^0.12.7",
|
|
78
78
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz",
|
|
79
|
-
"xlsx-now": "^0.2
|
|
79
|
+
"xlsx-now": "^0.4.2"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@types/big.js": "^7.0.0",
|