diff --git a/packages/datalib/src/runMacro.ts b/packages/datalib/src/runMacro.ts index a5b266dda..0e45bc4a1 100644 --- a/packages/datalib/src/runMacro.ts +++ b/packages/datalib/src/runMacro.ts @@ -10,6 +10,11 @@ const getMacroFunction = { (value, args, modules, rowIndex, row, columnName) => { ${code} } +`, + transformRows: (code) => ` +(rows, args, modules, selectedCells, cols) => { + ${code} +} `, }; @@ -20,6 +25,96 @@ const modules = { moment, }; +function runTramsformValue( + func, + macroArgs: {}, + data: FreeTableModel, + preview: boolean, + selectedCells: MacroSelectedCell[], + errors: string[] = [] +) { + const selectedRows = _.groupBy(selectedCells, 'row'); + const rows = data.rows.map((row, rowIndex) => { + const selectedRow = selectedRows[rowIndex]; + if (selectedRow) { + const modifiedFields = []; + let res = null; + for (const cell of selectedRow) { + const { column } = cell; + const oldValue = row[column]; + let newValue = oldValue; + try { + newValue = func(oldValue, macroArgs, modules, rowIndex, row, column); + } catch (err) { + errors.push(`Error processing column ${column} on row ${rowIndex}: ${err.message}`); + } + if (newValue != oldValue) { + if (res == null) { + res = { ...row }; + } + res[column] = newValue; + if (preview) modifiedFields.push(column); + } + } + if (res) { + if (modifiedFields.length > 0) { + return { + ...res, + __modifiedFields: new Set(modifiedFields), + }; + } + return res; + } + return row; + } else { + return row; + } + }); + + return { + structure: data.structure, + rows, + }; +} + +function removePreviewRowFlags(rows) { + rows = rows.filter((row) => row.__rowStatus != 'deleted'); + rows = rows.map((row) => { + if (row.__rowStatus || row.__modifiedFields) return _.omit(row, ['__rowStatus', '__modifiedFields']); + return row; + }); + return rows; +} + +function runTramsformRows( + func, + macroArgs: {}, + data: FreeTableModel, + preview: boolean, + selectedCells: MacroSelectedCell[], + errors: string[] = [] +) { + let rows = data.rows; + try { + rows = func( + data.rows, + macroArgs, + modules, + selectedCells, + data.structure.columns.map((x) => x.columnName) + ); + if (!preview) { + rows = removePreviewRowFlags(rows); + } + } catch (err) { + errors.push(`Error processing rows: ${err.message}`); + } + return { + structure: data.structure, + rows, + }; +} + export function runMacro( macro: MacroDefinition, macroArgs: {}, @@ -36,48 +131,10 @@ export function runMacro( return data; } if (macro.type == 'transformValue') { - const selectedRows = _.groupBy(selectedCells, 'row'); - const rows = data.rows.map((row, rowIndex) => { - const selectedRow = selectedRows[rowIndex]; - if (selectedRow) { - const modifiedFields = []; - let res = null; - for (const cell of selectedRow) { - const { column } = cell; - const oldValue = row[column]; - let newValue = oldValue; - try { - newValue = func(oldValue, macroArgs, modules, rowIndex, row, column); - } catch (err) { - errors.push(`Error processing column ${column} on row ${rowIndex}: ${err.message}`); - } - if (newValue != oldValue) { - if (res == null) { - res = { ...row }; - } - res[column] = newValue; - if (preview) modifiedFields.push(column); - } - } - if (res) { - if (modifiedFields.length > 0) { - return { - ...res, - __modifiedFields: new Set(modifiedFields), - }; - } - return res; - } - return row; - } else { - return row; - } - }); - - return { - structure: data.structure, - rows, - }; + return runTramsformValue(func, macroArgs, data, preview, selectedCells, errors); + } + if (macro.type == 'transformRows') { + return runTramsformRows(func, macroArgs, data, preview, selectedCells, errors); } return data; } diff --git a/packages/web/src/freetable/MacroPreviewGrider.ts b/packages/web/src/freetable/MacroPreviewGrider.ts index 1c3081715..6a42b8794 100644 --- a/packages/web/src/freetable/MacroPreviewGrider.ts +++ b/packages/web/src/freetable/MacroPreviewGrider.ts @@ -16,7 +16,7 @@ export default class MacroPreviewGrider extends Grider { getRowStatus(index): GriderRowStatus { const row = this.model.rows[index]; return { - status: 'regular', + status: (row && row.__rowStatus) || 'regular', modifiedFields: row ? row.__modifiedFields : null, }; } diff --git a/packages/web/src/freetable/macros.js b/packages/web/src/freetable/macros.js index 9e80cec38..e357dc8ef 100644 --- a/packages/web/src/freetable/macros.js +++ b/packages/web/src/freetable/macros.js @@ -36,9 +36,9 @@ const macros = [ }, ], code: ` - const rtext = args.isRegex ? args.find : modules.lodash.escapeRegExp(args.find); - const rflags = args.caseSensitive ? 'g' : 'ig'; - return value ? value.toString().replace(new RegExp(rtext, rflags), args.replace || '') : value +const rtext = args.isRegex ? args.find : modules.lodash.escapeRegExp(args.find); +const rflags = args.caseSensitive ? 'g' : 'ig'; +return value ? value.toString().replace(new RegExp(rtext, rflags), args.replace || '') : value `, }, { @@ -102,6 +102,41 @@ const macros = [ ], code: `return modules.moment().format(args.format)`, }, + { + title: 'Duplicate rows', + name: 'duplicateRows', + group: 'Tools', + description: 'Duplicate selected rows', + type: 'transformRows', + code: ` +const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row)); +const selectedRows = modules.lodash.groupBy(selectedCells, 'row'); +const maxIndex = _.max(selectedRowIndexes); +return [ + ...rows.slice(0, maxIndex + 1), + ...selectedRowIndexes.map(index => ({ + ..._.pick(rows[index], selectedRows[index].map(x=>x.column)), + __rowStatus: 'inserted', + })), + ...rows.slice(maxIndex + 1), +] + `, + }, + { + title: 'Delete empty rows', + name: 'deleteEmptyRows', + group: 'Tools', + description: 'Delete empty rows - rows with all values null or empty string', + type: 'transformRows', + code: ` +return rows.map(row => { + if (cols.find(col => row[col])) return row; + return { + ...row, + __rowStatus: 'deleted', + }; +}) +`} ]; export default macros;