mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) simplify document comparison code, and flesh out diff with local changes
Summary: With recent changes to action history, we can now remove the temporary `finalRowContent` field from change details, since all the information we need is now in the ActionSummary. We also now have more information about the state of the common ancestor, which previously we could not get either from ActionSummary or from `finalRowContent`. We take advantage of that to flesh out rendering differences where there are some changes locally and some changes remotely. There's still a lot more to do, this is just one step. I have added a link to the UI for viewing the comparison. I wouldn't want to advertise that link until diffs are robust to name changes. Test Plan: added test, updated tests Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2658
This commit is contained in:
@@ -13,6 +13,61 @@ import { CellDelta } from 'app/common/TabularDiff';
|
||||
import { DocStateComparisonDetails } from 'app/common/UserAPI';
|
||||
import { CellValue } from 'app/plugin/GristData';
|
||||
|
||||
/**
|
||||
* Represent extra rows in a table that correspond to rows added in a remote (right) document,
|
||||
* or removed in the local (left) document relative to a common ancestor.
|
||||
*
|
||||
* We assign synthetic row ids for these rows somewhat arbitrarily as follows:
|
||||
* - For rows added remotely, we double their id and flip sign
|
||||
* - For rows removed locally, we double their id, add one, and flip sign
|
||||
* This should be the only part of the code that knows that.
|
||||
*/
|
||||
export class ExtraRows {
|
||||
readonly leftTableDelta?: TableDelta;
|
||||
readonly rightTableDelta?: TableDelta;
|
||||
readonly rightAddRows: Set<number>;
|
||||
readonly rightRemoveRows: Set<number>;
|
||||
readonly leftAddRows: Set<number>;
|
||||
readonly leftRemoveRows: Set<number>;
|
||||
|
||||
/**
|
||||
* Map back from a possibly synthetic row id to an original strictly-positive row id.
|
||||
*/
|
||||
public static interpretRowId(rowId: number): { type: 'remote-add'|'local-remove'|'shared', id: number } {
|
||||
if (rowId >= 0) { return { type: 'shared', id: rowId }; }
|
||||
if (rowId % 2 === 0) { return { type: 'remote-add', id: -rowId / 2 }; }
|
||||
return { type: 'local-remove', id: (-rowId - 1) / 2 };
|
||||
}
|
||||
|
||||
public constructor(readonly tableId: string, readonly comparison?: DocStateComparisonDetails) {
|
||||
this.leftTableDelta = this.comparison?.leftChanges?.tableDeltas[this.tableId];
|
||||
this.rightTableDelta = this.comparison?.rightChanges?.tableDeltas[this.tableId];
|
||||
this.rightAddRows = new Set(this.rightTableDelta && this.rightTableDelta.addRows.map(id => -id*2));
|
||||
this.rightRemoveRows = new Set(this.rightTableDelta && this.rightTableDelta.removeRows);
|
||||
this.leftAddRows = new Set(this.leftTableDelta && this.leftTableDelta.addRows);
|
||||
this.leftRemoveRows = new Set(this.leftTableDelta && this.leftTableDelta.removeRows.map(id => -id*2 -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of extra synthetic row ids to add.
|
||||
*/
|
||||
public getExtraRows(): ReadonlyArray<number> {
|
||||
return [...this.rightAddRows].concat([...this.leftRemoveRows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the row as either remote-add, remote-remove, local-add, or local-remove.
|
||||
*/
|
||||
public getRowType(rowId: number) {
|
||||
if (this.rightAddRows.has(rowId)) { return 'remote-add'; }
|
||||
else if (this.leftAddRows.has(rowId)) { return 'local-add'; }
|
||||
else if (this.rightRemoveRows.has(rowId)) { return 'remote-remove'; }
|
||||
else if (this.leftRemoveRows.has(rowId)) { return 'local-remove'; }
|
||||
// TODO: consider what should happen when a row is removed both locally and remotely.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* A variant of DataTableModel that is aware of a comparison with another version of the table.
|
||||
@@ -154,25 +209,20 @@ export class TableDataWithDiff {
|
||||
parent: oldValue(left),
|
||||
local: newValue(left)
|
||||
} as CellVersions];
|
||||
} else {
|
||||
// No change in ActionSummary for this cell, but it could be a formula
|
||||
// column. So we do a crude comparison between the values available.
|
||||
// We won't be able to do anything useful for conflicts (e.g. to know
|
||||
// the display text in a reference columnn for the common parent).
|
||||
// We also won't be able to detect local changes at all.
|
||||
const parent = this.core.getValue(rowId, colId);
|
||||
const remote = this.rightTableDelta.finalRowContent?.[rowId]?.[colId];
|
||||
if (remote !== undefined && JSON.stringify(remote) !== JSON.stringify(parent)) {
|
||||
return [GristObjCode.Versions, {parent, remote} as CellVersions];
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
if (rowId < 0) {
|
||||
const value = this.rightTableDelta.finalRowContent?.[-rowId]?.[colId];
|
||||
} else {
|
||||
// keep row.id consistent with rowId for convenience.
|
||||
if (colId === 'id') { return - (value as number); }
|
||||
return value;
|
||||
if (colId === 'id') { return rowId; }
|
||||
const {type, id} = ExtraRows.interpretRowId(rowId);
|
||||
if (type === 'remote-add') {
|
||||
const cell = this.rightTableDelta.columnDeltas[colId]?.[id];
|
||||
const value = (cell !== undefined) ? newValue(cell) : undefined;
|
||||
return value;
|
||||
} else if (type === 'local-remove') {
|
||||
const cell = this.leftTableDelta.columnDeltas[colId]?.[id];
|
||||
const value = (cell !== undefined) ? oldValue(cell) : undefined;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return this.core.getValue(rowId, colId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user