@odoo/o-spreadsheet 18.0.0 → 18.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o-spreadsheet.cjs.js +567 -292
- package/dist/o-spreadsheet.d.ts +50 -18
- package/dist/o-spreadsheet.esm.js +567 -292
- package/dist/o-spreadsheet.iife.js +567 -292
- package/dist/o-spreadsheet.iife.min.js +522 -523
- package/dist/o_spreadsheet.xml +181 -30
- package/package.json +1 -1
|
@@ -2,13 +2,119 @@
|
|
|
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.0.
|
|
6
|
-
* @date 2024-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.0.2
|
|
6
|
+
* @date 2024-10-24T08:54:21.934Z
|
|
7
|
+
* @hash 788df92
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
|
|
11
11
|
|
|
12
|
+
function createActions(menuItems) {
|
|
13
|
+
return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
|
|
14
|
+
}
|
|
15
|
+
let nextItemId = 1;
|
|
16
|
+
function createAction(item) {
|
|
17
|
+
const name = item.name;
|
|
18
|
+
const children = item.children;
|
|
19
|
+
const description = item.description;
|
|
20
|
+
const icon = item.icon;
|
|
21
|
+
const secondaryIcon = item.secondaryIcon;
|
|
22
|
+
const itemId = item.id || nextItemId++;
|
|
23
|
+
return {
|
|
24
|
+
id: itemId.toString(),
|
|
25
|
+
name: typeof name === "function" ? name : () => name,
|
|
26
|
+
isVisible: item.isVisible ? item.isVisible : () => true,
|
|
27
|
+
isEnabled: item.isEnabled ? item.isEnabled : () => true,
|
|
28
|
+
isActive: item.isActive,
|
|
29
|
+
execute: item.execute,
|
|
30
|
+
children: children
|
|
31
|
+
? (env) => {
|
|
32
|
+
return children
|
|
33
|
+
.map((child) => (typeof child === "function" ? child(env) : child))
|
|
34
|
+
.flat()
|
|
35
|
+
.map(createAction);
|
|
36
|
+
}
|
|
37
|
+
: () => [],
|
|
38
|
+
isReadonlyAllowed: item.isReadonlyAllowed || false,
|
|
39
|
+
separator: item.separator || false,
|
|
40
|
+
icon: typeof icon === "function" ? icon : () => icon || "",
|
|
41
|
+
iconColor: item.iconColor,
|
|
42
|
+
secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
|
|
43
|
+
description: typeof description === "function" ? description : () => description || "",
|
|
44
|
+
textColor: item.textColor,
|
|
45
|
+
sequence: item.sequence || 0,
|
|
46
|
+
onStartHover: item.onStartHover,
|
|
47
|
+
onStopHover: item.onStopHover,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Registry
|
|
53
|
+
*
|
|
54
|
+
* The Registry class is basically just a mapping from a string key to an object.
|
|
55
|
+
* It is really not much more than an object. It is however useful for the
|
|
56
|
+
* following reasons:
|
|
57
|
+
*
|
|
58
|
+
* 1. it let us react and execute code when someone add something to the registry
|
|
59
|
+
* (for example, the FunctionRegistry subclass this for this purpose)
|
|
60
|
+
* 2. it throws an error when the get operation fails
|
|
61
|
+
* 3. it provides a chained API to add items to the registry.
|
|
62
|
+
*/
|
|
63
|
+
class Registry {
|
|
64
|
+
content = {};
|
|
65
|
+
/**
|
|
66
|
+
* Add an item to the registry
|
|
67
|
+
*
|
|
68
|
+
* Note that this also returns the registry, so another add method call can
|
|
69
|
+
* be chained
|
|
70
|
+
*/
|
|
71
|
+
add(key, value) {
|
|
72
|
+
this.content[key] = value;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get an item from the registry
|
|
77
|
+
*/
|
|
78
|
+
get(key) {
|
|
79
|
+
/**
|
|
80
|
+
* Note: key in {} is ~12 times slower than {}[key].
|
|
81
|
+
* So, we check the absence of key only when the direct access returns
|
|
82
|
+
* a falsy value. It's done to ensure that the registry can contains falsy values
|
|
83
|
+
*/
|
|
84
|
+
const content = this.content[key];
|
|
85
|
+
if (!content) {
|
|
86
|
+
if (!(key in this.content)) {
|
|
87
|
+
throw new Error(`Cannot find ${key} in this registry!`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return content;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if the key is already in the registry
|
|
94
|
+
*/
|
|
95
|
+
contains(key) {
|
|
96
|
+
return key in this.content;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get a list of all elements in the registry
|
|
100
|
+
*/
|
|
101
|
+
getAll() {
|
|
102
|
+
return Object.values(this.content);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a list of all keys in the registry
|
|
106
|
+
*/
|
|
107
|
+
getKeys() {
|
|
108
|
+
return Object.keys(this.content);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Remove an item from the registry
|
|
112
|
+
*/
|
|
113
|
+
remove(key) {
|
|
114
|
+
delete this.content[key];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
12
118
|
const CANVAS_SHIFT = 0.5;
|
|
13
119
|
// Colors
|
|
14
120
|
const HIGHLIGHT_COLOR = "#37A850";
|
|
@@ -268,7 +374,7 @@ const PIVOT_TABLE_CONFIG = {
|
|
|
268
374
|
bandedRows: true,
|
|
269
375
|
bandedColumns: false,
|
|
270
376
|
styleId: "TableStyleMedium5",
|
|
271
|
-
automaticAutofill:
|
|
377
|
+
automaticAutofill: false,
|
|
272
378
|
};
|
|
273
379
|
const DEFAULT_CURRENCY = {
|
|
274
380
|
symbol: "$",
|
|
@@ -5993,111 +6099,6 @@ class UuidGenerator {
|
|
|
5993
6099
|
}
|
|
5994
6100
|
}
|
|
5995
6101
|
|
|
5996
|
-
function createActions(menuItems) {
|
|
5997
|
-
return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
|
|
5998
|
-
}
|
|
5999
|
-
const uuidGenerator$1 = new UuidGenerator();
|
|
6000
|
-
function createAction(item) {
|
|
6001
|
-
const name = item.name;
|
|
6002
|
-
const children = item.children;
|
|
6003
|
-
const description = item.description;
|
|
6004
|
-
const icon = item.icon;
|
|
6005
|
-
const secondaryIcon = item.secondaryIcon;
|
|
6006
|
-
return {
|
|
6007
|
-
id: item.id || uuidGenerator$1.uuidv4(),
|
|
6008
|
-
name: typeof name === "function" ? name : () => name,
|
|
6009
|
-
isVisible: item.isVisible ? item.isVisible : () => true,
|
|
6010
|
-
isEnabled: item.isEnabled ? item.isEnabled : () => true,
|
|
6011
|
-
isActive: item.isActive,
|
|
6012
|
-
execute: item.execute,
|
|
6013
|
-
children: children
|
|
6014
|
-
? (env) => {
|
|
6015
|
-
return children
|
|
6016
|
-
.map((child) => (typeof child === "function" ? child(env) : child))
|
|
6017
|
-
.flat()
|
|
6018
|
-
.map(createAction);
|
|
6019
|
-
}
|
|
6020
|
-
: () => [],
|
|
6021
|
-
isReadonlyAllowed: item.isReadonlyAllowed || false,
|
|
6022
|
-
separator: item.separator || false,
|
|
6023
|
-
icon: typeof icon === "function" ? icon : () => icon || "",
|
|
6024
|
-
iconColor: item.iconColor,
|
|
6025
|
-
secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
|
|
6026
|
-
description: typeof description === "function" ? description : () => description || "",
|
|
6027
|
-
textColor: item.textColor,
|
|
6028
|
-
sequence: item.sequence || 0,
|
|
6029
|
-
onStartHover: item.onStartHover,
|
|
6030
|
-
onStopHover: item.onStopHover,
|
|
6031
|
-
};
|
|
6032
|
-
}
|
|
6033
|
-
|
|
6034
|
-
/**
|
|
6035
|
-
* Registry
|
|
6036
|
-
*
|
|
6037
|
-
* The Registry class is basically just a mapping from a string key to an object.
|
|
6038
|
-
* It is really not much more than an object. It is however useful for the
|
|
6039
|
-
* following reasons:
|
|
6040
|
-
*
|
|
6041
|
-
* 1. it let us react and execute code when someone add something to the registry
|
|
6042
|
-
* (for example, the FunctionRegistry subclass this for this purpose)
|
|
6043
|
-
* 2. it throws an error when the get operation fails
|
|
6044
|
-
* 3. it provides a chained API to add items to the registry.
|
|
6045
|
-
*/
|
|
6046
|
-
class Registry {
|
|
6047
|
-
content = {};
|
|
6048
|
-
/**
|
|
6049
|
-
* Add an item to the registry
|
|
6050
|
-
*
|
|
6051
|
-
* Note that this also returns the registry, so another add method call can
|
|
6052
|
-
* be chained
|
|
6053
|
-
*/
|
|
6054
|
-
add(key, value) {
|
|
6055
|
-
this.content[key] = value;
|
|
6056
|
-
return this;
|
|
6057
|
-
}
|
|
6058
|
-
/**
|
|
6059
|
-
* Get an item from the registry
|
|
6060
|
-
*/
|
|
6061
|
-
get(key) {
|
|
6062
|
-
/**
|
|
6063
|
-
* Note: key in {} is ~12 times slower than {}[key].
|
|
6064
|
-
* So, we check the absence of key only when the direct access returns
|
|
6065
|
-
* a falsy value. It's done to ensure that the registry can contains falsy values
|
|
6066
|
-
*/
|
|
6067
|
-
const content = this.content[key];
|
|
6068
|
-
if (!content) {
|
|
6069
|
-
if (!(key in this.content)) {
|
|
6070
|
-
throw new Error(`Cannot find ${key} in this registry!`);
|
|
6071
|
-
}
|
|
6072
|
-
}
|
|
6073
|
-
return content;
|
|
6074
|
-
}
|
|
6075
|
-
/**
|
|
6076
|
-
* Check if the key is already in the registry
|
|
6077
|
-
*/
|
|
6078
|
-
contains(key) {
|
|
6079
|
-
return key in this.content;
|
|
6080
|
-
}
|
|
6081
|
-
/**
|
|
6082
|
-
* Get a list of all elements in the registry
|
|
6083
|
-
*/
|
|
6084
|
-
getAll() {
|
|
6085
|
-
return Object.values(this.content);
|
|
6086
|
-
}
|
|
6087
|
-
/**
|
|
6088
|
-
* Get a list of all keys in the registry
|
|
6089
|
-
*/
|
|
6090
|
-
getKeys() {
|
|
6091
|
-
return Object.keys(this.content);
|
|
6092
|
-
}
|
|
6093
|
-
/**
|
|
6094
|
-
* Remove an item from the registry
|
|
6095
|
-
*/
|
|
6096
|
-
remove(key) {
|
|
6097
|
-
delete this.content[key];
|
|
6098
|
-
}
|
|
6099
|
-
}
|
|
6100
|
-
|
|
6101
6102
|
function getClipboardDataPositions(sheetId, zones) {
|
|
6102
6103
|
const lefts = new Set(zones.map((z) => z.left));
|
|
6103
6104
|
const rights = new Set(zones.map((z) => z.right));
|
|
@@ -8452,31 +8453,34 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
8452
8453
|
for (let col of columnsIndexes) {
|
|
8453
8454
|
const position = { col, row, sheetId };
|
|
8454
8455
|
const table = this.getters.getTable(position);
|
|
8455
|
-
if (!table
|
|
8456
|
+
if (!table) {
|
|
8456
8457
|
tableCellsInRow.push({});
|
|
8457
8458
|
continue;
|
|
8458
8459
|
}
|
|
8459
8460
|
const coreTable = this.getters.getCoreTable(position);
|
|
8460
8461
|
const tableZone = coreTable?.range.zone;
|
|
8462
|
+
let copiedTable = undefined;
|
|
8461
8463
|
// Copy whole table
|
|
8462
|
-
if (
|
|
8463
|
-
|
|
8464
|
+
if (!copiedTablesIds.has(table.id) &&
|
|
8465
|
+
coreTable &&
|
|
8466
|
+
tableZone &&
|
|
8467
|
+
zones.some((z) => isZoneInside(tableZone, z))) {
|
|
8468
|
+
copiedTablesIds.add(table.id);
|
|
8464
8469
|
const values = [];
|
|
8465
8470
|
for (const col of range(tableZone.left, tableZone.right + 1)) {
|
|
8466
8471
|
values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
|
|
8467
8472
|
}
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
},
|
|
8474
|
-
});
|
|
8475
|
-
}
|
|
8476
|
-
// Copy only style of cell
|
|
8477
|
-
else if (table) {
|
|
8478
|
-
tableCellsInRow.push({ style: this.getTableStyleToCopy(position) });
|
|
8473
|
+
copiedTable = {
|
|
8474
|
+
range: coreTable.range.rangeData,
|
|
8475
|
+
config: coreTable.config,
|
|
8476
|
+
type: coreTable.type,
|
|
8477
|
+
};
|
|
8479
8478
|
}
|
|
8479
|
+
tableCellsInRow.push({
|
|
8480
|
+
table: copiedTable,
|
|
8481
|
+
style: this.getTableStyleToCopy(position),
|
|
8482
|
+
isWholeTableCopied: copiedTablesIds.has(table.id),
|
|
8483
|
+
});
|
|
8480
8484
|
}
|
|
8481
8485
|
}
|
|
8482
8486
|
return {
|
|
@@ -8557,11 +8561,14 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
8557
8561
|
tableType: tableCell.table.type,
|
|
8558
8562
|
});
|
|
8559
8563
|
}
|
|
8560
|
-
// Do not paste table style if we're inside another table
|
|
8561
8564
|
// We cannot check for dynamic tables, because at this point the paste can have changed the evaluation, and the
|
|
8562
8565
|
// dynamic tables are not yet computed
|
|
8563
|
-
if (
|
|
8564
|
-
|
|
8566
|
+
if (this.getters.getCoreTable(position) || options?.pasteOption === "asValue") {
|
|
8567
|
+
return;
|
|
8568
|
+
}
|
|
8569
|
+
if ((!options?.pasteOption && !tableCell.isWholeTableCopied) ||
|
|
8570
|
+
options?.pasteOption === "onlyFormat") {
|
|
8571
|
+
if (tableCell.style?.style) {
|
|
8565
8572
|
this.dispatch("UPDATE_CELL", { ...position, style: tableCell.style.style });
|
|
8566
8573
|
}
|
|
8567
8574
|
if (tableCell.style?.border) {
|
|
@@ -9402,70 +9409,114 @@ const chartShowValuesPlugin = {
|
|
|
9402
9409
|
ctx.save();
|
|
9403
9410
|
ctx.textAlign = "center";
|
|
9404
9411
|
ctx.textBaseline = "middle";
|
|
9405
|
-
ctx.
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
const midRadius = (innerRadius + outerRadius) / 2;
|
|
9419
|
-
const x = bar.x + midRadius * Math.cos(midAngle);
|
|
9420
|
-
const y = bar.y + midRadius * Math.sin(midAngle) + 7;
|
|
9421
|
-
ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
|
|
9422
|
-
ctx.strokeStyle = chartFontColor(ctx.fillStyle);
|
|
9423
|
-
const value = options.callback(dataset._parsed[i]);
|
|
9424
|
-
ctx.strokeText(value, x, y);
|
|
9425
|
-
ctx.fillText(value, x, y);
|
|
9426
|
-
}
|
|
9427
|
-
break;
|
|
9428
|
-
}
|
|
9429
|
-
case "bar":
|
|
9430
|
-
case "line": {
|
|
9431
|
-
const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
|
|
9432
|
-
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
9433
|
-
const point = dataset.data[i];
|
|
9434
|
-
const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
|
|
9435
|
-
const displayedValue = options.callback(value - 0);
|
|
9436
|
-
let xPosition = 0, yPosition = 0;
|
|
9437
|
-
if (options.horizontal) {
|
|
9438
|
-
yPosition = point.y;
|
|
9439
|
-
if (value < 0) {
|
|
9440
|
-
ctx.textAlign = "right";
|
|
9441
|
-
xPosition = point.x - yOffset;
|
|
9442
|
-
}
|
|
9443
|
-
else {
|
|
9444
|
-
ctx.textAlign = "left";
|
|
9445
|
-
xPosition = point.x + yOffset;
|
|
9446
|
-
}
|
|
9447
|
-
}
|
|
9448
|
-
else {
|
|
9449
|
-
xPosition = point.x;
|
|
9450
|
-
if (value < 0) {
|
|
9451
|
-
ctx.textBaseline = "top";
|
|
9452
|
-
yPosition = point.y + yOffset;
|
|
9453
|
-
}
|
|
9454
|
-
else {
|
|
9455
|
-
ctx.textBaseline = "bottom";
|
|
9456
|
-
yPosition = point.y - yOffset;
|
|
9457
|
-
}
|
|
9458
|
-
}
|
|
9459
|
-
ctx.strokeText(displayedValue, xPosition, yPosition);
|
|
9460
|
-
ctx.fillText(displayedValue, xPosition, yPosition);
|
|
9461
|
-
}
|
|
9462
|
-
break;
|
|
9463
|
-
}
|
|
9464
|
-
}
|
|
9465
|
-
});
|
|
9412
|
+
ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
|
|
9413
|
+
switch (chart.config.type) {
|
|
9414
|
+
case "pie":
|
|
9415
|
+
case "doughnut":
|
|
9416
|
+
drawPieChartValues(chart, options, ctx);
|
|
9417
|
+
break;
|
|
9418
|
+
case "bar":
|
|
9419
|
+
case "line":
|
|
9420
|
+
options.horizontal
|
|
9421
|
+
? drawHorizontalBarChartValues(chart, options, ctx)
|
|
9422
|
+
: drawLineOrBarChartValues(chart, options, ctx);
|
|
9423
|
+
break;
|
|
9424
|
+
}
|
|
9466
9425
|
ctx.restore();
|
|
9467
9426
|
},
|
|
9468
9427
|
};
|
|
9428
|
+
function drawTextWithBackground(text, x, y, ctx) {
|
|
9429
|
+
ctx.lineWidth = 3; // Stroke the text with a big lineWidth width to have some kind of background
|
|
9430
|
+
ctx.strokeText(text, x, y);
|
|
9431
|
+
ctx.lineWidth = 1;
|
|
9432
|
+
ctx.fillText(text, x, y);
|
|
9433
|
+
}
|
|
9434
|
+
function drawLineOrBarChartValues(chart, options, ctx) {
|
|
9435
|
+
const yMax = chart.chartArea.bottom;
|
|
9436
|
+
const yMin = chart.chartArea.top;
|
|
9437
|
+
const textsPositions = {};
|
|
9438
|
+
for (const dataset of chart._metasets) {
|
|
9439
|
+
if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
|
|
9440
|
+
return; // ignore trend lines
|
|
9441
|
+
}
|
|
9442
|
+
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
9443
|
+
const value = dataset._parsed[i].y;
|
|
9444
|
+
const point = dataset.data[i];
|
|
9445
|
+
const xPosition = point.x;
|
|
9446
|
+
let yPosition = 0;
|
|
9447
|
+
if (chart.config.type === "line") {
|
|
9448
|
+
yPosition = point.y - 10;
|
|
9449
|
+
}
|
|
9450
|
+
else {
|
|
9451
|
+
yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
|
|
9452
|
+
}
|
|
9453
|
+
yPosition = Math.min(yPosition, yMax);
|
|
9454
|
+
yPosition = Math.max(yPosition, yMin);
|
|
9455
|
+
// Avoid overlapping texts with same X
|
|
9456
|
+
if (!textsPositions[xPosition]) {
|
|
9457
|
+
textsPositions[xPosition] = [];
|
|
9458
|
+
}
|
|
9459
|
+
for (const otherPosition of textsPositions[xPosition] || []) {
|
|
9460
|
+
if (Math.abs(otherPosition - yPosition) < 13) {
|
|
9461
|
+
yPosition = otherPosition - 13;
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
textsPositions[xPosition].push(yPosition);
|
|
9465
|
+
ctx.fillStyle = point.options.backgroundColor;
|
|
9466
|
+
ctx.strokeStyle = options.background || "#ffffff";
|
|
9467
|
+
drawTextWithBackground(options.callback(value - 0), xPosition, yPosition, ctx);
|
|
9468
|
+
}
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
function drawHorizontalBarChartValues(chart, options, ctx) {
|
|
9472
|
+
const xMax = chart.chartArea.right;
|
|
9473
|
+
const xMin = chart.chartArea.left;
|
|
9474
|
+
const textsPositions = {};
|
|
9475
|
+
for (const dataset of chart._metasets) {
|
|
9476
|
+
if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
|
|
9477
|
+
return; // ignore trend lines
|
|
9478
|
+
}
|
|
9479
|
+
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
9480
|
+
const value = dataset._parsed[i].x;
|
|
9481
|
+
const displayValue = options.callback(value - 0);
|
|
9482
|
+
const point = dataset.data[i];
|
|
9483
|
+
const yPosition = point.y;
|
|
9484
|
+
let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
|
|
9485
|
+
xPosition = Math.min(xPosition, xMax);
|
|
9486
|
+
xPosition = Math.max(xPosition, xMin);
|
|
9487
|
+
// Avoid overlapping texts with same Y
|
|
9488
|
+
if (!textsPositions[yPosition]) {
|
|
9489
|
+
textsPositions[yPosition] = [];
|
|
9490
|
+
}
|
|
9491
|
+
const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
|
|
9492
|
+
for (const otherPosition of textsPositions[yPosition]) {
|
|
9493
|
+
if (Math.abs(otherPosition - xPosition) < textWidth) {
|
|
9494
|
+
xPosition = otherPosition + textWidth + 3;
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
textsPositions[yPosition].push(xPosition);
|
|
9498
|
+
ctx.fillStyle = point.options.backgroundColor;
|
|
9499
|
+
ctx.strokeStyle = options.background || "#ffffff";
|
|
9500
|
+
drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9504
|
+
function drawPieChartValues(chart, options, ctx) {
|
|
9505
|
+
for (const dataset of chart._metasets) {
|
|
9506
|
+
for (let i = 0; i < dataset._parsed.length; i++) {
|
|
9507
|
+
const bar = dataset.data[i];
|
|
9508
|
+
const { startAngle, endAngle, innerRadius, outerRadius } = bar;
|
|
9509
|
+
const midAngle = (startAngle + endAngle) / 2;
|
|
9510
|
+
const midRadius = (innerRadius + outerRadius) / 2;
|
|
9511
|
+
const x = bar.x + midRadius * Math.cos(midAngle);
|
|
9512
|
+
const y = bar.y + midRadius * Math.sin(midAngle) + 7;
|
|
9513
|
+
ctx.fillStyle = chartFontColor(options.background);
|
|
9514
|
+
ctx.strokeStyle = options.background || "#ffffff";
|
|
9515
|
+
const value = options.callback(dataset._parsed[i]);
|
|
9516
|
+
drawTextWithBackground(value, x, y, ctx);
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9469
9520
|
|
|
9470
9521
|
/** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
|
|
9471
9522
|
const waterfallLinesPlugin = {
|
|
@@ -17841,7 +17892,7 @@ function assertDomainLength(domain) {
|
|
|
17841
17892
|
throw new EvaluationError(_t("Function PIVOT takes an even number of arguments."));
|
|
17842
17893
|
}
|
|
17843
17894
|
}
|
|
17844
|
-
function addPivotDependencies(evalContext, coreDefinition) {
|
|
17895
|
+
function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
|
|
17845
17896
|
//TODO This function can be very costly when used with PIVOT.VALUE and PIVOT.HEADER
|
|
17846
17897
|
const dependencies = [];
|
|
17847
17898
|
if (coreDefinition.type === "SPREADSHEET" && coreDefinition.dataSet) {
|
|
@@ -17853,7 +17904,7 @@ function addPivotDependencies(evalContext, coreDefinition) {
|
|
|
17853
17904
|
}
|
|
17854
17905
|
dependencies.push(range);
|
|
17855
17906
|
}
|
|
17856
|
-
for (const measure of
|
|
17907
|
+
for (const measure of forMeasures) {
|
|
17857
17908
|
if (measure.computedBy) {
|
|
17858
17909
|
const formula = evalContext.getters.getMeasureCompiledFormula(measure);
|
|
17859
17910
|
dependencies.push(...formula.dependencies.filter((range) => !range.invalidXc));
|
|
@@ -18286,7 +18337,7 @@ const PIVOT_VALUE = {
|
|
|
18286
18337
|
assertDomainLength(domainArgs);
|
|
18287
18338
|
const pivot = this.getters.getPivot(pivotId);
|
|
18288
18339
|
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
18289
|
-
addPivotDependencies(this, coreDefinition);
|
|
18340
|
+
addPivotDependencies(this, coreDefinition, coreDefinition.measures.filter((m) => m.id === _measure));
|
|
18290
18341
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
18291
18342
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
18292
18343
|
if (error) {
|
|
@@ -18316,7 +18367,7 @@ const PIVOT_HEADER = {
|
|
|
18316
18367
|
assertDomainLength(domainArgs);
|
|
18317
18368
|
const pivot = this.getters.getPivot(_pivotId);
|
|
18318
18369
|
const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
|
|
18319
|
-
addPivotDependencies(this, coreDefinition);
|
|
18370
|
+
addPivotDependencies(this, coreDefinition, []);
|
|
18320
18371
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
18321
18372
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
18322
18373
|
if (error) {
|
|
@@ -18367,7 +18418,7 @@ const PIVOT = {
|
|
|
18367
18418
|
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
18368
18419
|
const pivot = this.getters.getPivot(pivotId);
|
|
18369
18420
|
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
18370
|
-
addPivotDependencies(this, coreDefinition);
|
|
18421
|
+
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
|
|
18371
18422
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
18372
18423
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
18373
18424
|
if (error) {
|
|
@@ -21576,6 +21627,7 @@ function insertTokenAfterArgSeparator(tokenAtCursor, value) {
|
|
|
21576
21627
|
// replace the whole token
|
|
21577
21628
|
start = tokenAtCursor.start;
|
|
21578
21629
|
}
|
|
21630
|
+
this.composer.stopComposerRangeSelection();
|
|
21579
21631
|
this.composer.changeComposerCursorSelection(start, end);
|
|
21580
21632
|
this.composer.replaceComposerCursorSelection(value);
|
|
21581
21633
|
}
|
|
@@ -21593,6 +21645,7 @@ function insertTokenAfterLeftParenthesis(tokenAtCursor, value) {
|
|
|
21593
21645
|
// replace the whole token
|
|
21594
21646
|
start = tokenAtCursor.start;
|
|
21595
21647
|
}
|
|
21648
|
+
this.composer.stopComposerRangeSelection();
|
|
21596
21649
|
this.composer.changeComposerCursorSelection(start, end);
|
|
21597
21650
|
this.composer.replaceComposerCursorSelection(value);
|
|
21598
21651
|
}
|
|
@@ -21726,9 +21779,13 @@ autoCompleteProviders.add("pivot_group_fields", {
|
|
|
21726
21779
|
const colFields = columns.map((groupBy) => groupBy.nameWithGranularity);
|
|
21727
21780
|
const rowFields = rows.map((groupBy) => groupBy.nameWithGranularity);
|
|
21728
21781
|
const proposals = [];
|
|
21729
|
-
|
|
21782
|
+
let previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
|
|
21730
21783
|
? argGroupBys.at(-1)
|
|
21731
21784
|
: argGroupBys.at(-2);
|
|
21785
|
+
const isPositionalSupported = supportedPivotPositionalFormulaRegistry.get(pivot.type);
|
|
21786
|
+
if (isPositionalSupported && previousGroupBy?.startsWith("#")) {
|
|
21787
|
+
previousGroupBy = previousGroupBy.slice(1);
|
|
21788
|
+
}
|
|
21732
21789
|
if (previousGroupBy === undefined) {
|
|
21733
21790
|
proposals.push(colFields[0]);
|
|
21734
21791
|
proposals.push(rowFields[0]);
|
|
@@ -21750,7 +21807,7 @@ autoCompleteProviders.add("pivot_group_fields", {
|
|
|
21750
21807
|
return field ? makeFieldProposal(field, granularity) : undefined;
|
|
21751
21808
|
})
|
|
21752
21809
|
.concat(groupBys.map((groupBy) => {
|
|
21753
|
-
if (!
|
|
21810
|
+
if (!isPositionalSupported) {
|
|
21754
21811
|
return undefined;
|
|
21755
21812
|
}
|
|
21756
21813
|
const fieldName = groupBy.split(":")[0];
|
|
@@ -22185,6 +22242,27 @@ autofillModifiersRegistry
|
|
|
22185
22242
|
tooltip: content ? { props: { content: tooltipValue } } : undefined,
|
|
22186
22243
|
};
|
|
22187
22244
|
},
|
|
22245
|
+
})
|
|
22246
|
+
.add("DATE_INCREMENT_MODIFIER", {
|
|
22247
|
+
apply: (rule, data, getters) => {
|
|
22248
|
+
const date = toJsDate(rule.current, getters.getLocale());
|
|
22249
|
+
date.setFullYear(date.getFullYear() + rule.increment.years || 0);
|
|
22250
|
+
date.setMonth(date.getMonth() + rule.increment.months || 0);
|
|
22251
|
+
date.setDate(date.getDate() + rule.increment.days || 0);
|
|
22252
|
+
const value = jsDateToNumber(date);
|
|
22253
|
+
rule.current = value;
|
|
22254
|
+
const locale = getters.getLocale();
|
|
22255
|
+
const tooltipValue = formatValue(value, { format: data.cell?.format, locale });
|
|
22256
|
+
return {
|
|
22257
|
+
cellData: {
|
|
22258
|
+
border: data.border,
|
|
22259
|
+
style: data.cell && data.cell.style,
|
|
22260
|
+
format: data.cell && data.cell.format,
|
|
22261
|
+
content: value.toString(),
|
|
22262
|
+
},
|
|
22263
|
+
tooltip: value ? { props: { content: tooltipValue } } : undefined,
|
|
22264
|
+
};
|
|
22265
|
+
},
|
|
22188
22266
|
})
|
|
22189
22267
|
.add("COPY_MODIFIER", {
|
|
22190
22268
|
apply: (rule, data, getters) => {
|
|
@@ -22265,7 +22343,9 @@ function getGroup(cell, cells, filter) {
|
|
|
22265
22343
|
if (x === cell) {
|
|
22266
22344
|
found = true;
|
|
22267
22345
|
}
|
|
22268
|
-
const cellValue = x === undefined || x.isFormula
|
|
22346
|
+
const cellValue = x === undefined || x.isFormula
|
|
22347
|
+
? undefined
|
|
22348
|
+
: evaluateLiteral(x, { locale: DEFAULT_LOCALE, format: x.format });
|
|
22269
22349
|
if (cellValue && filter(cellValue)) {
|
|
22270
22350
|
group.push(cellValue);
|
|
22271
22351
|
}
|
|
@@ -22301,6 +22381,72 @@ function calculateIncrementBasedOnGroup(group) {
|
|
|
22301
22381
|
}
|
|
22302
22382
|
return increment;
|
|
22303
22383
|
}
|
|
22384
|
+
/**
|
|
22385
|
+
* Iterates on a list of date intervals.
|
|
22386
|
+
* if every interval is the same, return the interval
|
|
22387
|
+
* Otherwise return undefined
|
|
22388
|
+
*
|
|
22389
|
+
*/
|
|
22390
|
+
function getEqualInterval(intervals) {
|
|
22391
|
+
if (intervals.length < 2) {
|
|
22392
|
+
return intervals[0] || { years: 0, months: 0, days: 0 };
|
|
22393
|
+
}
|
|
22394
|
+
const equal = intervals.every((interval) => interval.years === intervals[0].years &&
|
|
22395
|
+
interval.months === intervals[0].months &&
|
|
22396
|
+
interval.days === intervals[0].days);
|
|
22397
|
+
return equal ? intervals[0] : undefined;
|
|
22398
|
+
}
|
|
22399
|
+
/**
|
|
22400
|
+
* Based on a group of dates, calculate the increment that should be applied
|
|
22401
|
+
* to the next date.
|
|
22402
|
+
*
|
|
22403
|
+
* This will compute the date difference in calendar terms (years, months, days)
|
|
22404
|
+
* In order to make abstraction of leap years and months with different number of days.
|
|
22405
|
+
*
|
|
22406
|
+
* In case the dates are not equidistant in calendar terms, no rule can be extrapolated
|
|
22407
|
+
* In case of equidistant dates, we either have in that order:
|
|
22408
|
+
* - exact date interval (e.g. +n year OR +n month OR +n day) in which case we increment by the same interval
|
|
22409
|
+
* - exact day interval (e.g. +n days) in which case we increment by the same day interval
|
|
22410
|
+
* - equidistant dates but not the same interval, in which case we return increment of the same interval
|
|
22411
|
+
*
|
|
22412
|
+
* */
|
|
22413
|
+
function calculateDateIncrementBasedOnGroup(group) {
|
|
22414
|
+
if (group.length < 2) {
|
|
22415
|
+
return 1;
|
|
22416
|
+
}
|
|
22417
|
+
const jsDates = group.map((date) => toJsDate(date, DEFAULT_LOCALE));
|
|
22418
|
+
const datesIntervals = getDateIntervals(jsDates);
|
|
22419
|
+
const datesEquidistantInterval = getEqualInterval(datesIntervals);
|
|
22420
|
+
if (datesEquidistantInterval === undefined) {
|
|
22421
|
+
// dates are not equidistant in terms of years, months or days, thus no rule can be extrapolated
|
|
22422
|
+
return undefined;
|
|
22423
|
+
}
|
|
22424
|
+
// The dates are apart by an exact interval of years, months or days
|
|
22425
|
+
// but not a combination of them
|
|
22426
|
+
const exactDateInterval = Object.values(datesEquidistantInterval).filter((value) => value !== 0).length === 1;
|
|
22427
|
+
const isSameDay = Object.values(datesEquidistantInterval).every((el) => el === 0); // handles time values (strict decimals)
|
|
22428
|
+
if (!exactDateInterval || isSameDay) {
|
|
22429
|
+
const timeIntervals = jsDates
|
|
22430
|
+
.map((date, index) => {
|
|
22431
|
+
if (index === 0) {
|
|
22432
|
+
return 0;
|
|
22433
|
+
}
|
|
22434
|
+
const previous = jsDates[index - 1];
|
|
22435
|
+
const days = Math.floor(date.getTime()) - Math.floor(previous.getTime());
|
|
22436
|
+
return days;
|
|
22437
|
+
})
|
|
22438
|
+
.slice(1);
|
|
22439
|
+
const equidistantDates = timeIntervals.every((interval) => interval === timeIntervals[0]);
|
|
22440
|
+
if (equidistantDates) {
|
|
22441
|
+
return group.length * (group[1] - group[0]);
|
|
22442
|
+
}
|
|
22443
|
+
}
|
|
22444
|
+
return {
|
|
22445
|
+
years: datesEquidistantInterval.years * group.length,
|
|
22446
|
+
months: datesEquidistantInterval.months * group.length,
|
|
22447
|
+
days: datesEquidistantInterval.days * group.length,
|
|
22448
|
+
};
|
|
22449
|
+
}
|
|
22304
22450
|
autofillRulesRegistry
|
|
22305
22451
|
.add("simple_value_copy", {
|
|
22306
22452
|
condition: (cell, cells) => {
|
|
@@ -22348,12 +22494,47 @@ autofillRulesRegistry
|
|
|
22348
22494
|
return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
|
|
22349
22495
|
},
|
|
22350
22496
|
sequence: 30,
|
|
22497
|
+
})
|
|
22498
|
+
.add("increment_dates", {
|
|
22499
|
+
condition: (cell, cells) => {
|
|
22500
|
+
return (!cell.isFormula &&
|
|
22501
|
+
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number &&
|
|
22502
|
+
!!cell.format &&
|
|
22503
|
+
isDateTimeFormat(cell.format));
|
|
22504
|
+
},
|
|
22505
|
+
generateRule: (cell, cells) => {
|
|
22506
|
+
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22507
|
+
!!evaluatedCell.format &&
|
|
22508
|
+
isDateTimeFormat(evaluatedCell.format)).map((cell) => Number(cell.value));
|
|
22509
|
+
const increment = calculateDateIncrementBasedOnGroup(group);
|
|
22510
|
+
if (increment === undefined) {
|
|
22511
|
+
return { type: "COPY_MODIFIER" };
|
|
22512
|
+
}
|
|
22513
|
+
/** requires to detect the current date (requires to be an integer value with the right format)
|
|
22514
|
+
* detect if year or if month or if day then extrapolate increment required (+1 month, +1 year + 1 day)
|
|
22515
|
+
*/
|
|
22516
|
+
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22517
|
+
if (typeof increment === "object") {
|
|
22518
|
+
return {
|
|
22519
|
+
type: "DATE_INCREMENT_MODIFIER",
|
|
22520
|
+
increment,
|
|
22521
|
+
current: evaluation.type === CellValueType.number ? evaluation.value : 0,
|
|
22522
|
+
};
|
|
22523
|
+
}
|
|
22524
|
+
return {
|
|
22525
|
+
type: "INCREMENT_MODIFIER",
|
|
22526
|
+
increment,
|
|
22527
|
+
current: evaluation.type === CellValueType.number ? evaluation.value : 0,
|
|
22528
|
+
};
|
|
22529
|
+
},
|
|
22530
|
+
sequence: 25,
|
|
22351
22531
|
})
|
|
22352
22532
|
.add("increment_number", {
|
|
22353
22533
|
condition: (cell) => !cell.isFormula &&
|
|
22354
22534
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22355
22535
|
generateRule: (cell, cells) => {
|
|
22356
|
-
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number
|
|
22536
|
+
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22537
|
+
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22357
22538
|
const increment = calculateIncrementBasedOnGroup(group);
|
|
22358
22539
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22359
22540
|
return {
|
|
@@ -22364,6 +22545,37 @@ autofillRulesRegistry
|
|
|
22364
22545
|
},
|
|
22365
22546
|
sequence: 40,
|
|
22366
22547
|
});
|
|
22548
|
+
/**
|
|
22549
|
+
* Returns the date intervals between consecutive dates of an array
|
|
22550
|
+
* in the format of { years: number, months: number, days: number }
|
|
22551
|
+
*
|
|
22552
|
+
* The split is necessary to make abstraction of leap years and
|
|
22553
|
+
* months with different number of days.
|
|
22554
|
+
*
|
|
22555
|
+
* @param dates
|
|
22556
|
+
*/
|
|
22557
|
+
function getDateIntervals(dates) {
|
|
22558
|
+
if (dates.length < 2) {
|
|
22559
|
+
return [{ years: 0, months: 0, days: 0 }];
|
|
22560
|
+
}
|
|
22561
|
+
const res = dates.map((date, index) => {
|
|
22562
|
+
if (index === 0) {
|
|
22563
|
+
return { years: 0, months: 0, days: 0 };
|
|
22564
|
+
}
|
|
22565
|
+
const previous = DateTime.fromTimestamp(dates[index - 1].getTime());
|
|
22566
|
+
const years = getTimeDifferenceInWholeYears(previous, date);
|
|
22567
|
+
const months = getTimeDifferenceInWholeMonths(previous, date) % 12;
|
|
22568
|
+
previous.setFullYear(previous.getFullYear() + years);
|
|
22569
|
+
previous.setMonth(previous.getMonth() + months);
|
|
22570
|
+
const days = getTimeDifferenceInWholeDays(previous, date);
|
|
22571
|
+
return {
|
|
22572
|
+
years,
|
|
22573
|
+
months,
|
|
22574
|
+
days,
|
|
22575
|
+
};
|
|
22576
|
+
});
|
|
22577
|
+
return res.slice(1);
|
|
22578
|
+
}
|
|
22367
22579
|
|
|
22368
22580
|
const cellPopoverRegistry = new Registry();
|
|
22369
22581
|
|
|
@@ -27291,7 +27503,7 @@ function load(data, verboseImport) {
|
|
|
27291
27503
|
if (!data) {
|
|
27292
27504
|
return createEmptyWorkbookData();
|
|
27293
27505
|
}
|
|
27294
|
-
console.
|
|
27506
|
+
console.debug("### Loading data ###");
|
|
27295
27507
|
const start = performance.now();
|
|
27296
27508
|
if (data["[Content_Types].xml"]) {
|
|
27297
27509
|
const reader = new XlsxReader(data);
|
|
@@ -27305,13 +27517,13 @@ function load(data, verboseImport) {
|
|
|
27305
27517
|
// apply migrations, if needed
|
|
27306
27518
|
if ("version" in data) {
|
|
27307
27519
|
if (data.version < CURRENT_VERSION) {
|
|
27308
|
-
console.
|
|
27520
|
+
console.debug("Migrating data from version", data.version);
|
|
27309
27521
|
data = migrate(data);
|
|
27310
27522
|
}
|
|
27311
27523
|
}
|
|
27312
27524
|
data = repairData(data);
|
|
27313
|
-
console.
|
|
27314
|
-
console.
|
|
27525
|
+
console.debug("Data loaded in", performance.now() - start, "ms");
|
|
27526
|
+
console.debug("###");
|
|
27315
27527
|
return data;
|
|
27316
27528
|
}
|
|
27317
27529
|
// -----------------------------------------------------------------------------
|
|
@@ -27341,7 +27553,7 @@ function migrate(data) {
|
|
|
27341
27553
|
for (let i = index; i < steps.length; i++) {
|
|
27342
27554
|
data = steps[i].migrate(data);
|
|
27343
27555
|
}
|
|
27344
|
-
console.
|
|
27556
|
+
console.debug("Data migrated in", performance.now() - start, "ms");
|
|
27345
27557
|
return data;
|
|
27346
27558
|
}
|
|
27347
27559
|
/**
|
|
@@ -27920,7 +28132,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
|
|
|
27920
28132
|
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
27921
28133
|
// tooltipItem.parsed can be an object or a number for pie charts
|
|
27922
28134
|
let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
|
|
27923
|
-
if (
|
|
28135
|
+
if (yLabel === undefined || yLabel === null) {
|
|
27924
28136
|
yLabel = tooltipItem.parsed;
|
|
27925
28137
|
}
|
|
27926
28138
|
const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
|
|
@@ -28361,13 +28573,10 @@ function createBarChartRuntime(chart, getters) {
|
|
|
28361
28573
|
* datasets to ensure the way we distinguish the originals and trendLine datasets after
|
|
28362
28574
|
*/
|
|
28363
28575
|
trendDatasets.forEach((x) => config.data.datasets.push(x));
|
|
28364
|
-
const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
|
|
28365
28576
|
config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
|
|
28366
|
-
|
|
28367
|
-
|
|
28368
|
-
|
|
28369
|
-
}
|
|
28370
|
-
return "";
|
|
28577
|
+
return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
|
|
28578
|
+
? undefined
|
|
28579
|
+
: "";
|
|
28371
28580
|
};
|
|
28372
28581
|
}
|
|
28373
28582
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
@@ -28737,7 +28946,6 @@ function createLineOrScatterChartRuntime(chart, getters) {
|
|
|
28737
28946
|
else if (axisType === "linear") {
|
|
28738
28947
|
config.options.scales.x.type = "linear";
|
|
28739
28948
|
config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
|
|
28740
|
-
config.options.plugins.tooltip.callbacks.title = () => "";
|
|
28741
28949
|
config.options.plugins.tooltip.callbacks.label = (tooltipItem) => {
|
|
28742
28950
|
const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
|
|
28743
28951
|
let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
|
|
@@ -28825,15 +29033,12 @@ function createLineOrScatterChartRuntime(chart, getters) {
|
|
|
28825
29033
|
* distinguish the originals and trendLine datasets after
|
|
28826
29034
|
*/
|
|
28827
29035
|
trendDatasets.forEach((x) => config.data.datasets.push(x));
|
|
28828
|
-
const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
|
|
28829
|
-
config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
|
|
28830
|
-
if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
|
|
28831
|
-
// @ts-expect-error
|
|
28832
|
-
return originalTooltipTitle?.(tooltipItems);
|
|
28833
|
-
}
|
|
28834
|
-
return "";
|
|
28835
|
-
};
|
|
28836
29036
|
}
|
|
29037
|
+
config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
|
|
29038
|
+
const displayTooltipTitle = axisType !== "linear" &&
|
|
29039
|
+
tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
|
|
29040
|
+
return displayTooltipTitle ? undefined : "";
|
|
29041
|
+
};
|
|
28837
29042
|
return {
|
|
28838
29043
|
chartJsConfig: config,
|
|
28839
29044
|
background: chart.background || BACKGROUND_CHART_COLOR,
|
|
@@ -28898,6 +29103,7 @@ class ComboChart extends AbstractChart {
|
|
|
28898
29103
|
ranges.push({
|
|
28899
29104
|
...this.dataSetDesign?.[i],
|
|
28900
29105
|
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
|
|
29106
|
+
type: this.dataSetDesign?.[i]?.type ?? (i ? "line" : "bar"),
|
|
28901
29107
|
});
|
|
28902
29108
|
}
|
|
28903
29109
|
return {
|
|
@@ -28943,9 +29149,13 @@ class ComboChart extends AbstractChart {
|
|
|
28943
29149
|
return new ComboChart(definition, this.sheetId, this.getters);
|
|
28944
29150
|
}
|
|
28945
29151
|
static getDefinitionFromContextCreation(context) {
|
|
29152
|
+
const dataSets = (context.range ?? []).map((ds, index) => ({
|
|
29153
|
+
...ds,
|
|
29154
|
+
type: index ? "line" : "bar",
|
|
29155
|
+
}));
|
|
28946
29156
|
return {
|
|
28947
29157
|
background: context.background,
|
|
28948
|
-
dataSets
|
|
29158
|
+
dataSets,
|
|
28949
29159
|
dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
|
|
28950
29160
|
aggregated: context.aggregated,
|
|
28951
29161
|
legendPosition: context.legendPosition ?? "top",
|
|
@@ -28990,7 +29200,6 @@ function createComboChartRuntime(chart, getters) {
|
|
|
28990
29200
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
|
|
28991
29201
|
const legend = {
|
|
28992
29202
|
labels: { color: fontColor },
|
|
28993
|
-
reverse: true,
|
|
28994
29203
|
};
|
|
28995
29204
|
if (chart.legendPosition === "none") {
|
|
28996
29205
|
legend.display = false;
|
|
@@ -29058,14 +29267,15 @@ function createComboChartRuntime(chart, getters) {
|
|
|
29058
29267
|
for (let [index, { label, data }] of dataSetsValues.entries()) {
|
|
29059
29268
|
const design = definition.dataSets[index];
|
|
29060
29269
|
const color = colors.next();
|
|
29270
|
+
const type = design?.type ?? "line";
|
|
29061
29271
|
const dataset = {
|
|
29062
29272
|
label: design?.label ?? label,
|
|
29063
29273
|
data,
|
|
29064
29274
|
borderColor: color,
|
|
29065
29275
|
backgroundColor: color,
|
|
29066
29276
|
yAxisID: design?.yAxisId ?? "y",
|
|
29067
|
-
type
|
|
29068
|
-
order:
|
|
29277
|
+
type,
|
|
29278
|
+
order: type === "bar" ? dataSetsValues.length + index : index,
|
|
29069
29279
|
};
|
|
29070
29280
|
config.data.datasets.push(dataset);
|
|
29071
29281
|
const trend = definition.dataSets?.[index].trend;
|
|
@@ -29092,13 +29302,10 @@ function createComboChartRuntime(chart, getters) {
|
|
|
29092
29302
|
* distinguish the originals and trendLine datasets after
|
|
29093
29303
|
*/
|
|
29094
29304
|
trendDatasets.forEach((x) => config.data.datasets.push(x));
|
|
29095
|
-
const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
|
|
29096
29305
|
config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
|
|
29097
|
-
|
|
29098
|
-
|
|
29099
|
-
|
|
29100
|
-
}
|
|
29101
|
-
return "";
|
|
29306
|
+
return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
|
|
29307
|
+
? undefined
|
|
29308
|
+
: "";
|
|
29102
29309
|
};
|
|
29103
29310
|
}
|
|
29104
29311
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
@@ -35294,6 +35501,7 @@ const CHECK_SVG = /*xml*/ `
|
|
|
35294
35501
|
<path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
|
|
35295
35502
|
</svg>
|
|
35296
35503
|
`;
|
|
35504
|
+
const CHECKBOX_WIDTH = 14;
|
|
35297
35505
|
css /* scss */ `
|
|
35298
35506
|
label.o-checkbox {
|
|
35299
35507
|
input {
|
|
@@ -35301,8 +35509,8 @@ css /* scss */ `
|
|
|
35301
35509
|
-webkit-appearance: none;
|
|
35302
35510
|
-moz-appearance: none;
|
|
35303
35511
|
border-radius: 0;
|
|
35304
|
-
width:
|
|
35305
|
-
height:
|
|
35512
|
+
width: ${CHECKBOX_WIDTH}px;
|
|
35513
|
+
height: ${CHECKBOX_WIDTH}px;
|
|
35306
35514
|
vertical-align: top;
|
|
35307
35515
|
box-sizing: border-box;
|
|
35308
35516
|
outline: none;
|
|
@@ -36087,7 +36295,6 @@ class BarConfigPanel extends GenericChartConfigPanel {
|
|
|
36087
36295
|
css /* scss */ `
|
|
36088
36296
|
.o_side_panel_collapsible_title {
|
|
36089
36297
|
font-size: 16px;
|
|
36090
|
-
font-weight: bold;
|
|
36091
36298
|
cursor: pointer;
|
|
36092
36299
|
padding: 6px 0px 6px 6px !important;
|
|
36093
36300
|
|
|
@@ -37067,6 +37274,9 @@ class ChartWithAxisDesignPanel extends Component {
|
|
|
37067
37274
|
getDataSeries() {
|
|
37068
37275
|
return this.props.definition.dataSets.map((d, i) => d.label ?? `${ChartTerms.Series} ${i + 1}`);
|
|
37069
37276
|
}
|
|
37277
|
+
getPolynomialDegrees() {
|
|
37278
|
+
return range(1, this.getMaxPolynomialDegree() + 1);
|
|
37279
|
+
}
|
|
37070
37280
|
updateSerieEditor(ev) {
|
|
37071
37281
|
const chartId = this.props.figureId;
|
|
37072
37282
|
const selectedIndex = ev.target.selectedIndex;
|
|
@@ -37183,12 +37393,7 @@ class ChartWithAxisDesignPanel extends Component {
|
|
|
37183
37393
|
}
|
|
37184
37394
|
onChangePolynomialDegree(ev) {
|
|
37185
37395
|
const element = ev.target;
|
|
37186
|
-
|
|
37187
|
-
if (order < 2) {
|
|
37188
|
-
element.value = `${this.getTrendLineConfiguration()?.order ?? 2}`;
|
|
37189
|
-
return;
|
|
37190
|
-
}
|
|
37191
|
-
this.updateTrendLineValue({ order });
|
|
37396
|
+
this.updateTrendLineValue({ order: parseInt(element.value) });
|
|
37192
37397
|
}
|
|
37193
37398
|
getTrendLineColor() {
|
|
37194
37399
|
return this.getTrendLineConfiguration()?.color ?? setColorAlpha(this.getDataSerieColor(), 0.5);
|
|
@@ -37210,6 +37415,36 @@ class ChartWithAxisDesignPanel extends Component {
|
|
|
37210
37415
|
};
|
|
37211
37416
|
this.props.updateChart(this.props.figureId, { dataSets });
|
|
37212
37417
|
}
|
|
37418
|
+
getMaxPolynomialDegree() {
|
|
37419
|
+
const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
|
|
37420
|
+
return Math.min(10, runtime.chartJsConfig.data.datasets[this.state.index].data.length - 1);
|
|
37421
|
+
}
|
|
37422
|
+
}
|
|
37423
|
+
|
|
37424
|
+
class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
|
|
37425
|
+
static template = "o-spreadsheet-ComboChartDesignPanel";
|
|
37426
|
+
seriesTypeChoices = [
|
|
37427
|
+
{ value: "bar", label: _t("Bar") },
|
|
37428
|
+
{ value: "line", label: _t("Line") },
|
|
37429
|
+
];
|
|
37430
|
+
updateDataSeriesType(type) {
|
|
37431
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
37432
|
+
if (!dataSets?.[this.state.index]) {
|
|
37433
|
+
return;
|
|
37434
|
+
}
|
|
37435
|
+
dataSets[this.state.index] = {
|
|
37436
|
+
...dataSets[this.state.index],
|
|
37437
|
+
type,
|
|
37438
|
+
};
|
|
37439
|
+
this.props.updateChart(this.props.figureId, { dataSets });
|
|
37440
|
+
}
|
|
37441
|
+
getDataSeriesType() {
|
|
37442
|
+
const dataSets = this.props.definition.dataSets;
|
|
37443
|
+
if (!dataSets?.[this.state.index]) {
|
|
37444
|
+
return "bar";
|
|
37445
|
+
}
|
|
37446
|
+
return dataSets[this.state.index].type ?? "line";
|
|
37447
|
+
}
|
|
37213
37448
|
}
|
|
37214
37449
|
|
|
37215
37450
|
class GaugeChartConfigPanel extends Component {
|
|
@@ -37638,7 +37873,7 @@ chartSidePanelComponentRegistry
|
|
|
37638
37873
|
})
|
|
37639
37874
|
.add("combo", {
|
|
37640
37875
|
configuration: GenericChartConfigPanel,
|
|
37641
|
-
design:
|
|
37876
|
+
design: ComboChartDesignPanel,
|
|
37642
37877
|
})
|
|
37643
37878
|
.add("pie", {
|
|
37644
37879
|
configuration: GenericChartConfigPanel,
|
|
@@ -39012,7 +39247,6 @@ css /* scss */ `
|
|
|
39012
39247
|
width: 142px;
|
|
39013
39248
|
.o-cf-preview-description-rule {
|
|
39014
39249
|
margin-bottom: 4px;
|
|
39015
|
-
font-weight: 600;
|
|
39016
39250
|
max-height: 2.8em;
|
|
39017
39251
|
line-height: 1.4em;
|
|
39018
39252
|
}
|
|
@@ -42014,7 +42248,6 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
|
|
|
42014
42248
|
sequence: 0,
|
|
42015
42249
|
autoSelectFirstProposal: true,
|
|
42016
42250
|
getProposals(tokenAtCursor) {
|
|
42017
|
-
// return []
|
|
42018
42251
|
const measureProposals = pivot.measures
|
|
42019
42252
|
.filter((m) => m !== forComputedMeasure)
|
|
42020
42253
|
.map((measure) => {
|
|
@@ -42817,8 +43050,9 @@ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
|
|
|
42817
43050
|
* This function converts a list of data entry into a spreadsheet pivot table.
|
|
42818
43051
|
*/
|
|
42819
43052
|
function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
|
|
43053
|
+
const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
|
|
42820
43054
|
const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
|
|
42821
|
-
computeWidthOfColumnsNodes(columnsTree,
|
|
43055
|
+
computeWidthOfColumnsNodes(columnsTree, measureIds.length);
|
|
42822
43056
|
const cols = columnsTreeToColumns(columnsTree, definition);
|
|
42823
43057
|
const rows = dataEntriesToRows(dataEntries, 0, definition.rows, [], []);
|
|
42824
43058
|
// Add the total row
|
|
@@ -42827,7 +43061,6 @@ function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
|
|
|
42827
43061
|
values: [],
|
|
42828
43062
|
indent: 0,
|
|
42829
43063
|
});
|
|
42830
|
-
const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
|
|
42831
43064
|
const fieldsType = {};
|
|
42832
43065
|
for (const columns of definition.columns) {
|
|
42833
43066
|
fieldsType[columns.fieldName] = columns.type;
|
|
@@ -43588,7 +43821,7 @@ pivotRegistry.add("SPREADSHEET", {
|
|
|
43588
43821
|
onIterationEndEvaluation: (pivot) => pivot.markAsDirtyForEvaluation(),
|
|
43589
43822
|
dateGranularities: [...dateGranularities],
|
|
43590
43823
|
datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
|
|
43591
|
-
isMeasureCandidate: (field) => !["
|
|
43824
|
+
isMeasureCandidate: (field) => !["datetime", "boolean"].includes(field.type),
|
|
43592
43825
|
isGroupable: () => true,
|
|
43593
43826
|
});
|
|
43594
43827
|
|
|
@@ -46117,13 +46350,10 @@ class GridCellIcon extends Component {
|
|
|
46117
46350
|
}
|
|
46118
46351
|
}
|
|
46119
46352
|
|
|
46120
|
-
const CHECKBOX_WIDTH = 15;
|
|
46121
46353
|
const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
|
|
46122
46354
|
css /* scss */ `
|
|
46123
46355
|
.o-dv-checkbox {
|
|
46124
46356
|
box-sizing: border-box !important;
|
|
46125
|
-
width: ${CHECKBOX_WIDTH}px;
|
|
46126
|
-
height: ${CHECKBOX_WIDTH}px;
|
|
46127
46357
|
accent-color: #808080;
|
|
46128
46358
|
margin: ${MARGIN}px;
|
|
46129
46359
|
/** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
|
|
@@ -46132,13 +46362,15 @@ css /* scss */ `
|
|
|
46132
46362
|
`;
|
|
46133
46363
|
class DataValidationCheckbox extends Component {
|
|
46134
46364
|
static template = "o-spreadsheet-DataValidationCheckbox";
|
|
46365
|
+
static components = {
|
|
46366
|
+
Checkbox,
|
|
46367
|
+
};
|
|
46135
46368
|
static props = {
|
|
46136
46369
|
cellPosition: Object,
|
|
46137
46370
|
};
|
|
46138
|
-
onCheckboxChange(
|
|
46139
|
-
const newValue = ev.target.checked;
|
|
46371
|
+
onCheckboxChange(value) {
|
|
46140
46372
|
const { sheetId, col, row } = this.props.cellPosition;
|
|
46141
|
-
const cellContent =
|
|
46373
|
+
const cellContent = value ? "TRUE" : "FALSE";
|
|
46142
46374
|
this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
|
|
46143
46375
|
}
|
|
46144
46376
|
get checkBoxValue() {
|
|
@@ -46952,7 +47184,12 @@ class GridAddRowsFooter extends Component {
|
|
|
46952
47184
|
class PaintFormatStore extends SpreadsheetStore {
|
|
46953
47185
|
mutators = ["activate", "cancel", "pasteFormat"];
|
|
46954
47186
|
highlightStore = this.get(HighlightStore);
|
|
46955
|
-
|
|
47187
|
+
clipboardHandlers = [
|
|
47188
|
+
new CellClipboardHandler(this.getters, this.model.dispatch),
|
|
47189
|
+
new BorderClipboardHandler(this.getters, this.model.dispatch),
|
|
47190
|
+
new TableClipboardHandler(this.getters, this.model.dispatch),
|
|
47191
|
+
new ConditionalFormatClipboardHandler(this.getters, this.model.dispatch),
|
|
47192
|
+
];
|
|
46956
47193
|
status = "inactive";
|
|
46957
47194
|
copiedData;
|
|
46958
47195
|
constructor(get) {
|
|
@@ -46962,6 +47199,13 @@ class PaintFormatStore extends SpreadsheetStore {
|
|
|
46962
47199
|
this.highlightStore.unRegister(this);
|
|
46963
47200
|
});
|
|
46964
47201
|
}
|
|
47202
|
+
handle(cmd) {
|
|
47203
|
+
switch (cmd.type) {
|
|
47204
|
+
case "PAINT_FORMAT":
|
|
47205
|
+
this.paintFormat(cmd.sheetId, cmd.target);
|
|
47206
|
+
break;
|
|
47207
|
+
}
|
|
47208
|
+
}
|
|
46965
47209
|
activate(args) {
|
|
46966
47210
|
this.copiedData = this.copyFormats();
|
|
46967
47211
|
this.status = args.persistent ? "persistent" : "oneOff";
|
|
@@ -46971,16 +47215,7 @@ class PaintFormatStore extends SpreadsheetStore {
|
|
|
46971
47215
|
this.copiedData = undefined;
|
|
46972
47216
|
}
|
|
46973
47217
|
pasteFormat(target) {
|
|
46974
|
-
|
|
46975
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
46976
|
-
this.cellClipboardHandler.paste({ zones: target, sheetId }, this.copiedData, {
|
|
46977
|
-
isCutOperation: false,
|
|
46978
|
-
pasteOption: "onlyFormat",
|
|
46979
|
-
});
|
|
46980
|
-
}
|
|
46981
|
-
if (this.status === "oneOff") {
|
|
46982
|
-
this.cancel();
|
|
46983
|
-
}
|
|
47218
|
+
this.model.dispatch("PAINT_FORMAT", { target, sheetId: this.getters.getActiveSheetId() });
|
|
46984
47219
|
}
|
|
46985
47220
|
get isActive() {
|
|
46986
47221
|
return this.status !== "inactive";
|
|
@@ -46988,7 +47223,24 @@ class PaintFormatStore extends SpreadsheetStore {
|
|
|
46988
47223
|
copyFormats() {
|
|
46989
47224
|
const sheetId = this.getters.getActiveSheetId();
|
|
46990
47225
|
const zones = this.getters.getSelectedZones();
|
|
46991
|
-
|
|
47226
|
+
const copiedData = {};
|
|
47227
|
+
for (const handler of this.clipboardHandlers) {
|
|
47228
|
+
Object.assign(copiedData, handler.copy(getClipboardDataPositions(sheetId, zones)));
|
|
47229
|
+
}
|
|
47230
|
+
return copiedData;
|
|
47231
|
+
}
|
|
47232
|
+
paintFormat(sheetId, target) {
|
|
47233
|
+
if (this.copiedData) {
|
|
47234
|
+
for (const handler of this.clipboardHandlers) {
|
|
47235
|
+
handler.paste({ zones: target, sheetId }, this.copiedData, {
|
|
47236
|
+
isCutOperation: false,
|
|
47237
|
+
pasteOption: "onlyFormat",
|
|
47238
|
+
});
|
|
47239
|
+
}
|
|
47240
|
+
}
|
|
47241
|
+
if (this.status === "oneOff") {
|
|
47242
|
+
this.cancel();
|
|
47243
|
+
}
|
|
46992
47244
|
}
|
|
46993
47245
|
get highlights() {
|
|
46994
47246
|
const data = this.copiedData;
|
|
@@ -55233,7 +55485,7 @@ class PivotCorePlugin extends CorePlugin {
|
|
|
55233
55485
|
case "DUPLICATE_PIVOT": {
|
|
55234
55486
|
const { pivotId, newPivotId } = cmd;
|
|
55235
55487
|
const pivot = deepCopy(this.getPivotCore(pivotId).definition);
|
|
55236
|
-
pivot.name =
|
|
55488
|
+
pivot.name = cmd.duplicatedPivotName ?? pivot.name + " (copy)";
|
|
55237
55489
|
this.addPivot(newPivotId, pivot);
|
|
55238
55490
|
break;
|
|
55239
55491
|
}
|
|
@@ -55273,7 +55525,7 @@ class PivotCorePlugin extends CorePlugin {
|
|
|
55273
55525
|
return `(#${formulaId}) ${this.getPivotName(pivotId)}`;
|
|
55274
55526
|
}
|
|
55275
55527
|
getPivotName(pivotId) {
|
|
55276
|
-
return
|
|
55528
|
+
return this.getPivotCore(pivotId).definition.name;
|
|
55277
55529
|
}
|
|
55278
55530
|
/**
|
|
55279
55531
|
* Returns the pivot core definition of the pivot with the given id.
|
|
@@ -56915,7 +57167,7 @@ class Evaluator {
|
|
|
56915
57167
|
cellsToCompute.addMany(arrayFormulasPositions);
|
|
56916
57168
|
cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
|
|
56917
57169
|
this.evaluate(cellsToCompute);
|
|
56918
|
-
console.
|
|
57170
|
+
console.debug("evaluate Cells", performance.now() - start, "ms");
|
|
56919
57171
|
}
|
|
56920
57172
|
getArrayFormulasImpactedByChangesOf(positions) {
|
|
56921
57173
|
const impactedPositions = this.createEmptyPositionSet();
|
|
@@ -56959,7 +57211,7 @@ class Evaluator {
|
|
|
56959
57211
|
const start = performance.now();
|
|
56960
57212
|
this.evaluatedCells = new PositionMap();
|
|
56961
57213
|
this.evaluate(this.getAllCells());
|
|
56962
|
-
console.
|
|
57214
|
+
console.debug("evaluate all cells", performance.now() - start, "ms");
|
|
56963
57215
|
}
|
|
56964
57216
|
evaluateFormulaResult(sheetId, formulaString) {
|
|
56965
57217
|
const compiledFormula = compile(formulaString);
|
|
@@ -59050,7 +59302,7 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
59050
59302
|
const ranking = {};
|
|
59051
59303
|
const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
|
|
59052
59304
|
const secondaryDimension = mainDimension === "row" ? "column" : "row";
|
|
59053
|
-
let pivotCells = this.getPivotValueCells();
|
|
59305
|
+
let pivotCells = this.getPivotValueCells(measure.id);
|
|
59054
59306
|
if (mainDimension === "column") {
|
|
59055
59307
|
// Transpose the pivot cells so we can do the same operations on the columns as on the rows
|
|
59056
59308
|
// This means that we need to transpose back the ranking at the end
|
|
@@ -59094,7 +59346,7 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
59094
59346
|
const cellsRunningTotals = {};
|
|
59095
59347
|
const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
|
|
59096
59348
|
const secondaryDimension = mainDimension === "row" ? "column" : "row";
|
|
59097
|
-
let pivotCells = this.getPivotValueCells();
|
|
59349
|
+
let pivotCells = this.getPivotValueCells(measure.id);
|
|
59098
59350
|
if (mainDimension === "column") {
|
|
59099
59351
|
// Transpose the pivot cells so we can do the same operations on the columns as on the rows
|
|
59100
59352
|
// This means that we need to transpose back the totals at the end
|
|
@@ -59173,10 +59425,10 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
59173
59425
|
const comparedValueNumber = this.strictMeasureValueToNumber(comparedValue);
|
|
59174
59426
|
return comparedValueNumber;
|
|
59175
59427
|
}
|
|
59176
|
-
getPivotValueCells() {
|
|
59428
|
+
getPivotValueCells(measureId) {
|
|
59177
59429
|
return this.getTableStructure()
|
|
59178
59430
|
.getPivotCells()
|
|
59179
|
-
.map((col) => col.filter((cell) => cell.type === "VALUE"))
|
|
59431
|
+
.map((col) => col.filter((cell) => cell.type === "VALUE" && cell.measure === measureId))
|
|
59180
59432
|
.filter((col) => col.length > 0);
|
|
59181
59433
|
}
|
|
59182
59434
|
measureValueToNumber(result) {
|
|
@@ -60705,7 +60957,7 @@ class Session extends EventBus {
|
|
|
60705
60957
|
this.onMessageReceived(message);
|
|
60706
60958
|
}
|
|
60707
60959
|
this.isReplayingInitialRevisions = false;
|
|
60708
|
-
console.
|
|
60960
|
+
console.debug("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
|
|
60709
60961
|
}
|
|
60710
60962
|
/**
|
|
60711
60963
|
* Notify the server that the user client left the collaborative session
|
|
@@ -60944,6 +61196,7 @@ class Session extends EventBus {
|
|
|
60944
61196
|
case "REMOTE_REVISION":
|
|
60945
61197
|
case "REVISION_REDONE":
|
|
60946
61198
|
case "REVISION_UNDONE":
|
|
61199
|
+
case "SNAPSHOT_CREATED":
|
|
60947
61200
|
return this.processedRevisions.has(message.nextRevisionId);
|
|
60948
61201
|
default:
|
|
60949
61202
|
return false;
|
|
@@ -61501,12 +61754,13 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
61501
61754
|
this.dispatch("DUPLICATE_PIVOT", {
|
|
61502
61755
|
pivotId,
|
|
61503
61756
|
newPivotId,
|
|
61757
|
+
duplicatedPivotName: _t("%s (copy)", this.getters.getPivotCoreDefinition(pivotId).name),
|
|
61504
61758
|
});
|
|
61505
61759
|
const activeSheetId = this.getters.getActiveSheetId();
|
|
61506
61760
|
const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
|
|
61507
61761
|
const formulaId = this.getters.getPivotFormulaId(newPivotId);
|
|
61508
61762
|
const newPivotName = this.getters.getPivotName(newPivotId);
|
|
61509
|
-
this.dispatch("CREATE_SHEET", {
|
|
61763
|
+
const result = this.dispatch("CREATE_SHEET", {
|
|
61510
61764
|
sheetId: newSheetId,
|
|
61511
61765
|
name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
|
|
61512
61766
|
newPivotName,
|
|
@@ -61514,20 +61768,19 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
61514
61768
|
})),
|
|
61515
61769
|
position,
|
|
61516
61770
|
});
|
|
61517
|
-
|
|
61518
|
-
|
|
61519
|
-
|
|
61520
|
-
|
|
61521
|
-
|
|
61522
|
-
content: `=PIVOT(${formulaId})`,
|
|
61523
|
-
});
|
|
61771
|
+
if (result.isSuccessful) {
|
|
61772
|
+
this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
|
|
61773
|
+
const pivot = this.getters.getPivot(pivotId);
|
|
61774
|
+
this.insertPivotWithTable(newSheetId, 0, 0, newPivotId, pivot.getTableStructure().export(), "dynamic");
|
|
61775
|
+
}
|
|
61524
61776
|
}
|
|
61525
61777
|
getPivotDuplicateSheetName(pivotName) {
|
|
61526
61778
|
let i = 1;
|
|
61527
61779
|
const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
|
|
61528
|
-
|
|
61780
|
+
const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
|
|
61781
|
+
let name = sanitizedName;
|
|
61529
61782
|
while (names.includes(name)) {
|
|
61530
|
-
name = `${
|
|
61783
|
+
name = `${sanitizedName} (${i})`;
|
|
61531
61784
|
i++;
|
|
61532
61785
|
}
|
|
61533
61786
|
return name;
|
|
@@ -66635,10 +66888,9 @@ css /* scss */ `
|
|
|
66635
66888
|
user-select: none;
|
|
66636
66889
|
color: ${TEXT_BODY};
|
|
66637
66890
|
|
|
66638
|
-
.o-
|
|
66891
|
+
.o-sidePanelTitle {
|
|
66639
66892
|
line-height: 20px;
|
|
66640
66893
|
font-size: 16px;
|
|
66641
|
-
font-weight: 600;
|
|
66642
66894
|
}
|
|
66643
66895
|
|
|
66644
66896
|
.o-sidePanelHeader {
|
|
@@ -66723,6 +66975,10 @@ css /* scss */ `
|
|
|
66723
66975
|
}
|
|
66724
66976
|
}
|
|
66725
66977
|
}
|
|
66978
|
+
|
|
66979
|
+
.o-fw-bold {
|
|
66980
|
+
font-weight: 500;
|
|
66981
|
+
}
|
|
66726
66982
|
`;
|
|
66727
66983
|
class SidePanel extends Component {
|
|
66728
66984
|
static template = "o-spreadsheet-SidePanel";
|
|
@@ -70880,6 +71136,15 @@ function addTableColumns(table, sheetData) {
|
|
|
70880
71136
|
["id", i + 1], // id cannot be 0
|
|
70881
71137
|
["name", colName],
|
|
70882
71138
|
];
|
|
71139
|
+
if (table.config.totalRow) {
|
|
71140
|
+
// Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
|
|
71141
|
+
// `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
|
|
71142
|
+
const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
|
|
71143
|
+
const colTotalContent = sheetData.cells[colTotalXc]?.content;
|
|
71144
|
+
if (colTotalContent?.startsWith("=")) {
|
|
71145
|
+
colAttributes.push(["totalsRowFunction", "custom"]);
|
|
71146
|
+
}
|
|
71147
|
+
}
|
|
70883
71148
|
columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
|
|
70884
71149
|
}
|
|
70885
71150
|
return escapeXml /*xml*/ `
|
|
@@ -70974,8 +71239,9 @@ function addRows(construct, data, sheet) {
|
|
|
70974
71239
|
}
|
|
70975
71240
|
else if (cell.content && cell.content !== "") {
|
|
70976
71241
|
const isTableHeader = isCellTableHeader(c, r, sheet);
|
|
71242
|
+
const isTableTotal = isCellTableTotal(c, r, sheet);
|
|
70977
71243
|
const isPlainText = !!(cell.format && isTextFormat(data.formats[cell.format]));
|
|
70978
|
-
({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
|
|
71244
|
+
({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
|
|
70979
71245
|
}
|
|
70980
71246
|
attributes.push(...additionalAttrs);
|
|
70981
71247
|
// prettier-ignore
|
|
@@ -71009,6 +71275,16 @@ function isCellTableHeader(col, row, sheet) {
|
|
|
71009
71275
|
return isInside(col, row, headerZone);
|
|
71010
71276
|
});
|
|
71011
71277
|
}
|
|
71278
|
+
function isCellTableTotal(col, row, sheet) {
|
|
71279
|
+
return sheet.tables.some((table) => {
|
|
71280
|
+
if (!table.config.totalRow) {
|
|
71281
|
+
return false;
|
|
71282
|
+
}
|
|
71283
|
+
const zone = toZone(table.range);
|
|
71284
|
+
const totalZone = { ...zone, top: zone.bottom };
|
|
71285
|
+
return isInside(col, row, totalZone);
|
|
71286
|
+
});
|
|
71287
|
+
}
|
|
71012
71288
|
function addHyperlinks(construct, data, sheetIndex) {
|
|
71013
71289
|
const sheet = data.sheets[sheetIndex];
|
|
71014
71290
|
const cells = sheet.cells;
|
|
@@ -71472,7 +71748,7 @@ class Model extends EventBus {
|
|
|
71472
71748
|
coreHandlers = [];
|
|
71473
71749
|
constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = false) {
|
|
71474
71750
|
const start = performance.now();
|
|
71475
|
-
console.
|
|
71751
|
+
console.debug("##### Model creation #####");
|
|
71476
71752
|
super();
|
|
71477
71753
|
setDefaultTranslationMethod();
|
|
71478
71754
|
stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
|
|
@@ -71499,7 +71775,6 @@ class Model extends EventBus {
|
|
|
71499
71775
|
isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
|
|
71500
71776
|
isDashboard: () => this.config.mode === "dashboard",
|
|
71501
71777
|
};
|
|
71502
|
-
this.uuidGenerator.setIsFastStrategy(true);
|
|
71503
71778
|
// Initiate stream processor
|
|
71504
71779
|
this.selection = new SelectionStreamProcessorImpl(this.getters);
|
|
71505
71780
|
this.coreHandlers.push(this.range);
|
|
@@ -71545,16 +71820,16 @@ class Model extends EventBus {
|
|
|
71545
71820
|
this.joinSession();
|
|
71546
71821
|
if (config.snapshotRequested) {
|
|
71547
71822
|
const startSnapshot = performance.now();
|
|
71548
|
-
console.
|
|
71823
|
+
console.debug("Snapshot requested");
|
|
71549
71824
|
this.session.snapshot(this.exportData());
|
|
71550
71825
|
this.garbageCollectExternalResources();
|
|
71551
|
-
console.
|
|
71826
|
+
console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
|
|
71552
71827
|
}
|
|
71553
71828
|
// mark all models as "raw", so they will not be turned into reactive objects
|
|
71554
71829
|
// by owl, since we do not rely on reactivity
|
|
71555
71830
|
markRaw(this);
|
|
71556
|
-
console.
|
|
71557
|
-
console.
|
|
71831
|
+
console.debug("Model created in", performance.now() - start, "ms");
|
|
71832
|
+
console.debug("######");
|
|
71558
71833
|
}
|
|
71559
71834
|
joinSession() {
|
|
71560
71835
|
this.session.join(this.config.client);
|
|
@@ -71778,7 +72053,7 @@ class Model extends EventBus {
|
|
|
71778
72053
|
this.finalize();
|
|
71779
72054
|
const time = performance.now() - start;
|
|
71780
72055
|
if (time > 5) {
|
|
71781
|
-
console.
|
|
72056
|
+
console.debug(type, time, "ms");
|
|
71782
72057
|
}
|
|
71783
72058
|
});
|
|
71784
72059
|
this.session.save(command, commands, changes);
|
|
@@ -72130,6 +72405,6 @@ const constants = {
|
|
|
72130
72405
|
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
|
|
72131
72406
|
|
|
72132
72407
|
|
|
72133
|
-
__info__.version = "18.0.
|
|
72134
|
-
__info__.date = "2024-
|
|
72135
|
-
__info__.hash = "
|
|
72408
|
+
__info__.version = "18.0.2";
|
|
72409
|
+
__info__.date = "2024-10-24T08:54:21.934Z";
|
|
72410
|
+
__info__.hash = "788df92";
|