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