diff --git a/app/common/DocActions.ts b/app/common/DocActions.ts index 1b137f4a..894c84c9 100644 --- a/app/common/DocActions.ts +++ b/app/common/DocActions.ts @@ -2,6 +2,10 @@ * 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'; + import map = require('lodash/map'); export type AddRecord = ['AddRecord', string, number, ColValues]; @@ -83,7 +87,6 @@ export function getTableId(action: DocAction): string { // Helper types used in the definitions above. -export type CellValue = number|string|boolean|null|[string, any?]; export interface ColValues { [colId: string]: CellValue; } export interface BulkColValues { [colId: string]: CellValue[]; } export interface ColInfoMap { [colId: string]: ColInfo; } @@ -98,11 +101,6 @@ export interface ColInfoWithId extends ColInfo { id: string; } -export interface RowRecord { - id: number; - [colId: string]: CellValue; -} - // 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 { diff --git a/app/plugin/GristAPI-ti.ts b/app/plugin/GristAPI-ti.ts index 096d0135..c1a599bf 100644 --- a/app/plugin/GristAPI-ti.ts +++ b/app/plugin/GristAPI-ti.ts @@ -23,6 +23,7 @@ export const GristDocAPI = t.iface([], { export const GristView = t.iface([], { "fetchSelectedTable": t.func("any"), + "fetchSelectedRecord": t.func("any", t.param("rowId", "number")), }); const exportedTypeSuite: t.ITypeSuite = { diff --git a/app/plugin/GristAPI.ts b/app/plugin/GristAPI.ts index 7560d613..405cef60 100644 --- a/app/plugin/GristAPI.ts +++ b/app/plugin/GristAPI.ts @@ -96,4 +96,7 @@ export interface GristView { // TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because // ts-interface-builder does not properly support index-signature. fetchSelectedTable(): Promise; + + // Similar TODO to fetchSelectedTable for return type. + fetchSelectedRecord(rowId: number): Promise; } diff --git a/app/plugin/GristData.ts b/app/plugin/GristData.ts new file mode 100644 index 00000000..c161ecb7 --- /dev/null +++ b/app/plugin/GristData.ts @@ -0,0 +1,6 @@ +export type CellValue = number|string|boolean|null|[string, any?]; + +export interface RowRecord { + id: number; + [colId: string]: CellValue; +} diff --git a/app/plugin/GristTable-ti.ts b/app/plugin/GristTable-ti.ts index e47f2103..237d94dc 100644 --- a/app/plugin/GristTable-ti.ts +++ b/app/plugin/GristTable-ti.ts @@ -1,3 +1,6 @@ +/** + * This module was automatically generated by `ts-interface-builder` + */ import * as t from "ts-interface-checker"; // tslint:disable:object-literal-key-quotes @@ -16,9 +19,17 @@ export const GristColumn = t.iface([], { "type": "string", }); +export const APIType = t.enumtype({ + "ImportSourceAPI": 0, + "ImportProcessorAPI": 1, + "ParseOptionsAPI": 2, + "ParseFileAPI": 3, +}); + const exportedTypeSuite: t.ITypeSuite = { GristTable, GristTables, GristColumn, + APIType, }; export default exportedTypeSuite; diff --git a/app/plugin/grist-plugin-api.ts b/app/plugin/grist-plugin-api.ts index be168ca3..62d7f295 100644 --- a/app/plugin/grist-plugin-api.ts +++ b/app/plugin/grist-plugin-api.ts @@ -19,6 +19,7 @@ // tslint:disable:no-console import { GristAPI, GristDocAPI, GristView, RPC_GRISTAPI_INTERFACE } from './GristAPI'; +import { RowRecord } from './GristData'; import { ImportSource, ImportSourceAPI, InternalImportSourceAPI } from './InternalImportSourceAPI'; import { RenderOptions, RenderTarget } from './RenderOptions'; import { checkers } from './TypeCheckers'; @@ -45,6 +46,40 @@ export const docApi = { export const on = rpc.on.bind(rpc); +// For custom widgets, add a handler that will be called whenever the +// row with the cursor changes - either by switching to a different row, or +// by some value within the row potentially changing. Handler may +// in the future be called with null if the cursor moves away from +// any row. +// TODO: currently this will be called even if the content of a different row +// changes. +export function onRecord(callback: (data: RowRecord | null) => unknown) { + on('message', async function(msg) { + if (!msg.tableId || !msg.rowId) { return; } + const rec = await docApi.fetchSelectedRecord(msg.rowId); + callback(rec); + }); +} + +// For custom widgets, add a handler that will be called whenever the +// selected records change. Handler will be called with a list of records. +export function onRecords(callback: (data: RowRecord[]) => unknown) { + on('message', async function(msg) { + if (!msg.tableId || !msg.dataChange) { return; } + const data = await docApi.fetchSelectedTable(); + if (!data.id) { return; } + const rows: RowRecord[] = []; + for (let i = 0; i < data.id.length; i++) { + const row: RowRecord = {id: data.id[i]}; + for (const key of Object.keys(data)) { + row[key] = data[key][i]; + } + rows.push(row); + } + callback(rows); + }); +} + /** * Calling `addImporter(...)` adds a safeBrowser importer. It is a short-hand for forwarding calls * to an `ImportSourceAPI` implementation registered in the file at `path`. It takes care of