mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
e5b67fee7e
Summary: Render simple differences between documents. * Show cell changes. * Show cell conflicts. * Show row additions/deletions. Doesn't support any schema changes, and is untested in the presence of schema changes. Any widgets that access row data without using `cells` fields won't receive correct data. Not addressed: * Rendering conflicts in mixed row addition/updating/deleting. * Column additions/deletions, option changes, etc. * Document level changes. * Table and column renames (though anticipated in ActionSummary structure). * Page-level changes. * Drawing attention to changes (marking changed pages+views, suppressing unchanged rows, etc). * Rendering differences in views other than GridView. * Adding UI for initiating a comparison. * Editing while comparing. Replaces {D2600} Test Plan: added tests Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2618
153 lines
6.3 KiB
TypeScript
153 lines
6.3 KiB
TypeScript
/**
|
|
* This mirrors action definitions from sandbox/grist/actions.py
|
|
*/
|
|
|
|
// Some definitions have moved to be part of plugin API.
|
|
import { CellValue } from 'app/plugin/GristData';
|
|
export { CellValue, RowRecord } from 'app/plugin/GristData';
|
|
|
|
// Part of a special CellValue used for comparisons, embedding several versions of a CellValue.
|
|
export type CellVersions =
|
|
{ parent: CellValue, remote: CellValue } |
|
|
{ parent: CellValue, local: CellValue } |
|
|
{ parent: CellValue, local: CellValue, remote: CellValue };
|
|
|
|
import map = require('lodash/map');
|
|
|
|
export type AddRecord = ['AddRecord', string, number, ColValues];
|
|
export type BulkAddRecord = ['BulkAddRecord', string, number[], BulkColValues];
|
|
export type RemoveRecord = ['RemoveRecord', string, number];
|
|
export type BulkRemoveRecord = ['BulkRemoveRecord', string, number[]];
|
|
export type UpdateRecord = ['UpdateRecord', string, number, ColValues];
|
|
export type BulkUpdateRecord = ['BulkUpdateRecord', string, number[], BulkColValues];
|
|
|
|
export type ReplaceTableData = ['ReplaceTableData', string, number[], BulkColValues];
|
|
|
|
// This is the format in which data comes when we fetch a table from the sandbox.
|
|
export type TableDataAction = ['TableData', string, number[], BulkColValues];
|
|
|
|
export type AddColumn = ['AddColumn', string, string, ColInfo];
|
|
export type RemoveColumn = ['RemoveColumn', string, string];
|
|
export type RenameColumn = ['RenameColumn', string, string, string];
|
|
export type ModifyColumn = ['ModifyColumn', string, string, ColInfo];
|
|
|
|
export type AddTable = ['AddTable', string, ColInfoWithId[]];
|
|
export type RemoveTable = ['RemoveTable', string];
|
|
export type RenameTable = ['RenameTable', string, string];
|
|
|
|
export type DocAction = (
|
|
AddRecord |
|
|
BulkAddRecord |
|
|
RemoveRecord |
|
|
BulkRemoveRecord |
|
|
UpdateRecord |
|
|
BulkUpdateRecord |
|
|
ReplaceTableData |
|
|
TableDataAction |
|
|
AddColumn |
|
|
RemoveColumn |
|
|
RenameColumn |
|
|
ModifyColumn |
|
|
AddTable |
|
|
RemoveTable |
|
|
RenameTable
|
|
);
|
|
|
|
// type guards for convenience - see:
|
|
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
|
|
export function isAddRecord(act: DocAction): act is AddRecord { return act[0] === 'AddRecord'; }
|
|
export function isBulkAddRecord(act: DocAction): act is BulkAddRecord { return act[0] === 'BulkAddRecord'; }
|
|
export function isRemoveRecord(act: DocAction): act is RemoveRecord { return act[0] === 'RemoveRecord'; }
|
|
export function isBulkRemoveRecord(act: DocAction): act is BulkRemoveRecord { return act[0] === 'BulkRemoveRecord'; }
|
|
export function isUpdateRecord(act: DocAction): act is UpdateRecord { return act[0] === 'UpdateRecord'; }
|
|
export function isBulkUpdateRecord(act: DocAction): act is BulkUpdateRecord { return act[0] === 'BulkUpdateRecord'; }
|
|
|
|
export function isReplaceTableData(act: DocAction): act is ReplaceTableData { return act[0] === 'ReplaceTableData'; }
|
|
|
|
export function isAddColumn(act: DocAction): act is AddColumn { return act[0] === 'AddColumn'; }
|
|
export function isRemoveColumn(act: DocAction): act is RemoveColumn { return act[0] === 'RemoveColumn'; }
|
|
export function isRenameColumn(act: DocAction): act is RenameColumn { return act[0] === 'RenameColumn'; }
|
|
export function isModifyColumn(act: DocAction): act is ModifyColumn { return act[0] === 'ModifyColumn'; }
|
|
|
|
export function isAddTable(act: DocAction): act is AddTable { return act[0] === 'AddTable'; }
|
|
export function isRemoveTable(act: DocAction): act is RemoveTable { return act[0] === 'RemoveTable'; }
|
|
export function isRenameTable(act: DocAction): act is RenameTable { return act[0] === 'RenameTable'; }
|
|
|
|
|
|
const SCHEMA_ACTIONS = new Set(['AddTable', 'RemoveTable', 'RenameTable', 'AddColumn',
|
|
'RemoveColumn', 'RenameColumn', 'ModifyColumn']);
|
|
|
|
/**
|
|
* Determines whether a given action is a schema action or not.
|
|
*/
|
|
export function isSchemaAction(action: DocAction): boolean {
|
|
return SCHEMA_ACTIONS.has(action[0]);
|
|
}
|
|
|
|
/**
|
|
* Returns the tableId from the action.
|
|
*/
|
|
export function getTableId(action: DocAction): string {
|
|
return action[1]; // It happens to always be in the same position in the action tuple.
|
|
}
|
|
|
|
// Helper types used in the definitions above.
|
|
|
|
export interface ColValues { [colId: string]: CellValue; }
|
|
export interface BulkColValues { [colId: string]: CellValue[]; }
|
|
export interface ColInfoMap { [colId: string]: ColInfo; }
|
|
|
|
export interface ColInfo {
|
|
type: string;
|
|
isFormula: boolean;
|
|
formula: string;
|
|
}
|
|
|
|
export interface ColInfoWithId extends ColInfo {
|
|
id: string;
|
|
}
|
|
|
|
// Multiple records in column-oriented format, i.e. same as BulkColValues but with a mandatory
|
|
// 'id' column. This is preferred over TableDataAction in external APIs.
|
|
export interface TableColValues {
|
|
id: number[];
|
|
[colId: string]: CellValue[];
|
|
}
|
|
|
|
// Both UserActions and DocActions are represented as [ActionName, ...actionArgs].
|
|
// TODO I think it's better to represent DocAction as a Buffer containing the marshalled action.
|
|
|
|
export type UserAction = Array<string|number|object|boolean|null|undefined>;
|
|
|
|
/**
|
|
* Gives a description for an action which involves setting values to a selection.
|
|
* @param {Array} action - The (Bulk)AddRecord/(Bulk)UpdateRecord action to describe.
|
|
* @param {Boolean} optExcludeVals - Indicates whether the values should be excluded from
|
|
* the description.
|
|
*/
|
|
export function getSelectionDesc(action: UserAction, optExcludeVals: boolean): string {
|
|
const table = action[1];
|
|
const rows = action[2];
|
|
const colValues: number[] = action[3] as any; // TODO: better typing - but code may evaporate
|
|
const columns = map(colValues, (values, col) => optExcludeVals ? col : `${col}: ${values}`);
|
|
const s = typeof rows === 'object' ? 's' : '';
|
|
return `table ${table}, row${s} ${rows}; ${columns.join(", ")}`;
|
|
}
|
|
|
|
// Convert from TableColValues (used by DocStorage and external APIs) to TableDataAction (used
|
|
// mainly by the sandbox).
|
|
export function toTableDataAction(tableId: string, colValues: TableColValues): TableDataAction {
|
|
const colData = {...colValues}; // Make a copy to avoid changing passed-in arguments.
|
|
const rowIds: number[] = colData.id;
|
|
delete colData.id;
|
|
return ['TableData', tableId, rowIds, colData];
|
|
}
|
|
|
|
// Convert from TableDataAction (used mainly by the sandbox) to TableColValues (used by DocStorage
|
|
// and external APIs).
|
|
export function fromTableDataAction(tableData: TableDataAction): TableColValues {
|
|
const rowIds: number[] = tableData[2];
|
|
const colValues: BulkColValues = tableData[3];
|
|
return {id: rowIds, ...colValues};
|
|
}
|