@odoo/o-spreadsheet 14.0.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/o_spreadsheet.cjs.js +31302 -0
  2. package/dist/o_spreadsheet.d.ts +6604 -0
  3. package/dist/o_spreadsheet.esm.js +31257 -0
  4. package/dist/o_spreadsheet.iife.js +31304 -0
  5. package/dist/o_spreadsheet.iife.min.js +3071 -0
  6. package/package.json +57 -15
  7. package/.eslintrc.json +0 -20
  8. package/.husky/post-checkout +0 -9
  9. package/.husky/pre-commit +0 -39
  10. package/.prettierignore +0 -1
  11. package/astToFormula.patch +0 -0
  12. package/demo/data.js +0 -255
  13. package/demo/index.html +0 -9
  14. package/demo/libs/Chart.bundle.js +0 -20755
  15. package/demo/main.js +0 -101
  16. package/doc/readme.md +0 -46
  17. package/rollup.config.js +0 -23
  18. package/src/base_plugin.ts +0 -102
  19. package/src/components/autofill.ts +0 -146
  20. package/src/components/bottom_bar.ts +0 -199
  21. package/src/components/color_picker.ts +0 -150
  22. package/src/components/composer/autocomplete_dropdown.ts +0 -135
  23. package/src/components/composer/composer.ts +0 -487
  24. package/src/components/composer/content_editable_helper.ts +0 -146
  25. package/src/components/figures/chart.ts +0 -70
  26. package/src/components/figures/container.ts +0 -268
  27. package/src/components/figures/text_figure.ts +0 -36
  28. package/src/components/grid.ts +0 -635
  29. package/src/components/helpers/dom_helpers.ts +0 -7
  30. package/src/components/helpers/drag_and_drop.ts +0 -17
  31. package/src/components/icons.ts +0 -36
  32. package/src/components/index.ts +0 -1
  33. package/src/components/menu.ts +0 -288
  34. package/src/components/overlay.ts +0 -457
  35. package/src/components/scrollbar.ts +0 -22
  36. package/src/components/selection_input.ts +0 -168
  37. package/src/components/side_panel/cell_is_rule_editor.ts +0 -169
  38. package/src/components/side_panel/chart_panel.ts +0 -104
  39. package/src/components/side_panel/color_scale_rule_editor.ts +0 -145
  40. package/src/components/side_panel/conditional_formatting.ts +0 -396
  41. package/src/components/side_panel/side_panel.ts +0 -127
  42. package/src/components/side_panel/translations_terms.ts +0 -45
  43. package/src/components/spreadsheet.ts +0 -198
  44. package/src/components/top_bar.ts +0 -535
  45. package/src/constants.ts +0 -26
  46. package/src/data.ts +0 -149
  47. package/src/fonts.ts +0 -41
  48. package/src/formulas/compiler.ts +0 -258
  49. package/src/formulas/composer_tokenizer.ts +0 -70
  50. package/src/formulas/index.ts +0 -15
  51. package/src/formulas/normalize.ts +0 -52
  52. package/src/formulas/parser.ts +0 -280
  53. package/src/formulas/range_tokenizer.ts +0 -97
  54. package/src/formulas/tokenizer.ts +0 -242
  55. package/src/functions/arguments.ts +0 -114
  56. package/src/functions/dates.ts +0 -302
  57. package/src/functions/helpers.ts +0 -592
  58. package/src/functions/index.ts +0 -52
  59. package/src/functions/module_database.ts +0 -282
  60. package/src/functions/module_date.ts +0 -743
  61. package/src/functions/module_info.ts +0 -69
  62. package/src/functions/module_logical.ts +0 -208
  63. package/src/functions/module_lookup.ts +0 -185
  64. package/src/functions/module_math.ts +0 -1252
  65. package/src/functions/module_operators.ts +0 -291
  66. package/src/functions/module_statistical.ts +0 -882
  67. package/src/functions/module_text.ts +0 -370
  68. package/src/helpers/color.ts +0 -30
  69. package/src/helpers/coordinates.ts +0 -71
  70. package/src/helpers/grid_manipulation.ts +0 -68
  71. package/src/helpers/index.ts +0 -7
  72. package/src/helpers/misc.ts +0 -76
  73. package/src/helpers/numbers.ts +0 -117
  74. package/src/helpers/uuid.ts +0 -19
  75. package/src/helpers/zones.ts +0 -263
  76. package/src/history.ts +0 -186
  77. package/src/index.ts +0 -84
  78. package/src/model.ts +0 -261
  79. package/src/plugins/autofill.ts +0 -412
  80. package/src/plugins/chart.ts +0 -404
  81. package/src/plugins/clipboard.ts +0 -461
  82. package/src/plugins/conditional_format.ts +0 -445
  83. package/src/plugins/core.ts +0 -1502
  84. package/src/plugins/edition.ts +0 -157
  85. package/src/plugins/evaluation.ts +0 -428
  86. package/src/plugins/figures.ts +0 -123
  87. package/src/plugins/formatting.ts +0 -630
  88. package/src/plugins/highlight.ts +0 -187
  89. package/src/plugins/index.ts +0 -32
  90. package/src/plugins/merge.ts +0 -524
  91. package/src/plugins/renderer.ts +0 -571
  92. package/src/plugins/selection.ts +0 -562
  93. package/src/plugins/selection_inputs.ts +0 -326
  94. package/src/registries/autofill_modifiers.ts +0 -76
  95. package/src/registries/autofill_rules.ts +0 -98
  96. package/src/registries/figure_registry.ts +0 -30
  97. package/src/registries/index.ts +0 -7
  98. package/src/registries/menu_items_registry.ts +0 -110
  99. package/src/registries/menus/cell_menu_registry.ts +0 -79
  100. package/src/registries/menus/col_menu_registry.ts +0 -63
  101. package/src/registries/menus/index.ts +0 -6
  102. package/src/registries/menus/menu_items_actions.ts +0 -425
  103. package/src/registries/menus/row_menu_registry.ts +0 -63
  104. package/src/registries/menus/sheet_menu_registry.ts +0 -75
  105. package/src/registries/menus/topbar_menu_registry.ts +0 -204
  106. package/src/registries/side_panel_registry.ts +0 -26
  107. package/src/registries/topbar_component_registry.ts +0 -12
  108. package/src/registry.ts +0 -51
  109. package/src/translation.ts +0 -44
  110. package/src/types/autofill.ts +0 -48
  111. package/src/types/chart.ts +0 -26
  112. package/src/types/commands.ts +0 -716
  113. package/src/types/conditional_formatting.ts +0 -124
  114. package/src/types/env.ts +0 -13
  115. package/src/types/functions.ts +0 -35
  116. package/src/types/getters.ts +0 -103
  117. package/src/types/index.ts +0 -20
  118. package/src/types/misc.ts +0 -143
  119. package/src/types/rendering.ts +0 -41
  120. package/src/types/workbook_data.ts +0 -62
  121. package/tests/__mocks__/uuid.ts +0 -7
  122. package/tests/canvas.mock.ts +0 -34
  123. package/tests/components/__mocks__/content_editable_helper.ts +0 -79
  124. package/tests/components/__mocks__/scrollbar.ts +0 -18
  125. package/tests/components/__snapshots__/autocomplete_dropdown_test.ts.snap +0 -29
  126. package/tests/components/__snapshots__/bottom_bar_test.ts.snap +0 -75
  127. package/tests/components/__snapshots__/context_menu_test.ts.snap +0 -88
  128. package/tests/components/__snapshots__/grid_test.ts.snap +0 -60
  129. package/tests/components/__snapshots__/spreadsheet_test.ts.snap +0 -455
  130. package/tests/components/__snapshots__/top_bar_test.ts.snap +0 -705
  131. package/tests/components/autocomplete_dropdown_test.ts +0 -315
  132. package/tests/components/autofill_test.ts +0 -167
  133. package/tests/components/bottom_bar_test.ts +0 -262
  134. package/tests/components/chart_side_panel_test.ts +0 -123
  135. package/tests/components/composer_test.ts +0 -400
  136. package/tests/components/context_menu_test.ts +0 -825
  137. package/tests/components/grid_manipulation_test.ts +0 -163
  138. package/tests/components/grid_test.ts +0 -581
  139. package/tests/components/overlay_test.ts +0 -541
  140. package/tests/components/selection_input_test.ts +0 -259
  141. package/tests/components/side_panel_test.ts +0 -148
  142. package/tests/components/spreadsheet_test.ts +0 -194
  143. package/tests/components/top_bar_test.ts +0 -427
  144. package/tests/data_test.ts +0 -175
  145. package/tests/dom_helper.ts +0 -42
  146. package/tests/formulas/__snapshots__/compiler_test.ts.snap +0 -302
  147. package/tests/formulas/compiler_test.ts +0 -266
  148. package/tests/formulas/composer_tokenizer_test.ts +0 -137
  149. package/tests/formulas/expressions_test.ts +0 -57
  150. package/tests/formulas/formulas_test.ts +0 -80
  151. package/tests/formulas/normalize_test.ts +0 -82
  152. package/tests/formulas/parser_test.ts +0 -224
  153. package/tests/formulas/range_tokenizer_test.ts +0 -187
  154. package/tests/formulas/tokenizer_test.ts +0 -164
  155. package/tests/functions/arguments_test.ts +0 -141
  156. package/tests/functions/dates_test.ts +0 -1081
  157. package/tests/functions/functions_test.ts +0 -53
  158. package/tests/functions/helper_test.ts +0 -98
  159. package/tests/functions/module_database_test.ts +0 -311
  160. package/tests/functions/module_date_test.ts +0 -941
  161. package/tests/functions/module_info_test.ts +0 -139
  162. package/tests/functions/module_logical_test.ts +0 -369
  163. package/tests/functions/module_lookup_test.ts +0 -541
  164. package/tests/functions/module_math_test.ts +0 -2482
  165. package/tests/functions/module_operators_test.ts +0 -955
  166. package/tests/functions/module_statistical_test.ts +0 -2014
  167. package/tests/functions/module_text_test.ts +0 -667
  168. package/tests/helpers/coordinates_test.ts +0 -25
  169. package/tests/helpers/misc_test.ts +0 -95
  170. package/tests/helpers/numbers_test.ts +0 -196
  171. package/tests/helpers/zones_test.ts +0 -97
  172. package/tests/helpers.ts +0 -384
  173. package/tests/menu_items_registry_test.ts +0 -540
  174. package/tests/model/plugins/__snapshots__/chart_test.ts.snap +0 -90
  175. package/tests/model/plugins/chart_test.ts +0 -562
  176. package/tests/model_test.ts +0 -82
  177. package/tests/plugins/__snapshots__/conditional_formatting_test.ts.snap +0 -131
  178. package/tests/plugins/__snapshots__/renderer_test.ts.snap +0 -371
  179. package/tests/plugins/autofill_test.ts +0 -457
  180. package/tests/plugins/borders_test.ts +0 -295
  181. package/tests/plugins/clipboard_test.ts +0 -1180
  182. package/tests/plugins/conditional_formatting_test.ts +0 -1253
  183. package/tests/plugins/core_test.ts +0 -614
  184. package/tests/plugins/edition_test.ts +0 -100
  185. package/tests/plugins/evaluation_async_test.ts +0 -332
  186. package/tests/plugins/evaluation_dates_test.ts +0 -79
  187. package/tests/plugins/evaluation_test.ts +0 -901
  188. package/tests/plugins/figures_test.ts +0 -327
  189. package/tests/plugins/formatting_test.ts +0 -391
  190. package/tests/plugins/grid_manipulation_test.ts +0 -1366
  191. package/tests/plugins/highlight_test.ts +0 -413
  192. package/tests/plugins/history_test.ts +0 -142
  193. package/tests/plugins/import_export_test.ts +0 -276
  194. package/tests/plugins/merges_test.ts +0 -490
  195. package/tests/plugins/navigation_test.ts +0 -181
  196. package/tests/plugins/plugins/evaluation_test.ts +0 -111
  197. package/tests/plugins/renderer_test.ts +0 -598
  198. package/tests/plugins/resizing_test.ts +0 -152
  199. package/tests/plugins/selection_input_test.ts +0 -409
  200. package/tests/plugins/selection_test.ts +0 -442
  201. package/tests/plugins/sheets_test.ts +0 -643
  202. package/tests/plugins/style_test.ts +0 -90
  203. package/tsconfig.json +0 -22
@@ -1,1502 +0,0 @@
1
- import { BasePlugin } from "../base_plugin";
2
- import { DEFAULT_CELL_HEIGHT, DEFAULT_CELL_WIDTH } from "../constants";
3
- import { cellReference, compile, normalize, rangeTokenize } from "../formulas/index";
4
- import { formatDateTime, InternalDate, parseDateTime } from "../functions/dates";
5
- import {
6
- formatNumber,
7
- formatStandardNumber,
8
- getComposerSheetName,
9
- getUnquotedSheetName,
10
- isNumber,
11
- mapCellsInZone,
12
- numberToLetters,
13
- parseNumber,
14
- recomputeZones,
15
- toCartesian,
16
- toXC,
17
- toZone,
18
- } from "../helpers/index";
19
- import { _lt } from "../translation";
20
- import {
21
- CancelledReason,
22
- Cell,
23
- CellData,
24
- Col,
25
- Command,
26
- CommandResult,
27
- HeaderData,
28
- NormalizedFormula,
29
- RenameSheetCommand,
30
- Row,
31
- Sheet,
32
- SheetData,
33
- WorkbookData,
34
- Zone,
35
- } from "../types/index";
36
-
37
- const nbspRegexp = new RegExp(String.fromCharCode(160), "g");
38
- const MIN_PADDING = 3;
39
-
40
- /**
41
- * Core Plugin
42
- *
43
- * This is the most fundamental of all plugins. It defines how to interact with
44
- * cell and sheet content.
45
- */
46
- export class CorePlugin extends BasePlugin {
47
- static getters = [
48
- "applyOffset",
49
- "getColsZone",
50
- "getRowsZone",
51
- "getCell",
52
- "getCellText",
53
- "zoneToXC",
54
- "getActiveSheet",
55
- "getSheetName",
56
- "getSheetIdByName",
57
- "getSheets",
58
- "getCol",
59
- "getRow",
60
- "getColCells",
61
- "getNumberCols",
62
- "getNumberRows",
63
- "getGridSize",
64
- "shouldShowFormulas",
65
- "getRangeValues",
66
- "getRangeFormattedValues",
67
- ];
68
-
69
- private sheetIds: { [name: string]: string } = {};
70
- private showFormulas: boolean = false;
71
-
72
- // ---------------------------------------------------------------------------
73
- // Command Handling
74
- // ---------------------------------------------------------------------------
75
-
76
- allowDispatch(cmd: Command): CommandResult {
77
- switch (cmd.type) {
78
- case "REMOVE_COLUMNS":
79
- return this.workbook.sheets[cmd.sheet].cols.length > cmd.columns.length
80
- ? { status: "SUCCESS" }
81
- : { status: "CANCELLED", reason: CancelledReason.NotEnoughColumns };
82
- case "REMOVE_ROWS":
83
- return this.workbook.sheets[cmd.sheet].rows.length > cmd.rows.length
84
- ? { status: "SUCCESS" }
85
- : { status: "CANCELLED", reason: CancelledReason.NotEnoughRows };
86
- case "CREATE_SHEET":
87
- case "DUPLICATE_SHEET":
88
- const { visibleSheets, sheets } = this.workbook;
89
- return !cmd.name || !visibleSheets.find((id) => sheets[id].name === cmd.name)
90
- ? { status: "SUCCESS" }
91
- : { status: "CANCELLED", reason: CancelledReason.WrongSheetName };
92
- case "MOVE_SHEET":
93
- const currentIndex = this.workbook.visibleSheets.findIndex((id) => id === cmd.sheet);
94
- if (currentIndex === -1) {
95
- return { status: "CANCELLED", reason: CancelledReason.WrongSheetName };
96
- }
97
- return (cmd.direction === "left" && currentIndex === 0) ||
98
- (cmd.direction === "right" && currentIndex === this.workbook.visibleSheets.length - 1)
99
- ? { status: "CANCELLED", reason: CancelledReason.WrongSheetMove }
100
- : { status: "SUCCESS" };
101
- case "RENAME_SHEET":
102
- return this.isRenameAllowed(cmd);
103
- case "DELETE_SHEET_CONFIRMATION":
104
- case "DELETE_SHEET":
105
- return this.workbook.visibleSheets.length > 1
106
- ? { status: "SUCCESS" }
107
- : { status: "CANCELLED", reason: CancelledReason.NotEnoughSheets };
108
- default:
109
- return { status: "SUCCESS" };
110
- }
111
- }
112
-
113
- handle(cmd: Command) {
114
- switch (cmd.type) {
115
- case "ACTIVATE_SHEET":
116
- this.history.updateState(["activeSheet"], this.workbook.sheets[cmd.to]);
117
- break;
118
- case "CREATE_SHEET":
119
- const sheet = this.createSheet(
120
- cmd.id,
121
- cmd.name || this.generateSheetName(),
122
- cmd.cols || 26,
123
- cmd.rows || 100
124
- );
125
- this.sheetIds[this.workbook.sheets[sheet].name] = sheet;
126
- if (cmd.activate) {
127
- this.dispatch("ACTIVATE_SHEET", { from: this.workbook.activeSheet.id, to: sheet });
128
- }
129
- break;
130
- case "MOVE_SHEET":
131
- this.moveSheet(cmd.sheet, cmd.direction);
132
- break;
133
- case "RENAME_SHEET":
134
- if (cmd.interactive) {
135
- this.interactiveRenameSheet(cmd.sheet, _lt("Rename Sheet"));
136
- } else {
137
- this.renameSheet(cmd.sheet, cmd.name!);
138
- }
139
- break;
140
- case "DUPLICATE_SHEET":
141
- this.duplicateSheet(cmd.sheet, cmd.id, cmd.name);
142
- break;
143
- case "DELETE_SHEET_CONFIRMATION":
144
- this.interactiveDeleteSheet(cmd.sheet);
145
- break;
146
- case "DELETE_SHEET":
147
- this.deleteSheet(cmd.sheet);
148
- break;
149
- case "DELETE_CONTENT":
150
- this.clearZones(cmd.sheet, cmd.target);
151
- break;
152
- case "SET_VALUE":
153
- const [col, row] = toCartesian(cmd.xc);
154
- this.dispatch("UPDATE_CELL", {
155
- sheet: cmd.sheetId ? cmd.sheetId : this.workbook.activeSheet.id,
156
- col,
157
- row,
158
- content: cmd.text,
159
- });
160
- break;
161
- case "UPDATE_CELL":
162
- this.updateCell(cmd.sheet, cmd.col, cmd.row, cmd);
163
- break;
164
- case "CLEAR_CELL":
165
- this.dispatch("UPDATE_CELL", {
166
- sheet: cmd.sheet,
167
- col: cmd.col,
168
- row: cmd.row,
169
- content: "",
170
- border: 0,
171
- style: 0,
172
- format: "",
173
- });
174
- break;
175
- case "AUTORESIZE_COLUMNS":
176
- for (let col of cmd.cols) {
177
- const size = this.getColMaxWidth(col);
178
- if (size !== 0) {
179
- this.setColSize(cmd.sheet, col, size + 2 * MIN_PADDING);
180
- }
181
- }
182
- break;
183
- case "AUTORESIZE_ROWS":
184
- for (let col of cmd.rows) {
185
- const size = this.getRowMaxHeight(col);
186
- if (size !== 0) {
187
- this.setRowSize(cmd.sheet, col, size + 2 * MIN_PADDING);
188
- }
189
- }
190
- break;
191
- case "RESIZE_COLUMNS":
192
- for (let col of cmd.cols) {
193
- this.setColSize(cmd.sheet, col, cmd.size);
194
- }
195
- break;
196
- case "RESIZE_ROWS":
197
- for (let row of cmd.rows) {
198
- this.setRowSize(cmd.sheet, row, cmd.size);
199
- }
200
- break;
201
- case "REMOVE_COLUMNS":
202
- this.removeColumns(cmd.sheet, cmd.columns);
203
- this.history.updateState(
204
- ["sheets", cmd.sheet, "colNumber"],
205
- this.workbook.sheets[cmd.sheet].colNumber - cmd.columns.length
206
- );
207
- break;
208
- case "REMOVE_ROWS":
209
- this.removeRows(cmd.sheet, cmd.rows);
210
- this.history.updateState(
211
- ["sheets", cmd.sheet, "rowNumber"],
212
- this.workbook.sheets[cmd.sheet].rowNumber - cmd.rows.length
213
- );
214
- break;
215
- case "ADD_COLUMNS":
216
- this.addColumns(cmd.sheet, cmd.column, cmd.position, cmd.quantity);
217
- this.history.updateState(
218
- ["activeSheet", "colNumber"],
219
- this.workbook.activeSheet.colNumber + cmd.quantity
220
- );
221
- break;
222
- case "ADD_ROWS":
223
- this.addRows(cmd.sheet, cmd.row, cmd.position, cmd.quantity);
224
- this.history.updateState(
225
- ["activeSheet", "rowNumber"],
226
- this.workbook.activeSheet.rowNumber + cmd.quantity
227
- );
228
- break;
229
- case "SET_FORMULA_VISIBILITY":
230
- this.showFormulas = cmd.show;
231
- break;
232
- }
233
- }
234
-
235
- // ---------------------------------------------------------------------------
236
- // Getters
237
- // ---------------------------------------------------------------------------
238
-
239
- applyOffset(formula: string, offsetX: number, offsetY: number): string {
240
- return rangeTokenize(formula)
241
- .map((t) => {
242
- if (t.type === "SYMBOL" && cellReference.test(t.value)) {
243
- const [xcs, sheetName] = t.value.split("!").reverse();
244
- const sheetId = this.getSheetIdByName(sheetName);
245
- if (xcs.includes(":")) {
246
- return this.updateRange(xcs, offsetX, offsetY, sheetId);
247
- }
248
- return this.updateReference(xcs, offsetX, offsetY, sheetId, true, false);
249
- }
250
- return t.value;
251
- })
252
- .join("");
253
- }
254
-
255
- getCell(col: number, row: number, sheetName?: string): Cell | null {
256
- let r;
257
- if (!sheetName) {
258
- r = this.workbook.activeSheet.rows[row];
259
- } else {
260
- const sheet = Object.values(this.workbook.sheets).find((x) => x.name === sheetName);
261
- if (sheet) {
262
- r = sheet.rows[row];
263
- } else {
264
- return null;
265
- }
266
- }
267
- return r ? r.cells[col] || null : null;
268
- }
269
-
270
- getCellText(cell: Cell): string {
271
- const value: unknown = this.showFormulas ? cell.content : cell.value;
272
- const shouldFormat = (value || value === 0) && cell.format && !cell.error && !cell.pending;
273
- const dateTimeFormat = shouldFormat && cell.format!.match(/y|m|d|:/);
274
- const numberFormat = shouldFormat && !dateTimeFormat;
275
- switch (typeof value) {
276
- case "string":
277
- return value;
278
- case "boolean":
279
- return value ? "TRUE" : "FALSE";
280
- case "number":
281
- if (dateTimeFormat) {
282
- return formatDateTime({ value, format: cell.format! });
283
- }
284
- if (numberFormat) {
285
- return formatNumber(value, cell.format!);
286
- }
287
- return formatStandardNumber(value);
288
- case "object":
289
- if (!value) return "0";
290
- if ("value" in value && "format" in value) {
291
- if (dateTimeFormat) {
292
- return formatDateTime(value as InternalDate, cell.format);
293
- }
294
- if (numberFormat) {
295
- return formatNumber((value as any).value, cell.format!);
296
- }
297
- if (value && (value as InternalDate).format.match(/y|m|d|:/)) {
298
- return formatDateTime(value as InternalDate);
299
- }
300
- }
301
- return value.toString();
302
- }
303
- return "";
304
- }
305
-
306
- /**
307
- * Converts a zone to a XC coordinate system
308
- *
309
- * The conversion also treats merges as one single cell
310
- *
311
- * Examples:
312
- * {top:0,left:0,right:0,bottom:0} ==> A1
313
- * {top:0,left:0,right:1,bottom:1} ==> A1:B2
314
- *
315
- * if A1:B2 is a merge:
316
- * {top:0,left:0,right:1,bottom:1} ==> A1
317
- * {top:1,left:0,right:1,bottom:2} ==> A1:B3
318
- *
319
- * if A1:B2 and A4:B5 are merges:
320
- * {top:1,left:0,right:1,bottom:3} ==> A1:A5
321
- */
322
- zoneToXC(zone: Zone): string {
323
- zone = this.getters.expandZone(zone);
324
- const topLeft = toXC(zone.left, zone.top);
325
- const botRight = toXC(zone.right, zone.bottom);
326
- if (
327
- topLeft != botRight &&
328
- this.getters.getMainCell(topLeft) !== this.getters.getMainCell(botRight)
329
- ) {
330
- return topLeft + ":" + botRight;
331
- }
332
-
333
- return topLeft;
334
- }
335
-
336
- /**
337
- * Returns the id (not the name) of the currently active sheet
338
- */
339
- getActiveSheet(): string {
340
- return this.workbook.activeSheet.id;
341
- }
342
-
343
- getSheetName(sheetId: string): string | undefined {
344
- return this.workbook.sheets[sheetId] && this.workbook.sheets[sheetId].name;
345
- }
346
-
347
- getSheetIdByName(name: string | undefined): string | undefined {
348
- return name && this.sheetIds[getUnquotedSheetName(name)];
349
- }
350
-
351
- getSheets(): Sheet[] {
352
- const { visibleSheets, sheets } = this.workbook;
353
- return visibleSheets.map((id) => sheets[id]);
354
- }
355
-
356
- getCol(sheetId: string, index: number): Col {
357
- return this.workbook.sheets[sheetId].cols[index];
358
- }
359
-
360
- getRow(sheetId: string, index: number): Row {
361
- return this.workbook.sheets[sheetId].rows[index];
362
- }
363
-
364
- /**
365
- * Returns all the cells of a col
366
- */
367
- getColCells(col: number): Cell[] {
368
- return this.workbook.activeSheet.rows.reduce(
369
- (acc: Cell[], cur) => (cur.cells[col] ? acc.concat(cur.cells[col]) : acc),
370
- []
371
- );
372
- }
373
-
374
- getNumberCols(sheetId: string): number {
375
- return this.workbook.sheets[sheetId].cols.length;
376
- }
377
-
378
- getNumberRows(sheetId: string): number {
379
- return this.workbook.sheets[sheetId].rows.length;
380
- }
381
-
382
- getColsZone(start: number, end: number): Zone {
383
- return {
384
- top: 0,
385
- bottom: this.workbook.activeSheet.rows.length - 1,
386
- left: start,
387
- right: end,
388
- };
389
- }
390
-
391
- getRowsZone(start: number, end: number): Zone {
392
- return {
393
- top: start,
394
- bottom: end,
395
- left: 0,
396
- right: this.workbook.activeSheet.cols.length - 1,
397
- };
398
- }
399
-
400
- getGridSize(): [number, number] {
401
- const activeSheet = this.workbook.activeSheet;
402
- const height = activeSheet.rows[activeSheet.rows.length - 1].end + DEFAULT_CELL_HEIGHT + 5;
403
- const width = activeSheet.cols[activeSheet.cols.length - 1].end + DEFAULT_CELL_WIDTH;
404
-
405
- return [width, height];
406
- }
407
-
408
- shouldShowFormulas(): boolean {
409
- return this.showFormulas;
410
- }
411
-
412
- getRangeValues(reference: string, defaultSheetId: string): any[][] {
413
- const [range, sheetName] = reference.split("!").reverse();
414
- const sheetId = sheetName ? this.sheetIds[sheetName] : defaultSheetId;
415
- return mapCellsInZone(toZone(range), this.workbook.sheets[sheetId], (cell) => cell.value);
416
- }
417
-
418
- getRangeFormattedValues(reference: string, defaultSheetId: string): string[][] {
419
- const [range, sheetName] = reference.split("!").reverse();
420
- const sheetId = sheetName ? this.sheetIds[sheetName] : defaultSheetId;
421
- return mapCellsInZone(
422
- toZone(range),
423
- this.workbook.sheets[sheetId],
424
- this.getters.getCellText,
425
- ""
426
- );
427
- }
428
-
429
- // ---------------------------------------------------------------------------
430
- // Row/Col manipulation
431
- // ---------------------------------------------------------------------------
432
-
433
- private getColMaxWidth(index: number): number {
434
- const cells = this.workbook.activeSheet.rows.reduce(
435
- (acc: Cell[], cur) => (cur.cells[index] ? acc.concat(cur.cells[index]) : acc),
436
- []
437
- );
438
- const sizes = cells.map(this.getters.getCellWidth);
439
- return Math.max(0, ...sizes);
440
- }
441
-
442
- private getRowMaxHeight(index: number): number {
443
- const cells = Object.values(this.workbook.activeSheet.rows[index].cells);
444
- const sizes = cells.map(this.getters.getCellHeight);
445
- return Math.max(0, ...sizes);
446
- }
447
-
448
- private setColSize(sheetId: string, index: number, size: number) {
449
- const cols = this.workbook.sheets[sheetId].cols;
450
- const col = cols[index];
451
- const delta = size - col.size;
452
- this.history.updateState(["sheets", sheetId, "cols", index, "size"], size);
453
- this.history.updateState(["sheets", sheetId, "cols", index, "end"], col.end + delta);
454
- for (let i = index + 1; i < cols.length; i++) {
455
- const col = cols[i];
456
- this.history.updateState(["sheets", sheetId, "cols", i, "start"], col.start + delta);
457
- this.history.updateState(["sheets", sheetId, "cols", i, "end"], col.end + delta);
458
- }
459
- }
460
-
461
- private setRowSize(sheetId: string, index: number, size: number) {
462
- const rows = this.workbook.sheets[sheetId].rows;
463
- const row = rows[index];
464
- const delta = size - row.size;
465
- this.history.updateState(["sheets", sheetId, "rows", index, "size"], size);
466
- this.history.updateState(["sheets", sheetId, "rows", index, "end"], row.end + delta);
467
- for (let i = index + 1; i < rows.length; i++) {
468
- const row = rows[i];
469
- this.history.updateState(["sheets", sheetId, "rows", i, "start"], row.start + delta);
470
- this.history.updateState(["sheets", sheetId, "rows", i, "end"], row.end + delta);
471
- }
472
- }
473
-
474
- /**
475
- * Delete column. This requires a lot of handling:
476
- * - Update all the formulas in all sheets
477
- * - Move the cells
478
- * - Update the cols/rows (size, number, (cells), ...)
479
- * - Reevaluate the cells
480
- *
481
- * @param sheetID ID of the sheet on which deletion should be applied
482
- * @param columns Columns to delete
483
- */
484
- private removeColumns(sheetID: string, columns: number[]) {
485
- // This is necessary because we have to delete elements in correct order:
486
- // begin with the end.
487
- columns.sort((a, b) => b - a);
488
- for (let column of columns) {
489
- // Update all the formulas.
490
- this.updateColumnsFormulas(column, -1, sheetID);
491
-
492
- // Move the cells.
493
- this.moveCellsHorizontally(column, -1, sheetID);
494
-
495
- // Effectively delete the element and recompute the left-right.
496
- this.manageColumnsHeaders(column, -1, sheetID);
497
- }
498
- }
499
-
500
- /**
501
- * Delete row. This requires a lot of handling:
502
- * - Update the merges
503
- * - Update all the formulas in all sheets
504
- * - Move the cells
505
- * - Update the cols/rows (size, number, (cells), ...)
506
- * - Reevaluate the cells
507
- *
508
- * @param sheetID ID of the sheet on which deletion should be applied
509
- * @param rows Rows to delete
510
- */
511
- private removeRows(sheetID: string, rows: number[]) {
512
- // This is necessary because we have to delete elements in correct order:
513
- // begin with the end.
514
- rows.sort((a, b) => b - a);
515
-
516
- const consecutiveRows = rows.reduce((groups, currentRow, index, rows) => {
517
- if (currentRow - rows[index - 1] === -1) {
518
- const lastGroup = groups[groups.length - 1];
519
- lastGroup.push(currentRow);
520
- } else {
521
- groups.push([currentRow]);
522
- }
523
- return groups;
524
- }, [] as number[][]);
525
-
526
- for (let group of consecutiveRows) {
527
- // Update all the formulas.
528
- this.updateRowsFormulas(group[0], -group.length, sheetID);
529
-
530
- // Move the cells.
531
- this.moveCellVerticallyBatched(group[group.length - 1], group[0], sheetID);
532
-
533
- // Effectively delete the element and recompute the left-right/top-bottom.
534
- group.map((row) => this.processRowsHeaderDelete(row, sheetID));
535
- }
536
- }
537
-
538
- private addColumns(
539
- sheetID: string,
540
- column: number,
541
- position: "before" | "after",
542
- quantity: number
543
- ) {
544
- // Update all the formulas.
545
- this.updateColumnsFormulas(position === "before" ? column - 1 : column, quantity, sheetID);
546
-
547
- // Move the cells.
548
- this.moveCellsHorizontally(position === "before" ? column : column + 1, quantity, sheetID);
549
-
550
- // Recompute the left-right/top-bottom.
551
- this.manageColumnsHeaders(column, quantity, sheetID);
552
- }
553
-
554
- private addRows(sheetID: string, row: number, position: "before" | "after", quantity: number) {
555
- for (let i = 0; i < quantity; i++) {
556
- this.addEmptyRow();
557
- }
558
- // Update all the formulas.
559
- this.updateRowsFormulas(position === "before" ? row - 1 : row, quantity, sheetID);
560
-
561
- // Move the cells.
562
- this.moveCellsVertically(position === "before" ? row : row + 1, quantity, sheetID);
563
-
564
- // Recompute the left-right/top-bottom.
565
- this.processRowsHeaderAdd(row, quantity);
566
- }
567
-
568
- private moveCellsHorizontally(base: number, step: number, sheetID: string) {
569
- return this.processCellsToMove(
570
- (cell) => cell.col >= base,
571
- (cell) => cell.col !== base || step !== -1,
572
- (cell) => {
573
- return {
574
- type: "UPDATE_CELL",
575
- sheet: sheetID,
576
- col: cell.col + step,
577
- row: cell.row,
578
- content: cell.content,
579
- border: cell.border,
580
- style: cell.style,
581
- format: cell.format,
582
- };
583
- },
584
- sheetID
585
- );
586
- }
587
-
588
- /**
589
- * Move all the cells that are from the row under `deleteToRow` up to `deleteFromRow`
590
- *
591
- * b.e.
592
- * move vertically with delete from 3 and delete to 5 will first clear all the cells from lines 3 to 5,
593
- * then take all the row starting at index 6 and add them back at index 3
594
- *
595
- * @param deleteFromRow the row index from which to start deleting
596
- * @param deleteToRow the row index until which the deleting must continue
597
- * @param the sheet from which to remove
598
- */
599
- private moveCellVerticallyBatched(deleteFromRow: number, deleteToRow: number, sheetID: string) {
600
- return this.processCellsToMove(
601
- ({ row }) => row >= deleteFromRow,
602
- ({ row }) => row > deleteToRow,
603
- (cell) => {
604
- return {
605
- type: "UPDATE_CELL",
606
- sheet: this.workbook.sheets[sheetID].id,
607
- col: cell.col,
608
- row: cell.row - (deleteToRow - deleteFromRow + 1),
609
- content: cell.content,
610
- border: cell.border,
611
- style: cell.style,
612
- format: cell.format,
613
- };
614
- },
615
- sheetID
616
- );
617
- }
618
-
619
- private moveCellsVertically(base: number, step: number, sheetID: string) {
620
- return this.processCellsToMove(
621
- (cell) => cell.row >= base,
622
- (cell) => cell.row !== base || step !== -1,
623
- (cell) => {
624
- return {
625
- type: "UPDATE_CELL",
626
- sheet: sheetID,
627
- col: cell.col,
628
- row: cell.row + step,
629
- content: cell.content,
630
- border: cell.border,
631
- style: cell.style,
632
- format: cell.format,
633
- };
634
- },
635
- sheetID
636
- );
637
- }
638
-
639
- private manageColumnsHeaders(base: number, step: number, sheetID: string) {
640
- const cols: Col[] = [];
641
- let start = 0;
642
- let colIndex = 0;
643
- const sheet = this.workbook.sheets[sheetID];
644
- for (let i in sheet.cols) {
645
- if (parseInt(i, 10) === base) {
646
- if (step !== -1) {
647
- const { size } = sheet.cols[colIndex];
648
- for (let a = 0; a < step; a++) {
649
- cols.push({
650
- name: numberToLetters(colIndex),
651
- size,
652
- start,
653
- end: start + size,
654
- });
655
- start += size;
656
- colIndex++;
657
- }
658
- } else {
659
- continue;
660
- }
661
- }
662
- const { size } = sheet.cols[i];
663
- cols.push({
664
- name: numberToLetters(colIndex),
665
- size,
666
- start,
667
- end: start + size,
668
- });
669
- start += size;
670
- colIndex++;
671
- }
672
- this.history.updateState(["sheets", sheetID, "cols"], cols);
673
- }
674
-
675
- private processRowsHeaderDelete(index: number, sheetID: string) {
676
- const rows: Row[] = [];
677
- let start = 0;
678
- let rowIndex = 0;
679
- const sheet = this.workbook.sheets[sheetID];
680
- const cellsQueue = sheet.rows.map((row) => row.cells);
681
- for (let i in sheet.rows) {
682
- const row = sheet.rows[i];
683
- const { size } = row;
684
- if (parseInt(i, 10) === index) {
685
- continue;
686
- }
687
- rowIndex++;
688
- rows.push({
689
- start,
690
- end: start + size,
691
- size,
692
- cells: cellsQueue.shift()!,
693
- name: String(rowIndex),
694
- });
695
- start += size;
696
- }
697
- this.history.updateState(["sheets", sheetID, "rows"], rows);
698
- }
699
-
700
- private processRowsHeaderAdd(index: number, quantity: number) {
701
- const rows: Row[] = [];
702
- let start = 0;
703
- let rowIndex = 0;
704
- let sizeIndex = 0;
705
- const cellsQueue = this.workbook.activeSheet.rows.map((row) => row.cells);
706
- for (let i in this.workbook.activeSheet.rows) {
707
- const { size } = this.workbook.activeSheet.rows[sizeIndex];
708
- if (parseInt(i, 10) < index || parseInt(i, 10) >= index + quantity) {
709
- sizeIndex++;
710
- }
711
- rowIndex++;
712
- rows.push({
713
- start,
714
- end: start + size,
715
- size,
716
- cells: cellsQueue.shift()!,
717
- name: String(rowIndex),
718
- });
719
- start += size;
720
- }
721
- this.history.updateState(["activeSheet", "rows"], rows);
722
- }
723
-
724
- private addEmptyRow() {
725
- const lastEnd = this.workbook.activeSheet.rows[this.workbook.activeSheet.rows.length - 1].end;
726
- const name = (this.workbook.activeSheet.rows.length + 1).toString();
727
- const newRows: Row[] = this.workbook.activeSheet.rows.slice();
728
- const size = 0;
729
- newRows.push({
730
- start: lastEnd,
731
- end: lastEnd + size,
732
- size,
733
- name,
734
- cells: {},
735
- });
736
- const path = ["activeSheet", "rows"];
737
- this.history.updateState(path, newRows);
738
- }
739
-
740
- private updateColumnsFormulas(base: number, step: number, sheetID: string) {
741
- return this.visitFormulas(
742
- this.workbook.sheets[sheetID].name,
743
- (value: string, sheet: string | undefined): string => {
744
- if (value.includes(":")) {
745
- return this.updateColumnsRange(value, sheet, base, step);
746
- }
747
- return this.updateColumnsRef(value, sheet, base, step);
748
- }
749
- );
750
- }
751
-
752
- private updateRowsFormulas(base: number, step: number, sheetID: string) {
753
- return this.visitFormulas(
754
- this.workbook.sheets[sheetID].name,
755
- (value: string, sheet: string | undefined): string => {
756
- if (value.includes(":")) {
757
- return this.updateRowsRange(value, sheet, base, step);
758
- }
759
- return this.updateRowsRef(value, sheet, base, step);
760
- }
761
- );
762
- }
763
-
764
- private processCellsToMove(
765
- shouldDelete: (cell: Cell) => boolean,
766
- shouldAdd: (cell: Cell) => boolean,
767
- buildCellToAdd: (cell: Cell) => Command,
768
- sheetId
769
- ) {
770
- const deleteCommands: Command[] = [];
771
- const addCommands: Command[] = [];
772
-
773
- const sheet = this.workbook.sheets[sheetId];
774
-
775
- for (let xc in sheet.cells) {
776
- let cell = sheet.cells[xc];
777
- if (shouldDelete(cell)) {
778
- const [col, row] = toCartesian(xc);
779
- deleteCommands.push({
780
- type: "CLEAR_CELL",
781
- sheet: sheet.id,
782
- col,
783
- row,
784
- });
785
- if (shouldAdd(cell)) {
786
- addCommands.push(buildCellToAdd(cell));
787
- }
788
- }
789
- }
790
- for (let cmd of deleteCommands) {
791
- this.dispatch(cmd.type, cmd);
792
- }
793
- for (let cmd of addCommands) {
794
- this.dispatch(cmd.type, cmd);
795
- }
796
- }
797
- // ---------------------------------------------------------------------------
798
- // Cells
799
- // ---------------------------------------------------------------------------
800
-
801
- private updateCell(sheet: string, col: number, row: number, data: CellData) {
802
- const _sheet = this.workbook.sheets[sheet];
803
- const current = _sheet.rows[row].cells[col];
804
- const xc = (current && current.xc) || toXC(col, row);
805
- const hasContent = "content" in data;
806
-
807
- // Compute the new cell properties
808
- const dataContent = data.content ? data.content.replace(nbspRegexp, "") : "";
809
- let content = hasContent ? dataContent : (current && current.content) || "";
810
- const style = "style" in data ? data.style : (current && current.style) || 0;
811
- const border = "border" in data ? data.border : (current && current.border) || 0;
812
- let format = "format" in data ? data.format : (current && current.format) || "";
813
-
814
- // if all are empty, we need to delete the underlying cell object
815
- if (!content && !style && !border && !format) {
816
- if (current) {
817
- // todo: make this work on other sheets
818
- this.history.updateSheet(_sheet, ["cells", xc], undefined);
819
- this.history.updateSheet(_sheet, ["rows", row, "cells", col], undefined);
820
- }
821
- return;
822
- }
823
-
824
- // compute the new cell value
825
- const didContentChange =
826
- (!current && dataContent) || (hasContent && current && current.content !== dataContent);
827
- let cell: Cell;
828
- if (current && !didContentChange) {
829
- cell = { col, row, xc, content, value: current.value, type: current.type };
830
- if (cell.type === "formula") {
831
- cell.error = current.error;
832
- cell.pending = current.pending;
833
- cell.formula = current.formula;
834
- }
835
- } else {
836
- // the current content cannot be reused, so we need to recompute the
837
- // derived values
838
- let type: Cell["type"] = content[0] === "=" ? "formula" : "text";
839
- let value: Cell["value"] = content;
840
- if (isNumber(content)) {
841
- value = parseNumber(content);
842
- type = "number";
843
- if (content.includes("%")) {
844
- format = content.includes(".") ? "0.00%" : "0%";
845
- }
846
- }
847
- let date = parseDateTime(content);
848
- if (date) {
849
- type = "date";
850
- value = date;
851
- content = formatDateTime(date);
852
- }
853
- const contentUpperCase = content.toUpperCase();
854
- if (contentUpperCase === "TRUE") {
855
- value = true;
856
- }
857
- if (contentUpperCase === "FALSE") {
858
- value = false;
859
- }
860
- cell = { col, row, xc, content, value, type };
861
- if (cell.type === "formula") {
862
- cell.error = undefined;
863
- try {
864
- let formulaString: NormalizedFormula = normalize(cell.content || "");
865
- let compiledFormula = compile(formulaString);
866
- cell.formula = {
867
- compiledFormula: compiledFormula,
868
- dependencies: formulaString.dependencies,
869
- text: formulaString.text,
870
- };
871
- } catch (e) {
872
- cell.value = "#BAD_EXPR";
873
- cell.error = e.message || _lt("Invalid Expression");
874
- }
875
- }
876
- }
877
- if (style) {
878
- cell.style = style;
879
- }
880
- if (border) {
881
- cell.border = border;
882
- }
883
- if (format) {
884
- cell.format = format;
885
- }
886
- // todo: make this work on other sheets
887
- this.history.updateSheet(_sheet, ["cells", xc], cell);
888
- this.history.updateSheet(_sheet, ["rows", row, "cells", col], cell);
889
- }
890
-
891
- private generateSheetName(): string {
892
- let i = 1;
893
- const names = this.getSheets().map((s) => s.name);
894
- const baseName = _lt("Sheet");
895
- let name = `${baseName}${i}`;
896
- while (names.includes(name)) {
897
- name = `${baseName}${i}`;
898
- i++;
899
- }
900
- return name;
901
- }
902
-
903
- private createSheet(id: string, name: string, cols: number, rows: number): string {
904
- const sheet: Sheet = {
905
- id,
906
- name,
907
- cells: {},
908
- colNumber: cols,
909
- rowNumber: rows,
910
- cols: createDefaultCols(cols),
911
- rows: createDefaultRows(rows),
912
- merges: {},
913
- mergeCellMap: {},
914
- };
915
- const visibleSheets = this.workbook.visibleSheets.slice();
916
- const index = visibleSheets.findIndex((id) => this.workbook.activeSheet.id === id);
917
- visibleSheets.splice(index + 1, 0, sheet.id);
918
- const sheets = this.workbook.sheets;
919
- this.history.updateState(["visibleSheets"], visibleSheets);
920
- this.history.updateState(["sheets"], Object.assign({}, sheets, { [sheet.id]: sheet }));
921
- return sheet.id;
922
- }
923
-
924
- private moveSheet(sheetId: string, direction: "left" | "right") {
925
- const visibleSheets = this.workbook.visibleSheets.slice();
926
- const currentIndex = visibleSheets.findIndex((id) => id === sheetId);
927
- const sheet = visibleSheets.splice(currentIndex, 1);
928
- visibleSheets.splice(currentIndex + (direction === "left" ? -1 : 1), 0, sheet[0]);
929
- this.history.updateState(["visibleSheets"], visibleSheets);
930
- }
931
-
932
- private isRenameAllowed(cmd: RenameSheetCommand): CommandResult {
933
- if (cmd.interactive) {
934
- return { status: "SUCCESS" };
935
- }
936
- const name = cmd.name && cmd.name.trim().toLowerCase();
937
- if (!name) {
938
- return { status: "CANCELLED", reason: CancelledReason.WrongSheetName };
939
- }
940
- return this.workbook.visibleSheets.findIndex(
941
- (id) => this.workbook.sheets[id].name.toLowerCase() === name
942
- ) === -1
943
- ? { status: "SUCCESS" }
944
- : { status: "CANCELLED", reason: CancelledReason.WrongSheetName };
945
- }
946
-
947
- private interactiveRenameSheet(sheet: string, title: string) {
948
- const placeholder = this.getSheetName(sheet)!;
949
- this.ui.editText(title, placeholder, (name: string | null) => {
950
- if (!name) {
951
- return;
952
- }
953
- const result = this.dispatch("RENAME_SHEET", { sheet, name });
954
- const sheetName = this.getSheetName(sheet);
955
- if (result.status === "CANCELLED" && sheetName !== name) {
956
- this.interactiveRenameSheet(sheet, _lt("Please enter a valid sheet name"));
957
- }
958
- });
959
- }
960
-
961
- private renameSheet(sheetId: string, name: string) {
962
- const sheet = this.workbook.sheets[sheetId];
963
- const oldName = sheet.name;
964
- this.history.updateSheet(sheet, ["name"], name.trim());
965
- const sheetIds = Object.assign({}, this.sheetIds);
966
- sheetIds[name] = sheet.id;
967
- delete sheetIds[oldName];
968
- this.history.updateLocalState(["sheetIds"], sheetIds);
969
- this.visitAllFormulasSymbols((value: string) => {
970
- let [val, sheetRef] = value.split("!").reverse();
971
- if (sheetRef) {
972
- sheetRef = getUnquotedSheetName(sheetRef);
973
- if (sheetRef === oldName) {
974
- if (val.includes(":")) {
975
- return this.updateRange(val, 0, 0, sheet.id);
976
- }
977
- return this.updateReference(val, 0, 0, sheet.id);
978
- }
979
- }
980
- return value;
981
- });
982
- }
983
-
984
- private duplicateSheet(fromId: string, toId: string, toName: string) {
985
- const sheet = this.workbook.sheets[fromId];
986
- const newSheet = JSON.parse(JSON.stringify(sheet));
987
- newSheet.id = toId;
988
- newSheet.name = toName;
989
- const visibleSheets = this.workbook.visibleSheets.slice();
990
- const currentIndex = visibleSheets.findIndex((id) => id === fromId);
991
- visibleSheets.splice(currentIndex + 1, 0, newSheet.id);
992
- this.history.updateState(["visibleSheets"], visibleSheets);
993
- this.history.updateState(
994
- ["sheets"],
995
- Object.assign({}, this.workbook.sheets, { [newSheet.id]: newSheet })
996
- );
997
-
998
- const sheetIds = Object.assign({}, this.sheetIds);
999
- sheetIds[newSheet.name] = newSheet.id;
1000
- this.history.updateLocalState(["sheetIds"], sheetIds);
1001
- this.dispatch("ACTIVATE_SHEET", { from: this.workbook.activeSheet.id, to: toId });
1002
- }
1003
-
1004
- private interactiveDeleteSheet(sheetId: string) {
1005
- this.ui.askConfirmation(_lt("Are you sure you want to delete this sheet ?"), () => {
1006
- this.dispatch("DELETE_SHEET", { sheet: sheetId });
1007
- });
1008
- }
1009
-
1010
- private deleteSheet(sheetId: string) {
1011
- const name = this.workbook.sheets[sheetId].name;
1012
- const sheets = Object.assign({}, this.workbook.sheets);
1013
- delete sheets[sheetId];
1014
- this.history.updateState(["sheets"], sheets);
1015
-
1016
- const visibleSheets = this.workbook.visibleSheets.slice();
1017
- const currentIndex = visibleSheets.findIndex((id) => id === sheetId);
1018
- visibleSheets.splice(currentIndex, 1);
1019
- this.history.updateState(["visibleSheets"], visibleSheets);
1020
-
1021
- const sheetIds = Object.assign({}, this.sheetIds);
1022
- delete sheetIds[name];
1023
- this.history.updateLocalState(["sheetIds"], sheetIds);
1024
- this.visitAllFormulasSymbols((value: string) => {
1025
- let [, sheetRef] = value.split("!").reverse();
1026
- if (sheetRef) {
1027
- sheetRef = getUnquotedSheetName(sheetRef);
1028
- if (sheetRef === name) {
1029
- return "#REF";
1030
- }
1031
- }
1032
- return value;
1033
- });
1034
- if (this.getActiveSheet() === sheetId) {
1035
- this.dispatch("ACTIVATE_SHEET", {
1036
- from: sheetId,
1037
- to: visibleSheets[Math.max(0, currentIndex - 1)],
1038
- });
1039
- }
1040
- }
1041
-
1042
- private clearZones(sheet: string, zones: Zone[]) {
1043
- // TODO: get cells from the actual sheet
1044
- const cells = this.workbook.activeSheet.cells;
1045
- for (let zone of zones) {
1046
- for (let col = zone.left; col <= zone.right; col++) {
1047
- for (let row = zone.top; row <= zone.bottom; row++) {
1048
- const xc = toXC(col, row);
1049
- if (xc in cells) {
1050
- this.dispatch("UPDATE_CELL", {
1051
- sheet,
1052
- content: "",
1053
- col,
1054
- row,
1055
- });
1056
- }
1057
- }
1058
- }
1059
- }
1060
- }
1061
-
1062
- // ---------------------------------------------------------------------------
1063
- // Cols/Rows addition/deletion offsets manipulation
1064
- // ---------------------------------------------------------------------------
1065
-
1066
- /**
1067
- * Update a reference by applying an offset to the column
1068
- *
1069
- * @param ref Reference to update
1070
- * @param sheet Id of the sheet, if cross-sheet reference
1071
- * @param base Index of the element added/removed
1072
- * @param step Number of elements added or -1 if removed
1073
- */
1074
- private updateColumnsRef = (
1075
- ref: string,
1076
- sheet: string | undefined,
1077
- base: number,
1078
- step: number
1079
- ): string => {
1080
- let x = toCartesian(ref)[0];
1081
- if (x === base && step === -1) {
1082
- return "#REF";
1083
- }
1084
- return this.updateReference(ref, x > base ? step : 0, 0, this.getSheetIdByName(sheet), false);
1085
- };
1086
-
1087
- /**
1088
- * Update a full range by appling an offset.
1089
- *
1090
- * @param ref Reference to update
1091
- * @param sheet Id of the sheet, if cross-sheet reference
1092
- * @param base Index of the element added/removed
1093
- * @param step Number of elements added or -1 if removed
1094
- */
1095
- private updateColumnsRange = (
1096
- ref: string,
1097
- sheet: string | undefined,
1098
- base: number,
1099
- step: number
1100
- ): string => {
1101
- const currentZone = toZone(ref);
1102
- const recomputedRefs =
1103
- step < 0
1104
- ? recomputeZones(
1105
- [ref],
1106
- [this.zoneToXC({ ...currentZone, left: base + step + 1, right: base })]
1107
- )
1108
- : [ref];
1109
-
1110
- if (recomputedRefs.length === 0) {
1111
- return "#REF";
1112
- }
1113
-
1114
- const leftZone = toZone(recomputedRefs[0]);
1115
-
1116
- const rightZone =
1117
- step < 0 && currentZone.left < base + step + 1 && base < currentZone.right
1118
- ? toZone(recomputedRefs[1])
1119
- : leftZone;
1120
-
1121
- const left = base < leftZone.left ? leftZone.left + step : leftZone.left;
1122
- const right = base < rightZone.right ? rightZone.right + step : rightZone.right;
1123
-
1124
- const [leftXc, rightXc] = ref.split(":");
1125
- const sheetId = this.getters.getSheetIdByName(sheet);
1126
- const newLeft = this.updateReference(leftXc, left - currentZone.left, 0, sheetId, false, false);
1127
- const newRight = this.updateReference(
1128
- rightXc,
1129
- right - currentZone.right,
1130
- 0,
1131
- sheetId,
1132
- false,
1133
- false
1134
- );
1135
- if (newLeft === newRight) {
1136
- return newLeft;
1137
- }
1138
- //As updateReference put the sheet in the ref, we need to remove it from the right part
1139
- return `${newLeft}:${newRight.split("!").pop()!}`;
1140
- };
1141
-
1142
- /**
1143
- * Update a reference by applying an offset to the row
1144
- *
1145
- * @param ref Reference to update
1146
- * @param sheet Id of the sheet, if cross-sheet reference
1147
- * @param base Index of the element added/removed
1148
- * @param step Number of elements added or -1 if removed
1149
- */
1150
- private updateRowsRef = (
1151
- ref: string,
1152
- sheet: string | undefined,
1153
- base: number,
1154
- step: number
1155
- ): string => {
1156
- let y = toCartesian(ref)[1];
1157
- if (base + step < y && y <= base) {
1158
- return "#REF";
1159
- }
1160
- return this.updateReference(ref, 0, y > base ? step : 0, this.getSheetIdByName(sheet), false);
1161
- };
1162
-
1163
- /**
1164
- * Update a full range by appling an offset.
1165
- *
1166
- * @param ref Reference to update
1167
- * @param sheet Id of the sheet, if cross-sheet reference
1168
- * @param base Index of the element added/removed
1169
- * @param step Number of elements added/removed (negative when removed)
1170
- */
1171
- private updateRowsRange = (
1172
- ref: string,
1173
- sheet: string | undefined,
1174
- base: number,
1175
- step: number
1176
- ): string => {
1177
- const currentZone = toZone(ref);
1178
- // base+step+1 === left element of the zone to remove
1179
- const recomputedRefs =
1180
- step < 0
1181
- ? recomputeZones(
1182
- [ref],
1183
- [this.zoneToXC({ ...currentZone, top: base + step + 1, bottom: base })]
1184
- )
1185
- : [ref];
1186
-
1187
- if (recomputedRefs.length === 0) {
1188
- return "#REF";
1189
- }
1190
-
1191
- const topZone = toZone(recomputedRefs[0]);
1192
-
1193
- // base+step+1 === top element of the zone to remove
1194
- const bottomZone =
1195
- step < 0 && currentZone.top < base + step + 1 && base < currentZone.bottom
1196
- ? toZone(recomputedRefs[1])
1197
- : topZone;
1198
-
1199
- const top = base < topZone.top ? topZone.top + step : topZone.top;
1200
- const bottom = base < bottomZone.bottom ? bottomZone.bottom + step : bottomZone.bottom;
1201
-
1202
- const [left, right] = ref.split(":");
1203
- const sheetId = this.getters.getSheetIdByName(sheet);
1204
- const newLeft = this.updateReference(left, 0, top - currentZone.top, sheetId, false, false);
1205
- const newRight = this.updateReference(
1206
- right,
1207
- 0,
1208
- bottom - currentZone.bottom,
1209
- sheetId,
1210
- false,
1211
- false
1212
- );
1213
- if (newLeft === newRight) {
1214
- return newLeft;
1215
- }
1216
- //As updateReference put the sheet in the ref, we need to remove it from the right part
1217
- return `${newLeft}:${newRight.split("!").pop()!}`;
1218
- };
1219
-
1220
- // ---------------------------------------------------------------------------
1221
- // Helpers
1222
- // ---------------------------------------------------------------------------
1223
-
1224
- /**
1225
- * Update a range with some offsets
1226
- */
1227
- private updateRange(
1228
- symbol: string,
1229
- offsetX: number,
1230
- offsetY: number,
1231
- sheetId: string | undefined
1232
- ): string {
1233
- let [left, right] = symbol.split(":");
1234
- left = this.updateReference(left, offsetX, offsetY, sheetId, true, false);
1235
- right = this.updateReference(right, offsetX, offsetY, sheetId, true, false);
1236
- if (left === "#REF" || right === "#REF") {
1237
- return "#REF";
1238
- }
1239
- //As updateReference put the sheet in the ref, we need to remove it from the right part
1240
- right = right.split("!").pop()!;
1241
- return `${left}:${right}`;
1242
- }
1243
-
1244
- /**
1245
- * Update a reference with some offsets.
1246
- */
1247
- private updateReference(
1248
- symbol: string,
1249
- offsetX: number,
1250
- offsetY: number,
1251
- sheetId: string | undefined,
1252
- updateFreeze: boolean = true,
1253
- checkSheetBoundary: boolean = true
1254
- ): string {
1255
- const xc = symbol.replace(/\$/g, "");
1256
- let [x, y] = toCartesian(xc);
1257
- const freezeCol = symbol.startsWith("$");
1258
- const freezeRow = symbol.includes("$", 1);
1259
- x += freezeCol && updateFreeze ? 0 : offsetX;
1260
- y += freezeRow && updateFreeze ? 0 : offsetY;
1261
- if (
1262
- x < 0 ||
1263
- y < 0 ||
1264
- (checkSheetBoundary &&
1265
- (x >= this.getters.getNumberCols(sheetId || this.getters.getActiveSheet()) ||
1266
- y >= this.getters.getNumberRows(sheetId || this.getters.getActiveSheet())))
1267
- ) {
1268
- return "#REF";
1269
- }
1270
- const sheetName = sheetId && getComposerSheetName(this.getters.getSheetName(sheetId)!);
1271
- return (
1272
- (sheetName ? `${sheetName}!` : "") +
1273
- (freezeCol ? "$" : "") +
1274
- numberToLetters(x) +
1275
- (freezeRow ? "$" : "") +
1276
- String(y + 1)
1277
- );
1278
- }
1279
-
1280
- private visitAllFormulasSymbols(cb: (value: string, sheetId: string) => string) {
1281
- for (let sheetId in this.workbook.sheets) {
1282
- const sheet = this.workbook.sheets[sheetId];
1283
- for (let [xc, cell] of Object.entries(sheet.cells)) {
1284
- if (cell.type === "formula") {
1285
- const content = rangeTokenize(cell.content!)
1286
- .map((t) => {
1287
- if (t.type === "SYMBOL" && cellReference.test(t.value)) {
1288
- return cb(t.value, sheet.id);
1289
- }
1290
- return t.value;
1291
- })
1292
- .join("");
1293
- if (content !== cell.content) {
1294
- const [col, row] = toCartesian(xc);
1295
- this.dispatch("UPDATE_CELL", {
1296
- sheet: sheet.id,
1297
- col,
1298
- row,
1299
- content,
1300
- });
1301
- }
1302
- }
1303
- }
1304
- }
1305
- }
1306
-
1307
- /**
1308
- * Apply a function to update the formula on every cells of every sheets which
1309
- * contains a formula
1310
- * @param cb Update formula function to apply
1311
- */
1312
- private visitFormulas(
1313
- sheetNameToFind: string,
1314
- cb: (value: string, sheet: string | undefined) => string
1315
- ) {
1316
- this.visitAllFormulasSymbols((content: string, sheetId: string): string => {
1317
- let [value, sheetRef] = content.split("!").reverse();
1318
- if (sheetRef) {
1319
- sheetRef = getUnquotedSheetName(sheetRef);
1320
- if (sheetRef === sheetNameToFind) {
1321
- return cb(value, sheetRef);
1322
- }
1323
- } else if (this.sheetIds[sheetNameToFind] === sheetId) {
1324
- return cb(value, undefined);
1325
- }
1326
- return content;
1327
- });
1328
- }
1329
-
1330
- // ---------------------------------------------------------------------------
1331
- // Import/Export
1332
- // ---------------------------------------------------------------------------
1333
-
1334
- import(data: WorkbookData) {
1335
- // we need to fill the sheetIds mapping first, because otherwise formulas
1336
- // that depends on a sheet not already imported will not be able to be
1337
- // compiled
1338
- for (let sheet of data.sheets) {
1339
- this.sheetIds[sheet.name] = sheet.id;
1340
- }
1341
-
1342
- for (let sheet of data.sheets) {
1343
- this.importSheet(sheet);
1344
- }
1345
- this.workbook.activeSheet = this.workbook.sheets[data.activeSheet];
1346
- }
1347
-
1348
- importSheet(data: SheetData) {
1349
- let { sheets, visibleSheets } = this.workbook;
1350
- const name = data.name || `Sheet${Object.keys(sheets).length + 1}`;
1351
- const { colNumber, rowNumber } = getSheetSize(data);
1352
- const sheet: Sheet = {
1353
- id: data.id,
1354
- name: name,
1355
- cells: {},
1356
- colNumber,
1357
- rowNumber,
1358
- cols: createCols(data.cols || {}, colNumber),
1359
- rows: createRows(data.rows || {}, rowNumber),
1360
- merges: {},
1361
- mergeCellMap: {},
1362
- };
1363
- visibleSheets = visibleSheets.slice();
1364
- visibleSheets.push(sheet.id);
1365
- this.history.updateState(["visibleSheets"], visibleSheets);
1366
- this.history.updateState(["sheets"], Object.assign({}, sheets, { [sheet.id]: sheet }));
1367
- // cells
1368
- for (let xc in data.cells) {
1369
- const cell = data.cells[xc];
1370
- const [col, row] = toCartesian(xc);
1371
- this.updateCell(data.id, col, row, cell);
1372
- }
1373
- }
1374
-
1375
- export(data: WorkbookData) {
1376
- data.sheets = this.workbook.visibleSheets.map((id) => {
1377
- const sheet = this.workbook.sheets[id];
1378
- const cells: { [key: string]: CellData } = {};
1379
- for (let [key, cell] of Object.entries(sheet.cells)) {
1380
- cells[key] = {
1381
- content: cell.content,
1382
- border: cell.border,
1383
- style: cell.style,
1384
- format: cell.format,
1385
- };
1386
- }
1387
- return {
1388
- id: sheet.id,
1389
- name: sheet.name,
1390
- colNumber: sheet.colNumber,
1391
- rowNumber: sheet.rowNumber,
1392
- rows: exportRows(sheet.rows),
1393
- cols: exportCols(sheet.cols),
1394
- merges: [], //exportMerges(sheet.merges),
1395
- cells: cells,
1396
- conditionalFormats: [],
1397
- figures: [],
1398
- };
1399
- });
1400
- data.activeSheet = this.workbook.activeSheet.id;
1401
- }
1402
- }
1403
-
1404
- function getSheetSize(data: SheetData): { rowNumber: number; colNumber: number } {
1405
- const positions = Object.keys(data.cells).map(toCartesian);
1406
- return {
1407
- rowNumber: Math.max(data.rowNumber, ...new Set(positions.map(([col, row]) => row + 1))),
1408
- colNumber: Math.max(data.colNumber, ...new Set(positions.map(([col, row]) => col + 1))),
1409
- };
1410
- }
1411
-
1412
- function createDefaultCols(colNumber: number): Col[] {
1413
- const cols: Col[] = [];
1414
- let current = 0;
1415
- for (let i = 0; i < colNumber; i++) {
1416
- const size = DEFAULT_CELL_WIDTH;
1417
- const col = {
1418
- start: current,
1419
- end: current + size,
1420
- size: size,
1421
- name: numberToLetters(i),
1422
- };
1423
- cols.push(col);
1424
- current = col.end;
1425
- }
1426
- return cols;
1427
- }
1428
-
1429
- function createDefaultRows(rowNumber: number): Row[] {
1430
- const rows: Row[] = [];
1431
- let current = 0;
1432
- for (let i = 0; i < rowNumber; i++) {
1433
- const size = DEFAULT_CELL_HEIGHT;
1434
- const row = {
1435
- start: current,
1436
- end: current + size,
1437
- size: size,
1438
- name: String(i + 1),
1439
- cells: {},
1440
- };
1441
- rows.push(row);
1442
- current = row.end;
1443
- }
1444
- return rows;
1445
- }
1446
-
1447
- function createCols(savedCols: { [key: number]: HeaderData }, colNumber: number): Col[] {
1448
- const cols: Col[] = [];
1449
- let current = 0;
1450
- for (let i = 0; i < colNumber; i++) {
1451
- const size = savedCols[i] ? savedCols[i].size || DEFAULT_CELL_WIDTH : DEFAULT_CELL_WIDTH;
1452
- const col = {
1453
- start: current,
1454
- end: current + size,
1455
- size: size,
1456
- name: numberToLetters(i),
1457
- };
1458
- cols.push(col);
1459
- current = col.end;
1460
- }
1461
- return cols;
1462
- }
1463
-
1464
- function createRows(savedRows: { [key: number]: HeaderData }, rowNumber: number): Row[] {
1465
- const rows: Row[] = [];
1466
- let current = 0;
1467
- for (let i = 0; i < rowNumber; i++) {
1468
- const size = savedRows[i] ? savedRows[i].size || DEFAULT_CELL_HEIGHT : DEFAULT_CELL_HEIGHT;
1469
- const row = {
1470
- start: current,
1471
- end: current + size,
1472
- size: size,
1473
- name: String(i + 1),
1474
- cells: {},
1475
- };
1476
- rows.push(row);
1477
- current = row.end;
1478
- }
1479
- return rows;
1480
- }
1481
-
1482
- function exportCols(cols: Col[]): { [key: number]: HeaderData } {
1483
- const exportedCols: { [key: number]: HeaderData } = {};
1484
- for (let i in cols) {
1485
- const col = cols[i];
1486
- if (col.size !== DEFAULT_CELL_WIDTH) {
1487
- exportedCols[i] = { size: col.size };
1488
- }
1489
- }
1490
- return exportedCols;
1491
- }
1492
-
1493
- function exportRows(rows: Row[]): { [key: number]: HeaderData } {
1494
- const exportedRows: { [key: number]: HeaderData } = {};
1495
- for (let i in rows) {
1496
- const row = rows[i];
1497
- if (row.size !== DEFAULT_CELL_HEIGHT) {
1498
- exportedRows[i] = { size: row.size };
1499
- }
1500
- }
1501
- return exportedRows;
1502
- }