@trebco/treb 28.10.5 → 28.13.2
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.
- package/dist/treb-spreadsheet-light.mjs +12 -12
- package/dist/treb-spreadsheet.mjs +15 -15
- package/dist/treb.d.ts +9 -1
- package/package.json +1 -1
- package/treb-calculator/src/calculator.ts +235 -68
- package/treb-calculator/src/descriptors.ts +5 -0
- package/treb-calculator/src/functions/base-functions.ts +119 -7
- package/treb-calculator/src/functions/text-functions.ts +45 -55
- package/treb-calculator/src/primitives.ts +11 -0
- package/treb-calculator/src/utilities.ts +55 -0
- package/treb-charts/src/chart-utils.ts +24 -3
- package/treb-charts/src/default-chart-renderer.ts +10 -1
- package/treb-charts/src/renderer.ts +34 -3
- package/treb-charts/style/charts.scss +5 -0
- package/treb-embed/src/embedded-spreadsheet.ts +161 -27
- package/treb-embed/style/dark-theme.scss +1 -0
- package/treb-export/src/drawing2/bubble-chart-template.ts +553 -0
- package/treb-export/src/drawing2/chart2.ts +84 -1
- package/treb-export/src/export-worker/export-worker.ts +1 -0
- package/treb-export/src/export2.ts +49 -10
- package/treb-export/src/import2.ts +30 -3
- package/treb-export/src/workbook-style2.ts +78 -14
- package/treb-export/src/workbook2.ts +27 -4
- package/treb-grid/src/editors/editor.ts +7 -0
- package/treb-grid/src/types/grid.ts +20 -9
- package/treb-grid/src/types/grid_base.ts +10 -105
- package/treb-grid/src/types/serialize_options.ts +5 -0
- package/treb-parser/src/parser-types.ts +27 -4
- package/treb-parser/src/parser.ts +74 -36
|
@@ -714,6 +714,9 @@ export class Exporter {
|
|
|
714
714
|
/** overload for return type */
|
|
715
715
|
public NormalizeAddress(unit: UnitRange, sheet: SerializedSheet): UnitRange;
|
|
716
716
|
|
|
717
|
+
/** extra overload */
|
|
718
|
+
public NormalizeAddress<UNIT = UnitAddress|UnitRange>(unit: UNIT, sheet: SerializedSheet): UNIT;
|
|
719
|
+
|
|
717
720
|
/**
|
|
718
721
|
* for charts we need addresses to be absolute ($) and ensure there's
|
|
719
722
|
* a sheet name -- use the active sheet if it's not explicitly referenced
|
|
@@ -738,6 +741,19 @@ export class Exporter {
|
|
|
738
741
|
|
|
739
742
|
}
|
|
740
743
|
|
|
744
|
+
public EnsureRange(unit: UnitAddress|UnitRange): UnitRange {
|
|
745
|
+
if (unit.type === 'range') {
|
|
746
|
+
return unit;
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
type: 'range',
|
|
750
|
+
start: unit,
|
|
751
|
+
end: unit,
|
|
752
|
+
label: unit.label,
|
|
753
|
+
id: unit.id,
|
|
754
|
+
position: unit.position,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
741
757
|
|
|
742
758
|
/**
|
|
743
759
|
* new-style annotation layout (kind of a two-cell anchor) to two-cell anchor
|
|
@@ -909,10 +925,13 @@ export class Exporter {
|
|
|
909
925
|
}
|
|
910
926
|
else if (/series/i.test(arg.name)) {
|
|
911
927
|
|
|
912
|
-
const [label, x, y] = arg.args; // y is required
|
|
928
|
+
const [label, x, y, z] = arg.args; // y is required
|
|
913
929
|
|
|
914
|
-
|
|
915
|
-
|
|
930
|
+
// FIXME: could be address also [x, y]
|
|
931
|
+
|
|
932
|
+
if (y && (y.type === 'range' || y.type === 'address')) {
|
|
933
|
+
|
|
934
|
+
options.data.push(this.EnsureRange(this.NormalizeAddress(y, sheet_source)));
|
|
916
935
|
|
|
917
936
|
if (label) {
|
|
918
937
|
|
|
@@ -933,9 +952,16 @@ export class Exporter {
|
|
|
933
952
|
}
|
|
934
953
|
|
|
935
954
|
if (!options.labels2) { options.labels2 = []; }
|
|
936
|
-
|
|
937
|
-
|
|
955
|
+
|
|
956
|
+
if (x && (x.type === 'range' || x.type === 'address')) {
|
|
957
|
+
options.labels2[options.data.length - 1] = this.EnsureRange(this.NormalizeAddress(x, sheet_source));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (z && (z.type === 'range' || z.type === 'address')) {
|
|
961
|
+
if (!options.labels3) { options.labels3 = []; }
|
|
962
|
+
options.labels3[options.data.length - 1] = this.EnsureRange(this.NormalizeAddress(z, sheet_source));
|
|
938
963
|
}
|
|
964
|
+
|
|
939
965
|
}
|
|
940
966
|
else {
|
|
941
967
|
console.info('invalid series missing Y', {y, arg, ref});
|
|
@@ -951,30 +977,39 @@ export class Exporter {
|
|
|
951
977
|
const parse_result = this.parser.Parse(annotation.formula || '');
|
|
952
978
|
if (parse_result.expression && parse_result.expression.type === 'call') {
|
|
953
979
|
|
|
954
|
-
let type = '';
|
|
980
|
+
let type = ''; // FIXME
|
|
981
|
+
|
|
955
982
|
switch (parse_result.expression.name.toLowerCase()) {
|
|
956
983
|
case 'line.chart':
|
|
957
984
|
type = 'scatter';
|
|
958
985
|
break;
|
|
986
|
+
|
|
987
|
+
case 'bubble.chart':
|
|
988
|
+
type = 'bubble';
|
|
989
|
+
break;
|
|
990
|
+
|
|
959
991
|
case 'scatter.line':
|
|
960
992
|
type = 'scatter2';
|
|
961
993
|
break;
|
|
994
|
+
|
|
962
995
|
case 'donut.chart':
|
|
963
996
|
type = 'donut';
|
|
964
997
|
break;
|
|
998
|
+
|
|
965
999
|
case 'bar.chart':
|
|
966
1000
|
type = 'bar';
|
|
967
1001
|
break;
|
|
1002
|
+
|
|
968
1003
|
case 'column.chart':
|
|
969
1004
|
type = 'column';
|
|
970
1005
|
break;
|
|
971
1006
|
}
|
|
972
1007
|
|
|
973
|
-
if (type === 'column' || type === 'donut' || type === 'bar' || type === 'scatter' || type === 'scatter2') {
|
|
1008
|
+
if (type === 'column' || type === 'donut' || type === 'bar' || type === 'scatter' || type === 'scatter2' || type === 'bubble') {
|
|
974
1009
|
|
|
975
1010
|
const options: ChartOptions = { type, data: [] };
|
|
976
1011
|
|
|
977
|
-
const title_index = (type === 'scatter2') ? 1 : 2;
|
|
1012
|
+
const title_index = (type === 'scatter2' || type === 'bubble') ? 1 : 2;
|
|
978
1013
|
const title_arg = parse_result.expression.args[title_index];
|
|
979
1014
|
|
|
980
1015
|
if (title_arg && title_arg.type === 'literal') {
|
|
@@ -1000,7 +1035,7 @@ export class Exporter {
|
|
|
1000
1035
|
|
|
1001
1036
|
if (parse_result.expression.args[0]) {
|
|
1002
1037
|
const arg0 = parse_result.expression.args[0];
|
|
1003
|
-
if (type === 'scatter2' || type === 'bar' || type === 'column' || type === 'scatter') {
|
|
1038
|
+
if (type === 'scatter2' || type === 'bar' || type === 'column' || type === 'scatter' || type === 'bubble') {
|
|
1004
1039
|
parse_series(arg0, options, sheet_source.name);
|
|
1005
1040
|
}
|
|
1006
1041
|
else if (arg0.type === 'range') {
|
|
@@ -1045,7 +1080,7 @@ export class Exporter {
|
|
|
1045
1080
|
*/
|
|
1046
1081
|
}
|
|
1047
1082
|
|
|
1048
|
-
if (type !== 'scatter2') {
|
|
1083
|
+
if (type !== 'scatter2' && type !== 'bubble') {
|
|
1049
1084
|
if (parse_result.expression.args[1] && parse_result.expression.args[1].type === 'range') {
|
|
1050
1085
|
options.labels = this.NormalizeAddress(parse_result.expression.args[1], sheet_source);
|
|
1051
1086
|
}
|
|
@@ -1064,6 +1099,10 @@ export class Exporter {
|
|
|
1064
1099
|
options.smooth = true;
|
|
1065
1100
|
}
|
|
1066
1101
|
}
|
|
1102
|
+
else if (type === 'bubble') {
|
|
1103
|
+
// ...
|
|
1104
|
+
// console.info({parse_result});
|
|
1105
|
+
}
|
|
1067
1106
|
|
|
1068
1107
|
// FIXME: fix this type (this happened when we switched from annotation
|
|
1069
1108
|
// class to a data interface)
|
|
@@ -81,7 +81,10 @@ export class Importer {
|
|
|
81
81
|
t?: string;
|
|
82
82
|
s?: string;
|
|
83
83
|
};
|
|
84
|
-
v?: string|number
|
|
84
|
+
v?: string|number|{
|
|
85
|
+
t$: string;
|
|
86
|
+
a$?: any;
|
|
87
|
+
};
|
|
85
88
|
f?: string|{
|
|
86
89
|
t$: string;
|
|
87
90
|
a$?: {
|
|
@@ -214,14 +217,17 @@ export class Importer {
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
if (typeof element.v !== 'undefined') {
|
|
217
|
-
|
|
220
|
+
|
|
221
|
+
const V = (typeof element.v === 'object') ? element.v?.t$ : element.v;
|
|
222
|
+
|
|
223
|
+
const num = Number(V.toString());
|
|
218
224
|
if (!isNaN(num)) {
|
|
219
225
|
calculated_type = 'number'; // ValueType.number;
|
|
220
226
|
calculated_value = num;
|
|
221
227
|
}
|
|
222
228
|
else {
|
|
223
229
|
calculated_type = 'string'; // ValueType.string;
|
|
224
|
-
calculated_value =
|
|
230
|
+
calculated_value = V.toString();
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
233
|
|
|
@@ -917,6 +923,27 @@ export class Importer {
|
|
|
917
923
|
const series = descriptor.chart?.series;
|
|
918
924
|
|
|
919
925
|
switch(descriptor.chart.type) {
|
|
926
|
+
|
|
927
|
+
case ChartType.Bubble:
|
|
928
|
+
type = 'treb-chart';
|
|
929
|
+
func = 'Bubble.Chart';
|
|
930
|
+
|
|
931
|
+
if (series && series.length) {
|
|
932
|
+
args[0] = `Group(${series.map(s => `Series(${
|
|
933
|
+
[
|
|
934
|
+
s.title || '',
|
|
935
|
+
s.values || '',
|
|
936
|
+
s.categories || '',
|
|
937
|
+
s.bubble_size || '',
|
|
938
|
+
|
|
939
|
+
].join(', ')
|
|
940
|
+
})`).join(', ')})`;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
args[1] = descriptor.chart.title;
|
|
944
|
+
|
|
945
|
+
break;
|
|
946
|
+
|
|
920
947
|
case ChartType.Scatter:
|
|
921
948
|
type = 'treb-chart';
|
|
922
949
|
func = 'Scatter.Line';
|
|
@@ -29,6 +29,9 @@ import { XMLUtils } from './xml-utils';
|
|
|
29
29
|
|
|
30
30
|
import { Unescape } from './unescape_xml';
|
|
31
31
|
|
|
32
|
+
// what's the default font size? ... 11pt?
|
|
33
|
+
const DEFAULT_FONT_SIZE = 11;
|
|
34
|
+
|
|
32
35
|
export interface Font {
|
|
33
36
|
size?: number;
|
|
34
37
|
name?: string;
|
|
@@ -269,13 +272,24 @@ export class StyleCache {
|
|
|
269
272
|
|
|
270
273
|
}
|
|
271
274
|
|
|
275
|
+
|
|
272
276
|
if (composite.font_size?.unit && composite.font_size.value) {
|
|
273
|
-
if (composite.font_size.unit
|
|
274
|
-
|
|
277
|
+
if (composite.font_size.unit === 'em') {
|
|
278
|
+
font.size = composite.font_size.value * DEFAULT_FONT_SIZE;
|
|
275
279
|
}
|
|
276
|
-
else {
|
|
280
|
+
else if (composite.font_size.unit === '%') {
|
|
281
|
+
font.size = composite.font_size.value * DEFAULT_FONT_SIZE / 100;
|
|
282
|
+
}
|
|
283
|
+
else if (composite.font_size.unit === 'pt' ){
|
|
277
284
|
font.size = composite.font_size.value;
|
|
278
285
|
}
|
|
286
|
+
else if (composite.font_size.unit === 'px' ){
|
|
287
|
+
font.size = composite.font_size.value * .75; // ?
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
console.warn(`Unhandled font size unit`, composite.font_size);
|
|
291
|
+
}
|
|
292
|
+
|
|
279
293
|
}
|
|
280
294
|
|
|
281
295
|
if (composite.bold) font.bold = true;
|
|
@@ -657,6 +671,19 @@ export class StyleCache {
|
|
|
657
671
|
|
|
658
672
|
// borders
|
|
659
673
|
|
|
674
|
+
const BorderEdgeToColor = (edge: BorderEdge): Color|undefined => {
|
|
675
|
+
|
|
676
|
+
// TODO: indexed
|
|
677
|
+
|
|
678
|
+
if (typeof edge.theme !== 'undefined') {
|
|
679
|
+
return {
|
|
680
|
+
theme: edge.theme,
|
|
681
|
+
tint: edge.tint,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
};
|
|
686
|
+
|
|
660
687
|
const border = this.borders[xf.border || 0];
|
|
661
688
|
if (border) {
|
|
662
689
|
if (border.bottom.style) {
|
|
@@ -666,10 +693,20 @@ export class StyleCache {
|
|
|
666
693
|
else {
|
|
667
694
|
props.border_bottom = 1;
|
|
668
695
|
}
|
|
696
|
+
props.border_bottom_fill = BorderEdgeToColor(border.bottom);
|
|
697
|
+
}
|
|
698
|
+
if (border.left.style) {
|
|
699
|
+
props.border_left = 1;
|
|
700
|
+
props.border_left_fill = BorderEdgeToColor(border.left);
|
|
701
|
+
}
|
|
702
|
+
if (border.top.style) {
|
|
703
|
+
props.border_top = 1;
|
|
704
|
+
props.border_top_fill = BorderEdgeToColor(border.top);
|
|
705
|
+
}
|
|
706
|
+
if (border.right.style) {
|
|
707
|
+
props.border_right = 1;
|
|
708
|
+
props.border_right_fill = BorderEdgeToColor(border.right);
|
|
669
709
|
}
|
|
670
|
-
if (border.left.style) props.border_left = 1;
|
|
671
|
-
if (border.top.style) props.border_top = 1;
|
|
672
|
-
if (border.right.style) props.border_right = 1;
|
|
673
710
|
}
|
|
674
711
|
|
|
675
712
|
return props;
|
|
@@ -1144,31 +1181,58 @@ export class StyleCache {
|
|
|
1144
1181
|
|
|
1145
1182
|
composite = FindAll('styleSheet/borders/border');
|
|
1146
1183
|
|
|
1184
|
+
const ElementToBorderEdge = (element: any, edge: BorderEdge) => {
|
|
1185
|
+
|
|
1186
|
+
if (element?.a$) {
|
|
1187
|
+
edge.style = element.a$.style;
|
|
1188
|
+
if (typeof element.color === 'object') {
|
|
1189
|
+
if (typeof element.color.a$?.indexed !== 'undefined') {
|
|
1190
|
+
edge.color = Number(element.color.a$.indexed);
|
|
1191
|
+
}
|
|
1192
|
+
if (typeof element.color.a$?.theme !== 'undefined') {
|
|
1193
|
+
edge.theme = Number(element.color.a$.theme);
|
|
1194
|
+
}
|
|
1195
|
+
if (typeof element.color.a$?.tint !== 'undefined') {
|
|
1196
|
+
edge.tint = Number(element.color.a$.tint);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
|
|
1147
1204
|
this.borders = composite.map(element => {
|
|
1148
1205
|
|
|
1149
1206
|
const border: BorderStyle = JSON.parse(JSON.stringify(default_border));
|
|
1150
1207
|
|
|
1208
|
+
/*
|
|
1151
1209
|
// we're relying on these being empty strings -> falsy, not a good look
|
|
1152
1210
|
|
|
1153
1211
|
if (element.left) {
|
|
1154
|
-
border.left.style = element.left.a$.style;
|
|
1155
|
-
border.left.color = Number(element.left.color?.a$?.indexed);
|
|
1212
|
+
// border.left.style = element.left.a$.style;
|
|
1213
|
+
// border.left.color = Number(element.left.color?.a$?.indexed);
|
|
1156
1214
|
}
|
|
1157
1215
|
|
|
1158
1216
|
if (element.right) {
|
|
1159
|
-
border.right.style = element.right.a$.style;
|
|
1160
|
-
border.right.color = Number(element.right.color?.a$?.indexed);
|
|
1217
|
+
// border.right.style = element.right.a$.style;
|
|
1218
|
+
// border.right.color = Number(element.right.color?.a$?.indexed);
|
|
1161
1219
|
}
|
|
1162
1220
|
|
|
1163
1221
|
if (element.top) {
|
|
1164
|
-
border.top.style = element.top.a$.style;
|
|
1165
|
-
border.top.color = Number(element.top.color?.a$?.indexed);
|
|
1222
|
+
// border.top.style = element.top.a$.style;
|
|
1223
|
+
// border.top.color = Number(element.top.color?.a$?.indexed);
|
|
1166
1224
|
}
|
|
1167
1225
|
|
|
1168
1226
|
if (element.bottom) {
|
|
1169
|
-
border.bottom.style = element.bottom.a$.style;
|
|
1170
|
-
border.bottom.color = Number(element.bottom.color?.a$?.indexed);
|
|
1227
|
+
// border.bottom.style = element.bottom.a$.style;
|
|
1228
|
+
// border.bottom.color = Number(element.bottom.color?.a$?.indexed);
|
|
1171
1229
|
}
|
|
1230
|
+
*/
|
|
1231
|
+
|
|
1232
|
+
ElementToBorderEdge(element.left, border.left);
|
|
1233
|
+
ElementToBorderEdge(element.right, border.right);
|
|
1234
|
+
ElementToBorderEdge(element.top, border.top);
|
|
1235
|
+
ElementToBorderEdge(element.bottom, border.bottom);
|
|
1172
1236
|
|
|
1173
1237
|
return border;
|
|
1174
1238
|
|
|
@@ -69,12 +69,13 @@ export const ConditionalFormatOperators: Record<string, string> = {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
export enum ChartType {
|
|
72
|
-
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie
|
|
72
|
+
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie, Bubble
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export interface ChartSeries {
|
|
76
76
|
values?: string;
|
|
77
77
|
categories?: string;
|
|
78
|
+
bubble_size?: string;
|
|
78
79
|
title?: string;
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -411,7 +412,7 @@ export class Workbook {
|
|
|
411
412
|
|
|
412
413
|
}
|
|
413
414
|
|
|
414
|
-
const ParseSeries = (node: any,
|
|
415
|
+
const ParseSeries = (node: any, type?: ChartType): ChartSeries[] => {
|
|
415
416
|
|
|
416
417
|
const series: ChartSeries[] = [];
|
|
417
418
|
|
|
@@ -447,7 +448,7 @@ export class Workbook {
|
|
|
447
448
|
}
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
if (
|
|
451
|
+
if (type === ChartType.Scatter || type === ChartType.Bubble) {
|
|
451
452
|
const x = XMLUtils.FindChild(series_node, 'c:xVal/c:numRef/c:f');
|
|
452
453
|
if (x) {
|
|
453
454
|
series_data.categories = x; // .text?.toString();
|
|
@@ -456,6 +457,14 @@ export class Workbook {
|
|
|
456
457
|
if (y) {
|
|
457
458
|
series_data.values = y; // .text?.toString();
|
|
458
459
|
}
|
|
460
|
+
|
|
461
|
+
if (type === ChartType.Bubble) {
|
|
462
|
+
const z = XMLUtils.FindChild(series_node, 'c:bubbleSize/c:numRef/c:f');
|
|
463
|
+
if (z) {
|
|
464
|
+
series_data.bubble_size = z; // .text?.toString();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
459
468
|
}
|
|
460
469
|
else {
|
|
461
470
|
const value_node = XMLUtils.FindChild(series_node, 'c:val/c:numRef/c:f');
|
|
@@ -522,9 +531,23 @@ export class Workbook {
|
|
|
522
531
|
node = XMLUtils.FindChild(xml, 'c:chartSpace/c:chart/c:plotArea/c:scatterChart');
|
|
523
532
|
if (node) {
|
|
524
533
|
result.type = ChartType.Scatter;
|
|
525
|
-
result.series = ParseSeries(node,
|
|
534
|
+
result.series = ParseSeries(node, ChartType.Scatter);
|
|
526
535
|
}
|
|
527
536
|
}
|
|
537
|
+
|
|
538
|
+
if (!node) {
|
|
539
|
+
node = XMLUtils.FindChild(xml, 'c:chartSpace/c:chart/c:plotArea/c:bubbleChart');
|
|
540
|
+
if (node) {
|
|
541
|
+
result.type = ChartType.Bubble;
|
|
542
|
+
result.series = ParseSeries(node, ChartType.Bubble);
|
|
543
|
+
console.info("Bubble series?", result.series);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!node) {
|
|
548
|
+
console.info("Chart type not handled");
|
|
549
|
+
}
|
|
550
|
+
|
|
528
551
|
// console.info("RX?", result);
|
|
529
552
|
|
|
530
553
|
return result;
|
|
@@ -789,6 +789,13 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
789
789
|
|
|
790
790
|
if (this.active_editor && !this.assume_formula) {
|
|
791
791
|
this.active_editor.node.spellcheck = !(this.text_formula);
|
|
792
|
+
|
|
793
|
+
// if not assuming formula, and it's not a formula, then exit.
|
|
794
|
+
|
|
795
|
+
if (!this.text_formula) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
792
799
|
}
|
|
793
800
|
|
|
794
801
|
// this is a short-circuit so we don't format the same text twice.
|
|
@@ -835,9 +835,9 @@ export class Grid extends GridBase {
|
|
|
835
835
|
}
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
-
|
|
838
|
+
/* *
|
|
839
839
|
* specialization: update selection, scroll offset
|
|
840
|
-
|
|
840
|
+
* /
|
|
841
841
|
public Serialize(options: SerializeOptions = {}): SerializedModel {
|
|
842
842
|
|
|
843
843
|
// selection moved to sheet, but it's not "live"; so we need to
|
|
@@ -855,6 +855,7 @@ export class Grid extends GridBase {
|
|
|
855
855
|
return super.Serialize(options);
|
|
856
856
|
|
|
857
857
|
}
|
|
858
|
+
*/
|
|
858
859
|
|
|
859
860
|
/**
|
|
860
861
|
* show or hide headers
|
|
@@ -1830,18 +1831,23 @@ export class Grid extends GridBase {
|
|
|
1830
1831
|
argument_separator: this.parser.argument_separator,
|
|
1831
1832
|
decimal_mark: this.parser.decimal_mark,
|
|
1832
1833
|
}
|
|
1834
|
+
|
|
1835
|
+
this.parser.Save();
|
|
1836
|
+
|
|
1833
1837
|
let convert = false;
|
|
1834
1838
|
|
|
1835
1839
|
if (options.argument_separator === ',' && this.parser.argument_separator !== ArgumentSeparatorType.Comma) {
|
|
1836
|
-
this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1837
|
-
this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1840
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1841
|
+
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1842
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
1838
1843
|
|
|
1839
1844
|
convert = true;
|
|
1840
1845
|
}
|
|
1841
1846
|
|
|
1842
1847
|
if (options.argument_separator === ';' && this.parser.argument_separator !== ArgumentSeparatorType.Semicolon) {
|
|
1843
|
-
this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1844
|
-
this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1848
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1849
|
+
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1850
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
1845
1851
|
|
|
1846
1852
|
convert = true;
|
|
1847
1853
|
}
|
|
@@ -1882,8 +1888,10 @@ export class Grid extends GridBase {
|
|
|
1882
1888
|
|
|
1883
1889
|
// reset
|
|
1884
1890
|
|
|
1885
|
-
this.parser.argument_separator = current.argument_separator;
|
|
1886
|
-
this.parser.decimal_mark = current.decimal_mark;
|
|
1891
|
+
// this.parser.argument_separator = current.argument_separator;
|
|
1892
|
+
// this.parser.decimal_mark = current.decimal_mark;
|
|
1893
|
+
|
|
1894
|
+
this.parser.Restore();
|
|
1887
1895
|
|
|
1888
1896
|
}
|
|
1889
1897
|
|
|
@@ -4439,7 +4447,10 @@ export class Grid extends GridBase {
|
|
|
4439
4447
|
|
|
4440
4448
|
if (this.overlay_editor?.selection) {
|
|
4441
4449
|
const value = this.overlay_editor?.edit_node.textContent || undefined;
|
|
4442
|
-
|
|
4450
|
+
|
|
4451
|
+
// let's support command+shift+enter on mac
|
|
4452
|
+
const array = (event.key === 'Enter' && (event.ctrlKey || (UA.is_mac && event.metaKey)) && event.shiftKey);
|
|
4453
|
+
|
|
4443
4454
|
this.SetInferredType(this.overlay_editor.selection, value, array);
|
|
4444
4455
|
}
|
|
4445
4456
|
|
|
@@ -461,13 +461,19 @@ export class GridBase {
|
|
|
461
461
|
// CSV assumes dot-decimal, correct? if we want to use the
|
|
462
462
|
// parser we will have to check (and set/reset) the separator
|
|
463
463
|
|
|
464
|
+
this.parser.Save();
|
|
465
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
466
|
+
|
|
467
|
+
/*
|
|
464
468
|
const toggle_separator = this.parser.decimal_mark === DecimalMarkType.Comma;
|
|
465
469
|
|
|
466
|
-
if (toggle_separator)
|
|
470
|
+
if (toggle_separator)
|
|
471
|
+
{
|
|
467
472
|
// swap
|
|
468
473
|
this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
469
474
|
this.parser.decimal_mark = DecimalMarkType.Period;
|
|
470
475
|
}
|
|
476
|
+
*/
|
|
471
477
|
|
|
472
478
|
const records = ParseCSV(text);
|
|
473
479
|
const arr = records.map((record) =>
|
|
@@ -481,11 +487,14 @@ export class GridBase {
|
|
|
481
487
|
return ValueParser.TryParse(field).value;
|
|
482
488
|
}));
|
|
483
489
|
|
|
490
|
+
/*
|
|
484
491
|
if (toggle_separator) {
|
|
485
492
|
// reset
|
|
486
493
|
this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
487
494
|
this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
488
495
|
}
|
|
496
|
+
*/
|
|
497
|
+
this.parser.Restore();
|
|
489
498
|
|
|
490
499
|
const end = {
|
|
491
500
|
row: Math.max(0, arr.length - 1),
|
|
@@ -735,110 +744,6 @@ export class GridBase {
|
|
|
735
744
|
this.model.user_data = undefined;
|
|
736
745
|
}
|
|
737
746
|
|
|
738
|
-
/**
|
|
739
|
-
* serialize data. this function used to (optionally) stringify
|
|
740
|
-
* by typescript has a problem figuring this out, so we will simplify
|
|
741
|
-
* the function.
|
|
742
|
-
*/
|
|
743
|
-
public Serialize(options: SerializeOptions = {}): SerializedModel {
|
|
744
|
-
|
|
745
|
-
// (removed UI stuff, that goes in subclass)
|
|
746
|
-
|
|
747
|
-
// selection moved to sheet, but it's not "live"; so we need to
|
|
748
|
-
// capture the primary selection in the current active sheet before
|
|
749
|
-
// we serialize it
|
|
750
|
-
|
|
751
|
-
// this.active_sheet.selection = JSON.parse(JSON.stringify(this.primary_selection));
|
|
752
|
-
|
|
753
|
-
// same for scroll offset
|
|
754
|
-
|
|
755
|
-
// this.active_sheet.scroll_offset = this.layout.scroll_offset;
|
|
756
|
-
|
|
757
|
-
// NOTE: annotations moved to sheets, they will be serialized in the sheets
|
|
758
|
-
|
|
759
|
-
const sheet_data = this.model.sheets.list.map((sheet) => sheet.toJSON(options));
|
|
760
|
-
|
|
761
|
-
// OK, not serializing tables in cells anymore. old comment about this:
|
|
762
|
-
//
|
|
763
|
-
// at the moment, tables are being serialized in cells. if we put them
|
|
764
|
-
// in here, then we have two records of the same data. that would be bad.
|
|
765
|
-
// I think this is probably the correct place, but if we put them here
|
|
766
|
-
// we need to stop serializing in cells. and I'm not sure that there are
|
|
767
|
-
// not some side-effects to that. hopefully not, but (...)
|
|
768
|
-
//
|
|
769
|
-
|
|
770
|
-
let tables: Table[] | undefined;
|
|
771
|
-
if (this.model.tables.size > 0) {
|
|
772
|
-
tables = Array.from(this.model.tables.values());
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// NOTE: moving into a structured object (the sheet data is also structured,
|
|
776
|
-
// of course) but we are moving things out of sheet (just named ranges atm))
|
|
777
|
-
|
|
778
|
-
let macro_functions: MacroFunction[] | undefined;
|
|
779
|
-
|
|
780
|
-
if (this.model.macro_functions.size) {
|
|
781
|
-
macro_functions = [];
|
|
782
|
-
for (const macro of this.model.macro_functions.values()) {
|
|
783
|
-
macro_functions.push({
|
|
784
|
-
...macro,
|
|
785
|
-
expression: undefined,
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// when serializing named expressions, we have to make sure
|
|
791
|
-
// that there's a sheet name in any address/range.
|
|
792
|
-
|
|
793
|
-
const named_expressions: SerializedNamedExpression[] = [];
|
|
794
|
-
if (this.model.named_expressions) {
|
|
795
|
-
|
|
796
|
-
for (const [name, expr] of this.model.named_expressions) {
|
|
797
|
-
this.parser.Walk(expr, unit => {
|
|
798
|
-
if (unit.type === 'address' || unit.type === 'range') {
|
|
799
|
-
const test = unit.type === 'range' ? unit.start : unit;
|
|
800
|
-
|
|
801
|
-
test.absolute_column = test.absolute_row = true;
|
|
802
|
-
|
|
803
|
-
if (!test.sheet) {
|
|
804
|
-
if (test.sheet_id) {
|
|
805
|
-
const sheet = this.model.sheets.Find(test.sheet_id);
|
|
806
|
-
if (sheet) {
|
|
807
|
-
test.sheet = sheet.name;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (!test.sheet) {
|
|
811
|
-
test.sheet = this.active_sheet.name;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
if (unit.type === 'range') {
|
|
816
|
-
unit.end.absolute_column = unit.end.absolute_row = true;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
return false;
|
|
820
|
-
}
|
|
821
|
-
return true;
|
|
822
|
-
});
|
|
823
|
-
const rendered = this.parser.Render(expr, { missing: '' });
|
|
824
|
-
named_expressions.push({
|
|
825
|
-
name, expression: rendered
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
return {
|
|
831
|
-
sheet_data,
|
|
832
|
-
active_sheet: this.active_sheet.id,
|
|
833
|
-
named_ranges: this.model.named_ranges.Count() ?
|
|
834
|
-
this.model.named_ranges.Serialize() :
|
|
835
|
-
undefined,
|
|
836
|
-
macro_functions,
|
|
837
|
-
tables,
|
|
838
|
-
named_expressions: named_expressions.length ? named_expressions : undefined,
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
}
|
|
842
747
|
|
|
843
748
|
// --- protected methods -----------------------------------------------------
|
|
844
749
|
|
|
@@ -69,4 +69,9 @@ export interface SerializeOptions {
|
|
|
69
69
|
/** share resources (images, for now) to prevent writing data URIs more than once */
|
|
70
70
|
share_resources?: boolean;
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* if a function has an export() handler, call that
|
|
74
|
+
*/
|
|
75
|
+
export_functions?: boolean;
|
|
76
|
+
|
|
72
77
|
}
|