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