/* * This file is part of TREB. * * TREB is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any * later version. * * TREB is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with TREB. If not, see . * * Copyright 2022-2026 trebco, llc. * info@treb.app * */ import type { ICellAddress, IArea, CellStyle, Color, CellValue, Table, TableSortType, TableTheme, IRectangle } from 'treb-base-types'; import type { ExpressionUnit } from 'treb-parser'; import type { BorderConstants } from './border_constants'; import type { Annotation, AnnotationData, ConditionalFormat, Sheet } from 'treb-data-model'; /** * switching to an exec-command based model, so we can serialize * data, layout and style changes; the intent is to support recording, * replaying, and transmitting modifications. * * NOTE regarding commands: let's make them as explicit as possible * (meaning fewer optional parameters). force callers to populate fields. * */ /** * commands are symbolic for (hopefully) faster switching. we use a * discriminated union for build-time parameter checks. see individual * command interfaces for comments. */ export enum CommandKey { /** use an empty value so all commands are non-zero */ Null = 0, InsertRows, InsertColumns, ResizeRows, ResizeColumns, Select, SetRange, UpdateStyle, UpdateBorders, Indent, MergeCells, UnmergeCells, Clear, UpdateTheme, SetNote, SetLink, Freeze, SetName, ShowHeaders, AddSheet, DuplicateSheet, DeleteSheet, ActivateSheet, RenameSheet, ReorderSheet, ShowSheet, DataValidation, Reset, SortTable, InsertTable, RemoveTable, AddConditionalFormat, RemoveConditionalFormat, TabColor, CreateAnnotation, RemoveAnnotation, } export interface RemoveAnnotationCommand { key: CommandKey.RemoveAnnotation; sheet: Sheet; annotation: Annotation; } export interface CreateAnnotationCommand { key: CommandKey.CreateAnnotation; sheet: Sheet; properties: Partial; add_to_sheet?: boolean; offset?: boolean; target?: IArea|IRectangle; focus?: boolean; } /** base type for sheet commands -- can select sheet by name, id or index */ export interface SheetSelection { index?: number; name?: string; id?: number; } /** * show or hide sheet. */ export interface ShowSheetCommand extends SheetSelection { key: CommandKey.ShowSheet; show: boolean; } /* export interface CommandBase { / ** * support commands that are not added to any recording or log. * this is for things like resizing rows/columns -- we don't necessarily * want to transmit every event, and if we don't support ephemeral commands * we will wind up working around the exec-command system, which I would * like to avoid. * / ephemeral?: boolean; } */ /** * insert a table at the given location */ export interface InsertTableCommand { key: CommandKey.InsertTable, area: IArea, /** optionally include a totals row */ totals?: boolean, /** * sortable. defaults to true. you can optionally * disable sorting, if you want. */ sortable?: boolean, /** * optional theme */ theme?: TableTheme, } /** * remove the table */ export interface RemoveTableCommand { key: CommandKey.RemoveTable, table: Table, } /** * sort a table. sorts are hard, meaning we actually move data around. * use copy/paste semantics for handling relative references (seems strange * to me, but hey). */ export interface SortTableCommand { key: CommandKey.SortTable, table: Table, column: number, asc: boolean; type: TableSortType, } /** * resize row(s). undefined means "all rows". undefined height * means "auto size". * * UPDATE: shrink is a flag you can set to prevent shrinking rows * when (and only when) auto-sizing, i.e. height is undefined */ export interface ResizeRowsCommand { key: CommandKey.ResizeRows; row?: number|number[]; height?: number; shrink?: boolean; sheet_id?: number; } /** * resize columns(s). undefined means "all columns". undefined * width means "auto size". */ export interface ResizeColumnsCommand { key: CommandKey.ResizeColumns; column?: number|number[]; width?: number; /** * for auto-resized columns, allow shrinking. defaults to true for * back-compat, so you must explicitly set it to false to disable shrinking. */ allow_shrinking?: boolean; sheet_id?: number; } /** insert one or more rows at the given insert point */ export interface InsertRowsCommand { key: CommandKey.InsertRows; before_row: number; count: number; sheet_id?: number; } /** insert one or more columns at the given insert point */ export interface InsertColumnsCommand { key: CommandKey.InsertColumns; before_column: number; count: number; sheet_id?: number; } /** show or hide headers */ export interface ShowHeadersCommand { key: CommandKey.ShowHeaders; show: boolean; } /** * set or clear name (omit range to clear) * * adding support for named expressions. you can pass either a range or * an expression. * * if you use the same name more than once, it will overwrite the old name, * even if you change types range/expression. * * passing neither will cause it to erase any existing named range OR named * expression. * */ export interface SetNameCommand { key: CommandKey.SetName; name: string; area?: IArea; expression?: ExpressionUnit; scope?: number; } export interface DataValidationCommand { key: CommandKey.DataValidation; area: IArea; range?: IArea; list?: CellValue[]; error?: boolean; } /** * not sure if we should be serializing selections... * we need some indication of primary/alternative */ export interface SelectCommand { key: CommandKey.Select; /** missing area implies clear selection (-> no selection) */ area?: IArea; /** missing target will set target as first cell of area */ target?: ICellAddress; } /** set data in cell or area */ export interface SetRangeCommand { key: CommandKey.SetRange; area: IArea|ICellAddress; /** set as array (usually control-enter) */ array?: boolean; /** * support R1C1 notation, which can come from API only (atm). this * flag does not mean the notation _is_ R1C1, just that we need to * check for it and handle it if found. */ r1c1?: boolean; /** missing data implies clear cell(s) */ value?: CellValue|CellValue[][]; } /** update borders for the given area. this is different than updating * style, because borders have slightly different semantics -- when applied * to an area, for example, "outside border" means the outside of the total * area, not the outside of each cell. */ export interface UpdateBordersCommand { key: CommandKey.UpdateBorders; area: IArea; borders: BorderConstants; // color?: string; // pending color?: Color; /** defaults to one. optional for the case of "none" (remove borders) */ width?: number; } export interface IndentCommand { key: CommandKey.Indent, area: IArea, delta: number, } /** update style in area. area can be cell(s), sheet, row(s), column(s) */ export interface UpdateStyleCommand { key: CommandKey.UpdateStyle; area: IArea|ICellAddress; style: CellStyle; delta?: boolean; } /** merge the given cells */ export interface MergeCellsCommand { key: CommandKey.MergeCells; area: IArea; } /** * unmerge the given cells. if the passed area doesn't exactly match a * merge area, we will look for merge areas inside the larger area and * unmerge those (generally useful when working with selections). */ export interface UnmergeCellsCommand { key: CommandKey.UnmergeCells; area: IArea; } /** set or clear note at given address. */ export interface SetNoteCommand { key: CommandKey.SetNote; area: ICellAddress; note?: string; } export interface SetLinkCommand { key: CommandKey.SetLink; area: ICellAddress; reference?: string; } /** * clear an area, or the entire sheet. * * because this command can omit area (meaning entire sheet), to * support remotes we need to add a separate parameter for sheet id. * * we could use infinite area as an indication it's a reset, but that's * not really the same thing -- that would be more like select all / clear. * * not sure why clear doubled as reset, except that it probably dated * from before we had multiple sheets. we're now splitting so there's an * explicit reset event. * * now that we have a separate reset, clear requires an area. * */ export interface ClearCommand { key: CommandKey.Clear; area: IArea; // sheet_id?: number; } /** * reset everything. */ export interface ResetCommand { key: CommandKey.Reset; } /** * set freeze area. set rows and columns to 0 to unfreeze. * highlight defaults to TRUE. */ export interface FreezeCommand { key: CommandKey.Freeze; rows: number; columns: number; highlight_transition?: boolean; sheet_id?: number; } /** * FIXME: should this command include theme properties, or can we * base it on the local theme? (...) probably the former, otherwise * you lose synchronization * / export interface UpdateThemeCommand { key: CommandKey.UpdateTheme; } */ export interface NullCommand { key: CommandKey.Null; } export interface AddSheetCommand { key: CommandKey.AddSheet; insert_index?: number; name?: string; /** switch to the sheet immediately */ show?: boolean; } export interface DuplicateSheetCommand extends SheetSelection { key: CommandKey.DuplicateSheet; new_name?: string; insert_before?: string|number; } export interface DeleteSheetCommand extends SheetSelection { key: CommandKey.DeleteSheet; } /** * activate a sheet. there are a couple of options for selecting * the sheet, defaulting to index (which defaults to 0) so if you * pass no selector it will select index 0. */ export interface ActivateSheetCommand extends SheetSelection { key: CommandKey.ActivateSheet; /** ... */ tab_bar_event?: boolean; force?: boolean; /** user action; use smooth scrolling */ user?: boolean; } /** * rename a sheet. options are like ActivateSheetCommand, except we * have to be a little careful about name old/new */ export interface RenameSheetCommand extends SheetSelection { key: CommandKey.RenameSheet; new_name: string; // required } /** * reorder sheet; move sheet (X) before (Y). if (Y) is larger than the * list length, moves to end. */ export interface ReorderSheetCommand { key: CommandKey.ReorderSheet; index: number; move_before: number; } /** * add conditional format */ export interface AddConditionalFormatCommand { key: CommandKey.AddConditionalFormat; format: ConditionalFormat; } export interface TabColorCommand { key: CommandKey.TabColor; sheet: Sheet; color?: Color; } /** * remove conditional format, either as an object or from a target * area. as an object, we'll match using object equivalence and not * identity. */ export interface RemoveConditionalFormatCommand { key: CommandKey.RemoveConditionalFormat; /** if format is omitted, we will remove all formats from the target range */ format?: ConditionalFormat; /** one of area or format should be supplied */ area?: IArea; } /** * ephemeral flag added to commands. * / export interface Ephemeral { ephemeral?: boolean; } */ /** * composite command type and ephemeral flag */ export type Command = ( NullCommand | ClearCommand | ResetCommand | SelectCommand | FreezeCommand | IndentCommand | SetNoteCommand | SetLinkCommand | SetNameCommand | AddSheetCommand | SetRangeCommand | TabColorCommand | SortTableCommand | ShowSheetCommand | MergeCellsCommand | ResizeRowsCommand | InsertRowsCommand | ShowHeadersCommand | DeleteSheetCommand | UpdateStyleCommand | InsertTableCommand | RemoveTableCommand | RenameSheetCommand | ReorderSheetCommand | UnmergeCellsCommand | ResizeColumnsCommand | InsertColumnsCommand | UpdateBordersCommand | ActivateSheetCommand | DataValidationCommand | DuplicateSheetCommand | CreateAnnotationCommand | RemoveAnnotationCommand | AddConditionalFormatCommand | RemoveConditionalFormatCommand ) ; // & Ephemeral; /** * record type for recording/logging commands */ export interface CommandRecord { command: Command[]; timestamp: number; }