@odoo/o-spreadsheet 18.0.0 → 18.0.1
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 +397 -173
- package/dist/o-spreadsheet.d.ts +25 -7
- package/dist/o-spreadsheet.esm.js +397 -173
- package/dist/o-spreadsheet.iife.js +397 -173
- package/dist/o-spreadsheet.iife.min.js +517 -519
- package/dist/o_spreadsheet.xml +154 -9
- 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.1
|
|
6
|
+
* @date 2024-10-14T07:54:24.768Z
|
|
7
|
+
* @hash 1771f68
|
|
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";
|
|
@@ -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) {
|
|
@@ -17843,7 +17850,7 @@ function assertDomainLength(domain) {
|
|
|
17843
17850
|
throw new EvaluationError(_t("Function PIVOT takes an even number of arguments."));
|
|
17844
17851
|
}
|
|
17845
17852
|
}
|
|
17846
|
-
function addPivotDependencies(evalContext, coreDefinition) {
|
|
17853
|
+
function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
|
|
17847
17854
|
//TODO This function can be very costly when used with PIVOT.VALUE and PIVOT.HEADER
|
|
17848
17855
|
const dependencies = [];
|
|
17849
17856
|
if (coreDefinition.type === "SPREADSHEET" && coreDefinition.dataSet) {
|
|
@@ -17855,7 +17862,7 @@ function addPivotDependencies(evalContext, coreDefinition) {
|
|
|
17855
17862
|
}
|
|
17856
17863
|
dependencies.push(range);
|
|
17857
17864
|
}
|
|
17858
|
-
for (const measure of
|
|
17865
|
+
for (const measure of forMeasures) {
|
|
17859
17866
|
if (measure.computedBy) {
|
|
17860
17867
|
const formula = evalContext.getters.getMeasureCompiledFormula(measure);
|
|
17861
17868
|
dependencies.push(...formula.dependencies.filter((range) => !range.invalidXc));
|
|
@@ -18288,7 +18295,7 @@ const PIVOT_VALUE = {
|
|
|
18288
18295
|
assertDomainLength(domainArgs);
|
|
18289
18296
|
const pivot = this.getters.getPivot(pivotId);
|
|
18290
18297
|
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
18291
|
-
addPivotDependencies(this, coreDefinition);
|
|
18298
|
+
addPivotDependencies(this, coreDefinition, coreDefinition.measures.filter((m) => m.id === _measure));
|
|
18292
18299
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
18293
18300
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
18294
18301
|
if (error) {
|
|
@@ -18318,7 +18325,7 @@ const PIVOT_HEADER = {
|
|
|
18318
18325
|
assertDomainLength(domainArgs);
|
|
18319
18326
|
const pivot = this.getters.getPivot(_pivotId);
|
|
18320
18327
|
const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
|
|
18321
|
-
addPivotDependencies(this, coreDefinition);
|
|
18328
|
+
addPivotDependencies(this, coreDefinition, []);
|
|
18322
18329
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
18323
18330
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
18324
18331
|
if (error) {
|
|
@@ -18369,7 +18376,7 @@ const PIVOT = {
|
|
|
18369
18376
|
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
18370
18377
|
const pivot = this.getters.getPivot(pivotId);
|
|
18371
18378
|
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
18372
|
-
addPivotDependencies(this, coreDefinition);
|
|
18379
|
+
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
|
|
18373
18380
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
18374
18381
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
18375
18382
|
if (error) {
|
|
@@ -21578,6 +21585,7 @@ function insertTokenAfterArgSeparator(tokenAtCursor, value) {
|
|
|
21578
21585
|
// replace the whole token
|
|
21579
21586
|
start = tokenAtCursor.start;
|
|
21580
21587
|
}
|
|
21588
|
+
this.composer.stopComposerRangeSelection();
|
|
21581
21589
|
this.composer.changeComposerCursorSelection(start, end);
|
|
21582
21590
|
this.composer.replaceComposerCursorSelection(value);
|
|
21583
21591
|
}
|
|
@@ -21595,6 +21603,7 @@ function insertTokenAfterLeftParenthesis(tokenAtCursor, value) {
|
|
|
21595
21603
|
// replace the whole token
|
|
21596
21604
|
start = tokenAtCursor.start;
|
|
21597
21605
|
}
|
|
21606
|
+
this.composer.stopComposerRangeSelection();
|
|
21598
21607
|
this.composer.changeComposerCursorSelection(start, end);
|
|
21599
21608
|
this.composer.replaceComposerCursorSelection(value);
|
|
21600
21609
|
}
|
|
@@ -22187,6 +22196,27 @@ autofillModifiersRegistry
|
|
|
22187
22196
|
tooltip: content ? { props: { content: tooltipValue } } : undefined,
|
|
22188
22197
|
};
|
|
22189
22198
|
},
|
|
22199
|
+
})
|
|
22200
|
+
.add("DATE_INCREMENT_MODIFIER", {
|
|
22201
|
+
apply: (rule, data, getters) => {
|
|
22202
|
+
const date = toJsDate(rule.current, getters.getLocale());
|
|
22203
|
+
date.setFullYear(date.getFullYear() + rule.increment.years || 0);
|
|
22204
|
+
date.setMonth(date.getMonth() + rule.increment.months || 0);
|
|
22205
|
+
date.setDate(date.getDate() + rule.increment.days || 0);
|
|
22206
|
+
const value = jsDateToNumber(date);
|
|
22207
|
+
rule.current = value;
|
|
22208
|
+
const locale = getters.getLocale();
|
|
22209
|
+
const tooltipValue = formatValue(value, { format: data.cell?.format, locale });
|
|
22210
|
+
return {
|
|
22211
|
+
cellData: {
|
|
22212
|
+
border: data.border,
|
|
22213
|
+
style: data.cell && data.cell.style,
|
|
22214
|
+
format: data.cell && data.cell.format,
|
|
22215
|
+
content: value.toString(),
|
|
22216
|
+
},
|
|
22217
|
+
tooltip: value ? { props: { content: tooltipValue } } : undefined,
|
|
22218
|
+
};
|
|
22219
|
+
},
|
|
22190
22220
|
})
|
|
22191
22221
|
.add("COPY_MODIFIER", {
|
|
22192
22222
|
apply: (rule, data, getters) => {
|
|
@@ -22267,7 +22297,9 @@ function getGroup(cell, cells, filter) {
|
|
|
22267
22297
|
if (x === cell) {
|
|
22268
22298
|
found = true;
|
|
22269
22299
|
}
|
|
22270
|
-
const cellValue = x === undefined || x.isFormula
|
|
22300
|
+
const cellValue = x === undefined || x.isFormula
|
|
22301
|
+
? undefined
|
|
22302
|
+
: evaluateLiteral(x, { locale: DEFAULT_LOCALE, format: x.format });
|
|
22271
22303
|
if (cellValue && filter(cellValue)) {
|
|
22272
22304
|
group.push(cellValue);
|
|
22273
22305
|
}
|
|
@@ -22303,6 +22335,72 @@ function calculateIncrementBasedOnGroup(group) {
|
|
|
22303
22335
|
}
|
|
22304
22336
|
return increment;
|
|
22305
22337
|
}
|
|
22338
|
+
/**
|
|
22339
|
+
* Iterates on a list of date intervals.
|
|
22340
|
+
* if every interval is the same, return the interval
|
|
22341
|
+
* Otherwise return undefined
|
|
22342
|
+
*
|
|
22343
|
+
*/
|
|
22344
|
+
function getEqualInterval(intervals) {
|
|
22345
|
+
if (intervals.length < 2) {
|
|
22346
|
+
return intervals[0] || { years: 0, months: 0, days: 0 };
|
|
22347
|
+
}
|
|
22348
|
+
const equal = intervals.every((interval) => interval.years === intervals[0].years &&
|
|
22349
|
+
interval.months === intervals[0].months &&
|
|
22350
|
+
interval.days === intervals[0].days);
|
|
22351
|
+
return equal ? intervals[0] : undefined;
|
|
22352
|
+
}
|
|
22353
|
+
/**
|
|
22354
|
+
* Based on a group of dates, calculate the increment that should be applied
|
|
22355
|
+
* to the next date.
|
|
22356
|
+
*
|
|
22357
|
+
* This will compute the date difference in calendar terms (years, months, days)
|
|
22358
|
+
* In order to make abstraction of leap years and months with different number of days.
|
|
22359
|
+
*
|
|
22360
|
+
* In case the dates are not equidistant in calendar terms, no rule can be extrapolated
|
|
22361
|
+
* In case of equidistant dates, we either have in that order:
|
|
22362
|
+
* - exact date interval (e.g. +n year OR +n month OR +n day) in which case we increment by the same interval
|
|
22363
|
+
* - exact day interval (e.g. +n days) in which case we increment by the same day interval
|
|
22364
|
+
* - equidistant dates but not the same interval, in which case we return increment of the same interval
|
|
22365
|
+
*
|
|
22366
|
+
* */
|
|
22367
|
+
function calculateDateIncrementBasedOnGroup(group) {
|
|
22368
|
+
if (group.length < 2) {
|
|
22369
|
+
return 1;
|
|
22370
|
+
}
|
|
22371
|
+
const jsDates = group.map((date) => toJsDate(date, DEFAULT_LOCALE));
|
|
22372
|
+
const datesIntervals = getDateIntervals(jsDates);
|
|
22373
|
+
const datesEquidistantInterval = getEqualInterval(datesIntervals);
|
|
22374
|
+
if (datesEquidistantInterval === undefined) {
|
|
22375
|
+
// dates are not equidistant in terms of years, months or days, thus no rule can be extrapolated
|
|
22376
|
+
return undefined;
|
|
22377
|
+
}
|
|
22378
|
+
// The dates are apart by an exact interval of years, months or days
|
|
22379
|
+
// but not a combination of them
|
|
22380
|
+
const exactDateInterval = Object.values(datesEquidistantInterval).filter((value) => value !== 0).length === 1;
|
|
22381
|
+
const isSameDay = Object.values(datesEquidistantInterval).every((el) => el === 0); // handles time values (strict decimals)
|
|
22382
|
+
if (!exactDateInterval || isSameDay) {
|
|
22383
|
+
const timeIntervals = jsDates
|
|
22384
|
+
.map((date, index) => {
|
|
22385
|
+
if (index === 0) {
|
|
22386
|
+
return 0;
|
|
22387
|
+
}
|
|
22388
|
+
const previous = jsDates[index - 1];
|
|
22389
|
+
const days = Math.floor(date.getTime()) - Math.floor(previous.getTime());
|
|
22390
|
+
return days;
|
|
22391
|
+
})
|
|
22392
|
+
.slice(1);
|
|
22393
|
+
const equidistantDates = timeIntervals.every((interval) => interval === timeIntervals[0]);
|
|
22394
|
+
if (equidistantDates) {
|
|
22395
|
+
return group.length * (group[1] - group[0]);
|
|
22396
|
+
}
|
|
22397
|
+
}
|
|
22398
|
+
return {
|
|
22399
|
+
years: datesEquidistantInterval.years * group.length,
|
|
22400
|
+
months: datesEquidistantInterval.months * group.length,
|
|
22401
|
+
days: datesEquidistantInterval.days * group.length,
|
|
22402
|
+
};
|
|
22403
|
+
}
|
|
22306
22404
|
autofillRulesRegistry
|
|
22307
22405
|
.add("simple_value_copy", {
|
|
22308
22406
|
condition: (cell, cells) => {
|
|
@@ -22350,12 +22448,47 @@ autofillRulesRegistry
|
|
|
22350
22448
|
return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
|
|
22351
22449
|
},
|
|
22352
22450
|
sequence: 30,
|
|
22451
|
+
})
|
|
22452
|
+
.add("increment_dates", {
|
|
22453
|
+
condition: (cell, cells) => {
|
|
22454
|
+
return (!cell.isFormula &&
|
|
22455
|
+
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number &&
|
|
22456
|
+
!!cell.format &&
|
|
22457
|
+
isDateTimeFormat(cell.format));
|
|
22458
|
+
},
|
|
22459
|
+
generateRule: (cell, cells) => {
|
|
22460
|
+
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22461
|
+
!!evaluatedCell.format &&
|
|
22462
|
+
isDateTimeFormat(evaluatedCell.format)).map((cell) => Number(cell.value));
|
|
22463
|
+
const increment = calculateDateIncrementBasedOnGroup(group);
|
|
22464
|
+
if (increment === undefined) {
|
|
22465
|
+
return { type: "COPY_MODIFIER" };
|
|
22466
|
+
}
|
|
22467
|
+
/** requires to detect the current date (requires to be an integer value with the right format)
|
|
22468
|
+
* detect if year or if month or if day then extrapolate increment required (+1 month, +1 year + 1 day)
|
|
22469
|
+
*/
|
|
22470
|
+
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22471
|
+
if (typeof increment === "object") {
|
|
22472
|
+
return {
|
|
22473
|
+
type: "DATE_INCREMENT_MODIFIER",
|
|
22474
|
+
increment,
|
|
22475
|
+
current: evaluation.type === CellValueType.number ? evaluation.value : 0,
|
|
22476
|
+
};
|
|
22477
|
+
}
|
|
22478
|
+
return {
|
|
22479
|
+
type: "INCREMENT_MODIFIER",
|
|
22480
|
+
increment,
|
|
22481
|
+
current: evaluation.type === CellValueType.number ? evaluation.value : 0,
|
|
22482
|
+
};
|
|
22483
|
+
},
|
|
22484
|
+
sequence: 25,
|
|
22353
22485
|
})
|
|
22354
22486
|
.add("increment_number", {
|
|
22355
22487
|
condition: (cell) => !cell.isFormula &&
|
|
22356
22488
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22357
22489
|
generateRule: (cell, cells) => {
|
|
22358
|
-
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number
|
|
22490
|
+
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22491
|
+
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22359
22492
|
const increment = calculateIncrementBasedOnGroup(group);
|
|
22360
22493
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22361
22494
|
return {
|
|
@@ -22366,6 +22499,37 @@ autofillRulesRegistry
|
|
|
22366
22499
|
},
|
|
22367
22500
|
sequence: 40,
|
|
22368
22501
|
});
|
|
22502
|
+
/**
|
|
22503
|
+
* Returns the date intervals between consecutive dates of an array
|
|
22504
|
+
* in the format of { years: number, months: number, days: number }
|
|
22505
|
+
*
|
|
22506
|
+
* The split is necessary to make abstraction of leap years and
|
|
22507
|
+
* months with different number of days.
|
|
22508
|
+
*
|
|
22509
|
+
* @param dates
|
|
22510
|
+
*/
|
|
22511
|
+
function getDateIntervals(dates) {
|
|
22512
|
+
if (dates.length < 2) {
|
|
22513
|
+
return [{ years: 0, months: 0, days: 0 }];
|
|
22514
|
+
}
|
|
22515
|
+
const res = dates.map((date, index) => {
|
|
22516
|
+
if (index === 0) {
|
|
22517
|
+
return { years: 0, months: 0, days: 0 };
|
|
22518
|
+
}
|
|
22519
|
+
const previous = DateTime.fromTimestamp(dates[index - 1].getTime());
|
|
22520
|
+
const years = getTimeDifferenceInWholeYears(previous, date);
|
|
22521
|
+
const months = getTimeDifferenceInWholeMonths(previous, date) % 12;
|
|
22522
|
+
previous.setFullYear(previous.getFullYear() + years);
|
|
22523
|
+
previous.setMonth(previous.getMonth() + months);
|
|
22524
|
+
const days = getTimeDifferenceInWholeDays(previous, date);
|
|
22525
|
+
return {
|
|
22526
|
+
years,
|
|
22527
|
+
months,
|
|
22528
|
+
days,
|
|
22529
|
+
};
|
|
22530
|
+
});
|
|
22531
|
+
return res.slice(1);
|
|
22532
|
+
}
|
|
22369
22533
|
|
|
22370
22534
|
const cellPopoverRegistry = new Registry();
|
|
22371
22535
|
|
|
@@ -28900,6 +29064,7 @@ class ComboChart extends AbstractChart {
|
|
|
28900
29064
|
ranges.push({
|
|
28901
29065
|
...this.dataSetDesign?.[i],
|
|
28902
29066
|
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
|
|
29067
|
+
type: this.dataSetDesign?.[i]?.type ?? (i ? "line" : "bar"),
|
|
28903
29068
|
});
|
|
28904
29069
|
}
|
|
28905
29070
|
return {
|
|
@@ -28945,9 +29110,13 @@ class ComboChart extends AbstractChart {
|
|
|
28945
29110
|
return new ComboChart(definition, this.sheetId, this.getters);
|
|
28946
29111
|
}
|
|
28947
29112
|
static getDefinitionFromContextCreation(context) {
|
|
29113
|
+
const dataSets = (context.range ?? []).map((ds, index) => ({
|
|
29114
|
+
...ds,
|
|
29115
|
+
type: index ? "line" : "bar",
|
|
29116
|
+
}));
|
|
28948
29117
|
return {
|
|
28949
29118
|
background: context.background,
|
|
28950
|
-
dataSets
|
|
29119
|
+
dataSets,
|
|
28951
29120
|
dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
|
|
28952
29121
|
aggregated: context.aggregated,
|
|
28953
29122
|
legendPosition: context.legendPosition ?? "top",
|
|
@@ -28992,7 +29161,6 @@ function createComboChartRuntime(chart, getters) {
|
|
|
28992
29161
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
|
|
28993
29162
|
const legend = {
|
|
28994
29163
|
labels: { color: fontColor },
|
|
28995
|
-
reverse: true,
|
|
28996
29164
|
};
|
|
28997
29165
|
if (chart.legendPosition === "none") {
|
|
28998
29166
|
legend.display = false;
|
|
@@ -29060,14 +29228,15 @@ function createComboChartRuntime(chart, getters) {
|
|
|
29060
29228
|
for (let [index, { label, data }] of dataSetsValues.entries()) {
|
|
29061
29229
|
const design = definition.dataSets[index];
|
|
29062
29230
|
const color = colors.next();
|
|
29231
|
+
const type = design?.type ?? "line";
|
|
29063
29232
|
const dataset = {
|
|
29064
29233
|
label: design?.label ?? label,
|
|
29065
29234
|
data,
|
|
29066
29235
|
borderColor: color,
|
|
29067
29236
|
backgroundColor: color,
|
|
29068
29237
|
yAxisID: design?.yAxisId ?? "y",
|
|
29069
|
-
type
|
|
29070
|
-
order:
|
|
29238
|
+
type,
|
|
29239
|
+
order: type === "bar" ? dataSetsValues.length + index : index,
|
|
29071
29240
|
};
|
|
29072
29241
|
config.data.datasets.push(dataset);
|
|
29073
29242
|
const trend = definition.dataSets?.[index].trend;
|
|
@@ -35296,6 +35465,7 @@ const CHECK_SVG = /*xml*/ `
|
|
|
35296
35465
|
<path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
|
|
35297
35466
|
</svg>
|
|
35298
35467
|
`;
|
|
35468
|
+
const CHECKBOX_WIDTH = 14;
|
|
35299
35469
|
css /* scss */ `
|
|
35300
35470
|
label.o-checkbox {
|
|
35301
35471
|
input {
|
|
@@ -35303,8 +35473,8 @@ css /* scss */ `
|
|
|
35303
35473
|
-webkit-appearance: none;
|
|
35304
35474
|
-moz-appearance: none;
|
|
35305
35475
|
border-radius: 0;
|
|
35306
|
-
width:
|
|
35307
|
-
height:
|
|
35476
|
+
width: ${CHECKBOX_WIDTH}px;
|
|
35477
|
+
height: ${CHECKBOX_WIDTH}px;
|
|
35308
35478
|
vertical-align: top;
|
|
35309
35479
|
box-sizing: border-box;
|
|
35310
35480
|
outline: none;
|
|
@@ -37214,6 +37384,32 @@ class ChartWithAxisDesignPanel extends owl.Component {
|
|
|
37214
37384
|
}
|
|
37215
37385
|
}
|
|
37216
37386
|
|
|
37387
|
+
class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
|
|
37388
|
+
static template = "o-spreadsheet-ComboChartDesignPanel";
|
|
37389
|
+
seriesTypeChoices = [
|
|
37390
|
+
{ value: "bar", label: _t("Bar") },
|
|
37391
|
+
{ value: "line", label: _t("Line") },
|
|
37392
|
+
];
|
|
37393
|
+
updateDataSeriesType(type) {
|
|
37394
|
+
const dataSets = [...this.props.definition.dataSets];
|
|
37395
|
+
if (!dataSets?.[this.state.index]) {
|
|
37396
|
+
return;
|
|
37397
|
+
}
|
|
37398
|
+
dataSets[this.state.index] = {
|
|
37399
|
+
...dataSets[this.state.index],
|
|
37400
|
+
type,
|
|
37401
|
+
};
|
|
37402
|
+
this.props.updateChart(this.props.figureId, { dataSets });
|
|
37403
|
+
}
|
|
37404
|
+
getDataSeriesType() {
|
|
37405
|
+
const dataSets = this.props.definition.dataSets;
|
|
37406
|
+
if (!dataSets?.[this.state.index]) {
|
|
37407
|
+
return "bar";
|
|
37408
|
+
}
|
|
37409
|
+
return dataSets[this.state.index].type ?? "line";
|
|
37410
|
+
}
|
|
37411
|
+
}
|
|
37412
|
+
|
|
37217
37413
|
class GaugeChartConfigPanel extends owl.Component {
|
|
37218
37414
|
static template = "o-spreadsheet-GaugeChartConfigPanel";
|
|
37219
37415
|
static components = { ChartErrorSection, ChartDataSeries };
|
|
@@ -37640,7 +37836,7 @@ chartSidePanelComponentRegistry
|
|
|
37640
37836
|
})
|
|
37641
37837
|
.add("combo", {
|
|
37642
37838
|
configuration: GenericChartConfigPanel,
|
|
37643
|
-
design:
|
|
37839
|
+
design: ComboChartDesignPanel,
|
|
37644
37840
|
})
|
|
37645
37841
|
.add("pie", {
|
|
37646
37842
|
configuration: GenericChartConfigPanel,
|
|
@@ -42819,8 +43015,9 @@ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
|
|
|
42819
43015
|
* This function converts a list of data entry into a spreadsheet pivot table.
|
|
42820
43016
|
*/
|
|
42821
43017
|
function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
|
|
43018
|
+
const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
|
|
42822
43019
|
const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
|
|
42823
|
-
computeWidthOfColumnsNodes(columnsTree,
|
|
43020
|
+
computeWidthOfColumnsNodes(columnsTree, measureIds.length);
|
|
42824
43021
|
const cols = columnsTreeToColumns(columnsTree, definition);
|
|
42825
43022
|
const rows = dataEntriesToRows(dataEntries, 0, definition.rows, [], []);
|
|
42826
43023
|
// Add the total row
|
|
@@ -42829,7 +43026,6 @@ function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
|
|
|
42829
43026
|
values: [],
|
|
42830
43027
|
indent: 0,
|
|
42831
43028
|
});
|
|
42832
|
-
const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
|
|
42833
43029
|
const fieldsType = {};
|
|
42834
43030
|
for (const columns of definition.columns) {
|
|
42835
43031
|
fieldsType[columns.fieldName] = columns.type;
|
|
@@ -43590,7 +43786,7 @@ pivotRegistry.add("SPREADSHEET", {
|
|
|
43590
43786
|
onIterationEndEvaluation: (pivot) => pivot.markAsDirtyForEvaluation(),
|
|
43591
43787
|
dateGranularities: [...dateGranularities],
|
|
43592
43788
|
datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
|
|
43593
|
-
isMeasureCandidate: (field) => !["
|
|
43789
|
+
isMeasureCandidate: (field) => !["datetime", "boolean"].includes(field.type),
|
|
43594
43790
|
isGroupable: () => true,
|
|
43595
43791
|
});
|
|
43596
43792
|
|
|
@@ -46119,13 +46315,10 @@ class GridCellIcon extends owl.Component {
|
|
|
46119
46315
|
}
|
|
46120
46316
|
}
|
|
46121
46317
|
|
|
46122
|
-
const CHECKBOX_WIDTH = 15;
|
|
46123
46318
|
const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
|
|
46124
46319
|
css /* scss */ `
|
|
46125
46320
|
.o-dv-checkbox {
|
|
46126
46321
|
box-sizing: border-box !important;
|
|
46127
|
-
width: ${CHECKBOX_WIDTH}px;
|
|
46128
|
-
height: ${CHECKBOX_WIDTH}px;
|
|
46129
46322
|
accent-color: #808080;
|
|
46130
46323
|
margin: ${MARGIN}px;
|
|
46131
46324
|
/** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
|
|
@@ -46134,13 +46327,15 @@ css /* scss */ `
|
|
|
46134
46327
|
`;
|
|
46135
46328
|
class DataValidationCheckbox extends owl.Component {
|
|
46136
46329
|
static template = "o-spreadsheet-DataValidationCheckbox";
|
|
46330
|
+
static components = {
|
|
46331
|
+
Checkbox,
|
|
46332
|
+
};
|
|
46137
46333
|
static props = {
|
|
46138
46334
|
cellPosition: Object,
|
|
46139
46335
|
};
|
|
46140
|
-
onCheckboxChange(
|
|
46141
|
-
const newValue = ev.target.checked;
|
|
46336
|
+
onCheckboxChange(value) {
|
|
46142
46337
|
const { sheetId, col, row } = this.props.cellPosition;
|
|
46143
|
-
const cellContent =
|
|
46338
|
+
const cellContent = value ? "TRUE" : "FALSE";
|
|
46144
46339
|
this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
|
|
46145
46340
|
}
|
|
46146
46341
|
get checkBoxValue() {
|
|
@@ -46954,7 +47149,11 @@ class GridAddRowsFooter extends owl.Component {
|
|
|
46954
47149
|
class PaintFormatStore extends SpreadsheetStore {
|
|
46955
47150
|
mutators = ["activate", "cancel", "pasteFormat"];
|
|
46956
47151
|
highlightStore = this.get(HighlightStore);
|
|
46957
|
-
|
|
47152
|
+
clipboardHandlers = [
|
|
47153
|
+
new CellClipboardHandler(this.getters, this.model.dispatch),
|
|
47154
|
+
new BorderClipboardHandler(this.getters, this.model.dispatch),
|
|
47155
|
+
new TableClipboardHandler(this.getters, this.model.dispatch),
|
|
47156
|
+
];
|
|
46958
47157
|
status = "inactive";
|
|
46959
47158
|
copiedData;
|
|
46960
47159
|
constructor(get) {
|
|
@@ -46975,10 +47174,12 @@ class PaintFormatStore extends SpreadsheetStore {
|
|
|
46975
47174
|
pasteFormat(target) {
|
|
46976
47175
|
if (this.copiedData) {
|
|
46977
47176
|
const sheetId = this.getters.getActiveSheetId();
|
|
46978
|
-
|
|
46979
|
-
|
|
46980
|
-
|
|
46981
|
-
|
|
47177
|
+
for (const handler of this.clipboardHandlers) {
|
|
47178
|
+
handler.paste({ zones: target, sheetId }, this.copiedData, {
|
|
47179
|
+
isCutOperation: false,
|
|
47180
|
+
pasteOption: "onlyFormat",
|
|
47181
|
+
});
|
|
47182
|
+
}
|
|
46982
47183
|
}
|
|
46983
47184
|
if (this.status === "oneOff") {
|
|
46984
47185
|
this.cancel();
|
|
@@ -46990,7 +47191,11 @@ class PaintFormatStore extends SpreadsheetStore {
|
|
|
46990
47191
|
copyFormats() {
|
|
46991
47192
|
const sheetId = this.getters.getActiveSheetId();
|
|
46992
47193
|
const zones = this.getters.getSelectedZones();
|
|
46993
|
-
|
|
47194
|
+
const copiedData = {};
|
|
47195
|
+
for (const handler of this.clipboardHandlers) {
|
|
47196
|
+
Object.assign(copiedData, handler.copy(getClipboardDataPositions(sheetId, zones)));
|
|
47197
|
+
}
|
|
47198
|
+
return copiedData;
|
|
46994
47199
|
}
|
|
46995
47200
|
get highlights() {
|
|
46996
47201
|
const data = this.copiedData;
|
|
@@ -59052,7 +59257,7 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
59052
59257
|
const ranking = {};
|
|
59053
59258
|
const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
|
|
59054
59259
|
const secondaryDimension = mainDimension === "row" ? "column" : "row";
|
|
59055
|
-
let pivotCells = this.getPivotValueCells();
|
|
59260
|
+
let pivotCells = this.getPivotValueCells(measure.id);
|
|
59056
59261
|
if (mainDimension === "column") {
|
|
59057
59262
|
// Transpose the pivot cells so we can do the same operations on the columns as on the rows
|
|
59058
59263
|
// This means that we need to transpose back the ranking at the end
|
|
@@ -59096,7 +59301,7 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
59096
59301
|
const cellsRunningTotals = {};
|
|
59097
59302
|
const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
|
|
59098
59303
|
const secondaryDimension = mainDimension === "row" ? "column" : "row";
|
|
59099
|
-
let pivotCells = this.getPivotValueCells();
|
|
59304
|
+
let pivotCells = this.getPivotValueCells(measure.id);
|
|
59100
59305
|
if (mainDimension === "column") {
|
|
59101
59306
|
// Transpose the pivot cells so we can do the same operations on the columns as on the rows
|
|
59102
59307
|
// This means that we need to transpose back the totals at the end
|
|
@@ -59175,10 +59380,10 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
59175
59380
|
const comparedValueNumber = this.strictMeasureValueToNumber(comparedValue);
|
|
59176
59381
|
return comparedValueNumber;
|
|
59177
59382
|
}
|
|
59178
|
-
getPivotValueCells() {
|
|
59383
|
+
getPivotValueCells(measureId) {
|
|
59179
59384
|
return this.getTableStructure()
|
|
59180
59385
|
.getPivotCells()
|
|
59181
|
-
.map((col) => col.filter((cell) => cell.type === "VALUE"))
|
|
59386
|
+
.map((col) => col.filter((cell) => cell.type === "VALUE" && cell.measure === measureId))
|
|
59182
59387
|
.filter((col) => col.length > 0);
|
|
59183
59388
|
}
|
|
59184
59389
|
measureValueToNumber(result) {
|
|
@@ -60946,6 +61151,7 @@ class Session extends EventBus {
|
|
|
60946
61151
|
case "REMOTE_REVISION":
|
|
60947
61152
|
case "REVISION_REDONE":
|
|
60948
61153
|
case "REVISION_UNDONE":
|
|
61154
|
+
case "SNAPSHOT_CREATED":
|
|
60949
61155
|
return this.processedRevisions.has(message.nextRevisionId);
|
|
60950
61156
|
default:
|
|
60951
61157
|
return false;
|
|
@@ -61508,7 +61714,7 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
61508
61714
|
const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
|
|
61509
61715
|
const formulaId = this.getters.getPivotFormulaId(newPivotId);
|
|
61510
61716
|
const newPivotName = this.getters.getPivotName(newPivotId);
|
|
61511
|
-
this.dispatch("CREATE_SHEET", {
|
|
61717
|
+
const result = this.dispatch("CREATE_SHEET", {
|
|
61512
61718
|
sheetId: newSheetId,
|
|
61513
61719
|
name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
|
|
61514
61720
|
newPivotName,
|
|
@@ -61516,20 +61722,19 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
61516
61722
|
})),
|
|
61517
61723
|
position,
|
|
61518
61724
|
});
|
|
61519
|
-
|
|
61520
|
-
|
|
61521
|
-
|
|
61522
|
-
|
|
61523
|
-
|
|
61524
|
-
content: `=PIVOT(${formulaId})`,
|
|
61525
|
-
});
|
|
61725
|
+
if (result.isSuccessful) {
|
|
61726
|
+
this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
|
|
61727
|
+
const pivot = this.getters.getPivot(pivotId);
|
|
61728
|
+
this.insertPivotWithTable(newSheetId, 0, 0, newPivotId, pivot.getTableStructure().export(), "dynamic");
|
|
61729
|
+
}
|
|
61526
61730
|
}
|
|
61527
61731
|
getPivotDuplicateSheetName(pivotName) {
|
|
61528
61732
|
let i = 1;
|
|
61529
61733
|
const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
|
|
61530
|
-
|
|
61734
|
+
const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
|
|
61735
|
+
let name = sanitizedName;
|
|
61531
61736
|
while (names.includes(name)) {
|
|
61532
|
-
name = `${
|
|
61737
|
+
name = `${sanitizedName} (${i})`;
|
|
61533
61738
|
i++;
|
|
61534
61739
|
}
|
|
61535
61740
|
return name;
|
|
@@ -70882,6 +71087,15 @@ function addTableColumns(table, sheetData) {
|
|
|
70882
71087
|
["id", i + 1], // id cannot be 0
|
|
70883
71088
|
["name", colName],
|
|
70884
71089
|
];
|
|
71090
|
+
if (table.config.totalRow) {
|
|
71091
|
+
// Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
|
|
71092
|
+
// `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
|
|
71093
|
+
const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
|
|
71094
|
+
const colTotalContent = sheetData.cells[colTotalXc]?.content;
|
|
71095
|
+
if (colTotalContent?.startsWith("=")) {
|
|
71096
|
+
colAttributes.push(["totalsRowFunction", "custom"]);
|
|
71097
|
+
}
|
|
71098
|
+
}
|
|
70885
71099
|
columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
|
|
70886
71100
|
}
|
|
70887
71101
|
return escapeXml /*xml*/ `
|
|
@@ -70976,8 +71190,9 @@ function addRows(construct, data, sheet) {
|
|
|
70976
71190
|
}
|
|
70977
71191
|
else if (cell.content && cell.content !== "") {
|
|
70978
71192
|
const isTableHeader = isCellTableHeader(c, r, sheet);
|
|
71193
|
+
const isTableTotal = isCellTableTotal(c, r, sheet);
|
|
70979
71194
|
const isPlainText = !!(cell.format && isTextFormat(data.formats[cell.format]));
|
|
70980
|
-
({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
|
|
71195
|
+
({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
|
|
70981
71196
|
}
|
|
70982
71197
|
attributes.push(...additionalAttrs);
|
|
70983
71198
|
// prettier-ignore
|
|
@@ -71011,6 +71226,16 @@ function isCellTableHeader(col, row, sheet) {
|
|
|
71011
71226
|
return isInside(col, row, headerZone);
|
|
71012
71227
|
});
|
|
71013
71228
|
}
|
|
71229
|
+
function isCellTableTotal(col, row, sheet) {
|
|
71230
|
+
return sheet.tables.some((table) => {
|
|
71231
|
+
if (!table.config.totalRow) {
|
|
71232
|
+
return false;
|
|
71233
|
+
}
|
|
71234
|
+
const zone = toZone(table.range);
|
|
71235
|
+
const totalZone = { ...zone, top: zone.bottom };
|
|
71236
|
+
return isInside(col, row, totalZone);
|
|
71237
|
+
});
|
|
71238
|
+
}
|
|
71014
71239
|
function addHyperlinks(construct, data, sheetIndex) {
|
|
71015
71240
|
const sheet = data.sheets[sheetIndex];
|
|
71016
71241
|
const cells = sheet.cells;
|
|
@@ -71501,7 +71726,6 @@ class Model extends EventBus {
|
|
|
71501
71726
|
isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
|
|
71502
71727
|
isDashboard: () => this.config.mode === "dashboard",
|
|
71503
71728
|
};
|
|
71504
|
-
this.uuidGenerator.setIsFastStrategy(true);
|
|
71505
71729
|
// Initiate stream processor
|
|
71506
71730
|
this.selection = new SelectionStreamProcessorImpl(this.getters);
|
|
71507
71731
|
this.coreHandlers.push(this.range);
|
|
@@ -72175,6 +72399,6 @@ exports.tokenColors = tokenColors;
|
|
|
72175
72399
|
exports.tokenize = tokenize;
|
|
72176
72400
|
|
|
72177
72401
|
|
|
72178
|
-
__info__.version = "18.0.
|
|
72179
|
-
__info__.date = "2024-
|
|
72180
|
-
__info__.hash = "
|
|
72402
|
+
__info__.version = "18.0.1";
|
|
72403
|
+
__info__.date = "2024-10-14T07:54:24.768Z";
|
|
72404
|
+
__info__.hash = "1771f68";
|