@odoo/o-spreadsheet 18.1.8 → 18.1.10
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/o-spreadsheet.cjs.js +1075 -774
- package/dist/o-spreadsheet.d.ts +46 -48
- package/dist/o-spreadsheet.esm.js +1075 -774
- package/dist/o-spreadsheet.iife.js +1075 -774
- package/dist/o-spreadsheet.iife.min.js +398 -377
- package/dist/o_spreadsheet.xml +32 -26
- package/package.json +4 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 18.1.
|
|
6
|
-
* @date 2025-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.1.10
|
|
6
|
+
* @date 2025-03-07T10:34:41.861Z
|
|
7
|
+
* @hash 31e4526
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function (exports, owl) {
|
|
@@ -2219,17 +2219,7 @@
|
|
|
2219
2219
|
*/
|
|
2220
2220
|
function toUnboundedZone(xc) {
|
|
2221
2221
|
const zone = toZoneWithoutBoundaryChanges(xc);
|
|
2222
|
-
|
|
2223
|
-
const tmp = zone.left;
|
|
2224
|
-
zone.left = zone.right;
|
|
2225
|
-
zone.right = tmp;
|
|
2226
|
-
}
|
|
2227
|
-
if (zone.bottom !== undefined && zone.bottom < zone.top) {
|
|
2228
|
-
const tmp = zone.top;
|
|
2229
|
-
zone.top = zone.bottom;
|
|
2230
|
-
zone.bottom = tmp;
|
|
2231
|
-
}
|
|
2232
|
-
return zone;
|
|
2222
|
+
return reorderZone(zone);
|
|
2233
2223
|
}
|
|
2234
2224
|
/**
|
|
2235
2225
|
* Convert from a cartesian reference to a Zone.
|
|
@@ -2503,11 +2493,11 @@
|
|
|
2503
2493
|
return positions;
|
|
2504
2494
|
}
|
|
2505
2495
|
function reorderZone(zone) {
|
|
2506
|
-
if (zone.left > zone.right) {
|
|
2507
|
-
zone = { left: zone.right, right: zone.left
|
|
2496
|
+
if (zone.right !== undefined && zone.left > zone.right) {
|
|
2497
|
+
zone = { ...zone, left: zone.right, right: zone.left };
|
|
2508
2498
|
}
|
|
2509
|
-
if (zone.top > zone.bottom) {
|
|
2510
|
-
zone = {
|
|
2499
|
+
if (zone.bottom !== undefined && zone.top > zone.bottom) {
|
|
2500
|
+
zone = { ...zone, top: zone.bottom, bottom: zone.top };
|
|
2511
2501
|
}
|
|
2512
2502
|
return zone;
|
|
2513
2503
|
}
|
|
@@ -3412,12 +3402,12 @@
|
|
|
3412
3402
|
function isRangeDependant(cmd) {
|
|
3413
3403
|
return "ranges" in cmd;
|
|
3414
3404
|
}
|
|
3415
|
-
function isZoneDependent(cmd) {
|
|
3416
|
-
return "zone" in cmd;
|
|
3417
|
-
}
|
|
3418
3405
|
function isPositionDependent(cmd) {
|
|
3419
3406
|
return "col" in cmd && "row" in cmd && "sheetId" in cmd;
|
|
3420
3407
|
}
|
|
3408
|
+
function isZoneDependent(cmd) {
|
|
3409
|
+
return "sheetId" in cmd && "zone" in cmd;
|
|
3410
|
+
}
|
|
3421
3411
|
const invalidateEvaluationCommands = new Set([
|
|
3422
3412
|
"RENAME_SHEET",
|
|
3423
3413
|
"DELETE_SHEET",
|
|
@@ -3429,6 +3419,7 @@
|
|
|
3429
3419
|
"REDO",
|
|
3430
3420
|
"ADD_MERGE",
|
|
3431
3421
|
"REMOVE_MERGE",
|
|
3422
|
+
"DUPLICATE_SHEET",
|
|
3432
3423
|
"UPDATE_LOCALE",
|
|
3433
3424
|
"ADD_PIVOT",
|
|
3434
3425
|
"UPDATE_PIVOT",
|
|
@@ -3458,7 +3449,6 @@
|
|
|
3458
3449
|
]);
|
|
3459
3450
|
const invalidateDependenciesCommands = new Set(["MOVE_RANGES"]);
|
|
3460
3451
|
const invalidateCFEvaluationCommands = new Set([
|
|
3461
|
-
"DUPLICATE_SHEET",
|
|
3462
3452
|
"EVALUATE_CELLS",
|
|
3463
3453
|
"ADD_CONDITIONAL_FORMAT",
|
|
3464
3454
|
"REMOVE_CONDITIONAL_FORMAT",
|
|
@@ -3628,6 +3618,7 @@
|
|
|
3628
3618
|
CommandResult["InvalidRange"] = "InvalidRange";
|
|
3629
3619
|
CommandResult["InvalidZones"] = "InvalidZones";
|
|
3630
3620
|
CommandResult["InvalidSheetId"] = "InvalidSheetId";
|
|
3621
|
+
CommandResult["InvalidCellId"] = "InvalidCellId";
|
|
3631
3622
|
CommandResult["InvalidFigureId"] = "InvalidFigureId";
|
|
3632
3623
|
CommandResult["InputAlreadyFocused"] = "InputAlreadyFocused";
|
|
3633
3624
|
CommandResult["MaximumRangesReached"] = "MaximumRangesReached";
|
|
@@ -4459,7 +4450,7 @@
|
|
|
4459
4450
|
* @param reverseSearch if true, search in the array starting from the end.
|
|
4460
4451
|
|
|
4461
4452
|
*/
|
|
4462
|
-
function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) {
|
|
4453
|
+
function linearSearch(data, target, mode, numberOfValues, getValueInData, lookupCaches, reverseSearch = false) {
|
|
4463
4454
|
if (target === undefined || target.value === null) {
|
|
4464
4455
|
return -1;
|
|
4465
4456
|
}
|
|
@@ -4468,17 +4459,48 @@
|
|
|
4468
4459
|
}
|
|
4469
4460
|
const _target = normalizeValue(target.value);
|
|
4470
4461
|
const getValue = reverseSearch
|
|
4471
|
-
? (data, i) => getValueInData(data, numberOfValues - i - 1)
|
|
4472
|
-
: getValueInData;
|
|
4462
|
+
? (data, i) => normalizeValue(getValueInData(data, numberOfValues - i - 1))
|
|
4463
|
+
: (data, i) => normalizeValue(getValueInData(data, i));
|
|
4464
|
+
// first check if the target is in the cache
|
|
4465
|
+
const isNotWildcardTarget = mode !== "wildcard" ||
|
|
4466
|
+
typeof _target !== "string" ||
|
|
4467
|
+
!(_target.includes("*") || _target.includes("?"));
|
|
4468
|
+
if (lookupCaches && isNotWildcardTarget) {
|
|
4469
|
+
const searchMode = reverseSearch ? "reverseSearch" : "forwardSearch";
|
|
4470
|
+
let cache = lookupCaches[searchMode].get(data);
|
|
4471
|
+
if (cache === undefined) {
|
|
4472
|
+
// build the cache for all the values
|
|
4473
|
+
cache = new Map();
|
|
4474
|
+
for (let i = 0; i < numberOfValues; i++) {
|
|
4475
|
+
const value = getValue(data, i) ?? null;
|
|
4476
|
+
if (!cache.has(value)) {
|
|
4477
|
+
cache.set(value, i);
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
lookupCaches[searchMode].set(data, cache);
|
|
4481
|
+
}
|
|
4482
|
+
if (cache.has(_target)) {
|
|
4483
|
+
const resultIndex = cache.get(_target);
|
|
4484
|
+
return reverseSearch ? numberOfValues - resultIndex - 1 : resultIndex;
|
|
4485
|
+
}
|
|
4486
|
+
if (mode === "strict") {
|
|
4487
|
+
return -1;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
// else perform the linear search
|
|
4491
|
+
const resultIndex = _linearSearch(data, _target, mode, numberOfValues, getValue);
|
|
4492
|
+
return reverseSearch && resultIndex !== -1 ? numberOfValues - resultIndex - 1 : resultIndex;
|
|
4493
|
+
}
|
|
4494
|
+
function _linearSearch(data, _target, mode, numberOfValues, getNormalizeValue) {
|
|
4473
4495
|
let indexMatchTarget = (i) => {
|
|
4474
|
-
return
|
|
4496
|
+
return getNormalizeValue(data, i) === _target;
|
|
4475
4497
|
};
|
|
4476
4498
|
if (mode === "wildcard" &&
|
|
4477
4499
|
typeof _target === "string" &&
|
|
4478
4500
|
(_target.includes("*") || _target.includes("?"))) {
|
|
4479
4501
|
const regExp = wildcardToRegExp(_target);
|
|
4480
4502
|
indexMatchTarget = (i) => {
|
|
4481
|
-
const value =
|
|
4503
|
+
const value = getNormalizeValue(data, i);
|
|
4482
4504
|
if (typeof value === "string") {
|
|
4483
4505
|
return regExp.test(value);
|
|
4484
4506
|
}
|
|
@@ -4489,7 +4511,7 @@
|
|
|
4489
4511
|
let closestMatchIndex = -1;
|
|
4490
4512
|
if (mode === "nextSmaller") {
|
|
4491
4513
|
indexMatchTarget = (i) => {
|
|
4492
|
-
const value =
|
|
4514
|
+
const value = getNormalizeValue(data, i);
|
|
4493
4515
|
if ((!closestMatch && compareCellValues(_target, value) >= 0) ||
|
|
4494
4516
|
(compareCellValues(_target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) {
|
|
4495
4517
|
closestMatch = value;
|
|
@@ -4500,7 +4522,7 @@
|
|
|
4500
4522
|
}
|
|
4501
4523
|
if (mode === "nextGreater") {
|
|
4502
4524
|
indexMatchTarget = (i) => {
|
|
4503
|
-
const value =
|
|
4525
|
+
const value = getNormalizeValue(data, i);
|
|
4504
4526
|
if ((!closestMatch && compareCellValues(_target, value) <= 0) ||
|
|
4505
4527
|
(compareCellValues(_target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) {
|
|
4506
4528
|
closestMatch = value;
|
|
@@ -4511,12 +4533,10 @@
|
|
|
4511
4533
|
}
|
|
4512
4534
|
for (let i = 0; i < numberOfValues; i++) {
|
|
4513
4535
|
if (indexMatchTarget(i)) {
|
|
4514
|
-
return
|
|
4536
|
+
return i;
|
|
4515
4537
|
}
|
|
4516
4538
|
}
|
|
4517
|
-
return
|
|
4518
|
-
? numberOfValues - closestMatchIndex - 1
|
|
4519
|
-
: closestMatchIndex;
|
|
4539
|
+
return closestMatchIndex;
|
|
4520
4540
|
}
|
|
4521
4541
|
/**
|
|
4522
4542
|
* Normalize a value.
|
|
@@ -6072,8 +6092,9 @@
|
|
|
6072
6092
|
if (zone.bottom !== zone.top && zone.left != zone.right) {
|
|
6073
6093
|
if (zone.right) {
|
|
6074
6094
|
for (let j = zone.left; j <= zone.right; ++j) {
|
|
6095
|
+
const datasetOptions = j === zone.left ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6075
6096
|
postProcessedRanges.push({
|
|
6076
|
-
...
|
|
6097
|
+
...datasetOptions,
|
|
6077
6098
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6078
6099
|
left: j,
|
|
6079
6100
|
right: j,
|
|
@@ -6085,8 +6106,9 @@
|
|
|
6085
6106
|
}
|
|
6086
6107
|
else {
|
|
6087
6108
|
for (let j = zone.top; j <= zone.bottom; ++j) {
|
|
6109
|
+
const datasetOptions = j === zone.top ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6088
6110
|
postProcessedRanges.push({
|
|
6089
|
-
...
|
|
6111
|
+
...datasetOptions,
|
|
6090
6112
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6091
6113
|
left: zone.left,
|
|
6092
6114
|
right: zone.right,
|
|
@@ -6490,10 +6512,11 @@
|
|
|
6490
6512
|
*
|
|
6491
6513
|
*/
|
|
6492
6514
|
smallUuid() {
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6515
|
+
if (window.crypto) {
|
|
6516
|
+
return "10000000-1000".replace(/[01]/g, (c) => {
|
|
6517
|
+
const n = Number(c);
|
|
6518
|
+
return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
|
|
6519
|
+
});
|
|
6497
6520
|
}
|
|
6498
6521
|
else {
|
|
6499
6522
|
// mainly for jest and other browsers that do not have the crypto functionality
|
|
@@ -6508,10 +6531,11 @@
|
|
|
6508
6531
|
* This method should be used when you need to avoid collisions at all costs, like the id of a revision.
|
|
6509
6532
|
*/
|
|
6510
6533
|
uuidv4() {
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6534
|
+
if (window.crypto) {
|
|
6535
|
+
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => {
|
|
6536
|
+
const n = Number(c);
|
|
6537
|
+
return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
|
|
6538
|
+
});
|
|
6515
6539
|
}
|
|
6516
6540
|
else {
|
|
6517
6541
|
// mainly for jest and other browsers that do not have the crypto functionality
|
|
@@ -10035,70 +10059,341 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
10035
10059
|
return bars.find((bar, i) => i > startIndex && bar.height !== 0);
|
|
10036
10060
|
}
|
|
10037
10061
|
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10062
|
+
const GAUGE_PADDING_SIDE = 30;
|
|
10063
|
+
const GAUGE_PADDING_TOP = 10;
|
|
10064
|
+
const GAUGE_PADDING_BOTTOM = 20;
|
|
10065
|
+
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
10066
|
+
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
10067
|
+
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
10068
|
+
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
10069
|
+
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
10070
|
+
function drawGaugeChart(canvas, runtime) {
|
|
10071
|
+
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
10072
|
+
canvas.width = canvasBoundingRect.width;
|
|
10073
|
+
canvas.height = canvasBoundingRect.height;
|
|
10074
|
+
const ctx = canvas.getContext("2d");
|
|
10075
|
+
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
10076
|
+
drawBackground(ctx, config);
|
|
10077
|
+
drawGauge(ctx, config);
|
|
10078
|
+
drawInflectionValues(ctx, config);
|
|
10079
|
+
drawLabels(ctx, config);
|
|
10080
|
+
drawTitle(ctx, config);
|
|
10081
|
+
}
|
|
10082
|
+
function drawGauge(ctx, config) {
|
|
10083
|
+
ctx.save();
|
|
10084
|
+
const gauge = config.gauge;
|
|
10085
|
+
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
10086
|
+
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
10087
|
+
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
10088
|
+
if (arcRadius < 0) {
|
|
10089
|
+
return;
|
|
10090
|
+
}
|
|
10091
|
+
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
10092
|
+
// Gauge background
|
|
10093
|
+
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
10094
|
+
ctx.beginPath();
|
|
10095
|
+
ctx.lineWidth = gauge.arcWidth;
|
|
10096
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
10097
|
+
ctx.stroke();
|
|
10098
|
+
// Gauge value
|
|
10099
|
+
ctx.strokeStyle = gauge.color;
|
|
10100
|
+
ctx.beginPath();
|
|
10101
|
+
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
10102
|
+
ctx.stroke();
|
|
10103
|
+
ctx.restore();
|
|
10104
|
+
}
|
|
10105
|
+
function drawBackground(ctx, config) {
|
|
10106
|
+
ctx.save();
|
|
10107
|
+
ctx.fillStyle = config.backgroundColor;
|
|
10108
|
+
ctx.fillRect(0, 0, config.width, config.height);
|
|
10109
|
+
ctx.restore();
|
|
10110
|
+
}
|
|
10111
|
+
function drawLabels(ctx, config) {
|
|
10112
|
+
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
10113
|
+
ctx.save();
|
|
10114
|
+
ctx.textAlign = "center";
|
|
10115
|
+
ctx.fillStyle = label.color;
|
|
10116
|
+
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
10117
|
+
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
10118
|
+
ctx.restore();
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
function drawInflectionValues(ctx, config) {
|
|
10122
|
+
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
10123
|
+
for (const inflectionValue of config.inflectionValues) {
|
|
10124
|
+
ctx.save();
|
|
10125
|
+
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
10126
|
+
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
10127
|
+
ctx.lineWidth = 2;
|
|
10128
|
+
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
10129
|
+
ctx.beginPath();
|
|
10130
|
+
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
10131
|
+
ctx.lineTo(0, -height - 3);
|
|
10132
|
+
ctx.stroke();
|
|
10133
|
+
ctx.textAlign = "center";
|
|
10134
|
+
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
10135
|
+
ctx.fillStyle = inflectionValue.color;
|
|
10136
|
+
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
10137
|
+
ctx.fillText(inflectionValue.label, 0, textY);
|
|
10138
|
+
ctx.restore();
|
|
10139
|
+
}
|
|
10140
|
+
}
|
|
10141
|
+
function drawTitle(ctx, config) {
|
|
10142
|
+
ctx.save();
|
|
10143
|
+
const title = config.title;
|
|
10144
|
+
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
10145
|
+
ctx.textBaseline = "middle";
|
|
10146
|
+
ctx.fillStyle = title.color;
|
|
10147
|
+
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
10148
|
+
ctx.restore();
|
|
10149
|
+
}
|
|
10150
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
10151
|
+
const maxValue = runtime.maxValue;
|
|
10152
|
+
const minValue = runtime.minValue;
|
|
10153
|
+
const gaugeValue = runtime.gaugeValue;
|
|
10154
|
+
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
10155
|
+
const gaugeArcWidth = gaugeRect.width / 6;
|
|
10156
|
+
const gaugePercentage = gaugeValue
|
|
10157
|
+
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
10158
|
+
: 0;
|
|
10159
|
+
const gaugeValuePosition = {
|
|
10160
|
+
x: boundingRect.width / 2,
|
|
10161
|
+
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
10044
10162
|
};
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
return this.chartRuntime.background;
|
|
10163
|
+
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
10164
|
+
// Scale down the font size if the gaugeRect is too small
|
|
10165
|
+
if (gaugeRect.height < 300) {
|
|
10166
|
+
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
10050
10167
|
}
|
|
10051
|
-
|
|
10052
|
-
|
|
10168
|
+
// Scale down the font size if the text is too long
|
|
10169
|
+
const maxTextWidth = gaugeRect.width / 2;
|
|
10170
|
+
const gaugeLabel = gaugeValue?.label || "-";
|
|
10171
|
+
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
10172
|
+
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
10053
10173
|
}
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10174
|
+
const minLabelPosition = {
|
|
10175
|
+
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
10176
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10177
|
+
};
|
|
10178
|
+
const maxLabelPosition = {
|
|
10179
|
+
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
10180
|
+
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
10181
|
+
};
|
|
10182
|
+
const textColor = chartMutedFontColor(runtime.background);
|
|
10183
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
10184
|
+
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
10185
|
+
if (runtime.title.text) {
|
|
10186
|
+
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
10060
10187
|
}
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10188
|
+
switch (runtime.title.align) {
|
|
10189
|
+
case "right":
|
|
10190
|
+
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
10191
|
+
break;
|
|
10192
|
+
case "center":
|
|
10193
|
+
x = (boundingRect.width - titleWidth) / 2;
|
|
10194
|
+
break;
|
|
10195
|
+
case "left":
|
|
10196
|
+
default:
|
|
10197
|
+
x = CHART_PADDING$1;
|
|
10198
|
+
break;
|
|
10199
|
+
}
|
|
10200
|
+
return {
|
|
10201
|
+
width: boundingRect.width,
|
|
10202
|
+
height: boundingRect.height,
|
|
10203
|
+
title: {
|
|
10204
|
+
label: runtime.title.text ?? "",
|
|
10205
|
+
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
10206
|
+
textPosition: {
|
|
10207
|
+
x,
|
|
10208
|
+
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
10209
|
+
},
|
|
10210
|
+
color: runtime.title.color ?? textColor,
|
|
10211
|
+
bold: runtime.title.bold,
|
|
10212
|
+
italic: runtime.title.italic,
|
|
10213
|
+
},
|
|
10214
|
+
backgroundColor: runtime.background,
|
|
10215
|
+
gauge: {
|
|
10216
|
+
rect: gaugeRect,
|
|
10217
|
+
arcWidth: gaugeArcWidth,
|
|
10218
|
+
percentage: clip(gaugePercentage, 0, 1),
|
|
10219
|
+
color: getGaugeColor(runtime),
|
|
10220
|
+
},
|
|
10221
|
+
inflectionValues,
|
|
10222
|
+
gaugeValue: {
|
|
10223
|
+
label: gaugeLabel,
|
|
10224
|
+
textPosition: gaugeValuePosition,
|
|
10225
|
+
fontSize: gaugeValueFontSize,
|
|
10226
|
+
color: textColor,
|
|
10227
|
+
},
|
|
10228
|
+
minLabel: {
|
|
10229
|
+
label: runtime.minValue.label,
|
|
10230
|
+
textPosition: minLabelPosition,
|
|
10231
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10232
|
+
color: textColor,
|
|
10233
|
+
},
|
|
10234
|
+
maxLabel: {
|
|
10235
|
+
label: runtime.maxValue.label,
|
|
10236
|
+
textPosition: maxLabelPosition,
|
|
10237
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10238
|
+
color: textColor,
|
|
10239
|
+
},
|
|
10240
|
+
};
|
|
10241
|
+
}
|
|
10242
|
+
/**
|
|
10243
|
+
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
10244
|
+
* space for the title and labels.
|
|
10245
|
+
*/
|
|
10246
|
+
function getGaugeRect(boundingRect, title) {
|
|
10247
|
+
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
10248
|
+
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
10249
|
+
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
10250
|
+
let gaugeWidth;
|
|
10251
|
+
let gaugeHeight;
|
|
10252
|
+
if (drawWidth > 2 * drawHeight) {
|
|
10253
|
+
gaugeWidth = 2 * drawHeight;
|
|
10254
|
+
gaugeHeight = drawHeight;
|
|
10255
|
+
}
|
|
10256
|
+
else {
|
|
10257
|
+
gaugeWidth = drawWidth;
|
|
10258
|
+
gaugeHeight = drawWidth / 2;
|
|
10259
|
+
}
|
|
10260
|
+
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
10261
|
+
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
10262
|
+
return {
|
|
10263
|
+
x: gaugeX,
|
|
10264
|
+
y: gaugeY,
|
|
10265
|
+
width: gaugeWidth,
|
|
10266
|
+
height: gaugeHeight,
|
|
10267
|
+
};
|
|
10268
|
+
}
|
|
10269
|
+
/**
|
|
10270
|
+
* Get the infliction values of the gauge, and where to draw them (the angle from the center of the gauge at which they are drawn).
|
|
10271
|
+
*
|
|
10272
|
+
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
10273
|
+
*/
|
|
10274
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
10275
|
+
const maxValue = runtime.maxValue;
|
|
10276
|
+
const minValue = runtime.minValue;
|
|
10277
|
+
const gaugeCircleCenter = {
|
|
10278
|
+
x: gaugeRect.x + gaugeRect.width / 2,
|
|
10279
|
+
y: gaugeRect.y + gaugeRect.height,
|
|
10280
|
+
};
|
|
10281
|
+
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
10282
|
+
const inflectionValues = [];
|
|
10283
|
+
const inflectionValuesTextRects = [];
|
|
10284
|
+
for (const inflectionValue of runtime.inflectionValues) {
|
|
10285
|
+
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
10286
|
+
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
10287
|
+
const angle = Math.PI - Math.PI * percentage;
|
|
10288
|
+
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
10289
|
+
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
10290
|
+
gaugeCircleCenter.x, // center of the gauge circle
|
|
10291
|
+
gaugeCircleCenter.y, // center of the gauge circle
|
|
10292
|
+
labelWidth + 2, // width of the text + some margin
|
|
10293
|
+
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
10294
|
+
);
|
|
10295
|
+
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
10296
|
+
? GAUGE_LABELS_FONT_SIZE
|
|
10297
|
+
: 0;
|
|
10298
|
+
inflectionValuesTextRects.push(textRect);
|
|
10299
|
+
inflectionValues.push({
|
|
10300
|
+
rotation: angle,
|
|
10301
|
+
label: inflectionValue.label,
|
|
10302
|
+
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
10303
|
+
color: textColor,
|
|
10304
|
+
offset,
|
|
10081
10305
|
});
|
|
10082
10306
|
}
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10307
|
+
return inflectionValues;
|
|
10308
|
+
}
|
|
10309
|
+
function getGaugeColor(runtime) {
|
|
10310
|
+
const gaugeValue = runtime.gaugeValue?.value;
|
|
10311
|
+
if (gaugeValue === undefined) {
|
|
10312
|
+
return GAUGE_BACKGROUND_COLOR;
|
|
10087
10313
|
}
|
|
10088
|
-
|
|
10089
|
-
const
|
|
10090
|
-
if (
|
|
10091
|
-
|
|
10092
|
-
if (chartData.options?.plugins?.title) {
|
|
10093
|
-
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
10094
|
-
}
|
|
10314
|
+
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
10315
|
+
const inflectionValue = runtime.inflectionValues[i];
|
|
10316
|
+
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
10317
|
+
return runtime.colors[i];
|
|
10095
10318
|
}
|
|
10096
|
-
else {
|
|
10097
|
-
|
|
10319
|
+
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
10320
|
+
return runtime.colors[i];
|
|
10321
|
+
}
|
|
10322
|
+
}
|
|
10323
|
+
return runtime.colors.at(-1);
|
|
10324
|
+
}
|
|
10325
|
+
function getSegmentsOfRectangle(rectangle) {
|
|
10326
|
+
return [
|
|
10327
|
+
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
10328
|
+
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
10329
|
+
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
10330
|
+
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
10331
|
+
];
|
|
10332
|
+
}
|
|
10333
|
+
/**
|
|
10334
|
+
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
10335
|
+
* is not handled.
|
|
10336
|
+
*/
|
|
10337
|
+
function doSegmentIntersect(segment1, segment2) {
|
|
10338
|
+
const A = segment1.start;
|
|
10339
|
+
const B = segment1.end;
|
|
10340
|
+
const C = segment2.start;
|
|
10341
|
+
const D = segment2.end;
|
|
10342
|
+
/**
|
|
10343
|
+
* Line segment intersection algorithm
|
|
10344
|
+
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
10345
|
+
*/
|
|
10346
|
+
function ccw(a, b, c) {
|
|
10347
|
+
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
10348
|
+
}
|
|
10349
|
+
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
10350
|
+
}
|
|
10351
|
+
function doRectanglesIntersect(rect1, rect2) {
|
|
10352
|
+
const segments1 = getSegmentsOfRectangle(rect1);
|
|
10353
|
+
const segments2 = getSegmentsOfRectangle(rect2);
|
|
10354
|
+
for (const segment1 of segments1) {
|
|
10355
|
+
for (const segment2 of segments2) {
|
|
10356
|
+
if (doSegmentIntersect(segment1, segment2)) {
|
|
10357
|
+
return true;
|
|
10358
|
+
}
|
|
10098
10359
|
}
|
|
10099
|
-
this.chart.config.options = chartData.options;
|
|
10100
|
-
this.chart.update();
|
|
10101
10360
|
}
|
|
10361
|
+
return false;
|
|
10362
|
+
}
|
|
10363
|
+
/**
|
|
10364
|
+
* Get the rectangle that is tangent to a circle at a given angle.
|
|
10365
|
+
*
|
|
10366
|
+
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
10367
|
+
*/
|
|
10368
|
+
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
10369
|
+
const cos = Math.cos(angle);
|
|
10370
|
+
const sin = Math.sin(angle);
|
|
10371
|
+
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
10372
|
+
const x = cos * radius;
|
|
10373
|
+
const y = sin * radius;
|
|
10374
|
+
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
10375
|
+
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
10376
|
+
const y2 = cos * (rectWidth / 2);
|
|
10377
|
+
const bottomRight = {
|
|
10378
|
+
x: x + x2 + circleCenterX,
|
|
10379
|
+
y: circleCenterY - (y - y2),
|
|
10380
|
+
};
|
|
10381
|
+
const bottomLeft = {
|
|
10382
|
+
x: x - x2 + circleCenterX,
|
|
10383
|
+
y: circleCenterY - (y + y2),
|
|
10384
|
+
};
|
|
10385
|
+
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
10386
|
+
const xp = cos * (radius + rectHeight);
|
|
10387
|
+
const yp = sin * (radius + rectHeight);
|
|
10388
|
+
const topLeft = {
|
|
10389
|
+
x: xp - x2 + circleCenterX,
|
|
10390
|
+
y: circleCenterY - (yp + y2),
|
|
10391
|
+
};
|
|
10392
|
+
const topRight = {
|
|
10393
|
+
x: xp + x2 + circleCenterX,
|
|
10394
|
+
y: circleCenterY - (yp - y2),
|
|
10395
|
+
};
|
|
10396
|
+
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
10102
10397
|
}
|
|
10103
10398
|
|
|
10104
10399
|
/**
|
|
@@ -10680,6 +10975,155 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
10680
10975
|
}
|
|
10681
10976
|
}
|
|
10682
10977
|
|
|
10978
|
+
const CHART_COMMON_OPTIONS = {
|
|
10979
|
+
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
10980
|
+
responsive: true, // will resize when its container is resized
|
|
10981
|
+
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
10982
|
+
elements: {
|
|
10983
|
+
line: {
|
|
10984
|
+
fill: false, // do not fill the area under line charts
|
|
10985
|
+
},
|
|
10986
|
+
point: {
|
|
10987
|
+
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
10988
|
+
},
|
|
10989
|
+
},
|
|
10990
|
+
animation: false,
|
|
10991
|
+
};
|
|
10992
|
+
function truncateLabel(label) {
|
|
10993
|
+
if (!label) {
|
|
10994
|
+
return "";
|
|
10995
|
+
}
|
|
10996
|
+
if (label.length > MAX_CHAR_LABEL) {
|
|
10997
|
+
return label.substring(0, MAX_CHAR_LABEL) + "…";
|
|
10998
|
+
}
|
|
10999
|
+
return label;
|
|
11000
|
+
}
|
|
11001
|
+
function chartToImage(runtime, figure, type) {
|
|
11002
|
+
// wrap the canvas in a div with a fixed size because chart.js would
|
|
11003
|
+
// fill the whole page otherwise
|
|
11004
|
+
const div = document.createElement("div");
|
|
11005
|
+
div.style.width = `${figure.width}px`;
|
|
11006
|
+
div.style.height = `${figure.height}px`;
|
|
11007
|
+
const canvas = document.createElement("canvas");
|
|
11008
|
+
div.append(canvas);
|
|
11009
|
+
canvas.setAttribute("width", figure.width.toString());
|
|
11010
|
+
canvas.setAttribute("height", figure.height.toString());
|
|
11011
|
+
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
11012
|
+
document.body.append(div);
|
|
11013
|
+
if ("chartJsConfig" in runtime) {
|
|
11014
|
+
const config = deepCopy(runtime.chartJsConfig);
|
|
11015
|
+
config.plugins = [backgroundColorChartJSPlugin];
|
|
11016
|
+
const Chart = getChartJSConstructor();
|
|
11017
|
+
const chart = new Chart(canvas, config);
|
|
11018
|
+
const imgContent = chart.toBase64Image();
|
|
11019
|
+
chart.destroy();
|
|
11020
|
+
div.remove();
|
|
11021
|
+
return imgContent;
|
|
11022
|
+
}
|
|
11023
|
+
else if (type === "scorecard") {
|
|
11024
|
+
const design = getScorecardConfiguration(figure, runtime);
|
|
11025
|
+
drawScoreChart(design, canvas);
|
|
11026
|
+
const imgContent = canvas.toDataURL();
|
|
11027
|
+
div.remove();
|
|
11028
|
+
return imgContent;
|
|
11029
|
+
}
|
|
11030
|
+
else if (type === "gauge") {
|
|
11031
|
+
drawGaugeChart(canvas, runtime);
|
|
11032
|
+
const imgContent = canvas.toDataURL();
|
|
11033
|
+
div.remove();
|
|
11034
|
+
return imgContent;
|
|
11035
|
+
}
|
|
11036
|
+
return undefined;
|
|
11037
|
+
}
|
|
11038
|
+
/**
|
|
11039
|
+
* Custom chart.js plugin to set the background color of the canvas
|
|
11040
|
+
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
11041
|
+
*/
|
|
11042
|
+
const backgroundColorChartJSPlugin = {
|
|
11043
|
+
id: "customCanvasBackgroundColor",
|
|
11044
|
+
beforeDraw: (chart) => {
|
|
11045
|
+
const { ctx } = chart;
|
|
11046
|
+
ctx.save();
|
|
11047
|
+
ctx.globalCompositeOperation = "destination-over";
|
|
11048
|
+
ctx.fillStyle = "#ffffff";
|
|
11049
|
+
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
11050
|
+
ctx.restore();
|
|
11051
|
+
},
|
|
11052
|
+
};
|
|
11053
|
+
/** Return window.Chart, making sure all our extensions are loaded in ChartJS */
|
|
11054
|
+
function getChartJSConstructor() {
|
|
11055
|
+
if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
|
|
11056
|
+
window.Chart.register(chartShowValuesPlugin);
|
|
11057
|
+
window.Chart.register(waterfallLinesPlugin);
|
|
11058
|
+
}
|
|
11059
|
+
return window.Chart;
|
|
11060
|
+
}
|
|
11061
|
+
|
|
11062
|
+
class ChartJsComponent extends owl.Component {
|
|
11063
|
+
static template = "o-spreadsheet-ChartJsComponent";
|
|
11064
|
+
static props = {
|
|
11065
|
+
figure: Object,
|
|
11066
|
+
};
|
|
11067
|
+
canvas = owl.useRef("graphContainer");
|
|
11068
|
+
chart;
|
|
11069
|
+
currentRuntime;
|
|
11070
|
+
get background() {
|
|
11071
|
+
return this.chartRuntime.background;
|
|
11072
|
+
}
|
|
11073
|
+
get canvasStyle() {
|
|
11074
|
+
return `background-color: ${this.background}`;
|
|
11075
|
+
}
|
|
11076
|
+
get chartRuntime() {
|
|
11077
|
+
const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
|
|
11078
|
+
if (!("chartJsConfig" in runtime)) {
|
|
11079
|
+
throw new Error("Unsupported chart runtime");
|
|
11080
|
+
}
|
|
11081
|
+
return runtime;
|
|
11082
|
+
}
|
|
11083
|
+
setup() {
|
|
11084
|
+
owl.onMounted(() => {
|
|
11085
|
+
const runtime = this.chartRuntime;
|
|
11086
|
+
this.currentRuntime = runtime;
|
|
11087
|
+
// Note: chartJS modify the runtime in place, so it's important to give it a copy
|
|
11088
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11089
|
+
});
|
|
11090
|
+
owl.onWillUnmount(() => this.chart?.destroy());
|
|
11091
|
+
owl.useEffect(() => {
|
|
11092
|
+
const runtime = this.chartRuntime;
|
|
11093
|
+
if (runtime !== this.currentRuntime) {
|
|
11094
|
+
if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
|
|
11095
|
+
this.chart?.destroy();
|
|
11096
|
+
this.createChart(deepCopy(runtime.chartJsConfig));
|
|
11097
|
+
}
|
|
11098
|
+
else {
|
|
11099
|
+
this.updateChartJs(deepCopy(runtime));
|
|
11100
|
+
}
|
|
11101
|
+
this.currentRuntime = runtime;
|
|
11102
|
+
}
|
|
11103
|
+
});
|
|
11104
|
+
}
|
|
11105
|
+
createChart(chartData) {
|
|
11106
|
+
const canvas = this.canvas.el;
|
|
11107
|
+
const ctx = canvas.getContext("2d");
|
|
11108
|
+
const Chart = getChartJSConstructor();
|
|
11109
|
+
this.chart = new Chart(ctx, chartData);
|
|
11110
|
+
}
|
|
11111
|
+
updateChartJs(chartRuntime) {
|
|
11112
|
+
const chartData = chartRuntime.chartJsConfig;
|
|
11113
|
+
if (chartData.data && chartData.data.datasets) {
|
|
11114
|
+
this.chart.data = chartData.data;
|
|
11115
|
+
if (chartData.options?.plugins?.title) {
|
|
11116
|
+
this.chart.config.options.plugins.title = chartData.options.plugins.title;
|
|
11117
|
+
}
|
|
11118
|
+
}
|
|
11119
|
+
else {
|
|
11120
|
+
this.chart.data.datasets = [];
|
|
11121
|
+
}
|
|
11122
|
+
this.chart.config.options = chartData.options;
|
|
11123
|
+
this.chart.update();
|
|
11124
|
+
}
|
|
11125
|
+
}
|
|
11126
|
+
|
|
10683
11127
|
class ScorecardChart extends owl.Component {
|
|
10684
11128
|
static template = "o-spreadsheet-ScorecardChart";
|
|
10685
11129
|
static props = {
|
|
@@ -12064,6 +12508,25 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
12064
12508
|
isExported: true,
|
|
12065
12509
|
};
|
|
12066
12510
|
// -----------------------------------------------------------------------------
|
|
12511
|
+
// LOG
|
|
12512
|
+
// -----------------------------------------------------------------------------
|
|
12513
|
+
const LOG = {
|
|
12514
|
+
description: _t("The logarithm of a number, for a given base."),
|
|
12515
|
+
args: [
|
|
12516
|
+
arg("value (number)", _t("The value for which to calculate the logarithm.")),
|
|
12517
|
+
arg("base (number, default=10)", _t("The base of the logarithm.")),
|
|
12518
|
+
],
|
|
12519
|
+
compute: function (value, base = { value: 10 }) {
|
|
12520
|
+
const _value = toNumber(value, this.locale);
|
|
12521
|
+
const _base = toNumber(base, this.locale);
|
|
12522
|
+
assert(() => _value > 0, _t("The value (%s) must be strictly positive.", _value.toString()));
|
|
12523
|
+
assert(() => _base > 0, _t("The base (%s) must be strictly positive.", _base.toString()));
|
|
12524
|
+
assert(() => _base !== 1, _t("The base must be different from 1."));
|
|
12525
|
+
return Math.log10(_value) / Math.log10(_base);
|
|
12526
|
+
},
|
|
12527
|
+
isExported: true,
|
|
12528
|
+
};
|
|
12529
|
+
// -----------------------------------------------------------------------------
|
|
12067
12530
|
// MOD
|
|
12068
12531
|
// -----------------------------------------------------------------------------
|
|
12069
12532
|
function mod(dividend, divisor) {
|
|
@@ -12603,6 +13066,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
12603
13066
|
ISODD: ISODD,
|
|
12604
13067
|
ISO_CEILING: ISO_CEILING,
|
|
12605
13068
|
LN: LN,
|
|
13069
|
+
LOG: LOG,
|
|
12606
13070
|
MOD: MOD,
|
|
12607
13071
|
MUNIT: MUNIT,
|
|
12608
13072
|
ODD: ODD,
|
|
@@ -18501,7 +18965,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18501
18965
|
const _isSorted = toBoolean(isSorted.value);
|
|
18502
18966
|
const colIndex = _isSorted
|
|
18503
18967
|
? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
|
|
18504
|
-
: linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
|
|
18968
|
+
: linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange, this.lookupCaches);
|
|
18505
18969
|
const col = _range[colIndex];
|
|
18506
18970
|
if (col === undefined) {
|
|
18507
18971
|
return valueNotAvailable(searchKey);
|
|
@@ -18656,7 +19120,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18656
19120
|
index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
|
|
18657
19121
|
break;
|
|
18658
19122
|
case 0:
|
|
18659
|
-
index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
|
|
19123
|
+
index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
|
|
18660
19124
|
break;
|
|
18661
19125
|
case -1:
|
|
18662
19126
|
index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
|
|
@@ -18724,7 +19188,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18724
19188
|
const _isSorted = toBoolean(isSorted.value);
|
|
18725
19189
|
const rowIndex = _isSorted
|
|
18726
19190
|
? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
|
|
18727
|
-
: linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
|
|
19191
|
+
: linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange, this.lookupCaches);
|
|
18728
19192
|
const value = _range[_index - 1][rowIndex];
|
|
18729
19193
|
if (value === undefined) {
|
|
18730
19194
|
return valueNotAvailable(searchKey);
|
|
@@ -18780,7 +19244,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18780
19244
|
const reverseSearch = _searchMode === -1;
|
|
18781
19245
|
const index = _searchMode === 2 || _searchMode === -2
|
|
18782
19246
|
? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
|
|
18783
|
-
: linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
|
|
19247
|
+
: linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
|
|
18784
19248
|
if (index !== -1) {
|
|
18785
19249
|
return lookupDirection === "col"
|
|
18786
19250
|
? _returnRange.map((col) => [col[index]])
|
|
@@ -22112,7 +22576,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22112
22576
|
condition: (cell) => !cell.isFormula &&
|
|
22113
22577
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.text &&
|
|
22114
22578
|
alphaNumericValueRegExp.test(cell.content),
|
|
22115
|
-
generateRule: (cell, cells) => {
|
|
22579
|
+
generateRule: (cell, cells, direction) => {
|
|
22116
22580
|
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
22117
22581
|
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
22118
22582
|
const numberPostfixLength = cell.content.length - prefix.length;
|
|
@@ -22120,7 +22584,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22120
22584
|
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
22121
22585
|
.filter((cell) => prefix === (cell.value ?? "").toString().match(stringPrefixRegExp)[0])
|
|
22122
22586
|
.map((cell) => parseInt((cell.value ?? "").toString().match(numberPostfixRegExp)[0]));
|
|
22123
|
-
|
|
22587
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22588
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22589
|
+
increment = -increment;
|
|
22590
|
+
}
|
|
22124
22591
|
return {
|
|
22125
22592
|
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
22126
22593
|
prefix,
|
|
@@ -22183,10 +22650,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22183
22650
|
.add("increment_number", {
|
|
22184
22651
|
condition: (cell) => !cell.isFormula &&
|
|
22185
22652
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22186
|
-
generateRule: (cell, cells) => {
|
|
22653
|
+
generateRule: (cell, cells, direction) => {
|
|
22187
22654
|
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22188
22655
|
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22189
|
-
|
|
22656
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22657
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22658
|
+
increment = -increment;
|
|
22659
|
+
}
|
|
22190
22660
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22191
22661
|
return {
|
|
22192
22662
|
type: "INCREMENT_MODIFIER",
|
|
@@ -22230,343 +22700,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22230
22700
|
|
|
22231
22701
|
const cellPopoverRegistry = new Registry();
|
|
22232
22702
|
|
|
22233
|
-
const GAUGE_PADDING_SIDE = 30;
|
|
22234
|
-
const GAUGE_PADDING_TOP = 10;
|
|
22235
|
-
const GAUGE_PADDING_BOTTOM = 20;
|
|
22236
|
-
const GAUGE_LABELS_FONT_SIZE = 12;
|
|
22237
|
-
const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
|
|
22238
|
-
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
22239
|
-
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
22240
|
-
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
22241
|
-
function drawGaugeChart(canvas, runtime) {
|
|
22242
|
-
const canvasBoundingRect = canvas.getBoundingClientRect();
|
|
22243
|
-
canvas.width = canvasBoundingRect.width;
|
|
22244
|
-
canvas.height = canvasBoundingRect.height;
|
|
22245
|
-
const ctx = canvas.getContext("2d");
|
|
22246
|
-
const config = getGaugeRenderingConfig(canvasBoundingRect, runtime, ctx);
|
|
22247
|
-
drawBackground(ctx, config);
|
|
22248
|
-
drawGauge(ctx, config);
|
|
22249
|
-
drawInflectionValues(ctx, config);
|
|
22250
|
-
drawLabels(ctx, config);
|
|
22251
|
-
drawTitle(ctx, config);
|
|
22252
|
-
}
|
|
22253
|
-
function drawGauge(ctx, config) {
|
|
22254
|
-
ctx.save();
|
|
22255
|
-
const gauge = config.gauge;
|
|
22256
|
-
const arcCenterX = gauge.rect.x + gauge.rect.width / 2;
|
|
22257
|
-
const arcCenterY = gauge.rect.y + gauge.rect.height;
|
|
22258
|
-
const arcRadius = gauge.rect.height - gauge.arcWidth / 2;
|
|
22259
|
-
if (arcRadius < 0) {
|
|
22260
|
-
return;
|
|
22261
|
-
}
|
|
22262
|
-
const gaugeAngle = gauge.percentage === 1 ? 0 : Math.PI * (1 + gauge.percentage);
|
|
22263
|
-
// Gauge background
|
|
22264
|
-
ctx.strokeStyle = GAUGE_BACKGROUND_COLOR;
|
|
22265
|
-
ctx.beginPath();
|
|
22266
|
-
ctx.lineWidth = gauge.arcWidth;
|
|
22267
|
-
ctx.arc(arcCenterX, arcCenterY, arcRadius, gaugeAngle, 0);
|
|
22268
|
-
ctx.stroke();
|
|
22269
|
-
// Gauge value
|
|
22270
|
-
ctx.strokeStyle = gauge.color;
|
|
22271
|
-
ctx.beginPath();
|
|
22272
|
-
ctx.arc(arcCenterX, arcCenterY, arcRadius, Math.PI, gaugeAngle);
|
|
22273
|
-
ctx.stroke();
|
|
22274
|
-
ctx.restore();
|
|
22275
|
-
}
|
|
22276
|
-
function drawBackground(ctx, config) {
|
|
22277
|
-
ctx.save();
|
|
22278
|
-
ctx.fillStyle = config.backgroundColor;
|
|
22279
|
-
ctx.fillRect(0, 0, config.width, config.height);
|
|
22280
|
-
ctx.restore();
|
|
22281
|
-
}
|
|
22282
|
-
function drawLabels(ctx, config) {
|
|
22283
|
-
for (const label of [config.minLabel, config.maxLabel, config.gaugeValue]) {
|
|
22284
|
-
ctx.save();
|
|
22285
|
-
ctx.textAlign = "center";
|
|
22286
|
-
ctx.fillStyle = label.color;
|
|
22287
|
-
ctx.font = `${label.fontSize}px ${DEFAULT_FONT}`;
|
|
22288
|
-
ctx.fillText(label.label, label.textPosition.x, label.textPosition.y);
|
|
22289
|
-
ctx.restore();
|
|
22290
|
-
}
|
|
22291
|
-
}
|
|
22292
|
-
function drawInflectionValues(ctx, config) {
|
|
22293
|
-
const { x: rectX, y: rectY, width, height } = config.gauge.rect;
|
|
22294
|
-
for (const inflectionValue of config.inflectionValues) {
|
|
22295
|
-
ctx.save();
|
|
22296
|
-
ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
|
|
22297
|
-
ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
|
|
22298
|
-
ctx.lineWidth = 2;
|
|
22299
|
-
ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
|
|
22300
|
-
ctx.beginPath();
|
|
22301
|
-
ctx.moveTo(0, -(height - config.gauge.arcWidth));
|
|
22302
|
-
ctx.lineTo(0, -height - 3);
|
|
22303
|
-
ctx.stroke();
|
|
22304
|
-
ctx.textAlign = "center";
|
|
22305
|
-
ctx.font = `${inflectionValue.fontSize}px ${DEFAULT_FONT}`;
|
|
22306
|
-
ctx.fillStyle = inflectionValue.color;
|
|
22307
|
-
const textY = -height - GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN - inflectionValue.offset;
|
|
22308
|
-
ctx.fillText(inflectionValue.label, 0, textY);
|
|
22309
|
-
ctx.restore();
|
|
22310
|
-
}
|
|
22311
|
-
}
|
|
22312
|
-
function drawTitle(ctx, config) {
|
|
22313
|
-
ctx.save();
|
|
22314
|
-
const title = config.title;
|
|
22315
|
-
ctx.font = getDefaultContextFont(title.fontSize, title.bold, title.italic);
|
|
22316
|
-
ctx.textBaseline = "middle";
|
|
22317
|
-
ctx.fillStyle = title.color;
|
|
22318
|
-
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
22319
|
-
ctx.restore();
|
|
22320
|
-
}
|
|
22321
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
22322
|
-
const maxValue = runtime.maxValue;
|
|
22323
|
-
const minValue = runtime.minValue;
|
|
22324
|
-
const gaugeValue = runtime.gaugeValue;
|
|
22325
|
-
const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
|
|
22326
|
-
const gaugeArcWidth = gaugeRect.width / 6;
|
|
22327
|
-
const gaugePercentage = gaugeValue
|
|
22328
|
-
? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
|
|
22329
|
-
: 0;
|
|
22330
|
-
const gaugeValuePosition = {
|
|
22331
|
-
x: boundingRect.width / 2,
|
|
22332
|
-
y: gaugeRect.y + gaugeRect.height - gaugeRect.height / 12,
|
|
22333
|
-
};
|
|
22334
|
-
let gaugeValueFontSize = GAUGE_DEFAULT_VALUE_FONT_SIZE;
|
|
22335
|
-
// Scale down the font size if the gaugeRect is too small
|
|
22336
|
-
if (gaugeRect.height < 300) {
|
|
22337
|
-
gaugeValueFontSize = gaugeValueFontSize * (gaugeRect.height / 300);
|
|
22338
|
-
}
|
|
22339
|
-
// Scale down the font size if the text is too long
|
|
22340
|
-
const maxTextWidth = gaugeRect.width / 2;
|
|
22341
|
-
const gaugeLabel = gaugeValue?.label || "-";
|
|
22342
|
-
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
|
|
22343
|
-
gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
22344
|
-
}
|
|
22345
|
-
const minLabelPosition = {
|
|
22346
|
-
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
22347
|
-
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
22348
|
-
};
|
|
22349
|
-
const maxLabelPosition = {
|
|
22350
|
-
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
22351
|
-
y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
|
|
22352
|
-
};
|
|
22353
|
-
const textColor = chartMutedFontColor(runtime.background);
|
|
22354
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
22355
|
-
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
22356
|
-
if (runtime.title.text) {
|
|
22357
|
-
({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
|
|
22358
|
-
}
|
|
22359
|
-
switch (runtime.title.align) {
|
|
22360
|
-
case "right":
|
|
22361
|
-
x = boundingRect.width - titleWidth - CHART_PADDING$1;
|
|
22362
|
-
break;
|
|
22363
|
-
case "center":
|
|
22364
|
-
x = (boundingRect.width - titleWidth) / 2;
|
|
22365
|
-
break;
|
|
22366
|
-
case "left":
|
|
22367
|
-
default:
|
|
22368
|
-
x = CHART_PADDING$1;
|
|
22369
|
-
break;
|
|
22370
|
-
}
|
|
22371
|
-
return {
|
|
22372
|
-
width: boundingRect.width,
|
|
22373
|
-
height: boundingRect.height,
|
|
22374
|
-
title: {
|
|
22375
|
-
label: runtime.title.text ?? "",
|
|
22376
|
-
fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
|
|
22377
|
-
textPosition: {
|
|
22378
|
-
x,
|
|
22379
|
-
y: CHART_PADDING_TOP + titleHeight / 2,
|
|
22380
|
-
},
|
|
22381
|
-
color: runtime.title.color ?? textColor,
|
|
22382
|
-
bold: runtime.title.bold,
|
|
22383
|
-
italic: runtime.title.italic,
|
|
22384
|
-
},
|
|
22385
|
-
backgroundColor: runtime.background,
|
|
22386
|
-
gauge: {
|
|
22387
|
-
rect: gaugeRect,
|
|
22388
|
-
arcWidth: gaugeArcWidth,
|
|
22389
|
-
percentage: clip(gaugePercentage, 0, 1),
|
|
22390
|
-
color: getGaugeColor(runtime),
|
|
22391
|
-
},
|
|
22392
|
-
inflectionValues,
|
|
22393
|
-
gaugeValue: {
|
|
22394
|
-
label: gaugeLabel,
|
|
22395
|
-
textPosition: gaugeValuePosition,
|
|
22396
|
-
fontSize: gaugeValueFontSize,
|
|
22397
|
-
color: textColor,
|
|
22398
|
-
},
|
|
22399
|
-
minLabel: {
|
|
22400
|
-
label: runtime.minValue.label,
|
|
22401
|
-
textPosition: minLabelPosition,
|
|
22402
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22403
|
-
color: textColor,
|
|
22404
|
-
},
|
|
22405
|
-
maxLabel: {
|
|
22406
|
-
label: runtime.maxValue.label,
|
|
22407
|
-
textPosition: maxLabelPosition,
|
|
22408
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22409
|
-
color: textColor,
|
|
22410
|
-
},
|
|
22411
|
-
};
|
|
22412
|
-
}
|
|
22413
|
-
/**
|
|
22414
|
-
* Get the rectangle in which the gauge will be drawn, based on the bounding rectangle of the canvas and leaving
|
|
22415
|
-
* space for the title and labels.
|
|
22416
|
-
*/
|
|
22417
|
-
function getGaugeRect(boundingRect, title) {
|
|
22418
|
-
const titleHeight = title ? GAUGE_TITLE_SECTION_HEIGHT : 0;
|
|
22419
|
-
const drawHeight = boundingRect.height - GAUGE_PADDING_BOTTOM - titleHeight - GAUGE_PADDING_TOP;
|
|
22420
|
-
const drawWidth = boundingRect.width - GAUGE_PADDING_SIDE * 2;
|
|
22421
|
-
let gaugeWidth;
|
|
22422
|
-
let gaugeHeight;
|
|
22423
|
-
if (drawWidth > 2 * drawHeight) {
|
|
22424
|
-
gaugeWidth = 2 * drawHeight;
|
|
22425
|
-
gaugeHeight = drawHeight;
|
|
22426
|
-
}
|
|
22427
|
-
else {
|
|
22428
|
-
gaugeWidth = drawWidth;
|
|
22429
|
-
gaugeHeight = drawWidth / 2;
|
|
22430
|
-
}
|
|
22431
|
-
const gaugeX = GAUGE_PADDING_SIDE + (drawWidth - gaugeWidth) / 2;
|
|
22432
|
-
const gaugeY = titleHeight + GAUGE_PADDING_TOP + (drawHeight - gaugeHeight) / 2;
|
|
22433
|
-
return {
|
|
22434
|
-
x: gaugeX,
|
|
22435
|
-
y: gaugeY,
|
|
22436
|
-
width: gaugeWidth,
|
|
22437
|
-
height: gaugeHeight,
|
|
22438
|
-
};
|
|
22439
|
-
}
|
|
22440
|
-
/**
|
|
22441
|
-
* Get the infliction values of the gauge, and where to draw them (the angle from the center of the gauge at which they are drawn).
|
|
22442
|
-
*
|
|
22443
|
-
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
22444
|
-
*/
|
|
22445
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
22446
|
-
const maxValue = runtime.maxValue;
|
|
22447
|
-
const minValue = runtime.minValue;
|
|
22448
|
-
const gaugeCircleCenter = {
|
|
22449
|
-
x: gaugeRect.x + gaugeRect.width / 2,
|
|
22450
|
-
y: gaugeRect.y + gaugeRect.height,
|
|
22451
|
-
};
|
|
22452
|
-
const textStyle = { fontSize: GAUGE_LABELS_FONT_SIZE };
|
|
22453
|
-
const inflectionValues = [];
|
|
22454
|
-
const inflectionValuesTextRects = [];
|
|
22455
|
-
for (const inflectionValue of runtime.inflectionValues) {
|
|
22456
|
-
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
22457
|
-
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
22458
|
-
const angle = Math.PI - Math.PI * percentage;
|
|
22459
|
-
const textRect = getRectangleTangentToCircle(angle, // angle between X axis and the point where the rectangle is tangent to the circle
|
|
22460
|
-
gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, // radius of the gauge circle + margin below text
|
|
22461
|
-
gaugeCircleCenter.x, // center of the gauge circle
|
|
22462
|
-
gaugeCircleCenter.y, // center of the gauge circle
|
|
22463
|
-
labelWidth + 2, // width of the text + some margin
|
|
22464
|
-
GAUGE_LABELS_FONT_SIZE // height of the text
|
|
22465
|
-
);
|
|
22466
|
-
let offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect))
|
|
22467
|
-
? GAUGE_LABELS_FONT_SIZE
|
|
22468
|
-
: 0;
|
|
22469
|
-
inflectionValuesTextRects.push(textRect);
|
|
22470
|
-
inflectionValues.push({
|
|
22471
|
-
rotation: angle,
|
|
22472
|
-
label: inflectionValue.label,
|
|
22473
|
-
fontSize: GAUGE_LABELS_FONT_SIZE,
|
|
22474
|
-
color: textColor,
|
|
22475
|
-
offset,
|
|
22476
|
-
});
|
|
22477
|
-
}
|
|
22478
|
-
return inflectionValues;
|
|
22479
|
-
}
|
|
22480
|
-
function getGaugeColor(runtime) {
|
|
22481
|
-
const gaugeValue = runtime.gaugeValue?.value;
|
|
22482
|
-
if (gaugeValue === undefined) {
|
|
22483
|
-
return GAUGE_BACKGROUND_COLOR;
|
|
22484
|
-
}
|
|
22485
|
-
for (let i = 0; i < runtime.inflectionValues.length; i++) {
|
|
22486
|
-
const inflectionValue = runtime.inflectionValues[i];
|
|
22487
|
-
if (inflectionValue.operator === "<" && gaugeValue < inflectionValue.value) {
|
|
22488
|
-
return runtime.colors[i];
|
|
22489
|
-
}
|
|
22490
|
-
else if (inflectionValue.operator === "<=" && gaugeValue <= inflectionValue.value) {
|
|
22491
|
-
return runtime.colors[i];
|
|
22492
|
-
}
|
|
22493
|
-
}
|
|
22494
|
-
return runtime.colors.at(-1);
|
|
22495
|
-
}
|
|
22496
|
-
function getSegmentsOfRectangle(rectangle) {
|
|
22497
|
-
return [
|
|
22498
|
-
{ start: rectangle.topLeft, end: rectangle.topRight },
|
|
22499
|
-
{ start: rectangle.topRight, end: rectangle.bottomRight },
|
|
22500
|
-
{ start: rectangle.bottomRight, end: rectangle.bottomLeft },
|
|
22501
|
-
{ start: rectangle.bottomLeft, end: rectangle.topLeft },
|
|
22502
|
-
];
|
|
22503
|
-
}
|
|
22504
|
-
/**
|
|
22505
|
-
* Check if two segment intersect. The case where the segments are colinear (both segments on the same line)
|
|
22506
|
-
* is not handled.
|
|
22507
|
-
*/
|
|
22508
|
-
function doSegmentIntersect(segment1, segment2) {
|
|
22509
|
-
const A = segment1.start;
|
|
22510
|
-
const B = segment1.end;
|
|
22511
|
-
const C = segment2.start;
|
|
22512
|
-
const D = segment2.end;
|
|
22513
|
-
/**
|
|
22514
|
-
* Line segment intersection algorithm
|
|
22515
|
-
* https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
|
|
22516
|
-
*/
|
|
22517
|
-
function ccw(a, b, c) {
|
|
22518
|
-
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
22519
|
-
}
|
|
22520
|
-
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
|
|
22521
|
-
}
|
|
22522
|
-
function doRectanglesIntersect(rect1, rect2) {
|
|
22523
|
-
const segments1 = getSegmentsOfRectangle(rect1);
|
|
22524
|
-
const segments2 = getSegmentsOfRectangle(rect2);
|
|
22525
|
-
for (const segment1 of segments1) {
|
|
22526
|
-
for (const segment2 of segments2) {
|
|
22527
|
-
if (doSegmentIntersect(segment1, segment2)) {
|
|
22528
|
-
return true;
|
|
22529
|
-
}
|
|
22530
|
-
}
|
|
22531
|
-
}
|
|
22532
|
-
return false;
|
|
22533
|
-
}
|
|
22534
|
-
/**
|
|
22535
|
-
* Get the rectangle that is tangent to a circle at a given angle.
|
|
22536
|
-
*
|
|
22537
|
-
* @param angle angle between X axis and the point where the rectangle is tangent to the circle
|
|
22538
|
-
*/
|
|
22539
|
-
function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY, rectWidth, rectHeight) {
|
|
22540
|
-
const cos = Math.cos(angle);
|
|
22541
|
-
const sin = Math.sin(angle);
|
|
22542
|
-
// x, y are the distance from the center of the circle to the point where the rectangle is tangent to the circle
|
|
22543
|
-
const x = cos * radius;
|
|
22544
|
-
const y = sin * radius;
|
|
22545
|
-
// x2, y2 are the distance from the point the rectangle is tangent to the circle to the bottom left corner of the rectangle
|
|
22546
|
-
const x2 = sin * (rectWidth / 2); // cos(angle + 90°) = sin(angle)
|
|
22547
|
-
const y2 = cos * (rectWidth / 2);
|
|
22548
|
-
const bottomRight = {
|
|
22549
|
-
x: x + x2 + circleCenterX,
|
|
22550
|
-
y: circleCenterY - (y - y2),
|
|
22551
|
-
};
|
|
22552
|
-
const bottomLeft = {
|
|
22553
|
-
x: x - x2 + circleCenterX,
|
|
22554
|
-
y: circleCenterY - (y + y2),
|
|
22555
|
-
};
|
|
22556
|
-
// Same as above but for the top corners of the rectangle (radius + rectangle height instead of radius)
|
|
22557
|
-
const xp = cos * (radius + rectHeight);
|
|
22558
|
-
const yp = sin * (radius + rectHeight);
|
|
22559
|
-
const topLeft = {
|
|
22560
|
-
x: xp - x2 + circleCenterX,
|
|
22561
|
-
y: circleCenterY - (yp + y2),
|
|
22562
|
-
};
|
|
22563
|
-
const topRight = {
|
|
22564
|
-
x: xp + x2 + circleCenterX,
|
|
22565
|
-
y: circleCenterY - (yp - y2),
|
|
22566
|
-
};
|
|
22567
|
-
return { bottomLeft, bottomRight, topRight, topLeft };
|
|
22568
|
-
}
|
|
22569
|
-
|
|
22570
22703
|
class GaugeChartComponent extends owl.Component {
|
|
22571
22704
|
static template = "o-spreadsheet-GaugeChartComponent";
|
|
22572
22705
|
canvas = owl.useRef("chartContainer");
|
|
@@ -22599,81 +22732,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22599
22732
|
return color;
|
|
22600
22733
|
}
|
|
22601
22734
|
|
|
22602
|
-
const CHART_COMMON_OPTIONS = {
|
|
22603
|
-
// https://www.chartjs.org/docs/latest/general/responsive.html
|
|
22604
|
-
responsive: true, // will resize when its container is resized
|
|
22605
|
-
maintainAspectRatio: false, // doesn't maintain the aspect ratio (width/height =2 by default) so the user has the choice of the exact layout
|
|
22606
|
-
elements: {
|
|
22607
|
-
line: {
|
|
22608
|
-
fill: false, // do not fill the area under line charts
|
|
22609
|
-
},
|
|
22610
|
-
point: {
|
|
22611
|
-
hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby
|
|
22612
|
-
},
|
|
22613
|
-
},
|
|
22614
|
-
animation: false,
|
|
22615
|
-
};
|
|
22616
|
-
function truncateLabel(label) {
|
|
22617
|
-
if (!label) {
|
|
22618
|
-
return "";
|
|
22619
|
-
}
|
|
22620
|
-
if (label.length > MAX_CHAR_LABEL) {
|
|
22621
|
-
return label.substring(0, MAX_CHAR_LABEL) + "…";
|
|
22622
|
-
}
|
|
22623
|
-
return label;
|
|
22624
|
-
}
|
|
22625
|
-
function chartToImage(runtime, figure, type) {
|
|
22626
|
-
// wrap the canvas in a div with a fixed size because chart.js would
|
|
22627
|
-
// fill the whole page otherwise
|
|
22628
|
-
const div = document.createElement("div");
|
|
22629
|
-
div.style.width = `${figure.width}px`;
|
|
22630
|
-
div.style.height = `${figure.height}px`;
|
|
22631
|
-
const canvas = document.createElement("canvas");
|
|
22632
|
-
div.append(canvas);
|
|
22633
|
-
canvas.setAttribute("width", figure.width.toString());
|
|
22634
|
-
canvas.setAttribute("height", figure.height.toString());
|
|
22635
|
-
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
22636
|
-
document.body.append(div);
|
|
22637
|
-
if ("chartJsConfig" in runtime) {
|
|
22638
|
-
const config = deepCopy(runtime.chartJsConfig);
|
|
22639
|
-
config.plugins = [backgroundColorChartJSPlugin];
|
|
22640
|
-
const chart = new window.Chart(canvas, config);
|
|
22641
|
-
const imgContent = chart.toBase64Image();
|
|
22642
|
-
chart.destroy();
|
|
22643
|
-
div.remove();
|
|
22644
|
-
return imgContent;
|
|
22645
|
-
}
|
|
22646
|
-
else if (type === "scorecard") {
|
|
22647
|
-
const design = getScorecardConfiguration(figure, runtime);
|
|
22648
|
-
drawScoreChart(design, canvas);
|
|
22649
|
-
const imgContent = canvas.toDataURL();
|
|
22650
|
-
div.remove();
|
|
22651
|
-
return imgContent;
|
|
22652
|
-
}
|
|
22653
|
-
else if (type === "gauge") {
|
|
22654
|
-
drawGaugeChart(canvas, runtime);
|
|
22655
|
-
const imgContent = canvas.toDataURL();
|
|
22656
|
-
div.remove();
|
|
22657
|
-
return imgContent;
|
|
22658
|
-
}
|
|
22659
|
-
return undefined;
|
|
22660
|
-
}
|
|
22661
|
-
/**
|
|
22662
|
-
* Custom chart.js plugin to set the background color of the canvas
|
|
22663
|
-
* https://github.com/chartjs/Chart.js/blob/8fdf76f8f02d31684d34704341a5d9217e977491/docs/configuration/canvas-background.md
|
|
22664
|
-
*/
|
|
22665
|
-
const backgroundColorChartJSPlugin = {
|
|
22666
|
-
id: "customCanvasBackgroundColor",
|
|
22667
|
-
beforeDraw: (chart) => {
|
|
22668
|
-
const { ctx } = chart;
|
|
22669
|
-
ctx.save();
|
|
22670
|
-
ctx.globalCompositeOperation = "destination-over";
|
|
22671
|
-
ctx.fillStyle = "#ffffff";
|
|
22672
|
-
ctx.fillRect(0, 0, chart.width, chart.height);
|
|
22673
|
-
ctx.restore();
|
|
22674
|
-
},
|
|
22675
|
-
};
|
|
22676
|
-
|
|
22677
22735
|
/**
|
|
22678
22736
|
* Represent a raw XML string
|
|
22679
22737
|
*/
|
|
@@ -22735,6 +22793,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22735
22793
|
const CONTENT_TYPES = {
|
|
22736
22794
|
workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
22737
22795
|
sheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
|
22796
|
+
metadata: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
|
|
22738
22797
|
sharedStrings: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
|
|
22739
22798
|
styles: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
|
|
22740
22799
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
@@ -22747,6 +22806,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22747
22806
|
const XLSX_RELATION_TYPE = {
|
|
22748
22807
|
document: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
|
22749
22808
|
sheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
|
|
22809
|
+
metadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
|
|
22750
22810
|
sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
|
|
22751
22811
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
22752
22812
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
@@ -22756,6 +22816,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22756
22816
|
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
22757
22817
|
image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
22758
22818
|
};
|
|
22819
|
+
const ARRAY_FORMULA_URI = "bdbb8cdc-fa1e-496e-a857-3c3f30c029c3";
|
|
22759
22820
|
const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
22760
22821
|
const HEIGHT_FACTOR = 0.75; // 100px => 75 u
|
|
22761
22822
|
/**
|
|
@@ -25321,29 +25382,34 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
25321
25382
|
* In all the sheets, replace the table-only references in the formula cells with standard references.
|
|
25322
25383
|
*/
|
|
25323
25384
|
function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
25324
|
-
for (let
|
|
25325
|
-
const tables = xlsxSheets.find((s) => s.sheetName ===
|
|
25385
|
+
for (let tableSheet of convertedSheets) {
|
|
25386
|
+
const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
|
|
25326
25387
|
for (let table of tables) {
|
|
25327
25388
|
const tabRef = table.name + "[";
|
|
25328
|
-
for (let
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
25337
|
-
|
|
25338
|
-
|
|
25339
|
-
|
|
25389
|
+
for (let sheet of convertedSheets) {
|
|
25390
|
+
for (let xc in sheet.cells) {
|
|
25391
|
+
const cell = sheet.cells[xc];
|
|
25392
|
+
let cellContent = sheet.cells[xc];
|
|
25393
|
+
if (cell && cellContent && cellContent.startsWith("=")) {
|
|
25394
|
+
let refIndex;
|
|
25395
|
+
while ((refIndex = cellContent.indexOf(tabRef)) !== -1) {
|
|
25396
|
+
let endIndex = refIndex + tabRef.length;
|
|
25397
|
+
let openBrackets = 1;
|
|
25398
|
+
while (openBrackets > 0 && endIndex < cellContent.length) {
|
|
25399
|
+
if (cellContent[endIndex] === "[") {
|
|
25400
|
+
openBrackets++;
|
|
25401
|
+
}
|
|
25402
|
+
else if (cellContent[endIndex] === "]") {
|
|
25403
|
+
openBrackets--;
|
|
25404
|
+
}
|
|
25405
|
+
endIndex++;
|
|
25406
|
+
}
|
|
25407
|
+
let reference = cellContent.slice(refIndex + tabRef.length, endIndex - 1);
|
|
25408
|
+
const sheetPrefix = tableSheet.id === sheet.id ? "" : tableSheet.name + "!";
|
|
25409
|
+
const convertedRef = convertTableReference(sheetPrefix, reference, table, xc);
|
|
25410
|
+
cellContent =
|
|
25411
|
+
cellContent.slice(0, refIndex) + convertedRef + cellContent.slice(endIndex);
|
|
25340
25412
|
}
|
|
25341
|
-
reference = reference.slice(0, endIndex);
|
|
25342
|
-
const convertedRef = convertTableReference(reference, table, xc);
|
|
25343
|
-
cellContent =
|
|
25344
|
-
cellContent.slice(0, refIndex) +
|
|
25345
|
-
convertedRef +
|
|
25346
|
-
cellContent.slice(tabRef.length + refIndex + endIndex + 1);
|
|
25347
25413
|
}
|
|
25348
25414
|
sheet.cells[xc] = cellContent;
|
|
25349
25415
|
}
|
|
@@ -25352,11 +25418,17 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
25352
25418
|
}
|
|
25353
25419
|
}
|
|
25354
25420
|
/**
|
|
25355
|
-
* Convert table-specific references in formulas into standard references.
|
|
25421
|
+
* Convert table-specific references in formulas into standard references. A table reference is composed of columns names,
|
|
25422
|
+
* and of keywords determining the rows of the table to reference.
|
|
25356
25423
|
*
|
|
25357
25424
|
* A reference in a table can have the form (only the part between brackets should be given to this function):
|
|
25358
25425
|
* - tableName[colName] : reference to the whole column "colName"
|
|
25426
|
+
* - tableName[#keyword] : reference to the whatever row the keyword refers to
|
|
25359
25427
|
* - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
|
|
25428
|
+
* - tableName[[#keyword], [colName]:[col2Name]] : reference to some of the element(s) of the columns colName to col2Name
|
|
25429
|
+
* - tableName[[#keyword1], [#keyword2], [colName]] : reference to all the rows referenced by the keywords in the column colName
|
|
25430
|
+
* - tableName[[#keyword1], [colName], [#keyword2]]: the keywords and colName can be in any order
|
|
25431
|
+
*
|
|
25360
25432
|
*
|
|
25361
25433
|
* The available keywords are :
|
|
25362
25434
|
* - #All : all the column (including totals)
|
|
@@ -25364,58 +25436,109 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
25364
25436
|
* - #Headers : only the header of the column
|
|
25365
25437
|
* - #Totals : only the totals of the column
|
|
25366
25438
|
* - #This Row : only the element in the same row as the cell
|
|
25439
|
+
*
|
|
25440
|
+
* Note that the only valid combination of multiple keywords are #Data + #Totals and #Headers + #Data.
|
|
25367
25441
|
*/
|
|
25368
|
-
function convertTableReference(expr, table, cellXc) {
|
|
25369
|
-
|
|
25442
|
+
function convertTableReference(sheetPrefix, expr, table, cellXc) {
|
|
25443
|
+
// TODO: Ideally we'd want to make a real tokenizer, this simple approach won't work if for example the column name
|
|
25444
|
+
// contain # or , characters. But that's probably an edge case that we can ignore for now.
|
|
25445
|
+
const parts = expr.split(",").map((part) => part.trim());
|
|
25370
25446
|
const tableZone = toZone(table.ref);
|
|
25371
|
-
const
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
25447
|
+
const colIndexes = [];
|
|
25448
|
+
const rowIndexes = [];
|
|
25449
|
+
const foundKeywords = [];
|
|
25450
|
+
for (const part of parts) {
|
|
25451
|
+
if (removeBrackets(part).startsWith("#")) {
|
|
25452
|
+
const keyWord = removeBrackets(part);
|
|
25453
|
+
foundKeywords.push(keyWord);
|
|
25454
|
+
switch (keyWord) {
|
|
25455
|
+
case "#All":
|
|
25456
|
+
rowIndexes.push(tableZone.top, tableZone.bottom);
|
|
25457
|
+
break;
|
|
25458
|
+
case "#Data":
|
|
25459
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25460
|
+
const bottom = table.totalsRowCount
|
|
25461
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25462
|
+
: tableZone.bottom;
|
|
25463
|
+
rowIndexes.push(top, bottom);
|
|
25464
|
+
break;
|
|
25465
|
+
case "#This Row":
|
|
25466
|
+
rowIndexes.push(toCartesian(cellXc).row);
|
|
25467
|
+
break;
|
|
25468
|
+
case "#Headers":
|
|
25469
|
+
if (!table.headerRowCount) {
|
|
25470
|
+
return CellErrorType.InvalidReference;
|
|
25471
|
+
}
|
|
25472
|
+
rowIndexes.push(tableZone.top);
|
|
25473
|
+
break;
|
|
25474
|
+
case "#Totals":
|
|
25475
|
+
if (!table.totalsRowCount) {
|
|
25476
|
+
return CellErrorType.InvalidReference;
|
|
25477
|
+
}
|
|
25478
|
+
rowIndexes.push(tableZone.bottom);
|
|
25479
|
+
break;
|
|
25480
|
+
}
|
|
25382
25481
|
}
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25400
|
-
if (!table.headerRowCount) {
|
|
25401
|
-
isReferencedZoneValid = false;
|
|
25402
|
-
}
|
|
25403
|
-
break;
|
|
25404
|
-
case "#Totals":
|
|
25405
|
-
refZone.top = refZone.bottom = tableZone.bottom;
|
|
25406
|
-
if (!table.totalsRowCount) {
|
|
25407
|
-
isReferencedZoneValid = false;
|
|
25482
|
+
else {
|
|
25483
|
+
const columns = part
|
|
25484
|
+
.split(":")
|
|
25485
|
+
.map((part) => part.trim())
|
|
25486
|
+
.map(removeBrackets);
|
|
25487
|
+
if (colIndexes.length) {
|
|
25488
|
+
return CellErrorType.InvalidReference;
|
|
25489
|
+
}
|
|
25490
|
+
const colRelativeIndex = table.cols.findIndex((col) => col.name === columns[0]);
|
|
25491
|
+
if (colRelativeIndex === -1) {
|
|
25492
|
+
return CellErrorType.InvalidReference;
|
|
25493
|
+
}
|
|
25494
|
+
colIndexes.push(colRelativeIndex + tableZone.left);
|
|
25495
|
+
if (columns[1]) {
|
|
25496
|
+
const colRelativeIndex2 = table.cols.findIndex((col) => col.name === columns[1]);
|
|
25497
|
+
if (colRelativeIndex2 === -1) {
|
|
25498
|
+
return CellErrorType.InvalidReference;
|
|
25408
25499
|
}
|
|
25409
|
-
|
|
25500
|
+
colIndexes.push(colRelativeIndex2 + tableZone.left);
|
|
25501
|
+
}
|
|
25410
25502
|
}
|
|
25411
|
-
const colRef = refElements[1].slice(1, refElements[1].length - 1);
|
|
25412
|
-
const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
|
|
25413
|
-
refZone.left = refZone.right = colRelativeIndex + tableZone.left;
|
|
25414
25503
|
}
|
|
25415
|
-
if (!
|
|
25504
|
+
if (!areKeywordsCompatible(foundKeywords)) {
|
|
25416
25505
|
return CellErrorType.InvalidReference;
|
|
25417
25506
|
}
|
|
25418
|
-
|
|
25507
|
+
if (rowIndexes.length === 0) {
|
|
25508
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25509
|
+
const bottom = table.totalsRowCount
|
|
25510
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25511
|
+
: tableZone.bottom;
|
|
25512
|
+
rowIndexes.push(top, bottom);
|
|
25513
|
+
}
|
|
25514
|
+
if (colIndexes.length === 0) {
|
|
25515
|
+
colIndexes.push(tableZone.left, tableZone.right);
|
|
25516
|
+
}
|
|
25517
|
+
const refZone = {
|
|
25518
|
+
top: Math.min(...rowIndexes),
|
|
25519
|
+
left: Math.min(...colIndexes),
|
|
25520
|
+
bottom: Math.max(...rowIndexes),
|
|
25521
|
+
right: Math.max(...colIndexes),
|
|
25522
|
+
};
|
|
25523
|
+
return sheetPrefix + zoneToXc(refZone);
|
|
25524
|
+
}
|
|
25525
|
+
function removeBrackets(str) {
|
|
25526
|
+
return str.startsWith("[") && str.endsWith("]") ? str.slice(1, str.length - 1) : str;
|
|
25527
|
+
}
|
|
25528
|
+
function areKeywordsCompatible(keywords) {
|
|
25529
|
+
if (keywords.length < 2) {
|
|
25530
|
+
return true;
|
|
25531
|
+
}
|
|
25532
|
+
else if (keywords.length > 2) {
|
|
25533
|
+
return false;
|
|
25534
|
+
}
|
|
25535
|
+
else if (keywords.includes("#Data") && keywords.includes("#Totals")) {
|
|
25536
|
+
return true;
|
|
25537
|
+
}
|
|
25538
|
+
else if (keywords.includes("#Headers") && keywords.includes("#Data")) {
|
|
25539
|
+
return true;
|
|
25540
|
+
}
|
|
25541
|
+
return false;
|
|
25419
25542
|
}
|
|
25420
25543
|
|
|
25421
25544
|
// -------------------------------------
|
|
@@ -28268,7 +28391,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28268
28391
|
}
|
|
28269
28392
|
function getPyramidChartData(definition, dataSets, labelRange, getters) {
|
|
28270
28393
|
const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
|
|
28271
|
-
const barDataset = barChartData.dataSetsValues;
|
|
28394
|
+
const barDataset = barChartData.dataSetsValues.filter((ds) => !ds.hidden);
|
|
28272
28395
|
const pyramidDatasetValues = [];
|
|
28273
28396
|
if (barDataset[0]) {
|
|
28274
28397
|
const pyramidData = barDataset[0].data.map((value) => (value > 0 ? value : 0));
|
|
@@ -28583,11 +28706,12 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28583
28706
|
}
|
|
28584
28707
|
let missingTimeAdapterAlreadyWarned = false;
|
|
28585
28708
|
function isLuxonTimeAdapterInstalled() {
|
|
28586
|
-
|
|
28709
|
+
const Chart = getChartJSConstructor();
|
|
28710
|
+
if (!Chart) {
|
|
28587
28711
|
return false;
|
|
28588
28712
|
}
|
|
28589
28713
|
// @ts-ignore
|
|
28590
|
-
const adapter = new
|
|
28714
|
+
const adapter = new Chart._adapters._date({});
|
|
28591
28715
|
const isInstalled = adapter._id === "luxon";
|
|
28592
28716
|
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
28593
28717
|
missingTimeAdapterAlreadyWarned = true;
|
|
@@ -28745,10 +28869,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28745
28869
|
function getChartDatasetValues(getters, dataSets) {
|
|
28746
28870
|
const datasetValues = [];
|
|
28747
28871
|
for (const [dsIndex, ds] of Object.entries(dataSets)) {
|
|
28748
|
-
if (getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left)) {
|
|
28749
|
-
continue;
|
|
28750
|
-
}
|
|
28751
28872
|
let label;
|
|
28873
|
+
let hidden = getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left);
|
|
28752
28874
|
if (ds.labelCell) {
|
|
28753
28875
|
const labelRange = ds.labelCell;
|
|
28754
28876
|
const cell = labelRange
|
|
@@ -28775,9 +28897,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28775
28897
|
data.fill(1);
|
|
28776
28898
|
}
|
|
28777
28899
|
else if (data.every((cell) => cell === undefined || cell === null || !isNumber(cell.toString(), DEFAULT_LOCALE))) {
|
|
28778
|
-
|
|
28900
|
+
hidden = true;
|
|
28779
28901
|
}
|
|
28780
|
-
datasetValues.push({ data, label });
|
|
28902
|
+
datasetValues.push({ data, label, hidden });
|
|
28781
28903
|
}
|
|
28782
28904
|
return datasetValues;
|
|
28783
28905
|
}
|
|
@@ -28788,12 +28910,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28788
28910
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28789
28911
|
const trendDatasets = [];
|
|
28790
28912
|
for (const index in dataSetsValues) {
|
|
28791
|
-
let { label, data } = dataSetsValues[index];
|
|
28913
|
+
let { label, data, hidden } = dataSetsValues[index];
|
|
28792
28914
|
label = definition.dataSets?.[index].label || label;
|
|
28793
28915
|
const backgroundColor = colors.next();
|
|
28794
28916
|
const dataset = {
|
|
28795
28917
|
label,
|
|
28796
28918
|
data,
|
|
28919
|
+
hidden,
|
|
28797
28920
|
borderColor: definition.background || BACKGROUND_CHART_COLOR,
|
|
28798
28921
|
borderWidth: definition.stacked ? 1 : 0,
|
|
28799
28922
|
backgroundColor,
|
|
@@ -28826,6 +28949,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28826
28949
|
const labelsWithSubTotals = [];
|
|
28827
28950
|
let lastValue = 0;
|
|
28828
28951
|
for (const dataSetsValue of dataSetsValues) {
|
|
28952
|
+
if (dataSetsValue.hidden) {
|
|
28953
|
+
continue;
|
|
28954
|
+
}
|
|
28829
28955
|
for (let i = 0; i < dataSetsValue.data.length; i++) {
|
|
28830
28956
|
const data = dataSetsValue.data[i];
|
|
28831
28957
|
labelsWithSubTotals.push(labels[i]);
|
|
@@ -28861,7 +28987,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28861
28987
|
const trendDatasets = [];
|
|
28862
28988
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28863
28989
|
for (let index = 0; index < dataSetsValues.length; index++) {
|
|
28864
|
-
let { label, data } = dataSetsValues[index];
|
|
28990
|
+
let { label, data, hidden } = dataSetsValues[index];
|
|
28865
28991
|
label = definition.dataSets?.[index].label || label;
|
|
28866
28992
|
const color = colors.next();
|
|
28867
28993
|
if (axisType && ["linear", "time"].includes(axisType)) {
|
|
@@ -28871,6 +28997,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28871
28997
|
const dataset = {
|
|
28872
28998
|
label,
|
|
28873
28999
|
data,
|
|
29000
|
+
hidden,
|
|
28874
29001
|
tension: 0, // 0 -> render straight lines, which is much faster
|
|
28875
29002
|
borderColor: color,
|
|
28876
29003
|
backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
|
|
@@ -28903,11 +29030,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28903
29030
|
const dataSets = [];
|
|
28904
29031
|
const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
|
|
28905
29032
|
const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
|
|
28906
|
-
for (const { label, data } of dataSetsValues) {
|
|
29033
|
+
for (const { label, data, hidden } of dataSetsValues) {
|
|
29034
|
+
if (hidden)
|
|
29035
|
+
continue;
|
|
28907
29036
|
const dataset = {
|
|
28908
29037
|
label,
|
|
28909
29038
|
data,
|
|
28910
|
-
borderColor:
|
|
29039
|
+
borderColor: definition.background || "#FFFFFF",
|
|
28911
29040
|
backgroundColor,
|
|
28912
29041
|
hoverOffset: 30,
|
|
28913
29042
|
};
|
|
@@ -28921,7 +29050,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28921
29050
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28922
29051
|
const trendDatasets = [];
|
|
28923
29052
|
for (let index = 0; index < dataSetsValues.length; index++) {
|
|
28924
|
-
let { label, data } = dataSetsValues[index];
|
|
29053
|
+
let { label, data, hidden } = dataSetsValues[index];
|
|
28925
29054
|
label = definition.dataSets?.[index].label || label;
|
|
28926
29055
|
const design = definition.dataSets?.[index];
|
|
28927
29056
|
const color = colors.next();
|
|
@@ -28929,6 +29058,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28929
29058
|
const dataset = {
|
|
28930
29059
|
label: label,
|
|
28931
29060
|
data,
|
|
29061
|
+
hidden,
|
|
28932
29062
|
borderColor: color,
|
|
28933
29063
|
backgroundColor: color,
|
|
28934
29064
|
yAxisID: definition.dataSets?.[index].yAxisId || "y",
|
|
@@ -28953,7 +29083,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28953
29083
|
const fill = definition.fillArea ?? false;
|
|
28954
29084
|
const colors = getChartColorsGenerator(definition, dataSetsValues.length);
|
|
28955
29085
|
for (let i = 0; i < dataSetsValues.length; i++) {
|
|
28956
|
-
let { label, data } = dataSetsValues[i];
|
|
29086
|
+
let { label, data, hidden } = dataSetsValues[i];
|
|
28957
29087
|
if (definition.dataSets?.[i]?.label) {
|
|
28958
29088
|
label = definition.dataSets[i].label;
|
|
28959
29089
|
}
|
|
@@ -28961,6 +29091,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
28961
29091
|
const dataset = {
|
|
28962
29092
|
label,
|
|
28963
29093
|
data,
|
|
29094
|
+
hidden,
|
|
28964
29095
|
borderColor,
|
|
28965
29096
|
backgroundColor: borderColor,
|
|
28966
29097
|
};
|
|
@@ -29106,6 +29237,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
29106
29237
|
hidden: false,
|
|
29107
29238
|
lineWidth: 2,
|
|
29108
29239
|
})),
|
|
29240
|
+
filter: (legendItem, data) => {
|
|
29241
|
+
return "datasetIndex" in legendItem
|
|
29242
|
+
? !data.datasets[legendItem.datasetIndex].hidden
|
|
29243
|
+
: true;
|
|
29244
|
+
},
|
|
29109
29245
|
},
|
|
29110
29246
|
};
|
|
29111
29247
|
}
|
|
@@ -29167,6 +29303,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
29167
29303
|
}
|
|
29168
29304
|
return legendValues;
|
|
29169
29305
|
},
|
|
29306
|
+
filter: (legendItem, data) => {
|
|
29307
|
+
return "datasetIndex" in legendItem
|
|
29308
|
+
? !data.datasets[legendItem.datasetIndex].hidden
|
|
29309
|
+
: true;
|
|
29310
|
+
},
|
|
29170
29311
|
},
|
|
29171
29312
|
onClick: () => { }, // Disables click interaction with the waterfall chart legend items
|
|
29172
29313
|
};
|
|
@@ -29250,6 +29391,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
29250
29391
|
...legendLabelConfig,
|
|
29251
29392
|
};
|
|
29252
29393
|
}),
|
|
29394
|
+
filter: (legendItem, data) => {
|
|
29395
|
+
return "datasetIndex" in legendItem
|
|
29396
|
+
? !data.datasets[legendItem.datasetIndex].hidden
|
|
29397
|
+
: true;
|
|
29398
|
+
},
|
|
29253
29399
|
},
|
|
29254
29400
|
};
|
|
29255
29401
|
}
|
|
@@ -32262,10 +32408,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32262
32408
|
this.currentDisplayValue = newDisplay;
|
|
32263
32409
|
if (!anchor)
|
|
32264
32410
|
return;
|
|
32265
|
-
el.style.top = "";
|
|
32266
|
-
el.style.left = "";
|
|
32267
|
-
el.style["max-height"] = "";
|
|
32268
|
-
el.style["max-width"] = "";
|
|
32269
32411
|
const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
|
|
32270
32412
|
let elDims = {
|
|
32271
32413
|
width: el.getBoundingClientRect().width,
|
|
@@ -33039,6 +33181,100 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33039
33181
|
function getOpenedMenus() {
|
|
33040
33182
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
33041
33183
|
}
|
|
33184
|
+
function getCurrentSelection(el) {
|
|
33185
|
+
let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
|
|
33186
|
+
let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
|
|
33187
|
+
let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
|
|
33188
|
+
return {
|
|
33189
|
+
start: startSizeBefore,
|
|
33190
|
+
end: endSizeBefore,
|
|
33191
|
+
};
|
|
33192
|
+
}
|
|
33193
|
+
function getStartAndEndSelection(el) {
|
|
33194
|
+
const selection = document.getSelection();
|
|
33195
|
+
return {
|
|
33196
|
+
startElement: selection.anchorNode || el,
|
|
33197
|
+
startSelectionOffset: selection.anchorOffset,
|
|
33198
|
+
endElement: selection.focusNode || el,
|
|
33199
|
+
endSelectionOffset: selection.focusOffset,
|
|
33200
|
+
};
|
|
33201
|
+
}
|
|
33202
|
+
/**
|
|
33203
|
+
* Computes the text 'index' inside this.el based on the currently selected node and its offset.
|
|
33204
|
+
* The selected node is either a Text node or an Element node.
|
|
33205
|
+
*
|
|
33206
|
+
* case 1 -Text node:
|
|
33207
|
+
* the offset is the number of characters from the start of the node. We have to add this offset to the
|
|
33208
|
+
* content length of all previous nodes.
|
|
33209
|
+
*
|
|
33210
|
+
* case 2 - Element node:
|
|
33211
|
+
* the offset is the number of child nodes before the selected node. We have to add the content length of
|
|
33212
|
+
* all the nodes prior to the selected node as well as the content of the child node before the offset.
|
|
33213
|
+
*
|
|
33214
|
+
* See the MDN documentation for more details.
|
|
33215
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
|
|
33216
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
|
|
33217
|
+
*
|
|
33218
|
+
*/
|
|
33219
|
+
function findSelectionIndex(el, nodeToFind, nodeOffset) {
|
|
33220
|
+
let usedCharacters = 0;
|
|
33221
|
+
let it = iterateChildren(el);
|
|
33222
|
+
let current = it.next();
|
|
33223
|
+
let isFirstParagraph = true;
|
|
33224
|
+
while (!current.done && current.value !== nodeToFind) {
|
|
33225
|
+
if (!current.value.hasChildNodes()) {
|
|
33226
|
+
if (current.value.textContent) {
|
|
33227
|
+
usedCharacters += current.value.textContent.length;
|
|
33228
|
+
}
|
|
33229
|
+
}
|
|
33230
|
+
// One new paragraph = one new line character, except for the first paragraph
|
|
33231
|
+
if (current.value.nodeName === "P" ||
|
|
33232
|
+
(current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
|
|
33233
|
+
) {
|
|
33234
|
+
if (isFirstParagraph) {
|
|
33235
|
+
isFirstParagraph = false;
|
|
33236
|
+
}
|
|
33237
|
+
else {
|
|
33238
|
+
usedCharacters++;
|
|
33239
|
+
}
|
|
33240
|
+
}
|
|
33241
|
+
current = it.next();
|
|
33242
|
+
}
|
|
33243
|
+
if (current.value !== nodeToFind) {
|
|
33244
|
+
/** This situation can happen if the code is called while the selection is not currently on the element.
|
|
33245
|
+
* In this case, we return 0 because we don't know the size of the text before the selection.
|
|
33246
|
+
*
|
|
33247
|
+
* A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
|
|
33248
|
+
*/
|
|
33249
|
+
return 0;
|
|
33250
|
+
}
|
|
33251
|
+
else {
|
|
33252
|
+
if (!current.value.hasChildNodes()) {
|
|
33253
|
+
usedCharacters += nodeOffset;
|
|
33254
|
+
}
|
|
33255
|
+
else {
|
|
33256
|
+
const children = [...current.value.childNodes].slice(0, nodeOffset);
|
|
33257
|
+
usedCharacters += children.reduce((acc, child, index) => {
|
|
33258
|
+
if (child.textContent !== null) {
|
|
33259
|
+
// need to account for paragraph nodes that implicitly add a new line
|
|
33260
|
+
// except for the last paragraph
|
|
33261
|
+
let chars = child.textContent.length;
|
|
33262
|
+
if (child.nodeName === "P" && index !== children.length - 1) {
|
|
33263
|
+
chars++;
|
|
33264
|
+
}
|
|
33265
|
+
return acc + chars;
|
|
33266
|
+
}
|
|
33267
|
+
else {
|
|
33268
|
+
return acc;
|
|
33269
|
+
}
|
|
33270
|
+
}, 0);
|
|
33271
|
+
}
|
|
33272
|
+
}
|
|
33273
|
+
if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
|
|
33274
|
+
usedCharacters++;
|
|
33275
|
+
}
|
|
33276
|
+
return usedCharacters;
|
|
33277
|
+
}
|
|
33042
33278
|
const letterRegex = /^[a-zA-Z]$/;
|
|
33043
33279
|
/**
|
|
33044
33280
|
* Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
|
|
@@ -33709,6 +33945,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33709
33945
|
drawScoreChart: drawScoreChart,
|
|
33710
33946
|
formatChartDatasetValue: formatChartDatasetValue,
|
|
33711
33947
|
formatTickValue: formatTickValue,
|
|
33948
|
+
getChartJSConstructor: getChartJSConstructor,
|
|
33712
33949
|
getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
|
|
33713
33950
|
getDefinedAxis: getDefinedAxis,
|
|
33714
33951
|
getPieColors: getPieColors,
|
|
@@ -37592,6 +37829,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
37592
37829
|
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
|
|
37593
37830
|
dataSets: this.dataSeriesRanges,
|
|
37594
37831
|
});
|
|
37832
|
+
if (this.state.datasetDispatchResult.isSuccessful) {
|
|
37833
|
+
this.dataSeriesRanges = this.env.model.getters.getChartDefinition(this.props.figureId).dataSets;
|
|
37834
|
+
}
|
|
37595
37835
|
}
|
|
37596
37836
|
getDataSeriesRanges() {
|
|
37597
37837
|
return this.dataSeriesRanges;
|
|
@@ -38274,7 +38514,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38274
38514
|
.o-font-size-editor {
|
|
38275
38515
|
height: calc(100% - 4px);
|
|
38276
38516
|
input.o-font-size {
|
|
38277
|
-
outline
|
|
38517
|
+
outline: none;
|
|
38278
38518
|
height: 20px;
|
|
38279
38519
|
width: 23px;
|
|
38280
38520
|
}
|
|
@@ -39887,6 +40127,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
39887
40127
|
if (currentStart === start && currentEnd === end) {
|
|
39888
40128
|
return;
|
|
39889
40129
|
}
|
|
40130
|
+
if (selection.rangeCount === 0) {
|
|
40131
|
+
const range = document.createRange();
|
|
40132
|
+
selection.addRange(range);
|
|
40133
|
+
}
|
|
39890
40134
|
const currentRange = selection.getRangeAt(0);
|
|
39891
40135
|
let range;
|
|
39892
40136
|
if (this.el.contains(currentRange.startContainer)) {
|
|
@@ -39914,8 +40158,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
39914
40158
|
}
|
|
39915
40159
|
let startNode = this.findChildAtCharacterIndex(start);
|
|
39916
40160
|
let endNode = this.findChildAtCharacterIndex(end);
|
|
39917
|
-
|
|
39918
|
-
|
|
40161
|
+
// setEnd (setStart) will result in a collapsed range if the end point is before the start point
|
|
40162
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd
|
|
40163
|
+
if (start <= end) {
|
|
40164
|
+
range.setStart(startNode.node, startNode.offset);
|
|
40165
|
+
range.setEnd(endNode.node, endNode.offset);
|
|
40166
|
+
}
|
|
40167
|
+
else {
|
|
40168
|
+
range.setStart(endNode.node, endNode.offset);
|
|
40169
|
+
range.setEnd(startNode.node, startNode.offset);
|
|
40170
|
+
}
|
|
39919
40171
|
}
|
|
39920
40172
|
}
|
|
39921
40173
|
/**
|
|
@@ -40049,7 +40301,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40049
40301
|
if (!focusedNode || !this.el.contains(focusedNode))
|
|
40050
40302
|
return;
|
|
40051
40303
|
const element = focusedNode instanceof HTMLElement ? focusedNode : focusedNode.parentElement;
|
|
40052
|
-
element?.scrollIntoView({ block: "nearest" });
|
|
40304
|
+
element?.scrollIntoView?.({ block: "nearest" });
|
|
40053
40305
|
}
|
|
40054
40306
|
/**
|
|
40055
40307
|
* remove the current selection of the user
|
|
@@ -40069,100 +40321,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40069
40321
|
* finds the indexes of the current selection.
|
|
40070
40322
|
* */
|
|
40071
40323
|
getCurrentSelection() {
|
|
40072
|
-
|
|
40073
|
-
let startSizeBefore = this.findSelectionIndex(startElement, startSelectionOffset);
|
|
40074
|
-
let endSizeBefore = this.findSelectionIndex(endElement, endSelectionOffset);
|
|
40075
|
-
return {
|
|
40076
|
-
start: startSizeBefore,
|
|
40077
|
-
end: endSizeBefore,
|
|
40078
|
-
};
|
|
40079
|
-
}
|
|
40080
|
-
/**
|
|
40081
|
-
* Computes the text 'index' inside this.el based on the currently selected node and its offset.
|
|
40082
|
-
* The selected node is either a Text node or an Element node.
|
|
40083
|
-
*
|
|
40084
|
-
* case 1 -Text node:
|
|
40085
|
-
* the offset is the number of characters from the start of the node. We have to add this offset to the
|
|
40086
|
-
* content length of all previous nodes.
|
|
40087
|
-
*
|
|
40088
|
-
* case 2 - Element node:
|
|
40089
|
-
* the offset is the number of child nodes before the selected node. We have to add the content length of
|
|
40090
|
-
* all the bnodes prior to the selected node as well as the content of the child node before the offset.
|
|
40091
|
-
*
|
|
40092
|
-
* See the MDN documentation for more details.
|
|
40093
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
|
|
40094
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
|
|
40095
|
-
*
|
|
40096
|
-
*/
|
|
40097
|
-
findSelectionIndex(nodeToFind, nodeOffset) {
|
|
40098
|
-
let usedCharacters = 0;
|
|
40099
|
-
let it = iterateChildren(this.el);
|
|
40100
|
-
let current = it.next();
|
|
40101
|
-
let isFirstParagraph = true;
|
|
40102
|
-
while (!current.done && current.value !== nodeToFind) {
|
|
40103
|
-
if (!current.value.hasChildNodes()) {
|
|
40104
|
-
if (current.value.textContent) {
|
|
40105
|
-
usedCharacters += current.value.textContent.length;
|
|
40106
|
-
}
|
|
40107
|
-
}
|
|
40108
|
-
// One new paragraph = one new line character, except for the first paragraph
|
|
40109
|
-
if (current.value.nodeName === "P" ||
|
|
40110
|
-
(current.value.nodeName === "DIV" && current.value !== this.el) // On paste, the HTML may contain <div> instead of <p>
|
|
40111
|
-
) {
|
|
40112
|
-
if (isFirstParagraph) {
|
|
40113
|
-
isFirstParagraph = false;
|
|
40114
|
-
}
|
|
40115
|
-
else {
|
|
40116
|
-
usedCharacters++;
|
|
40117
|
-
}
|
|
40118
|
-
}
|
|
40119
|
-
current = it.next();
|
|
40120
|
-
}
|
|
40121
|
-
if (current.value !== nodeToFind) {
|
|
40122
|
-
/** This situation can happen if the code is called while the selection is not currently on the ContentEditableHelper.
|
|
40123
|
-
* In this case, we return 0 because we don't know the size of the text before the selection.
|
|
40124
|
-
*
|
|
40125
|
-
* A known occurence is triggered since the introduction of commit d4663158 (PR #2038).
|
|
40126
|
-
*
|
|
40127
|
-
* FIXME: find a way to test eventhough the selection API is not available in jsDOM.
|
|
40128
|
-
*/
|
|
40129
|
-
return 0;
|
|
40130
|
-
}
|
|
40131
|
-
else {
|
|
40132
|
-
if (!current.value.hasChildNodes()) {
|
|
40133
|
-
usedCharacters += nodeOffset;
|
|
40134
|
-
}
|
|
40135
|
-
else {
|
|
40136
|
-
const children = [...current.value.childNodes].slice(0, nodeOffset);
|
|
40137
|
-
usedCharacters += children.reduce((acc, child, index) => {
|
|
40138
|
-
if (child.textContent !== null) {
|
|
40139
|
-
// need to account for paragraph nodes that implicitely add a new line
|
|
40140
|
-
// except for the last paragraph
|
|
40141
|
-
let chars = child.textContent.length;
|
|
40142
|
-
if (child.nodeName === "P" && index !== children.length - 1) {
|
|
40143
|
-
chars++;
|
|
40144
|
-
}
|
|
40145
|
-
return acc + chars;
|
|
40146
|
-
}
|
|
40147
|
-
else {
|
|
40148
|
-
return acc;
|
|
40149
|
-
}
|
|
40150
|
-
}, 0);
|
|
40151
|
-
}
|
|
40152
|
-
}
|
|
40153
|
-
if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
|
|
40154
|
-
usedCharacters++;
|
|
40155
|
-
}
|
|
40156
|
-
return usedCharacters;
|
|
40157
|
-
}
|
|
40158
|
-
getStartAndEndSelection() {
|
|
40159
|
-
const selection = document.getSelection();
|
|
40160
|
-
return {
|
|
40161
|
-
startElement: selection.anchorNode || this.el,
|
|
40162
|
-
startSelectionOffset: selection.anchorOffset,
|
|
40163
|
-
endElement: selection.focusNode || this.el,
|
|
40164
|
-
endSelectionOffset: selection.focusOffset,
|
|
40165
|
-
};
|
|
40324
|
+
return getCurrentSelection(this.el);
|
|
40166
40325
|
}
|
|
40167
40326
|
getText() {
|
|
40168
40327
|
let text = "";
|
|
@@ -40312,8 +40471,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40312
40471
|
}
|
|
40313
40472
|
|
|
40314
40473
|
.o-composer-assistant {
|
|
40315
|
-
|
|
40316
|
-
margin: 1px 4px;
|
|
40474
|
+
margin-top: 1px;
|
|
40317
40475
|
|
|
40318
40476
|
.o-semi-bold {
|
|
40319
40477
|
/* FIXME: to remove in favor of Bootstrap
|
|
@@ -40364,10 +40522,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40364
40522
|
});
|
|
40365
40523
|
compositionActive = false;
|
|
40366
40524
|
spreadsheetRect = useSpreadsheetRect();
|
|
40367
|
-
get
|
|
40525
|
+
get assistantStyleProperties() {
|
|
40368
40526
|
const composerRect = this.composerRef.el.getBoundingClientRect();
|
|
40369
40527
|
const assistantStyle = {};
|
|
40370
|
-
|
|
40528
|
+
const minWidth = Math.min(this.props.rect?.width || Infinity, ASSISTANT_WIDTH);
|
|
40529
|
+
assistantStyle["min-width"] = `${minWidth}px`;
|
|
40371
40530
|
const proposals = this.autoCompleteState.provider?.proposals;
|
|
40372
40531
|
const proposalsHaveDescription = proposals?.some((proposal) => proposal.description);
|
|
40373
40532
|
if (this.functionDescriptionState.showDescription || proposalsHaveDescription) {
|
|
@@ -40391,13 +40550,29 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40391
40550
|
}
|
|
40392
40551
|
}
|
|
40393
40552
|
else {
|
|
40394
|
-
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom}px`;
|
|
40553
|
+
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom - 1}px`; // -1: margin
|
|
40395
40554
|
if (composerRect.left + ASSISTANT_WIDTH + SCROLLBAR_WIDTH + CLOSE_ICON_RADIUS >
|
|
40396
40555
|
this.spreadsheetRect.width) {
|
|
40397
40556
|
assistantStyle.right = `${CLOSE_ICON_RADIUS}px`;
|
|
40398
40557
|
}
|
|
40399
40558
|
}
|
|
40400
|
-
return
|
|
40559
|
+
return assistantStyle;
|
|
40560
|
+
}
|
|
40561
|
+
get assistantStyle() {
|
|
40562
|
+
const allProperties = this.assistantStyleProperties;
|
|
40563
|
+
return cssPropertiesToCss({
|
|
40564
|
+
"max-height": allProperties["max-height"],
|
|
40565
|
+
width: allProperties["width"],
|
|
40566
|
+
"min-width": allProperties["min-width"],
|
|
40567
|
+
});
|
|
40568
|
+
}
|
|
40569
|
+
get assistantContainerStyle() {
|
|
40570
|
+
const allProperties = this.assistantStyleProperties;
|
|
40571
|
+
return cssPropertiesToCss({
|
|
40572
|
+
top: allProperties["top"],
|
|
40573
|
+
right: allProperties["right"],
|
|
40574
|
+
transform: allProperties["transform"],
|
|
40575
|
+
});
|
|
40401
40576
|
}
|
|
40402
40577
|
// we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
|
|
40403
40578
|
shouldProcessInputEvents = false;
|
|
@@ -46334,9 +46509,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
46334
46509
|
pivot: this.draft,
|
|
46335
46510
|
});
|
|
46336
46511
|
this.draft = null;
|
|
46337
|
-
if (!this.alreadyNotified &&
|
|
46338
|
-
!this.isDynamicPivotInViewport() &&
|
|
46339
|
-
this.isStaticPivotInViewport()) {
|
|
46512
|
+
if (!this.alreadyNotified && this.isUpdatedPivotVisibleInViewportOnlyAsStaticPivot()) {
|
|
46340
46513
|
const formulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46341
46514
|
const pivotExample = `=PIVOT(${formulaId})`;
|
|
46342
46515
|
this.alreadyNotified = true;
|
|
@@ -46392,29 +46565,33 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
46392
46565
|
this.applyUpdate();
|
|
46393
46566
|
}
|
|
46394
46567
|
}
|
|
46395
|
-
|
|
46396
|
-
|
|
46397
|
-
|
|
46398
|
-
|
|
46399
|
-
|
|
46400
|
-
|
|
46401
|
-
|
|
46402
|
-
}
|
|
46403
|
-
}
|
|
46404
|
-
}
|
|
46405
|
-
return false;
|
|
46406
|
-
}
|
|
46407
|
-
isStaticPivotInViewport() {
|
|
46568
|
+
/**
|
|
46569
|
+
* @returns true if the updated pivot is visible in the viewport only as a
|
|
46570
|
+
* static pivot and not as a dynamic pivot
|
|
46571
|
+
*/
|
|
46572
|
+
isUpdatedPivotVisibleInViewportOnlyAsStaticPivot() {
|
|
46573
|
+
let staticPivotCount = 0;
|
|
46574
|
+
const updatedPivotFormulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46408
46575
|
for (const position of this.getters.getVisibleCellPositions()) {
|
|
46409
46576
|
const cell = this.getters.getCell(position);
|
|
46410
46577
|
if (cell?.isFormula) {
|
|
46411
46578
|
const pivotFunction = getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
46412
|
-
|
|
46413
|
-
|
|
46579
|
+
const pivotFormulaId = pivotFunction?.args[0]?.value;
|
|
46580
|
+
if (pivotFunction && updatedPivotFormulaId === pivotFormulaId.toString()) {
|
|
46581
|
+
if (pivotFunction.functionName === "PIVOT") {
|
|
46582
|
+
// if we have at least one dynamic pivot visible inserted the viewport
|
|
46583
|
+
// we return false
|
|
46584
|
+
return false;
|
|
46585
|
+
}
|
|
46586
|
+
else {
|
|
46587
|
+
staticPivotCount++;
|
|
46588
|
+
}
|
|
46414
46589
|
}
|
|
46415
46590
|
}
|
|
46416
46591
|
}
|
|
46417
|
-
return
|
|
46592
|
+
// we return true if there are only static pivots visible inserted the viewport,
|
|
46593
|
+
// otherwise false
|
|
46594
|
+
return staticPivotCount > 0;
|
|
46418
46595
|
}
|
|
46419
46596
|
addDefaultDateTimeGranularity(fields, definition) {
|
|
46420
46597
|
const { columns, rows } = definition;
|
|
@@ -53117,6 +53294,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53117
53294
|
return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
|
|
53118
53295
|
case "CLEAR_CELL":
|
|
53119
53296
|
return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
|
|
53297
|
+
case "UPDATE_CELL_POSITION":
|
|
53298
|
+
return !cmd.cellId || this.cells[cmd.sheetId]?.[cmd.cellId]
|
|
53299
|
+
? "Success" /* CommandResult.Success */
|
|
53300
|
+
: "InvalidCellId" /* CommandResult.InvalidCellId */;
|
|
53120
53301
|
default:
|
|
53121
53302
|
return "Success" /* CommandResult.Success */;
|
|
53122
53303
|
}
|
|
@@ -53161,6 +53342,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53161
53342
|
case "DELETE_CONTENT":
|
|
53162
53343
|
this.clearZones(cmd.sheetId, cmd.target);
|
|
53163
53344
|
break;
|
|
53345
|
+
case "DELETE_SHEET": {
|
|
53346
|
+
this.history.update("cells", cmd.sheetId, undefined);
|
|
53347
|
+
}
|
|
53164
53348
|
}
|
|
53165
53349
|
}
|
|
53166
53350
|
clearZones(sheetId, zones) {
|
|
@@ -53951,6 +54135,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53951
54135
|
allowDispatch(cmd) {
|
|
53952
54136
|
switch (cmd.type) {
|
|
53953
54137
|
case "ADD_CONDITIONAL_FORMAT":
|
|
54138
|
+
if (cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId))) {
|
|
54139
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
54140
|
+
}
|
|
53954
54141
|
return this.checkValidations(cmd, this.checkCFRule, this.checkEmptyRange, this.checkCFHasChanged);
|
|
53955
54142
|
case "CHANGE_CONDITIONAL_FORMAT_PRIORITY":
|
|
53956
54143
|
return this.checkValidPriorityChange(cmd.cfId, cmd.delta, cmd.sheetId);
|
|
@@ -54367,8 +54554,17 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54367
54554
|
allowDispatch(cmd) {
|
|
54368
54555
|
switch (cmd.type) {
|
|
54369
54556
|
case "ADD_DATA_VALIDATION_RULE":
|
|
54557
|
+
if (!this.getters.tryGetSheet(cmd.sheetId)) {
|
|
54558
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
54559
|
+
}
|
|
54560
|
+
if (cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId))) {
|
|
54561
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
54562
|
+
}
|
|
54370
54563
|
return this.checkValidations(cmd, this.chainValidations(this.checkEmptyRange, this.checkValidRange, this.checkCriterionTypeIsValid, this.checkCriterionHasValidNumberOfValues, this.checkCriterionValuesAreValid));
|
|
54371
54564
|
case "REMOVE_DATA_VALIDATION_RULE":
|
|
54565
|
+
if (!this.getters.tryGetSheet(cmd.sheetId)) {
|
|
54566
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
54567
|
+
}
|
|
54372
54568
|
if (!this.rules[cmd.sheetId].find((rule) => rule.id === cmd.id)) {
|
|
54373
54569
|
return "UnknownDataValidationRule" /* CommandResult.UnknownDataValidationRule */;
|
|
54374
54570
|
}
|
|
@@ -54595,6 +54791,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54595
54791
|
class FigurePlugin extends CorePlugin {
|
|
54596
54792
|
static getters = ["getFigures", "getFigure", "getFigureSheetId"];
|
|
54597
54793
|
figures = {};
|
|
54794
|
+
insertionOrders = []; // TODO use a list in master
|
|
54598
54795
|
// ---------------------------------------------------------------------------
|
|
54599
54796
|
// Command Handling
|
|
54600
54797
|
// ---------------------------------------------------------------------------
|
|
@@ -54697,11 +54894,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54697
54894
|
}
|
|
54698
54895
|
addFigure(figure, sheetId) {
|
|
54699
54896
|
this.history.update("figures", sheetId, figure.id, figure);
|
|
54897
|
+
this.history.update("insertionOrders", this.insertionOrders.length, figure.id);
|
|
54700
54898
|
}
|
|
54701
54899
|
deleteSheet(sheetId) {
|
|
54900
|
+
this.history.update("insertionOrders", this.insertionOrders.filter((id) => !this.figures[sheetId]?.[id]));
|
|
54702
54901
|
this.history.update("figures", sheetId, undefined);
|
|
54703
54902
|
}
|
|
54704
54903
|
removeFigure(id, sheetId) {
|
|
54904
|
+
this.history.update("insertionOrders", this.insertionOrders.filter((figureId) => figureId !== id));
|
|
54705
54905
|
this.history.update("figures", sheetId, id, undefined);
|
|
54706
54906
|
}
|
|
54707
54907
|
checkFigureExists(sheetId, figureId) {
|
|
@@ -54720,7 +54920,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54720
54920
|
// Getters
|
|
54721
54921
|
// ---------------------------------------------------------------------------
|
|
54722
54922
|
getFigures(sheetId) {
|
|
54723
|
-
|
|
54923
|
+
const figures = [];
|
|
54924
|
+
for (const figureId of this.insertionOrders) {
|
|
54925
|
+
const figure = this.figures[sheetId]?.[figureId];
|
|
54926
|
+
if (figure) {
|
|
54927
|
+
figures.push(figure);
|
|
54928
|
+
}
|
|
54929
|
+
}
|
|
54930
|
+
return figures;
|
|
54724
54931
|
}
|
|
54725
54932
|
getFigure(sheetId, figureId) {
|
|
54726
54933
|
return this.figures[sheetId]?.[figureId];
|
|
@@ -54733,11 +54940,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54733
54940
|
// ---------------------------------------------------------------------------
|
|
54734
54941
|
import(data) {
|
|
54735
54942
|
for (let sheet of data.sheets) {
|
|
54736
|
-
const figures
|
|
54737
|
-
|
|
54738
|
-
|
|
54739
|
-
});
|
|
54740
|
-
this.figures[sheet.id] = figures;
|
|
54943
|
+
for (const figure of sheet.figures) {
|
|
54944
|
+
this.addFigure(figure, sheet.id);
|
|
54945
|
+
}
|
|
54741
54946
|
}
|
|
54742
54947
|
}
|
|
54743
54948
|
export(data) {
|
|
@@ -56163,6 +56368,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
56163
56368
|
case "CREATE_SHEET": {
|
|
56164
56369
|
return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
|
|
56165
56370
|
}
|
|
56371
|
+
case "DUPLICATE_SHEET": {
|
|
56372
|
+
return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
|
|
56373
|
+
}
|
|
56166
56374
|
case "MOVE_SHEET":
|
|
56167
56375
|
try {
|
|
56168
56376
|
const currentIndex = this.orderedSheetIds.findIndex((id) => id === cmd.sheetId);
|
|
@@ -56974,6 +57182,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
56974
57182
|
checkZonesAreInSheet(cmd) {
|
|
56975
57183
|
if (!("sheetId" in cmd))
|
|
56976
57184
|
return "Success" /* CommandResult.Success */;
|
|
57185
|
+
if ("ranges" in cmd &&
|
|
57186
|
+
cmd.ranges.some((rangeData) => rangeData._sheetId !== "" && !this.getters.tryGetSheet(rangeData._sheetId))) {
|
|
57187
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
57188
|
+
}
|
|
56977
57189
|
return this.checkZonesExistInSheet(cmd.sheetId, this.getCommandZones(cmd));
|
|
56978
57190
|
}
|
|
56979
57191
|
}
|
|
@@ -56982,6 +57194,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
56982
57194
|
class TablePlugin extends CorePlugin {
|
|
56983
57195
|
static getters = ["getCoreTable", "getCoreTables", "getCoreTableMatchingTopLeft"];
|
|
56984
57196
|
tables = {};
|
|
57197
|
+
insertionOrders = {};
|
|
56985
57198
|
adaptRanges(applyChange, sheetId) {
|
|
56986
57199
|
const sheetIds = sheetId ? [sheetId] : this.getters.getSheetIds();
|
|
56987
57200
|
for (const sheetId of sheetIds) {
|
|
@@ -56993,6 +57206,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
56993
57206
|
allowDispatch(cmd) {
|
|
56994
57207
|
switch (cmd.type) {
|
|
56995
57208
|
case "CREATE_TABLE":
|
|
57209
|
+
if (cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId) || rangeData._sheetId !== cmd.sheetId)) {
|
|
57210
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
57211
|
+
}
|
|
56996
57212
|
const zones = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone);
|
|
56997
57213
|
if (!areZonesContinuous(zones)) {
|
|
56998
57214
|
return "NonContinuousTargets" /* CommandResult.NonContinuousTargets */;
|
|
@@ -57023,11 +57239,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57023
57239
|
switch (cmd.type) {
|
|
57024
57240
|
case "CREATE_SHEET":
|
|
57025
57241
|
this.history.update("tables", cmd.sheetId, {});
|
|
57242
|
+
this.history.update("insertionOrders", cmd.sheetId, []);
|
|
57026
57243
|
break;
|
|
57027
57244
|
case "DELETE_SHEET": {
|
|
57028
57245
|
const tables = { ...this.tables };
|
|
57029
57246
|
delete tables[cmd.sheetId];
|
|
57030
57247
|
this.history.update("tables", tables);
|
|
57248
|
+
this.history.update("insertionOrders", cmd.sheetId, undefined);
|
|
57031
57249
|
break;
|
|
57032
57250
|
}
|
|
57033
57251
|
case "DUPLICATE_SHEET": {
|
|
@@ -57039,6 +57257,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57039
57257
|
: this.copyStaticTableForSheet(cmd.sheetIdTo, table);
|
|
57040
57258
|
}
|
|
57041
57259
|
this.history.update("tables", cmd.sheetIdTo, newTables);
|
|
57260
|
+
this.history.update("insertionOrders", cmd.sheetIdTo, [
|
|
57261
|
+
...(this.insertionOrders[cmd.sheetId] ?? []),
|
|
57262
|
+
]);
|
|
57042
57263
|
break;
|
|
57043
57264
|
}
|
|
57044
57265
|
case "CREATE_TABLE": {
|
|
@@ -57052,6 +57273,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57052
57273
|
? this.createDynamicTable(id, union, config)
|
|
57053
57274
|
: this.createStaticTable(id, cmd.tableType, union, config);
|
|
57054
57275
|
this.history.update("tables", cmd.sheetId, newTable.id, newTable);
|
|
57276
|
+
this.history.update("insertionOrders", cmd.sheetId, [
|
|
57277
|
+
...(this.insertionOrders[cmd.sheetId] ?? []),
|
|
57278
|
+
newTable.id,
|
|
57279
|
+
]);
|
|
57055
57280
|
break;
|
|
57056
57281
|
}
|
|
57057
57282
|
case "REMOVE_TABLE": {
|
|
@@ -57062,6 +57287,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57062
57287
|
}
|
|
57063
57288
|
}
|
|
57064
57289
|
this.history.update("tables", cmd.sheetId, tables);
|
|
57290
|
+
this.history.update("insertionOrders", cmd.sheetId, this.insertionOrders[cmd.sheetId]?.filter((id) => id in tables));
|
|
57065
57291
|
break;
|
|
57066
57292
|
}
|
|
57067
57293
|
case "UPDATE_TABLE": {
|
|
@@ -57097,7 +57323,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57097
57323
|
}
|
|
57098
57324
|
}
|
|
57099
57325
|
getCoreTables(sheetId) {
|
|
57100
|
-
|
|
57326
|
+
const tables = [];
|
|
57327
|
+
for (const tableId of this.insertionOrders[sheetId] || []) {
|
|
57328
|
+
const table = this.tables[sheetId][tableId];
|
|
57329
|
+
if (table) {
|
|
57330
|
+
tables.push(table);
|
|
57331
|
+
}
|
|
57332
|
+
}
|
|
57333
|
+
return tables;
|
|
57101
57334
|
}
|
|
57102
57335
|
getCoreTable({ sheetId, col, row }) {
|
|
57103
57336
|
return this.getCoreTables(sheetId).find((table) => isInside(col, row, table.range.zone));
|
|
@@ -57380,6 +57613,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57380
57613
|
// ---------------------------------------------------------------------------
|
|
57381
57614
|
import(data) {
|
|
57382
57615
|
for (const sheet of data.sheets) {
|
|
57616
|
+
const tableIds = [];
|
|
57383
57617
|
for (const tableData of sheet.tables || []) {
|
|
57384
57618
|
const uuid = `${nextTableId++}`;
|
|
57385
57619
|
const tableConfig = tableData.config || DEFAULT_TABLE_CONFIG;
|
|
@@ -57389,7 +57623,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57389
57623
|
? this.createDynamicTable(uuid, range, tableConfig)
|
|
57390
57624
|
: this.createStaticTable(uuid, tableType, range, tableConfig);
|
|
57391
57625
|
this.history.update("tables", sheet.id, table.id, table);
|
|
57626
|
+
tableIds.push(table.id);
|
|
57392
57627
|
}
|
|
57628
|
+
this.history.update("insertionOrders", sheet.id, tableIds);
|
|
57393
57629
|
}
|
|
57394
57630
|
}
|
|
57395
57631
|
export(data) {
|
|
@@ -57429,7 +57665,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57429
57665
|
allowDispatch(cmd) {
|
|
57430
57666
|
switch (cmd.type) {
|
|
57431
57667
|
case "GROUP_HEADERS": {
|
|
57432
|
-
const { start, end } = cmd;
|
|
57668
|
+
const { start, end, sheetId } = cmd;
|
|
57669
|
+
if (!this.getters.tryGetSheet(sheetId)) {
|
|
57670
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
57671
|
+
}
|
|
57433
57672
|
if (!this.getters.doesHeadersExist(cmd.sheetId, cmd.dimension, [start, end])) {
|
|
57434
57673
|
return "InvalidHeaderGroupStartEnd" /* CommandResult.InvalidHeaderGroupStartEnd */;
|
|
57435
57674
|
}
|
|
@@ -57442,7 +57681,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57442
57681
|
break;
|
|
57443
57682
|
}
|
|
57444
57683
|
case "UNGROUP_HEADERS": {
|
|
57445
|
-
const { start, end } = cmd;
|
|
57684
|
+
const { start, end, sheetId } = cmd;
|
|
57685
|
+
if (!this.getters.tryGetSheet(sheetId)) {
|
|
57686
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
57687
|
+
}
|
|
57446
57688
|
if (!this.getters.doesHeadersExist(cmd.sheetId, cmd.dimension, [start, end])) {
|
|
57447
57689
|
return "InvalidHeaderGroupStartEnd" /* CommandResult.InvalidHeaderGroupStartEnd */;
|
|
57448
57690
|
}
|
|
@@ -57453,6 +57695,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57453
57695
|
}
|
|
57454
57696
|
case "UNFOLD_HEADER_GROUP":
|
|
57455
57697
|
case "FOLD_HEADER_GROUP":
|
|
57698
|
+
if (!this.getters.tryGetSheet(cmd.sheetId)) {
|
|
57699
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
57700
|
+
}
|
|
57456
57701
|
const group = this.findGroupWithStartEnd(cmd.sheetId, cmd.dimension, cmd.start, cmd.end);
|
|
57457
57702
|
if (!group) {
|
|
57458
57703
|
return "UnknownHeaderGroup" /* CommandResult.UnknownHeaderGroup */;
|
|
@@ -57853,6 +58098,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57853
58098
|
return this.checkDuplicatedMeasureIds(cmd.pivot);
|
|
57854
58099
|
}
|
|
57855
58100
|
case "UPDATE_PIVOT": {
|
|
58101
|
+
if (!(cmd.pivotId in this.pivots)) {
|
|
58102
|
+
return "PivotIdNotFound" /* CommandResult.PivotIdNotFound */;
|
|
58103
|
+
}
|
|
57856
58104
|
if (deepEquals(cmd.pivot, this.pivots[cmd.pivotId]?.definition)) {
|
|
57857
58105
|
return "NoChanges" /* CommandResult.NoChanges */;
|
|
57858
58106
|
}
|
|
@@ -57869,6 +58117,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57869
58117
|
return "EmptyName" /* CommandResult.EmptyName */;
|
|
57870
58118
|
}
|
|
57871
58119
|
break;
|
|
58120
|
+
case "REMOVE_PIVOT":
|
|
58121
|
+
case "DUPLICATE_PIVOT":
|
|
57872
58122
|
case "INSERT_PIVOT": {
|
|
57873
58123
|
if (!(cmd.pivotId in this.pivots)) {
|
|
57874
58124
|
return "PivotIdNotFound" /* CommandResult.PivotIdNotFound */;
|
|
@@ -57918,7 +58168,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57918
58168
|
break;
|
|
57919
58169
|
}
|
|
57920
58170
|
case "UPDATE_PIVOT": {
|
|
57921
|
-
this.history.update("pivots", cmd.pivotId, "definition", cmd.pivot);
|
|
58171
|
+
this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
|
|
57922
58172
|
this.compileCalculatedMeasures(cmd.pivot.measures);
|
|
57923
58173
|
break;
|
|
57924
58174
|
}
|
|
@@ -57989,7 +58239,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57989
58239
|
// Private
|
|
57990
58240
|
// -------------------------------------------------------------------------
|
|
57991
58241
|
addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
|
|
57992
|
-
this.history.update("pivots", pivotId, { definition: pivot, formulaId });
|
|
58242
|
+
this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
|
|
57993
58243
|
this.compileCalculatedMeasures(pivot.measures);
|
|
57994
58244
|
this.history.update("formulaIds", formulaId, pivotId);
|
|
57995
58245
|
this.history.update("nextFormulaId", this.nextFormulaId + 1);
|
|
@@ -59575,6 +59825,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
59575
59825
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
59576
59826
|
this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
|
|
59577
59827
|
this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
|
|
59828
|
+
this.compilationParams.evalContext.lookupCaches = {
|
|
59829
|
+
forwardSearch: new Map(),
|
|
59830
|
+
reverseSearch: new Map(),
|
|
59831
|
+
};
|
|
59578
59832
|
}
|
|
59579
59833
|
createEmptyPositionSet() {
|
|
59580
59834
|
const sheetSizes = {};
|
|
@@ -60177,6 +60431,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
60177
60431
|
exportForExcel(data) {
|
|
60178
60432
|
for (const sheet of data.sheets) {
|
|
60179
60433
|
sheet.cellValues = {};
|
|
60434
|
+
sheet.formulaSpillRanges = {};
|
|
60180
60435
|
}
|
|
60181
60436
|
for (const position of this.evaluator.getEvaluatedPositions()) {
|
|
60182
60437
|
const evaluatedCell = this.evaluator.getEvaluatedCell(position);
|
|
@@ -60188,8 +60443,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
60188
60443
|
const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
|
|
60189
60444
|
const formulaCell = this.getCorrespondingFormulaCell(position);
|
|
60190
60445
|
if (formulaCell) {
|
|
60446
|
+
const cell = this.getters.getCell(position);
|
|
60191
60447
|
isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
|
|
60192
|
-
isFormula = isExported;
|
|
60448
|
+
isFormula = isExported && cell?.content === formulaCell.content;
|
|
60193
60449
|
// If the cell contains a non-exported formula and that is evaluates to
|
|
60194
60450
|
// nothing* ,we don't export it.
|
|
60195
60451
|
// * non-falsy value are relevant and so are 0 and FALSE, which only leaves
|
|
@@ -60212,7 +60468,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
60212
60468
|
content = !isExported ? newContent : exportedCellData;
|
|
60213
60469
|
}
|
|
60214
60470
|
exportedSheetData.cells[xc] = content;
|
|
60215
|
-
exportedSheetData.cellValues[xc] = value;
|
|
60471
|
+
exportedSheetData.cellValues[xc] = evaluatedCell.type !== "error" ? value : undefined;
|
|
60472
|
+
const spillZone = this.getSpreadZone(position);
|
|
60473
|
+
if (spillZone) {
|
|
60474
|
+
exportedSheetData.formulaSpillRanges[xc] = this.getters.getRangeString(this.getters.getRangeFromZone(position.sheetId, spillZone), position.sheetId);
|
|
60475
|
+
}
|
|
60216
60476
|
}
|
|
60217
60477
|
}
|
|
60218
60478
|
/**
|
|
@@ -62419,7 +62679,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
62419
62679
|
getRule(cell, cells) {
|
|
62420
62680
|
const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
62421
62681
|
const rule = rules.find((rule) => rule.condition(cell, cells));
|
|
62422
|
-
return rule && rule.generateRule(cell, cells);
|
|
62682
|
+
return rule && this.direction && rule.generateRule(cell, cells, this.direction);
|
|
62423
62683
|
}
|
|
62424
62684
|
/**
|
|
62425
62685
|
* Create the generator to be able to autofill the next cells.
|
|
@@ -62895,6 +63155,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
62895
63155
|
};
|
|
62896
63156
|
}
|
|
62897
63157
|
function createSheetTransformation(toTransform, executed) {
|
|
63158
|
+
if (toTransform.sheetId === executed.sheetId) {
|
|
63159
|
+
toTransform = { ...toTransform, sheetId: `${toTransform.sheetId}~` };
|
|
63160
|
+
}
|
|
62898
63161
|
if (toTransform.name === executed.name) {
|
|
62899
63162
|
return {
|
|
62900
63163
|
...toTransform,
|
|
@@ -63538,15 +63801,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
63538
63801
|
}
|
|
63539
63802
|
this.sendPendingMessage();
|
|
63540
63803
|
}
|
|
63541
|
-
dropPendingRevision(revisionId) {
|
|
63542
|
-
this.revisions.drop(revisionId);
|
|
63543
|
-
const revisionIds = this.pendingMessages
|
|
63544
|
-
.filter((message) => message.type === "REMOTE_REVISION")
|
|
63545
|
-
.map((message) => message.nextRevisionId);
|
|
63546
|
-
this.trigger("pending-revisions-dropped", { revisionIds });
|
|
63547
|
-
this.waitingAck = false;
|
|
63548
|
-
this.waitingUndoRedoAck = false;
|
|
63549
|
-
}
|
|
63550
63804
|
/**
|
|
63551
63805
|
* Send the next pending message
|
|
63552
63806
|
*/
|
|
@@ -63555,15 +63809,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
63555
63809
|
if (!message)
|
|
63556
63810
|
return;
|
|
63557
63811
|
if (message.type === "REMOTE_REVISION") {
|
|
63558
|
-
|
|
63812
|
+
let revision = this.revisions.get(message.nextRevisionId);
|
|
63559
63813
|
if (revision.commands.length === 0) {
|
|
63560
63814
|
/**
|
|
63561
|
-
* The command is empty, we have to
|
|
63815
|
+
* The command is empty, we have to rebase all the next local revisions
|
|
63562
63816
|
* to avoid issues with undo/redo
|
|
63563
63817
|
*/
|
|
63564
|
-
this.
|
|
63565
|
-
|
|
63566
|
-
return;
|
|
63818
|
+
this.revisions.rebase(revision.id);
|
|
63819
|
+
revision = this.revisions.get(message.nextRevisionId);
|
|
63567
63820
|
}
|
|
63568
63821
|
message = {
|
|
63569
63822
|
...message,
|
|
@@ -63599,18 +63852,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
63599
63852
|
case "REVISION_UNDONE": {
|
|
63600
63853
|
this.waitingAck = false;
|
|
63601
63854
|
this.pendingMessages = this.pendingMessages.filter((msg) => msg.nextRevisionId !== message.nextRevisionId);
|
|
63602
|
-
const
|
|
63603
|
-
|
|
63604
|
-
if (firstTransformedRevisionIndex !== -1) {
|
|
63855
|
+
const firstPendingRevisionId = this.pendingMessages.findIndex((message) => message.type === "REMOTE_REVISION");
|
|
63856
|
+
if (firstPendingRevisionId !== -1) {
|
|
63605
63857
|
/**
|
|
63606
63858
|
* Some revisions undergo transformations that may cause issues with
|
|
63607
63859
|
* undo/redo if the transformation is destructive (we don't get back
|
|
63608
63860
|
* the original command by transforming it with the inverse).
|
|
63609
|
-
* To prevent these problems, we must
|
|
63861
|
+
* To prevent these problems, we must rebase all subsequent local
|
|
63610
63862
|
* revisions.
|
|
63611
63863
|
*/
|
|
63612
|
-
this.
|
|
63613
|
-
this.pendingMessages = this.pendingMessages.slice(0, firstTransformedRevisionIndex);
|
|
63864
|
+
this.revisions.rebase(this.pendingMessages[firstPendingRevisionId].nextRevisionId);
|
|
63614
63865
|
}
|
|
63615
63866
|
this.serverRevisionId = message.nextRevisionId;
|
|
63616
63867
|
this.processedRevisions.add(message.nextRevisionId);
|
|
@@ -64738,6 +64989,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
64738
64989
|
*/
|
|
64739
64990
|
checkZonesAreInSheet(cmd) {
|
|
64740
64991
|
const sheetId = "sheetId" in cmd ? cmd.sheetId : this.getters.tryGetActiveSheetId();
|
|
64992
|
+
if ("ranges" in cmd &&
|
|
64993
|
+
cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId))) {
|
|
64994
|
+
return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
|
|
64995
|
+
}
|
|
64741
64996
|
const zones = this.getters.getCommandZones(cmd);
|
|
64742
64997
|
if (!sheetId && zones.length > 0) {
|
|
64743
64998
|
return "NoActiveSheet" /* CommandResult.NoActiveSheet */;
|
|
@@ -65292,7 +65547,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65292
65547
|
super(config);
|
|
65293
65548
|
this.session = config.session;
|
|
65294
65549
|
this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
|
|
65295
|
-
this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
|
|
65296
65550
|
this.session.on("snapshot", this, () => {
|
|
65297
65551
|
this.undoStack = [];
|
|
65298
65552
|
this.redoStack = [];
|
|
@@ -65362,10 +65616,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
65362
65616
|
const lastNonRedoRevision = this.getPossibleRevisionToRepeat();
|
|
65363
65617
|
return canRepeatRevision(lastNonRedoRevision);
|
|
65364
65618
|
}
|
|
65365
|
-
drop(revisionIds) {
|
|
65366
|
-
this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));
|
|
65367
|
-
this.redoStack = [];
|
|
65368
|
-
}
|
|
65369
65619
|
onNewLocalStateUpdate({ id }) {
|
|
65370
65620
|
this.undoStack.push(id);
|
|
65371
65621
|
this.redoStack = [];
|
|
@@ -68069,7 +68319,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
68069
68319
|
case "UNGROUP_HEADERS":
|
|
68070
68320
|
case "GROUP_HEADERS":
|
|
68071
68321
|
case "CREATE_SHEET":
|
|
68072
|
-
this.
|
|
68322
|
+
if (this.getters.tryGetSheet(cmd.sheetId)) {
|
|
68323
|
+
this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
|
|
68324
|
+
}
|
|
68073
68325
|
break;
|
|
68074
68326
|
case "DUPLICATE_SHEET":
|
|
68075
68327
|
this.headerPositions[cmd.sheetIdTo] = deepCopy(this.headerPositions[cmd.sheetId]);
|
|
@@ -68077,12 +68329,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
68077
68329
|
}
|
|
68078
68330
|
}
|
|
68079
68331
|
finalize() {
|
|
68080
|
-
|
|
68081
|
-
|
|
68332
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
68333
|
+
// sheets can be created without this plugin being aware of it
|
|
68334
|
+
// in concurrent situations.
|
|
68335
|
+
if (this.isDirty || !this.headerPositions[sheetId]) {
|
|
68082
68336
|
this.headerPositions[sheetId] = this.computeHeaderPositionsOfSheet(sheetId);
|
|
68083
68337
|
}
|
|
68084
|
-
this.isDirty = false;
|
|
68085
68338
|
}
|
|
68339
|
+
this.isDirty = false;
|
|
68086
68340
|
}
|
|
68087
68341
|
/**
|
|
68088
68342
|
* Returns the size, start and end coordinates of a column on an unfolded sheet
|
|
@@ -71503,9 +71757,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
71503
71757
|
this.fastForward();
|
|
71504
71758
|
this.insert(redoId, this.buildEmpty(redoId), insertAfter);
|
|
71505
71759
|
}
|
|
71506
|
-
|
|
71760
|
+
rebase(operationId) {
|
|
71761
|
+
const operation = this.get(operationId);
|
|
71762
|
+
const execution = [...this.tree.execution(this.HEAD_BRANCH).startAfter(operationId)];
|
|
71507
71763
|
this.revertBefore(operationId);
|
|
71764
|
+
const baseId = this.HEAD_OPERATION.id;
|
|
71508
71765
|
this.tree.drop(operationId);
|
|
71766
|
+
this.insert(operationId, operation, baseId);
|
|
71767
|
+
for (const { operation } of execution) {
|
|
71768
|
+
this.insert(operation.id, operation.data, this.HEAD_OPERATION.id);
|
|
71769
|
+
}
|
|
71509
71770
|
}
|
|
71510
71771
|
/**
|
|
71511
71772
|
* Revert the state as it was *before* the given operation was executed.
|
|
@@ -72948,7 +73209,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
72948
73209
|
`;
|
|
72949
73210
|
}
|
|
72950
73211
|
|
|
72951
|
-
function addFormula(formula, value) {
|
|
73212
|
+
function addFormula(formula, value, formulaSpillRange) {
|
|
72952
73213
|
if (!formula) {
|
|
72953
73214
|
return { attrs: [], node: escapeXml `` };
|
|
72954
73215
|
}
|
|
@@ -72956,10 +73217,17 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
72956
73217
|
if (type === undefined) {
|
|
72957
73218
|
return { attrs: [], node: escapeXml `` };
|
|
72958
73219
|
}
|
|
72959
|
-
const attrs = [
|
|
73220
|
+
const attrs = [
|
|
73221
|
+
["cm", "1"],
|
|
73222
|
+
["t", type],
|
|
73223
|
+
];
|
|
72960
73224
|
const XlsxFormula = adaptFormulaToExcel(formula);
|
|
72961
73225
|
const exportedValue = adaptFormulaValueToExcel(value);
|
|
72962
|
-
|
|
73226
|
+
// We treat all formulas as array formulas (a simple formula
|
|
73227
|
+
// is an array formula that spills on only one cell) to avoid
|
|
73228
|
+
// trying to detect spilling sub-formulas which is not a trivial task.
|
|
73229
|
+
let node;
|
|
73230
|
+
node = escapeXml /*xml*/ `<f t="array" ref="${formulaSpillRange}">${XlsxFormula}</f><v>${exportedValue}</v>`;
|
|
72963
73231
|
return { attrs, node };
|
|
72964
73232
|
}
|
|
72965
73233
|
function addContent(content, sharedStrings, forceString = false) {
|
|
@@ -73949,7 +74217,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
73949
74217
|
let cellNode = escapeXml ``;
|
|
73950
74218
|
// Either formula or static value inside the cell
|
|
73951
74219
|
if (content?.startsWith("=") && value !== undefined) {
|
|
73952
|
-
const res = addFormula(content, value);
|
|
74220
|
+
const res = addFormula(content, value, sheet.formulaSpillRanges[xc] ?? xc);
|
|
73953
74221
|
if (!res) {
|
|
73954
74222
|
continue;
|
|
73955
74223
|
}
|
|
@@ -74235,6 +74503,30 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74235
74503
|
`;
|
|
74236
74504
|
files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, "sheet"));
|
|
74237
74505
|
}
|
|
74506
|
+
const sheetMetadataXml = escapeXml /*xml*/ `
|
|
74507
|
+
<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">
|
|
74508
|
+
<metadataTypes count="1">
|
|
74509
|
+
<metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1"
|
|
74510
|
+
pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1"
|
|
74511
|
+
clearComments="1" assign="1" coerce="1" cellMeta="1" />
|
|
74512
|
+
</metadataTypes>
|
|
74513
|
+
<futureMetadata name="XLDAPR" count="1">
|
|
74514
|
+
<bk>
|
|
74515
|
+
<extLst>
|
|
74516
|
+
<ext uri="{${ARRAY_FORMULA_URI}}">
|
|
74517
|
+
<xda:dynamicArrayProperties fDynamic="1" fCollapsed="0" />
|
|
74518
|
+
</ext>
|
|
74519
|
+
</extLst>
|
|
74520
|
+
</bk>
|
|
74521
|
+
</futureMetadata>
|
|
74522
|
+
<cellMetadata count="1">
|
|
74523
|
+
<bk>
|
|
74524
|
+
<rc t="1" v="0" />
|
|
74525
|
+
</bk>
|
|
74526
|
+
</cellMetadata>
|
|
74527
|
+
</metadata>
|
|
74528
|
+
`;
|
|
74529
|
+
files.push(createXMLFile(parseXML(sheetMetadataXml), "xl/metadata.xml", "metadata"));
|
|
74238
74530
|
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74239
74531
|
type: XLSX_RELATION_TYPE.sharedStrings,
|
|
74240
74532
|
target: "sharedStrings.xml",
|
|
@@ -74243,6 +74535,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74243
74535
|
type: XLSX_RELATION_TYPE.styles,
|
|
74244
74536
|
target: "styles.xml",
|
|
74245
74537
|
});
|
|
74538
|
+
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74539
|
+
type: XLSX_RELATION_TYPE.metadata,
|
|
74540
|
+
target: "metadata.xml",
|
|
74541
|
+
});
|
|
74246
74542
|
return files;
|
|
74247
74543
|
}
|
|
74248
74544
|
/**
|
|
@@ -74616,6 +74912,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
74616
74912
|
dispatch: (command) => {
|
|
74617
74913
|
const result = this.checkDispatchAllowed(command);
|
|
74618
74914
|
if (!result.isSuccessful) {
|
|
74915
|
+
// core views plugins need to be invalidated
|
|
74916
|
+
this.dispatchToHandlers(this.coreHandlers, {
|
|
74917
|
+
type: "UNDO",
|
|
74918
|
+
commands: [command],
|
|
74919
|
+
});
|
|
74619
74920
|
return;
|
|
74620
74921
|
}
|
|
74621
74922
|
this.isReplayingCommand = true;
|
|
@@ -75166,9 +75467,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
75166
75467
|
exports.tokenize = tokenize;
|
|
75167
75468
|
|
|
75168
75469
|
|
|
75169
|
-
__info__.version = "18.1.
|
|
75170
|
-
__info__.date = "2025-
|
|
75171
|
-
__info__.hash = "
|
|
75470
|
+
__info__.version = "18.1.10";
|
|
75471
|
+
__info__.date = "2025-03-07T10:34:41.861Z";
|
|
75472
|
+
__info__.hash = "31e4526";
|
|
75172
75473
|
|
|
75173
75474
|
|
|
75174
75475
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|