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