@trebco/treb 25.9.1 → 26.0.5

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.
@@ -100,7 +100,7 @@ export interface ClickFunctionResult {
100
100
  export type ClickFunction = (options: ClickFunctionOptions) => ClickFunctionResult;
101
101
 
102
102
 
103
- /**
103
+ /*
104
104
  * restructuring from the old system, which had lots of separate arrays for
105
105
  * things. for the most part I think having a single array (or object) with
106
106
  * objects will be more useful (if not necessarily more efficient). the
@@ -121,11 +121,11 @@ export type ClickFunction = (options: ClickFunctionOptions) => ClickFunctionResu
121
121
  * validation TODO: date, number, boolean, &c
122
122
  */
123
123
  export enum ValidationType {
124
- List,
125
- Date,
126
- Range,
127
- Number,
128
- Boolean,
124
+ List = 'list',
125
+ Date = 'date',
126
+ Range = 'range',
127
+ Number = 'number',
128
+ Boolean = 'boolean',
129
129
  }
130
130
 
131
131
  export interface DataValidationBase {
@@ -133,25 +133,25 @@ export interface DataValidationBase {
133
133
  }
134
134
 
135
135
  export interface DataValidationRange extends DataValidationBase {
136
- type: ValidationType.Range;
136
+ type: 'range'; // ValidationType.Range;
137
137
  area: IArea;
138
138
  }
139
139
 
140
140
  export interface DataValidationList extends DataValidationBase {
141
- type: ValidationType.List;
141
+ type: 'list'; // ValidationType.List;
142
142
  list: CellValue[];
143
143
  }
144
144
 
145
145
  export interface DataValidationDate extends DataValidationBase {
146
- type: ValidationType.Date;
146
+ type: 'date'; // ValidationType.Date;
147
147
  }
148
148
 
149
149
  export interface DataValidationNumber extends DataValidationBase {
150
- type: ValidationType.Number;
150
+ type: 'number'; // ValidationType.Number;
151
151
  }
152
152
 
153
153
  export interface DataValidationBoolean extends DataValidationBase {
154
- type: ValidationType.Boolean;
154
+ type: 'boolean'; // ValidationType.Boolean;
155
155
  }
156
156
 
157
157
  export type DataValidation
@@ -29,7 +29,7 @@ import { Area, IsCellAddress } from './area';
29
29
  import type { DataValidation } from './cell';
30
30
  import { Cell } from './cell';
31
31
  import type { Table } from './table';
32
- import { ValueType, GetValueType } from './value-type';
32
+ import { type SerializedValueType, ValueType, GetValueType, ValueTypeList } from './value-type';
33
33
  import type { CellValue, UnionValue } from './union';
34
34
  import type { Style } from './style';
35
35
 
@@ -80,10 +80,10 @@ export interface BaseCellData {
80
80
  area?: IArea;
81
81
  merge_area?: IArea;
82
82
  validation?: DataValidation;
83
- calculated_type?: ValueType;
83
+ calculated_type?: SerializedValueType; // ValueType;
84
84
  note?: string;
85
85
  hyperlink?: string;
86
- type?: ValueType;
86
+ type?: SerializedValueType; // ValueType;
87
87
  sheet_id?: number;
88
88
  // locked?: boolean;
89
89
  }
@@ -132,6 +132,17 @@ export const IsNestedRowArray = (test: NestedRowData[]|NestedColumnData[]): test
132
132
 
133
133
  // ...
134
134
 
135
+
136
+ /**
137
+ * this is the reverse map, i.e. type => number
138
+ * FIXME: why is this getting exported by the API generator?
139
+ * FIXME: I get why it's dynamic, but for practical purposes why not just
140
+ * create a static map?
141
+ */
142
+ const ValueTypeMap =
143
+ ValueTypeList.map((key, index) => ({ [key]: index })).reduce((set, value) => ({...set, ...value}), {}) as Record<SerializedValueType, ValueType>;
144
+
145
+
135
146
  /**
136
147
  * collection of cells, basically a wrapper around an
137
148
  * array, with some accessor and control methods.
@@ -357,6 +368,53 @@ export class Cells {
357
368
  this.columns_ = columns;
358
369
  }
359
370
 
371
+ public SerializedTypeToValueType(type?: SerializedValueType|ValueType): ValueType|undefined {
372
+
373
+ if (!type) {
374
+ return undefined;
375
+ }
376
+
377
+ if (typeof type === 'number') {
378
+ return type as ValueType;
379
+ }
380
+
381
+ return ValueTypeMap[type] || undefined;
382
+
383
+ }
384
+
385
+ public ValueTypeToSerializedType(type?: ValueType): SerializedValueType|undefined {
386
+ return type ? ValueTypeList[type] : undefined;
387
+ }
388
+
389
+ /**
390
+ * this method is used for importing legacy data validation types. in those
391
+ * those we used a numeric enum. we're just dropping that altogether (c.f.
392
+ * ValueType, which we're keeping) so we need to translate for backcompat.
393
+ * it's ugly, but it gets us to a better place. we can probably drop at some
394
+ * point in the future.
395
+ *
396
+ * export enum ValidationType {
397
+ * List = 'list',
398
+ * Date = 'date',
399
+ * Range = 'range',
400
+ * Number = 'number',
401
+ * Boolean = 'boolean',
402
+ * }
403
+ *
404
+ */
405
+ public ImportDataValidation(value: DataValidation): DataValidation|undefined {
406
+
407
+ if (typeof (value as any).type === 'number') {
408
+ const types = ['list', 'date', 'range', 'number', 'boolean'];
409
+ (value as any).type = types[(value as any).type];
410
+ if (!value.type) {
411
+ return undefined;
412
+ }
413
+ }
414
+
415
+ return value;
416
+ }
417
+
360
418
  /**
361
419
  * UPDATE: adding optional style refs, for export
362
420
  */
@@ -423,7 +481,7 @@ export class Cells {
423
481
  if (typeof obj.calculated !== 'undefined') {
424
482
  // cell.calculated = obj.calculated;
425
483
  // cell.calculated_type = obj.calculated_type;
426
- cell.SetCalculatedValue(obj.calculated, obj.calculated_type);
484
+ cell.SetCalculatedValue(obj.calculated, this.SerializedTypeToValueType(obj.calculated_type));
427
485
  }
428
486
 
429
487
  if (style_refs) {
@@ -494,7 +552,13 @@ export class Cells {
494
552
  }
495
553
 
496
554
  if (obj.validation) {
497
- cell.validation = obj.validation;
555
+
556
+ // the old type used a numeric enum. we just dropped that in favor
557
+ // of a string enum, so we can export it as a type. but for backwards
558
+ // compatibility we still need to think about the numeric enum.
559
+
560
+ cell.validation = this.ImportDataValidation(obj.validation);
561
+
498
562
  }
499
563
 
500
564
  }
@@ -606,7 +670,9 @@ export class Cells {
606
670
  obj.hyperlink = cell.hyperlink;
607
671
  }
608
672
 
609
- if (options.preserve_type) obj.type = cell.type;
673
+ if (options.preserve_type) {
674
+ obj.type = this.ValueTypeToSerializedType(cell.type);
675
+ }
610
676
  if (options.sheet_id) obj.sheet_id = options.sheet_id;
611
677
  if (options.calculated_value &&
612
678
  typeof cell.calculated !== 'undefined') { // && cell.calculated_type !== ValueType.error) {
@@ -614,7 +680,7 @@ export class Cells {
614
680
 
615
681
  // always preserve error type, because we can't infer
616
682
  if (options.preserve_type || cell.calculated_type === ValueType.error) {
617
- obj.calculated_type = cell.calculated_type;
683
+ obj.calculated_type = this.ValueTypeToSerializedType(cell.calculated_type);
618
684
  }
619
685
  }
620
686
  if (cell.table && table_head) {
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import type { Style } from './style';
23
- import type { ValueType } from './value-type';
23
+ import type { SerializedValueType, ValueType } from './value-type';
24
24
  import type { IArea } from './area';
25
25
  import type { AnnotationLayout } from './layout';
26
26
  import type { DataValidation } from './cell';
@@ -30,10 +30,10 @@ import type { AnnotationType } from 'treb-grid';
30
30
  export interface CellParseResult {
31
31
  row: number,
32
32
  column: number,
33
- type: ValueType,
33
+ type: SerializedValueType; // ValueType,
34
34
  value: number|string|undefined|boolean,
35
35
  calculated?: number|string|undefined|boolean,
36
- calculated_type?: ValueType,
36
+ calculated_type?: SerializedValueType; // ValueType,
37
37
  style_ref?: number,
38
38
  hyperlink?: string,
39
39
  validation?: DataValidation,
@@ -84,12 +84,44 @@ export const IsDimensionedQuantity = (value: unknown): value is DimensionedQuant
84
84
  && (typeof (value as DimensionedQuantity).unit === 'string');
85
85
  };
86
86
 
87
+ /**
88
+ * this is the list of value types. internally, we use an enum. I don't
89
+ * want to change that, at least not at the moment, but that presents a
90
+ * problem for exporting types.
91
+ *
92
+ * we'll switch to string types for import/export, although we still support
93
+ * importing the old numeric enum types for backwards compatibility.
94
+ */
95
+ export const ValueTypeList = [
96
+ 'undefined',
97
+ 'formula',
98
+ 'string',
99
+ 'number',
100
+ 'boolean',
101
+ 'object',
102
+ 'error',
103
+ 'complex',
104
+ 'array',
105
+ 'dimensioned_quantity',
106
+ ] as const;
107
+
87
108
  /**
88
- * I _think_ using enums is faster. I'm not actually sure about that, though.
89
- * it stands to reason that a single int compare is faster than a string
90
- * compare, but you never know with javascript. undefined preferred over null.
91
- * formula implies a string.
92
- *
109
+ * string types for import/export
110
+ */
111
+ export type SerializedValueType = typeof ValueTypeList[number];
112
+
113
+ /**
114
+ * this enum goes back a long way and is pretty ingrained, so I don't
115
+ * want to change it (at least not right now). but if we're exporting types,
116
+ * using enums is a problem.
117
+ *
118
+ * what we will do is keep the enum internally but switch the exported type
119
+ * to a string. the problem then becomes keeping the types matched up
120
+ * properly. I can't arrive at a good way of doing that automatically.
121
+ *
122
+ * old comments:
123
+ * ---
124
+ *
93
125
  * undefined is 0 so we can test it as falsy.
94
126
  *
95
127
  * we're passing this type information out to calculators, so it needs
@@ -46,23 +46,61 @@ export enum SaveFileType {
46
46
  * I would like to do it, though, that `any` looks bad in the public API.
47
47
  */
48
48
  export interface TREBDocument {
49
+
50
+ /** app name, as identifier */
49
51
  app: string;
52
+
53
+ /** app version. we'll warn if you use a file from a newer version */
50
54
  version: string;
55
+
56
+ /**
57
+ * revision number. this is a value that increments on any document change,
58
+ * useful for checking if a document is "dirty".
59
+ */
51
60
  revision?: number;
61
+
62
+ /** document name */
52
63
  name?: string;
64
+
65
+ /**
66
+ * opaque user data. we don't read or parse this, but applications can
67
+ * use it to store arbitrary data.
68
+ */
53
69
  user_data?: any;
54
- sheet_data?: SerializedSheet|SerializedSheet[]; // NOTE: support old version, but it would be nice to drop
70
+
71
+ /**
72
+ * per-sheet data. this should be an array, but for historical reasons
73
+ * we still support a single sheet outside of an array.
74
+ */
75
+ sheet_data?: SerializedSheet|SerializedSheet[];
76
+
77
+ /** document decimal mark */
55
78
  decimal_mark?: '.' | ',';
79
+
80
+ /** active sheet. if unset we'll show the first un-hidden sheet */
56
81
  active_sheet?: number;
82
+
83
+ /**
84
+ * this document includes rendered calculated values. using this lets the
85
+ * app show a document faster, without requiring an initial calculation.
86
+ */
57
87
  rendered_values?: boolean;
58
88
 
59
- // named_ranges?: {[index: string]: IArea};
89
+ /** document named ranges */
60
90
  named_ranges?: Record<string, IArea>;
61
91
 
62
- macro_functions?: SerializedMacroFunction[];
92
+ /** document named expressions */
63
93
  named_expressions?: SerializedNamedExpression[];
94
+
95
+ /** document macro functions */
96
+ macro_functions?: SerializedMacroFunction[];
97
+
98
+ /** document tables */
64
99
  tables?: Table[];
100
+
101
+ /** document shared resources (usually images) */
65
102
  shared_resources?: Record<string, string>;
103
+
66
104
  }
67
105
 
68
106
  export interface ResizeEvent {
@@ -510,6 +510,66 @@ export class Exporter {
510
510
  }
511
511
  */
512
512
 
513
+ /**
514
+ * FIXME: we might not always need this.
515
+ */
516
+ public SheetStyle(sheet: SerializedSheet, style_cache: StyleCache) {
517
+
518
+ if (!sheet.sheet_style) {
519
+ return 0;
520
+ }
521
+
522
+ const options = style_cache.StyleOptionsFromProperties(sheet.sheet_style);
523
+ return style_cache.EnsureStyle(options);
524
+
525
+ }
526
+
527
+ public RowStyle(sheet: SerializedSheet, style_cache: StyleCache, row: number) {
528
+
529
+ const cell_style_refs = sheet.styles || sheet.cell_style_refs || [];
530
+ const list: Style.Properties[] = [sheet.sheet_style];
531
+
532
+ if (sheet.row_style) {
533
+ let style = sheet.row_style[row];
534
+ if (typeof style === 'number') {
535
+ style = cell_style_refs[style];
536
+ if (style) {
537
+ list.push(style);
538
+ }
539
+ }
540
+ else if (style) {
541
+ list.push(style);
542
+ }
543
+ }
544
+
545
+ const options = style_cache.StyleOptionsFromProperties(Style.Composite(list));
546
+ return style_cache.EnsureStyle(options);
547
+
548
+ }
549
+
550
+ public ColumnStyle(sheet: SerializedSheet, style_cache: StyleCache, column: number) {
551
+
552
+ const cell_style_refs = sheet.styles || sheet.cell_style_refs || [];
553
+ const list: Style.Properties[] = [sheet.sheet_style];
554
+
555
+ if (sheet.column_style) {
556
+ let style = sheet.column_style[column];
557
+ if (typeof style === 'number') {
558
+ style = cell_style_refs[style];
559
+ if (style) {
560
+ list.push(style);
561
+ }
562
+ }
563
+ else if (style) {
564
+ list.push(style);
565
+ }
566
+ }
567
+
568
+ const options = style_cache.StyleOptionsFromProperties(Style.Composite(list));
569
+ return style_cache.EnsureStyle(options);
570
+
571
+ }
572
+
513
573
  public StyleFromCell(sheet: SerializedSheet, style_cache: StyleCache, row: number, column: number, style: Style.Properties = {}) {
514
574
 
515
575
  //if (row === 2 && column === 5)
@@ -1205,6 +1265,8 @@ export class Exporter {
1205
1265
  const cells = new Cells();
1206
1266
  cells.FromJSON(sheet.data, cell_style_refs);
1207
1267
 
1268
+ // console.info({ss: sheet.sheet_style, sheet});
1269
+
1208
1270
  // these are cells with style but no contents
1209
1271
 
1210
1272
  for (const entry of sheet.cell_styles) {
@@ -1249,7 +1311,20 @@ export class Exporter {
1249
1311
 
1250
1312
  // --
1251
1313
 
1314
+ //
1315
+ // this is a map of column number -> column style. we need this
1316
+ // for two things: (1) so we can skip cells that are empty, but
1317
+ // have a style from the column; and (2) so we can create the list
1318
+ // of columns, including styles.
1319
+ //
1320
+ const column_style_map: number[] = [];
1321
+
1322
+ const sheet_style = this.SheetStyle(sheet, style_cache);
1323
+
1252
1324
  for (let r = 0; r < cells.data.length; r++ ) {
1325
+
1326
+ const row_style = this.RowStyle(sheet, style_cache, r);
1327
+
1253
1328
  if (cells.data[r] && cells.data[r].length) {
1254
1329
 
1255
1330
  // push out the extent (reversed)
@@ -1262,6 +1337,11 @@ export class Exporter {
1262
1337
  const row: any = [];
1263
1338
 
1264
1339
  for (let c = 0; c < cells.data[r].length; c++) {
1340
+
1341
+ if (!column_style_map[c]) {
1342
+ column_style_map[c] = this.ColumnStyle(sheet, style_cache, c);
1343
+ }
1344
+
1265
1345
  const cell = cells.data[r][c];
1266
1346
  if (cell) {
1267
1347
 
@@ -1447,6 +1527,17 @@ export class Exporter {
1447
1527
  // s is style, index into the style table
1448
1528
  const s: number|undefined = this.StyleFromCell(sheet, style_cache, r, c, cell.style);
1449
1529
 
1530
+ if (cell.type === ValueType.undefined) {
1531
+
1532
+ // you can skip if (1) there's a row style, and style === row style;
1533
+ // (2) there's a column style, no row style, and style === column style
1534
+
1535
+ if ((row_style && s === row_style) ||
1536
+ (!row_style && (column_style_map[c] && s === column_style_map[c]))) {
1537
+ continue; // can skip
1538
+ }
1539
+ }
1540
+
1450
1541
  // v (child element) is the value
1451
1542
  let v: CellValue = undefined;
1452
1543
  let t: string|undefined;
@@ -1516,7 +1607,13 @@ export class Exporter {
1516
1607
  element.a$.t = t;
1517
1608
  }
1518
1609
  if (s !== undefined) {
1610
+
1611
+ // we could skip this if it's equal to row style,
1612
+ // or there is no row style and it's equal to column style
1613
+ // or there is no column style and it's equal to sheet style
1614
+
1519
1615
  element.a$.s = s;
1616
+
1520
1617
  }
1521
1618
  if (f !== undefined) {
1522
1619
  element.f = f;
@@ -1530,11 +1627,12 @@ export class Exporter {
1530
1627
  }
1531
1628
  }
1532
1629
 
1533
- if (row.length) {
1630
+ if (row.length || (row_style && row_style !== sheet_style)) {
1631
+
1534
1632
  const row_data: any = {
1535
1633
  a$: {
1536
1634
  r: r + 1,
1537
- spans: `${span.start + 1}:${span.end + 1}`,
1635
+ spans: `${span.start + 1}:${span.end + 1}`, // this works out to 0:0 for an empty row, will that work?
1538
1636
  },
1539
1637
  c: row,
1540
1638
  };
@@ -1546,6 +1644,11 @@ export class Exporter {
1546
1644
  row_data.a$.ht = sheet.row_height[r] * 3 / 4;
1547
1645
  }
1548
1646
 
1647
+ if (row_style && row_style !== sheet_style) {
1648
+ row_data.a$.s = row_style;
1649
+ row_data.a$.customFormat = 1;
1650
+ }
1651
+
1549
1652
  sheet_data.row.push(row_data);
1550
1653
  }
1551
1654
 
@@ -1563,6 +1666,10 @@ export class Exporter {
1563
1666
  index: number;
1564
1667
  }> = [];
1565
1668
 
1669
+ // we only need to include column style if it's !== sheet style,
1670
+ // because we'll have a default entry for columns that have the
1671
+ // sheet style. this is only for columns that are different.
1672
+
1566
1673
  if (sheet.default_column_width) {
1567
1674
  dom.worksheet.sheetFormatPr.a$.defaultColWidth = // sheet.default_column_width * one_hundred_pixels / 100;
1568
1675
  PixelsToColumnWidth(sheet.default_column_width);
@@ -1582,7 +1689,14 @@ export class Exporter {
1582
1689
 
1583
1690
  }
1584
1691
 
1692
+ let style = column_style_map[c];
1693
+ if (style && style !== sheet_style) {
1694
+ entry.style = style;
1695
+ }
1696
+
1697
+ /*
1585
1698
  let style = sheet.column_style[c];
1699
+
1586
1700
  if (typeof style === 'number') {
1587
1701
  style = cell_style_refs[style];
1588
1702
  if (style) {
@@ -1592,6 +1706,7 @@ export class Exporter {
1592
1706
  else if (style) {
1593
1707
  entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(style));
1594
1708
  }
1709
+ */
1595
1710
 
1596
1711
  //if (sheet.column_style[c]) {
1597
1712
  // entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(sheet.column_style[c]));
@@ -1605,8 +1720,73 @@ export class Exporter {
1605
1720
  // we're short-cutting here, these should be arranged in blocks if
1606
1721
  // there's overlap. not sure how much of an issue that is though.
1607
1722
 
1608
- if (column_entries.length) {
1609
- for (const entry of column_entries) {
1723
+ if (column_entries.length || sheet_style) {
1724
+
1725
+ const filled: any[] = [];
1726
+ const default_column_width = PixelsToColumnWidth(sheet.default_column_width || 90);
1727
+
1728
+ // FIXME: can merge these two branches
1729
+
1730
+ { // if (sheet_style) {
1731
+
1732
+ let start_index = 0;
1733
+ for (const entry of column_entries) {
1734
+ if (!entry) { continue; }
1735
+
1736
+ // fill with defaults
1737
+
1738
+ if (sheet_style && (entry.index > start_index + 1)) {
1739
+ filled.push({
1740
+ a$: {
1741
+ min: start_index + 1,
1742
+ max: entry.index,
1743
+ style: sheet_style,
1744
+ width: default_column_width,
1745
+ },
1746
+ });
1747
+ }
1748
+
1749
+ const a$: any = {
1750
+ min: entry.index + 1,
1751
+ max: entry.index + 1,
1752
+ };
1753
+ if (entry.style === undefined) {
1754
+ a$.style = sheet_style;
1755
+ }
1756
+ else {
1757
+ a$.style = entry.style;
1758
+ }
1759
+ if (entry.width !== undefined) {
1760
+ a$.width = entry.width;
1761
+ a$.customWidth = 1;
1762
+ }
1763
+ else {
1764
+ a$.width = default_column_width;
1765
+ }
1766
+
1767
+ filled.push({a$});
1768
+
1769
+ start_index = entry.index;
1770
+
1771
+ }
1772
+
1773
+ if (sheet_style && (start_index < 16384)) { // OK, sure why not
1774
+ filled.push({
1775
+ a$: {
1776
+ min: start_index + 1,
1777
+ max: 16384,
1778
+ style: sheet_style,
1779
+ width: default_column_width,
1780
+ },
1781
+ });
1782
+ }
1783
+
1784
+ dom.worksheet.cols.col = filled;
1785
+
1786
+ }
1787
+
1788
+ /*
1789
+ else {
1610
1790
  dom.worksheet.cols.col = column_entries.map(entry => {
1611
1791
  const a$: any = {
1612
1792
  min: entry.index + 1,
@@ -1621,12 +1801,15 @@ export class Exporter {
1621
1801
  }
1622
1802
  else {
1623
1803
  a$.width = // (sheet.default_column_width || 100) / 100 * one_hundred_pixels;
1624
- PixelsToColumnWidth(sheet.default_column_width || 90);
1804
+ default_column_width; // PixelsToColumnWidth(sheet.default_column_width || 90);
1625
1805
  }
1626
1806
  return {a$};
1627
1807
  });
1628
1808
  }
1809
+ console.info({cols: dom.worksheet.cols});
1810
+ */
1629
1811
  }
1812
+
1630
1813
  else {
1631
1814
  delete dom.worksheet.cols;
1632
1815
  }