hyperformula
Version:
HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas
678 lines (676 loc) • 22.2 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.UndoRedo = exports.SetSheetContentUndoEntry = exports.SetRowOrderUndoEntry = exports.SetColumnOrderUndoEntry = exports.SetCellContentsUndoEntry = exports.RenameSheetUndoEntry = exports.RemoveSheetUndoEntry = exports.RemoveRowsUndoEntry = exports.RemoveNamedExpressionUndoEntry = exports.RemoveColumnsUndoEntry = exports.PasteUndoEntry = exports.MoveRowsUndoEntry = exports.MoveColumnsUndoEntry = exports.MoveCellsUndoEntry = exports.ClearSheetUndoEntry = exports.ChangeNamedExpressionUndoEntry = exports.BatchUndoEntry = exports.BaseUndoEntry = exports.AddSheetUndoEntry = exports.AddRowsUndoEntry = exports.AddNamedExpressionUndoEntry = exports.AddColumnsUndoEntry = void 0;
var _Cell = require("./Cell");
var _Operations = require("./Operations");
/**
* @license
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/
class BaseUndoEntry {}
exports.BaseUndoEntry = BaseUndoEntry;
class RemoveRowsUndoEntry extends BaseUndoEntry {
constructor(command, rowsRemovals) {
super();
this.command = command;
this.rowsRemovals = rowsRemovals;
}
doUndo(undoRedo) {
undoRedo.undoRemoveRows(this);
}
doRedo(undoRedo) {
undoRedo.redoRemoveRows(this);
}
}
exports.RemoveRowsUndoEntry = RemoveRowsUndoEntry;
class MoveCellsUndoEntry extends BaseUndoEntry {
constructor(sourceLeftCorner, width, height, destinationLeftCorner, overwrittenCellsData, addedGlobalNamedExpressions, version) {
super();
this.sourceLeftCorner = sourceLeftCorner;
this.width = width;
this.height = height;
this.destinationLeftCorner = destinationLeftCorner;
this.overwrittenCellsData = overwrittenCellsData;
this.addedGlobalNamedExpressions = addedGlobalNamedExpressions;
this.version = version;
}
doUndo(undoRedo) {
undoRedo.undoMoveCells(this);
}
doRedo(undoRedo) {
undoRedo.redoMoveCells(this);
}
}
exports.MoveCellsUndoEntry = MoveCellsUndoEntry;
class AddRowsUndoEntry extends BaseUndoEntry {
constructor(command) {
super();
this.command = command;
}
doUndo(undoRedo) {
undoRedo.undoAddRows(this);
}
doRedo(undoRedo) {
undoRedo.redoAddRows(this);
}
}
exports.AddRowsUndoEntry = AddRowsUndoEntry;
class SetRowOrderUndoEntry extends BaseUndoEntry {
constructor(sheetId, rowMapping, oldContent) {
super();
this.sheetId = sheetId;
this.rowMapping = rowMapping;
this.oldContent = oldContent;
}
doUndo(undoRedo) {
undoRedo.undoSetRowOrder(this);
}
doRedo(undoRedo) {
undoRedo.redoSetRowOrder(this);
}
}
exports.SetRowOrderUndoEntry = SetRowOrderUndoEntry;
class SetColumnOrderUndoEntry extends BaseUndoEntry {
constructor(sheetId, columnMapping, oldContent) {
super();
this.sheetId = sheetId;
this.columnMapping = columnMapping;
this.oldContent = oldContent;
}
doUndo(undoRedo) {
undoRedo.undoSetColumnOrder(this);
}
doRedo(undoRedo) {
undoRedo.redoSetColumnOrder(this);
}
}
exports.SetColumnOrderUndoEntry = SetColumnOrderUndoEntry;
class SetSheetContentUndoEntry extends BaseUndoEntry {
constructor(sheetId, oldSheetContent, newSheetContent) {
super();
this.sheetId = sheetId;
this.oldSheetContent = oldSheetContent;
this.newSheetContent = newSheetContent;
}
doUndo(undoRedo) {
undoRedo.undoSetSheetContent(this);
}
doRedo(undoRedo) {
undoRedo.redoSetSheetContent(this);
}
}
exports.SetSheetContentUndoEntry = SetSheetContentUndoEntry;
class MoveRowsUndoEntry extends BaseUndoEntry {
constructor(sheet, startRow, numberOfRows, targetRow, version) {
super();
this.sheet = sheet;
this.startRow = startRow;
this.numberOfRows = numberOfRows;
this.targetRow = targetRow;
this.version = version;
this.undoStart = this.startRow < this.targetRow ? this.targetRow - this.numberOfRows : this.targetRow;
this.undoEnd = this.startRow > this.targetRow ? this.startRow + this.numberOfRows : this.startRow;
}
doUndo(undoRedo) {
undoRedo.undoMoveRows(this);
}
doRedo(undoRedo) {
undoRedo.redoMoveRows(this);
}
}
exports.MoveRowsUndoEntry = MoveRowsUndoEntry;
class MoveColumnsUndoEntry extends BaseUndoEntry {
constructor(sheet, startColumn, numberOfColumns, targetColumn, version) {
super();
this.sheet = sheet;
this.startColumn = startColumn;
this.numberOfColumns = numberOfColumns;
this.targetColumn = targetColumn;
this.version = version;
this.undoStart = this.startColumn < this.targetColumn ? this.targetColumn - this.numberOfColumns : this.targetColumn;
this.undoEnd = this.startColumn > this.targetColumn ? this.startColumn + this.numberOfColumns : this.startColumn;
}
doUndo(undoRedo) {
undoRedo.undoMoveColumns(this);
}
doRedo(undoRedo) {
undoRedo.redoMoveColumns(this);
}
}
exports.MoveColumnsUndoEntry = MoveColumnsUndoEntry;
class AddColumnsUndoEntry extends BaseUndoEntry {
constructor(command) {
super();
this.command = command;
}
doUndo(undoRedo) {
undoRedo.undoAddColumns(this);
}
doRedo(undoRedo) {
undoRedo.redoAddColumns(this);
}
}
exports.AddColumnsUndoEntry = AddColumnsUndoEntry;
class RemoveColumnsUndoEntry extends BaseUndoEntry {
constructor(command, columnsRemovals) {
super();
this.command = command;
this.columnsRemovals = columnsRemovals;
}
doUndo(undoRedo) {
undoRedo.undoRemoveColumns(this);
}
doRedo(undoRedo) {
undoRedo.redoRemoveColumns(this);
}
}
exports.RemoveColumnsUndoEntry = RemoveColumnsUndoEntry;
class AddSheetUndoEntry extends BaseUndoEntry {
constructor(sheetName, sheetId) {
super();
this.sheetName = sheetName;
this.sheetId = sheetId;
}
doUndo(undoRedo) {
undoRedo.undoAddSheet(this);
}
doRedo(undoRedo) {
undoRedo.redoAddSheet(this);
}
}
exports.AddSheetUndoEntry = AddSheetUndoEntry;
class RemoveSheetUndoEntry extends BaseUndoEntry {
constructor(sheetName, sheetId, oldSheetContent, scopedNamedExpressions) {
super();
this.sheetName = sheetName;
this.sheetId = sheetId;
this.oldSheetContent = oldSheetContent;
this.scopedNamedExpressions = scopedNamedExpressions;
}
doUndo(undoRedo) {
undoRedo.undoRemoveSheet(this);
}
doRedo(undoRedo) {
undoRedo.redoRemoveSheet(this);
}
}
/**
* Undo entry for renaming a sheet.
*
* When renaming a sheet to a name that was previously referenced (but didn't exist),
* a placeholder sheet gets merged into the renamed sheet. In this case:
* - `version` contains the transformation version for restoring formulas during undo
* - `mergedPlaceholderSheetId` contains the ID of the placeholder sheet that was merged
*
* When renaming to a name not previously referenced, both optional params are undefined.
*/
exports.RemoveSheetUndoEntry = RemoveSheetUndoEntry;
class RenameSheetUndoEntry extends BaseUndoEntry {
constructor(sheetId, oldName, newName, version, mergedPlaceholderSheetId) {
super();
this.sheetId = sheetId;
this.oldName = oldName;
this.newName = newName;
this.version = version;
this.mergedPlaceholderSheetId = mergedPlaceholderSheetId;
}
doUndo(undoRedo) {
undoRedo.undoRenameSheet(this);
}
doRedo(undoRedo) {
undoRedo.redoRenameSheet(this);
}
}
exports.RenameSheetUndoEntry = RenameSheetUndoEntry;
class ClearSheetUndoEntry extends BaseUndoEntry {
constructor(sheetId, oldSheetContent) {
super();
this.sheetId = sheetId;
this.oldSheetContent = oldSheetContent;
}
doUndo(undoRedo) {
undoRedo.undoClearSheet(this);
}
doRedo(undoRedo) {
undoRedo.redoClearSheet(this);
}
}
exports.ClearSheetUndoEntry = ClearSheetUndoEntry;
class SetCellContentsUndoEntry extends BaseUndoEntry {
constructor(cellContents) {
super();
this.cellContents = cellContents;
}
doUndo(undoRedo) {
undoRedo.undoSetCellContents(this);
}
doRedo(undoRedo) {
undoRedo.redoSetCellContents(this);
}
}
exports.SetCellContentsUndoEntry = SetCellContentsUndoEntry;
class PasteUndoEntry extends BaseUndoEntry {
constructor(targetLeftCorner, oldContent, newContent, addedGlobalNamedExpressions) {
super();
this.targetLeftCorner = targetLeftCorner;
this.oldContent = oldContent;
this.newContent = newContent;
this.addedGlobalNamedExpressions = addedGlobalNamedExpressions;
}
doUndo(undoRedo) {
undoRedo.undoPaste(this);
}
doRedo(undoRedo) {
undoRedo.redoPaste(this);
}
}
exports.PasteUndoEntry = PasteUndoEntry;
class AddNamedExpressionUndoEntry extends BaseUndoEntry {
constructor(name, newContent, scope, options) {
super();
this.name = name;
this.newContent = newContent;
this.scope = scope;
this.options = options;
}
doUndo(undoRedo) {
undoRedo.undoAddNamedExpression(this);
}
doRedo(undoRedo) {
undoRedo.redoAddNamedExpression(this);
}
}
exports.AddNamedExpressionUndoEntry = AddNamedExpressionUndoEntry;
class RemoveNamedExpressionUndoEntry extends BaseUndoEntry {
constructor(namedExpression, content, scope) {
super();
this.namedExpression = namedExpression;
this.content = content;
this.scope = scope;
}
doUndo(undoRedo) {
undoRedo.undoRemoveNamedExpression(this);
}
doRedo(undoRedo) {
undoRedo.redoRemoveNamedExpression(this);
}
}
exports.RemoveNamedExpressionUndoEntry = RemoveNamedExpressionUndoEntry;
class ChangeNamedExpressionUndoEntry extends BaseUndoEntry {
constructor(namedExpression, newContent, oldContent, scope, options) {
super();
this.namedExpression = namedExpression;
this.newContent = newContent;
this.oldContent = oldContent;
this.scope = scope;
this.options = options;
}
doUndo(undoRedo) {
undoRedo.undoChangeNamedExpression(this);
}
doRedo(undoRedo) {
undoRedo.redoChangeNamedExpression(this);
}
}
exports.ChangeNamedExpressionUndoEntry = ChangeNamedExpressionUndoEntry;
class BatchUndoEntry extends BaseUndoEntry {
constructor() {
super(...arguments);
this.operations = [];
}
add(operation) {
this.operations.push(operation);
}
*reversedOperations() {
for (let i = this.operations.length - 1; i >= 0; i--) {
yield this.operations[i];
}
}
doUndo(undoRedo) {
undoRedo.undoBatch(this);
}
doRedo(undoRedo) {
undoRedo.redoBatch(this);
}
}
exports.BatchUndoEntry = BatchUndoEntry;
class UndoRedo {
constructor(config, operations) {
this.operations = operations;
this.oldData = new Map();
this.undoStack = [];
this.redoStack = [];
this.undoLimit = config.undoLimit;
}
saveOperation(operation) {
if (this.batchUndoEntry !== undefined) {
this.batchUndoEntry.add(operation);
} else {
this.addUndoEntry(operation);
}
}
beginBatchMode() {
this.batchUndoEntry = new BatchUndoEntry();
}
commitBatchMode() {
if (this.batchUndoEntry === undefined) {
throw Error('Batch mode wasn\'t started');
}
this.addUndoEntry(this.batchUndoEntry);
this.batchUndoEntry = undefined;
}
storeDataForVersion(version, address, astHash) {
if (!this.oldData.has(version)) {
this.oldData.set(version, []);
}
const currentOldData = this.oldData.get(version);
currentOldData.push([address, astHash]);
}
clearRedoStack() {
this.redoStack = [];
}
clearUndoStack() {
this.undoStack = [];
}
isUndoStackEmpty() {
return this.undoStack.length === 0;
}
isRedoStackEmpty() {
return this.redoStack.length === 0;
}
undo() {
const operation = this.undoStack.pop();
if (!operation) {
throw Error('Attempted to undo without operation on stack');
}
this.undoEntry(operation);
this.redoStack.push(operation);
}
undoBatch(batchOperation) {
for (const operation of batchOperation.reversedOperations()) {
this.undoEntry(operation);
}
}
undoRemoveRows(operation) {
this.operations.forceApplyPostponedTransformations();
const {
command: {
sheet
},
rowsRemovals
} = operation;
for (let i = rowsRemovals.length - 1; i >= 0; --i) {
const rowsRemoval = rowsRemovals[i];
this.operations.addRows(new _Operations.AddRowsCommand(sheet, [[rowsRemoval.rowFrom, rowsRemoval.rowCount]]));
for (const {
address,
cellType
} of rowsRemoval.removedCells) {
this.operations.restoreCell(address, cellType);
}
this.restoreOldDataFromVersion(rowsRemoval.version - 1);
}
}
undoRemoveColumns(operation) {
this.operations.forceApplyPostponedTransformations();
const {
command: {
sheet
},
columnsRemovals
} = operation;
for (let i = columnsRemovals.length - 1; i >= 0; --i) {
const columnsRemoval = columnsRemovals[i];
this.operations.addColumns(new _Operations.AddColumnsCommand(sheet, [[columnsRemoval.columnFrom, columnsRemoval.columnCount]]));
for (const {
address,
cellType
} of columnsRemoval.removedCells) {
this.operations.restoreCell(address, cellType);
}
this.restoreOldDataFromVersion(columnsRemoval.version - 1);
}
}
undoAddRows(operation) {
const addedRowsSpans = operation.command.rowsSpans();
for (let i = addedRowsSpans.length - 1; i >= 0; --i) {
const addedRows = addedRowsSpans[i];
this.operations.removeRows(new _Operations.RemoveRowsCommand(operation.command.sheet, [[addedRows.rowStart, addedRows.numberOfRows]]));
}
}
undoAddColumns(operation) {
const addedColumnsSpans = operation.command.columnsSpans();
for (let i = addedColumnsSpans.length - 1; i >= 0; --i) {
const addedColumns = addedColumnsSpans[i];
this.operations.removeColumns(new _Operations.RemoveColumnsCommand(operation.command.sheet, [[addedColumns.columnStart, addedColumns.numberOfColumns]]));
}
}
undoSetCellContents(operation) {
for (const cellContentData of operation.cellContents) {
const address = cellContentData.address;
const [oldContentAddress, oldContent] = cellContentData.oldContent;
if (!(0, _Cell.equalSimpleCellAddress)(address, oldContentAddress)) {
this.operations.setCellEmpty(address);
}
this.operations.restoreCell(oldContentAddress, oldContent);
}
}
undoPaste(operation) {
this.restoreOperationOldContent(operation.oldContent);
for (const namedExpression of operation.addedGlobalNamedExpressions) {
this.operations.removeNamedExpression(namedExpression);
}
}
undoMoveRows(operation) {
const {
sheet
} = operation;
this.operations.moveRows(sheet, operation.undoStart, operation.numberOfRows, operation.undoEnd);
this.restoreOldDataFromVersion(operation.version - 1);
}
undoMoveColumns(operation) {
const {
sheet
} = operation;
this.operations.moveColumns(sheet, operation.undoStart, operation.numberOfColumns, operation.undoEnd);
this.restoreOldDataFromVersion(operation.version - 1);
}
undoMoveCells(operation) {
this.operations.forceApplyPostponedTransformations();
this.operations.moveCells(operation.destinationLeftCorner, operation.width, operation.height, operation.sourceLeftCorner);
this.restoreOperationOldContent(operation.overwrittenCellsData);
this.restoreOldDataFromVersion(operation.version - 1);
for (const namedExpression of operation.addedGlobalNamedExpressions) {
this.operations.removeNamedExpression(namedExpression);
}
}
undoAddSheet(operation) {
const {
sheetName
} = operation;
this.operations.removeSheetByName(sheetName);
}
undoRemoveSheet(operation) {
this.operations.forceApplyPostponedTransformations();
const {
oldSheetContent,
sheetId,
scopedNamedExpressions,
sheetName
} = operation;
this.operations.addSheetWithId(sheetId, sheetName);
for (let rowIndex = 0; rowIndex < oldSheetContent.length; rowIndex++) {
const row = oldSheetContent[rowIndex];
for (let col = 0; col < row.length; col++) {
const cellType = row[col];
const address = (0, _Cell.simpleCellAddress)(sheetId, col, rowIndex);
this.operations.restoreCell(address, cellType);
}
}
for (const [namedexpression, content] of scopedNamedExpressions) {
this.operations.restoreNamedExpression(namedexpression, content, sheetId);
}
}
undoRenameSheet(operation) {
this.operations.forceApplyPostponedTransformations();
this.operations.renameSheet(operation.sheetId, operation.oldName);
if (operation.mergedPlaceholderSheetId !== undefined && operation.version !== undefined) {
this.operations.addPlaceholderSheetWithId(operation.mergedPlaceholderSheetId, operation.newName);
this.restoreOldDataFromVersion(operation.version - 1);
}
}
undoClearSheet(operation) {
const {
oldSheetContent,
sheetId
} = operation;
for (let rowIndex = 0; rowIndex < oldSheetContent.length; rowIndex++) {
const row = oldSheetContent[rowIndex];
for (let col = 0; col < row.length; col++) {
const cellType = row[col];
const address = (0, _Cell.simpleCellAddress)(sheetId, col, rowIndex);
this.operations.restoreCell(address, cellType);
}
}
}
undoSetSheetContent(operation) {
const {
oldSheetContent,
sheetId
} = operation;
this.operations.clearSheet(sheetId);
for (let rowIndex = 0; rowIndex < oldSheetContent.length; rowIndex++) {
const row = oldSheetContent[rowIndex];
for (let col = 0; col < row.length; col++) {
const cellType = row[col];
const address = (0, _Cell.simpleCellAddress)(sheetId, col, rowIndex);
this.operations.restoreCell(address, cellType);
}
}
}
undoAddNamedExpression(operation) {
this.operations.removeNamedExpression(operation.name, operation.scope);
}
undoRemoveNamedExpression(operation) {
this.operations.restoreNamedExpression(operation.namedExpression, operation.content, operation.scope);
}
undoChangeNamedExpression(operation) {
this.operations.restoreNamedExpression(operation.namedExpression, operation.oldContent, operation.scope);
}
undoSetRowOrder(operation) {
this.restoreOperationOldContent(operation.oldContent);
}
undoSetColumnOrder(operation) {
this.restoreOperationOldContent(operation.oldContent);
}
redo() {
const operation = this.redoStack.pop();
if (!operation) {
throw Error('Attempted to redo without operation on stack');
}
this.redoEntry(operation);
this.undoStack.push(operation);
}
redoBatch(batchOperation) {
for (const operation of batchOperation.operations) {
this.redoEntry(operation);
}
}
redoRemoveRows(operation) {
this.operations.removeRows(operation.command);
}
redoMoveCells(operation) {
this.operations.moveCells(operation.sourceLeftCorner, operation.width, operation.height, operation.destinationLeftCorner);
}
redoRemoveColumns(operation) {
this.operations.removeColumns(operation.command);
}
redoPaste(operation) {
const {
targetLeftCorner,
newContent
} = operation;
const height = newContent.length;
const width = newContent[0].length;
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
const address = (0, _Cell.simpleCellAddress)(targetLeftCorner.sheet, targetLeftCorner.col + x, targetLeftCorner.row + y);
this.operations.restoreCell(address, newContent[y][x]);
}
}
}
redoSetCellContents(operation) {
for (const cellContentData of operation.cellContents) {
this.operations.setCellContent(cellContentData.address, cellContentData.newContent);
}
}
redoAddRows(operation) {
this.operations.addRows(operation.command);
}
redoAddColumns(operation) {
this.operations.addColumns(operation.command);
}
redoRemoveSheet(operation) {
this.operations.removeSheetByName(operation.sheetName);
}
redoAddSheet(operation) {
this.operations.addSheetWithId(operation.sheetId, operation.sheetName);
}
redoRenameSheet(operation) {
this.operations.renameSheet(operation.sheetId, operation.newName);
}
redoMoveRows(operation) {
this.operations.moveRows(operation.sheet, operation.startRow, operation.numberOfRows, operation.targetRow);
}
redoMoveColumns(operation) {
this.operations.moveColumns(operation.sheet, operation.startColumn, operation.numberOfColumns, operation.targetColumn);
}
redoClearSheet(operation) {
this.operations.clearSheet(operation.sheetId);
}
redoSetSheetContent(operation) {
const {
sheetId,
newSheetContent
} = operation;
this.operations.setSheetContent(sheetId, newSheetContent);
}
redoAddNamedExpression(operation) {
this.operations.addNamedExpression(operation.name, operation.newContent, operation.scope, operation.options);
}
redoRemoveNamedExpression(operation) {
this.operations.removeNamedExpression(operation.namedExpression.displayName, operation.scope);
}
redoChangeNamedExpression(operation) {
this.operations.changeNamedExpressionExpression(operation.namedExpression.displayName, operation.newContent, operation.scope, operation.options);
}
redoSetRowOrder(operation) {
this.operations.setRowOrder(operation.sheetId, operation.rowMapping);
}
redoSetColumnOrder(operation) {
this.operations.setColumnOrder(operation.sheetId, operation.columnMapping);
}
addUndoEntry(operation) {
this.undoStack.push(operation);
this.undoStack.splice(0, Math.max(0, this.undoStack.length - this.undoLimit));
}
undoEntry(operation) {
operation.doUndo(this);
}
restoreOperationOldContent(oldContent) {
for (const [address, clipboardCell] of oldContent) {
this.operations.restoreCell(address, clipboardCell);
}
}
redoEntry(operation) {
operation.doRedo(this);
}
restoreOldDataFromVersion(version) {
const oldDataToRestore = this.oldData.get(version) || [];
for (const entryToRestore of oldDataToRestore) {
const [address, hash] = entryToRestore;
this.operations.setFormulaToCellFromCache(hash, address);
}
}
}
exports.UndoRedo = UndoRedo;